mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-29 23:41:19 -06:00
327 lines
12 KiB
C++
327 lines
12 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <cmath>
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <half.hpp>
|
|
|
|
#include "gcn_test_runner.hpp"
|
|
#include "instructions.hpp"
|
|
#include "translator.hpp"
|
|
|
|
class GcnTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {}
|
|
|
|
void TearDown() override {}
|
|
};
|
|
|
|
struct F32x2 {
|
|
float a;
|
|
float b;
|
|
};
|
|
|
|
// Example
|
|
// TEST_F(GcnTest, test_name) {
|
|
// // Runner sets the vulkan context
|
|
// auto runner = gcn_test::Runner::instance().value();
|
|
//
|
|
// // v_add_f32 v0, v0, v1
|
|
// auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
//
|
|
// // run<T> tells how to interpret the result (only 32bit as of now)
|
|
// // the second argument is templated, it can be at most 4 u32s
|
|
// // the data is accessible by the instruction in v0-4 and s0-4 (mirrored)
|
|
// // the result has to be placed in v0
|
|
// auto result = runner->run<float>(spirv, F32x2{1.5f, 6.0f});
|
|
//
|
|
// EXPECT_TRUE(result.has_value());
|
|
// EXPECT_EQ(*result, 7.5f);
|
|
// }
|
|
|
|
TEST_F(GcnTest, add_f32) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<float>(spirv, F32x2{1.5f, 6.0f});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 7.5f);
|
|
}
|
|
|
|
TEST_F(GcnTest, add_nan) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<float>(spirv, F32x2{1.0f, std::numeric_limits<float>::quiet_NaN()});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_TRUE(std::isnan(*result));
|
|
}
|
|
|
|
using half = half_float::half;
|
|
|
|
struct F16x2 {
|
|
half a;
|
|
half b = half(0.0f);
|
|
|
|
bool operator==(const F16x2& rhs) const = default;
|
|
};
|
|
|
|
static_assert(sizeof(F16x2) == sizeof(float));
|
|
|
|
TEST_F(GcnTest, add_f16) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F16, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<F16x2>(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, F16x2{half(2.0f)});
|
|
}
|
|
|
|
TEST_F(GcnTest, add_f16_clamp) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetClamp(true).Get());
|
|
auto result = runner->run<F16x2>(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, F16x2{half(1.0f)}); //confirmed with neo
|
|
}
|
|
|
|
TEST_F(GcnTest, add_f16_neg) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetNeg({true, true, false}).Get());
|
|
auto result = runner->run<F16x2>(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ((*result).a, half(-2.0f)); //confirmed with neo
|
|
}
|
|
|
|
TEST_F(GcnTest, add_f16_opsel_hi) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetOpSel({true, true, false, true}).Get());
|
|
auto result = runner->run<F16x2>(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(1.0f), half(2.0f)}});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ((*result).a, half(1.0f));
|
|
EXPECT_EQ((*result).b, half(4.0f));
|
|
}
|
|
|
|
TEST_F(GcnTest, sub_f16) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_SUB_F16, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<F16x2>(spirv, std::array{F16x2{half(0.0f)}, F16x2{half(1.0f)}});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, F16x2{half(-1.0f)}); //confirmed with neo
|
|
}
|
|
|
|
TEST_F(GcnTest, mul_legacy_nan) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{u32(0), u32(0x7fc00000)});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0);
|
|
}
|
|
|
|
TEST_F(GcnTest, mul_nan) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<float>(spirv, std::array{u32(0), u32(0x7fc00000)});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_TRUE(std::isnan(*result));
|
|
}
|
|
|
|
TEST_F(GcnTest, min_legacy_nan) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{u32(0), u32(0x7fc00000)});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x7fc00000);
|
|
}
|
|
|
|
TEST_F(GcnTest, min_nan) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get());
|
|
auto result = runner->run<float>(spirv, std::array{u32(0), u32(0x7fc00000)});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0);
|
|
}
|
|
|
|
TEST_F(GcnTest, add3_u32_1) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0, 1, 2});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 3);
|
|
}
|
|
|
|
TEST_F(GcnTest, add3_u32_2) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
auto big = 2000000000;
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{big, big, big});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x65A0BC00);
|
|
}
|
|
|
|
TEST_F(GcnTest, add3_u32_3) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
auto big = 2000000000;
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{big, big, big});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x65A0BC00);
|
|
}
|
|
|
|
TEST_F(GcnTest, add3_u32_4) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0, 1, 2});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x80000003);
|
|
}
|
|
|
|
TEST_F(GcnTest, or3_u32_1) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0xF0F0F0F0, 0x07070707, 0x11111111});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0xF7F7F7F7);
|
|
}
|
|
|
|
TEST_F(GcnTest, or3_u32_2) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0x07070707, 0x11111111, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x57575757);
|
|
}
|
|
|
|
TEST_F(GcnTest, or3_u32_3) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
auto big = 2000000000;
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0x07070707, 0x11111111, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x57575757);
|
|
}
|
|
|
|
TEST_F(GcnTest, or3_u32_4) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({0,0,1}).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0x07070707, 0x11111111, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0xD7575757);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_1) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0xF0F0F0F0, 0x07070707, 0x11111111});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x11111111);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_2) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Mul2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0x40404040, 0x40404040, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x40404040);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_3) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get());
|
|
auto result = runner->run<u32>(spirv, std::array{0x40404040, 0x40404040, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x40404040);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_4) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0x07070707, 0x11111111, 0xF0F0F0F0});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0xF1F1F1F1);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_5) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).SetAbs({1,0,0}).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0x77777777, 0xB0B0B0B0, 0x11111111});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0xB1313131);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_6) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Mul2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0x40404040, 0xB0B0B0B0, 0x11111111});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x11111111);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_7) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Div2).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0xB0B0B0B0, 0x77777777, 0x40404040});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x70707070);
|
|
}
|
|
|
|
TEST_F(GcnTest, and_or_b32_8) {
|
|
auto runner = gcn_test::Runner::instance().value();
|
|
|
|
auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetAbs({1,1,0}).Get());
|
|
auto result = runner->run<u32>(spirv, std::array<u32,3>{0xB0B0B0B0, 0x11111111, 0x11111111});
|
|
|
|
EXPECT_TRUE(result.has_value());
|
|
EXPECT_EQ(*result, 0x11111111);
|
|
}
|