diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index a9013344a..e6ad801c1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -710,10 +710,14 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data) { if (component >= plan.info.component_count) return; const auto bits = plan.info.component_bits[component]; - if (plan.info.type == Format::ComponentType::Snorm && bits == 16u) { + if (bits == 16u && (plan.info.type == Format::ComponentType::Snorm || + plan.info.type == Format::ComponentType::Float)) { const auto value = EmitBitCastF32U32(ctx.state, data); - data = EmitPackSnorm2x16( - ctx.state, EmitCompositeConstructF32x2(ctx.state, value, ConstantF32Value(ctx.state, 0.0f))); + const auto pair = EmitCompositeConstructF32x2(ctx.state, value, + ConstantF32Value(ctx.state, 0.0f)); + data = plan.info.type == Format::ComponentType::Float + ? EmitPackHalf2x16(ctx.state, pair) + : EmitPackSnorm2x16(ctx.state, pair); } if (bits == 8u || bits == 16u) { StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component], diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 0c53a9201..2fd746460 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -23706,6 +23706,38 @@ TestCase BufferStoreFormatXResource16UintPreservesAdjacentLanes() { return test; } +TestCase BufferStoreFormatXyzwFloat16ConvertsComponents() { + using O = ShaderOpcode; + + // This finite F32 has low bits 0x7e63, which form an F16 NaN if truncated. + constexpr std::array values = { + std::bit_cast(0x3f807e63u), -2.0f, 0.5f, 0.25f, + -4.0f, 3.0f, 0.0f, 2.0f}; + std::vector code; + for (u32 record = 0; record < 2; record++) { + for (u32 component = 0; component < 4; component++) { + AppendVMovLiteral(&code, component, + std::bit_cast(values[record * 4 + component])); + } + AppendVMovU32(&code, 20, record); + code.push_back(EncodeMubuf0(0x07u, 0, true, false)); + code.push_back(EncodeMubuf1(0, 0, 20)); + } + AppendEnd(&code); + + TestCase test; + test.name = "BufferStoreFormatXyzwFloat16ConvertsComponents"; + test.code = std::move(code); + test.initial = std::vector(4, 0xdeadbeefu); + test.expected = {0xc0003c04u, 0x34003800u, 0x4200c400u, 0x40000000u}; + test.user_data = MakeStructuredStorageBufferData( + 8, 2, false, BufferFormat(Prospero::BufferFormat::k16_16_16_16Float)); + test.has_user_data = true; + test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM}; + test.required_spirv = {"PackHalf2x16"}; + return test; +} + TestCase BufferStoreFormatXyzwSnorm16CapturedSkinningVectors() { using O = ShaderOpcode; @@ -28430,6 +28462,7 @@ std::vector MakeCases() { AddCase(BufferFormatStoreVariants); AddCase(BufferStoreFormatXResource16UintWritesHalfword); AddCase(BufferStoreFormatXResource16UintPreservesAdjacentLanes); + AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents); AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors); AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords); AddCase(BufferLoadFormatXResource8UintZeroExtendsByte); @@ -33158,6 +33191,11 @@ int main(int argc, char **argv) { RunCase(&vulkan, BufferFormatStoreVariants()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--buffer-float16-store-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--s-ashr-i64-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ScalarAshrI64Edges(false)); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index ddedfd269..11155a25f 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6711,9 +6711,9 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() { Check(result.resources.buffers.size() == 1 && result.resources.buffers[0].dwords[2] == 5u, "formatted store test did not preserve descriptor NumRecords"); - Check((result.decoded_dump.find("buffer_store_format_x") != std::string::npos), + Check((result.decoded_dump.find("BUFFER_STORE_FORMAT_X") != std::string::npos), "formatted store regression did not decode buffer_store_format_x"); - Check((result.ir_dump.find("typed=0 formatted=1") != std::string::npos), + Check((result.decoded_dump.find("typed=0 formatted=1") != std::string::npos), "formatted store regression did not preserve formatted metadata"); CheckSpirvBinaryValidates(result.spirv); @@ -6723,6 +6723,15 @@ void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() { Check( !SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"), "formatted store SPIR-V baked descriptor NumRecords into a store guard"); + + user_data[1] = 8u << 16u; + user_data[3] = static_cast(Prospero::BufferFormat::k16_16_16_16Float) << 12u; + options.user_data = user_data; + result = RecompileForTest(shader, options); + Check((DisassembleSpirvBinary(result.spirv).find("PackHalf2x16") != + std::string::npos), + "formatted half-float store did not convert F32 to F16"); + CheckSpirvBinaryValidates(result.spirv); } void TestNewShaderRecompilerTypedBufferTranslation() { @@ -13515,6 +13524,7 @@ int main() { TestNewShaderRecompilerNativeWideBufferIr(); TestNewShaderRecompilerScalarB64LaneTranslation(); TestNewShaderRecompilerMubufFormatTranslation(); + TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly(); TestNewShaderRecompilerTypedBufferTranslation(); TestNewShaderRecompilerDsReadWrite2Translation(); TestNewShaderRecompilerDsWideAndAtomicTranslation();