void TestTrinityStackInstruction() { namespace D = ShaderRecompiler::Decoder; const std::array code{0xdb840008u, 0x01010a09u, 0xdb840010u, 0x02010a09u, EncodeSopp(0x01)}; D::Program decoded; D::DecodeProgram(code, decoded); const auto& first = decoded.instructions.front(); Check(first.opcode == D::Opcode::DS_BVH_STACK_PUSH8_POP1_RTN_B32 && first.src_count == 3 && first.offset == 8 && first.dst.reg == 1 && first.src0.reg == 9 && first.src1.reg == 10 && first.src2.reg == 1, "Trinity stack operands or capacity were not decoded"); ShaderComputeInputInfo compute{}; compute.lds_size_dwords = 1024; auto options = MakeCompileOptions(ShaderType::Compute); options.input_info.compute = &compute; const auto result = RecompileForTest(code, options); Check(CountSourceOccurrences(result.ir_dump, " = BvhStack ") == 2 && result.program.info.uses_lds && result.program.info.bvh_stack && !result.program.info.shared_int64_atomics, "unused stack results discarded LDS writes or selected 64-bit atomics"); Check(SpirvInstructionOpcodeCount(result.spirv, 54u) == 2 && SpirvInstructionOpcodeCount(result.spirv, 246u) == 1, "Trinity stack calls duplicated the function or unrolled its eight pushes"); CheckSpirvBinaryValidates(result.spirv); compute.lds_storage = true; CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv); options = MakeCompileOptions(ShaderType::Pixel); CheckSpirvBinaryValidates(RecompileForTest(code, options).spirv); for (const auto words : {std::array{0xdb860008u, 0x01010a09u}, std::array{0xdb840007u, 0x01010a09u}, std::array{0xdb840020u, 0x01010a09u}, std::array{0xdb840008u, 0x01f90a09u}, std::array{0xdb840008u, 0x09010a09u}}) { const std::array invalid{words[0], words[1], EncodeSopp(0x01)}; D::DecodeProgram(invalid, decoded); Check(decoded.instructions[0].opcode == D::Opcode::UNSUPPORTED, "unsupported stack encoding was silently accepted"); } } void TestTrinityBvhInstruction() { namespace D = ShaderRecompiler::Decoder; const std::array code{0xf1949f05u, 0x00010000u, 0x0a030201u, 0x0000040du, EncodeMubuf0(0x1e, 0, false), EncodeMubuf1(0, 0, 0), EncodeMubuf0(0x1e, 16, false), EncodeMubuf1(4, 0, 0), EncodeMubuf0(0x1d, 32, false), EncodeMubuf1(8, 0, 0), EncodeMubuf0(0x1e, 40, false), EncodeMubuf1(10, 0, 0), EncodeMubuf0(0x1d, 56, false), EncodeMubuf1(14, 0, 0), EncodeSopp(0x01)}; D::Program decoded; D::DecodeProgram(code, decoded); const auto& inst = decoded.instructions.front(); Check(inst.opcode == D::Opcode::IMAGE_BVH8_INTERSECT_RAY && inst.word_count == 4 && inst.data_dwords == 10 && inst.image_address_components == 11 && inst.image_nsa_addr[3] == 10 && inst.image_nsa_addr[5] == 12 && inst.image_nsa_addr[6] == 13 && inst.image_nsa_addr[8] == 15 && inst.image_nsa_addr[9] == 4 && inst.src1.reg == 4, "Trinity BVH8 grouped ray registers were not decoded"); const auto result = RecompileForTest(code, MakeCompileOptions(ShaderType::Compute)); Check(result.program.info.bvh8 && !result.program.info.bvh && result.program.info.uses_dma && result.program.info.images.empty(), "Trinity BVH8 did not use its separate raw-memory helper"); Check(CountSourceOccurrences(result.ir_dump, " = Bvh8Intersect ") == 1, "Trinity BVH8 result and ray writes duplicated the intersection"); CheckSpirvBinaryValidates(result.spirv); Check(SpirvInstructionOpcodeCount(result.spirv, 54u) == 6 && SpirvInstructionOpcodeCount(result.spirv, 246u) == 1, "Trinity BVH8 duplicated helpers or unrolled its four-child loop"); CheckSpirvBinaryValidates(RecompileForTest(code, MakeCompileOptions(ShaderType::Pixel)).spirv); for (const auto words : {std::array{0xf1949f05u, 0x40010000u, 0x0a030201u, 0x0000040du}, std::array{0xf1949f05u, 0x0001f700u, 0x0a030201u, 0x0000040du}, std::array{0xf1949f05u, 0x00010000u, 0xfe030201u, 0x0000040du}, std::array{0xf1949f05u, 0x00010700u, 0x0a030201u, 0x0000040du}, std::array{0xf1949f05u, 0x00010000u, 0x0a030201u, 0x0000040bu}}) { const std::array invalid{words[0], words[1], words[2], words[3], EncodeSopp(0x01)}; D::DecodeProgram(invalid, decoded); Check(decoded.instructions[0].opcode == D::Opcode::UNSUPPORTED, "Trinity BVH8 accepted truncated ray or destination registers"); } } void TestRayTracingInstructions() { TestTrinityBvhInstruction(); TestTrinityStackInstruction(); namespace D = ShaderRecompiler::Decoder; const std::array code{ 0xf1989f01u, 0x00081014u, EncodeMubuf0(0x1e, 0, false), EncodeMubuf1(16, 0, 0), 0xf1989f07u, 0x00071001u, 0x3d3e3c15u, 0x38095851u, 0x00003a39u, EncodeMubuf0(0x1e, 16, false), EncodeMubuf1(16, 0, 0), 0xf19c9f01u, 0x00081014u, EncodeMubuf0(0x1e, 32, false), EncodeMubuf1(16, 0, 0), // Captured 016c1ab44ee6a526: pointer v2:v7 and final inverse component v5. 0xf19c9f07u, 0x00090202u, 0x28263607u, 0x221f1e29u, 0x00050403u, EncodeMubuf0(0x1e, 48, false), EncodeMubuf1(2, 0, 0), EncodeSopp(0x01)}; D::Program decoded; D::DecodeProgram(code, decoded); Check(decoded.instructions.size() == 9 && decoded.instructions.back().opcode == D::Opcode::S_ENDPGM, "decoder stopped before instructions following BVH intersections"); for (const auto index : {0u, 2u}) { const auto &inst = decoded.instructions[index]; Check(inst.opcode == D::Opcode::IMAGE_BVH_INTERSECT_RAY && inst.image_address_components == 11 && inst.data_dwords == 4 && inst.image_r128, "BVH32 instruction lost its ray or descriptor width"); } for (const auto index : {4u, 6u}) { const auto &inst = decoded.instructions[index]; Check(inst.opcode == D::Opcode::IMAGE_BVH_INTERSECT_RAY && inst.opcode_id == 0xe7 && inst.image_address_components == 12 && inst.data_dwords == 4 && inst.image_r128, "BVH64 instruction lost its full node pointer or ray width"); } Check(decoded.instructions[6].word_count == 5 && decoded.instructions[6].image_nsa_addr[0] == 7 && decoded.instructions[6].image_nsa_addr[10] == 5, "captured BVH64 NSA pointer or final ray component was not decoded"); Check(decoded.instructions[2].image_nsa_addr[9] == 58, "BVH NSA ray components were not decoded"); const auto result = RecompileForTest(code, MakeCompileOptions(ShaderType::Compute)); Check(CountSourceOccurrences(result.ir_dump, "BvhIntersect") == 4 && result.program.info.uses_dma && result.program.info.images.empty() && result.program.info.samplers.empty(), "BVH intersections did not use GPU descriptors and raw guest memory"); CheckSpirvBinaryValidates(result.spirv); const std::array literal{EncodeSMovB32(0, 255), 0xf1989f01u, EncodeSopp(0x01)}; D::DecodeProgram(literal, decoded); Check(decoded.instructions.size() == 2 && decoded.instructions[0].opcode == D::Opcode::S_MOV_B32, "BVH-shaped scalar literal was decoded as an instruction"); #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS for (const auto words : {std::array{0xf19c9f01u, 0x40081014u}, // BVH64 A16 std::array{0xf1989f01u, 0x40081014u}, // BVH32 A16 std::array{0xf1981f01u, 0x00081014u}, // not R128 std::array{0xf1989f01u, 0x80081014u}}) { // D16 const std::array unsupported{words[0], words[1], EncodeSopp(0x01)}; ExpectFatal([&]() { (void)RecompileForTest(unsupported, MakeCompileOptions(ShaderType::Compute)); }, "unsupported BVH encoding was silently accepted"); } for (const auto encoding : {0xf1989f01u, 0xf19c9f01u}) { for (const auto nsa : {1u, 2u}) { std::array truncated{encoding | (nsa << 1), 0x00081014u}; truncated[2 + nsa] = EncodeSopp(0x01); ExpectFatal([&]() { (void)RecompileForTest(std::span(truncated).first(3 + nsa), MakeCompileOptions(ShaderType::Compute)); }, "BVH NSA instruction accepted an incomplete ray address"); } } #endif }