diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 675ae9ca6..8059a51ed 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -541,6 +541,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_vop3_interp_decode_tests EXCLUDE_FROM_ALL tests/Vop3InterpDecode.cpp) +target_link_libraries(agc_vop3_interp_decode_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_vop3_interp_decode_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_vop3_interp_decode_tests) +if(BUILD_TESTING) + set_target_properties(agc_vop3_interp_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_vop3_interp_decode COMMAND agc_vop3_interp_decode_tests) + if(WIN32) + set_tests_properties(agc_vop3_interp_decode PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_pass_hazards_tests EXCLUDE_FROM_ALL tests/PassHazards.cpp Graphics/src/PassHazards.cpp) target_include_directories(agc_pass_hazards_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_pass_hazards_tests PRIVATE libc) diff --git a/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp new file mode 100644 index 000000000..f362f67d6 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp @@ -0,0 +1,108 @@ +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" +#include +#include +#include +#include +#include + +using namespace ShaderRecompiler; + +namespace { + +struct Accepted { + std::vector words; + RdnaOpcode op; + const char* name; + RdnaOperandKind source0Kind; + std::uint32_t source0Value; + std::uint32_t attribute; + std::uint32_t channel; + bool absolute; + bool negate; + bool clamp; + std::uint32_t omod; +}; + +struct Refused { + std::vector words; + const char* reason; + const char* name; +}; + +} + +int main() { + constexpr std::uint32_t Destination = 5; + const std::vector accepted{ + {{0xd6000005u, 0x02020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr0.x", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, false, false, 0u}, + {{0xd6010005u, 0x02020440u}, RdnaOpcode::VInterpP2F32, "v_interp_p2_f32_e64 v5, v2, attr0.y", RdnaOperandKind::VectorRegister, 2u, 0u, 1u, false, false, false, 0u}, + {{0xd6020005u, 0x02000200u}, RdnaOpcode::VInterpMovF32, "v_interp_mov_f32_e64 v5, p20, attr0.x", RdnaOperandKind::IntegerInlineConstant, 1u, 0u, 0u, false, false, false, 0u}, + {{0xd6000005u, 0x0202041fu}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr31.x", RdnaOperandKind::VectorRegister, 2u, 31u, 0u, false, false, false, 0u}, + {{0xd6000005u, 0x02020420u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr32.x", RdnaOperandKind::VectorRegister, 2u, 32u, 0u, false, false, false, 0u}, + {{0xd6000005u, 0x0203fe00u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v255, attr0.x", RdnaOperandKind::VectorRegister, 255u, 0u, 0u, false, false, false, 0u}, + {{0xd6000205u, 0x02020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, |v2|, attr0.x", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, true, false, false, 0u}, + {{0xd6000005u, 0x42020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, -v2, attr0.x", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, true, false, 0u}, + {{0xd6008005u, 0x02020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr0.x clamp", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, false, true, 0u}, + {{0xd6000005u, 0x0a020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr0.x mul:2", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, false, false, 1u}, + {{0xd6000005u, 0x12020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr0.x mul:4", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, false, false, 2u}, + {{0xd6000005u, 0x1a020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, v2, attr0.x div:2", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, false, false, false, 3u}, + {{0xd6000205u, 0x42020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, -|v2|, attr0.x", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, true, true, false, 0u}, + {{0xd6000205u, 0x5a020400u}, RdnaOpcode::VInterpP1F32, "v_interp_p1_f32_e64 v5, -|v2|, attr0.x div:2", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, true, true, false, 3u}, + {{0xd6010005u, 0x42020481u}, RdnaOpcode::VInterpP2F32, "v_interp_p2_f32_e64 v5, -v2, attr1.z", RdnaOperandKind::VectorRegister, 2u, 1u, 2u, false, true, false, 0u}, + {{0xd6018205u, 0x02020400u}, RdnaOpcode::VInterpP2F32, "v_interp_p2_f32_e64 v5, |v2|, attr0.x clamp", RdnaOperandKind::VectorRegister, 2u, 0u, 0u, true, false, true, 0u}, + {{0xd6028005u, 0x02000000u}, RdnaOpcode::VInterpMovF32, "v_interp_mov_f32_e64 v5, p10, attr0.x clamp", RdnaOperandKind::IntegerInlineConstant, 0u, 0u, 0u, false, false, true, 0u}, + {{0xd6020005u, 0x0a000000u}, RdnaOpcode::VInterpMovF32, "v_interp_mov_f32_e64 v5, p10, attr0.x mul:2", RdnaOperandKind::IntegerInlineConstant, 0u, 0u, 0u, false, false, false, 1u}, + }; + const std::vector refused{ + {{0xd6000005u, 0x02000400u}, "not a vector register", "v_interp_p1_f32_e64 with a non-VGPR coordinate"}, + {{0xd6020005u, 0x02000600u}, "not an interpolation parameter", "v_interp_mov_f32_e64 with a reserved parameter"}, + {{0xd6000005u, 0x02060400u}, "third source is not the inline constant 0", "v_interp_p1_f32_e64 with a third source other than the inline constant 0"}, + {{0xd6000805u, 0x02020400u}, "modifiers are not implemented", "v_interp_p1_f32_e64 with op_sel"}, + {{0xd6000005u, 0x02020500u}, "modifiers are not implemented", "v_interp_p1_f32_e64 with the packed-half selector"}, + {{0xd6000105u, 0x02020400u}, "modifiers are not implemented", "v_interp_p1_f32_e64 with an absolute source 0"}, + {{0xd6020005u, 0x22000200u}, "modifiers are not implemented", "v_interp_mov_f32_e64 with a negated parameter"}, + }; + int failures = 0; + for (const auto& test : accepted) { + auto code = test.words; + code.push_back(0xbf810000u); + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + if (decoded.instructions.empty()) { + std::fprintf(stderr, "%s decoded no instruction\n", test.name); + ++failures; + continue; + } + const auto& instruction = decoded.instructions[0]; + const auto source0Value = test.source0Kind == RdnaOperandKind::VectorRegister ? instruction.source0.reg : instruction.source0.value; + if (instruction.op != test.op || instruction.wordCount != test.words.size() || instruction.family != RdnaInstructionFamily::VOP3 || + instruction.destination.kind != RdnaOperandKind::VectorRegister || instruction.destination.reg != Destination || + instruction.destination.clamp != test.clamp || instruction.destination.omod != test.omod || instruction.sourceCount != 3u || + instruction.source0.kind != test.source0Kind || source0Value != test.source0Value || + instruction.source0.absolute != test.absolute || instruction.source0.negate != test.negate || + instruction.source1.kind != RdnaOperandKind::IntegerInlineConstant || instruction.source1.value != test.attribute || + instruction.source2.kind != RdnaOperandKind::IntegerInlineConstant || instruction.source2.value != test.channel) { + std::fprintf(stderr, "%s decoded to the wrong opcode or operands\n", test.name); + ++failures; + } + } catch (const std::exception& error) { + std::fprintf(stderr, "%s was refused: %s\n", test.name, error.what()); + ++failures; + } + } + for (const auto& test : refused) { + auto code = test.words; + code.push_back(0xbf810000u); + std::string refusal; + try { + static_cast(RdnaInstructionDecoder{}.Decode(code)); + } catch (const std::exception& error) { + refusal = error.what(); + } + if (refusal.find(test.reason) == std::string::npos) { + std::fprintf(stderr, "%s was not refused\n", test.name); + ++failures; + } + } + return failures == 0 ? 0 : 1; +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 0e6078d53..4fe6efbd5 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -529,6 +529,9 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = { {0x311u, RdnaOpcode::VPackB32F16}, {0x314u, RdnaOpcode::VLshlrevB16}, {0x319u, RdnaOpcode::VSubrevI32}, + {0x200u, RdnaOpcode::VInterpP1F32}, + {0x201u, RdnaOpcode::VInterpP2F32}, + {0x202u, RdnaOpcode::VInterpMovF32}, {0x342u, RdnaOpcode::VInterpP1llF16}, {0x343u, RdnaOpcode::VInterpP1lvF16}, {0x344u, RdnaOpcode::VPermB32}, @@ -2114,6 +2117,47 @@ void decodeVop3F16Interpolation(RdnaInstruction& instruction, std::uint32_t vdst } } +bool isVop3F32InterpolationOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VInterpP1F32 || opcode == RdnaOpcode::VInterpP2F32 || opcode == RdnaOpcode::VInterpMovF32; +} + +void decodeVop3F32Interpolation(RdnaInstruction& instruction, std::uint32_t vdst, std::uint32_t src0, std::uint32_t src1, std::uint32_t src2, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) { + const bool moves = instruction.op == RdnaOpcode::VInterpMovF32; + const RdnaOperand thirdSource = DecodeRdnaScalarSource(src2, instruction.programCounter); + if (thirdSource.kind != RdnaOperandKind::IntegerInlineConstant || thirdSource.value != 0u) { + throw std::invalid_argument("VOP3 interpolation third source is not the inline constant 0"); + } + if ((abs & 0x5u) != 0u || (neg & 0x5u) != 0u || opSel != 0u || (src0 >> 8u) != 0u || (moves && (abs | neg) != 0u)) { + throw std::invalid_argument("VOP3 interpolation modifiers are not implemented"); + } + if (moves) { + if (src1 >= 3u) { + throw std::invalid_argument("VOP3 interpolation source is not an interpolation parameter"); + } + } else if (src1 < 256u) { + throw std::invalid_argument("VOP3 interpolation source is not a vector register"); + } + instruction.destination = DecodeRdnaVectorGpr(vdst); + instruction.destination.clamp = clamp != 0u; + instruction.destination.omod = omod; + if (moves) { + instruction.source0.kind = RdnaOperandKind::IntegerInlineConstant; + instruction.source0.value = src1; + instruction.source0.signedVal = static_cast(src1); + } else { + instruction.source0 = DecodeRdnaVectorGpr(src1 - 256u); + instruction.source0.absolute = (abs & 0x2u) != 0u; + instruction.source0.negate = (neg & 0x2u) != 0u; + } + instruction.source1.kind = RdnaOperandKind::IntegerInlineConstant; + instruction.source1.value = src0 & 0x3fu; + instruction.source1.signedVal = static_cast(instruction.source1.value); + instruction.source2.kind = RdnaOperandKind::IntegerInlineConstant; + instruction.source2.value = (src0 >> 6u) & 0x3u; + instruction.source2.signedVal = static_cast(instruction.source2.value); + instruction.sourceCount = 3; +} + void applyNativeVop3SourceModifiers(RdnaInstruction& instruction, std::uint32_t abs, std::uint32_t neg) { RdnaOperand* sources[] = {&instruction.source0, &instruction.source1, &instruction.source2}; for (std::uint32_t i = 0; i < instruction.sourceCount && i < 3u; ++i) { @@ -2278,6 +2322,10 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span