From de9c15fa79e1e2049054d07bdb95109d3423c800 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 5 Oct 2026 03:58:33 +0400 Subject: [PATCH] shader: encode SNORM and scaled formatted buffer stores Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> --- .../backend/spirv/spirvEmitterMemory.cpp | 45 +++++--- tests/ShaderRecompilerComputeTests.cpp | 105 ++++++++++++++++++ 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index 1393f7bbe..a863cd737 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -710,24 +710,37 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx, const Format::BufferFormatInfo& info, uint32_t component, uint32_t data) { + auto& state = ctx.state; const auto bits = info.component_bits[component]; - if (info.type == Format::ComponentType::Unorm) { - const auto value = EmitBitCastF32U32(ctx.state, data); - const auto clamped = EmitFPMin32( - ctx.state, EmitFPMax32(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)), - ConstantF32Value(ctx.state, 1.0f)); - const auto scaled = EmitFPMul32( - ctx.state, clamped, ConstantF32Value(ctx.state, float((1u << bits) - 1u))); - return Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state), - EmitFPRoundEven32(ctx.state, scaled)); + const bool normalized = info.type == Format::ComponentType::Unorm || + info.type == Format::ComponentType::Snorm; + const bool scaled = info.type == Format::ComponentType::Uscaled || + info.type == Format::ComponentType::Sscaled; + if (normalized || scaled) { + // Two-bit packed channels are unsigned even in signed formats. + const bool is_signed = bits != 2u && (info.type == Format::ComponentType::Snorm || + info.type == Format::ComponentType::Sscaled); + const auto max_value = static_cast((1u << (bits - (is_signed ? 1u : 0u))) - 1u); + const auto lower = normalized ? (is_signed ? -1.0f : 0.0f) + : (is_signed ? -max_value - 1.0f : 0.0f); + const auto upper = normalized ? 1.0f : max_value; + auto value = Select(state, TypeF32(state), EmitClassifyF32Bits(state, data).nan, + ConstantF32Value(state, 0.0f), EmitBitCastF32U32(state, data)); + value = EmitGlsl( + state, value, ConstantF32Value(state, lower), ConstantF32Value(state, upper)); + if (normalized) { + value = EmitFPRoundEven32( + state, EmitFPMul32(state, value, ConstantF32Value(state, max_value))); + } + // Float-to-integer conversion truncates scaled values toward zero. + value = Unary(state, is_signed ? spv::OpConvertFToS : spv::OpConvertFToU, + is_signed ? TypeI32(state) : TypeU32(state), value); + return is_signed ? Unary(state, spv::OpBitcast, TypeU32(state), value) : value; } - if (bits == 16u && (info.type == Format::ComponentType::Snorm || - info.type == Format::ComponentType::Float)) { - const auto value = EmitBitCastF32U32(ctx.state, data); - const auto pair = EmitCompositeConstructF32x2(ctx.state, value, - ConstantF32Value(ctx.state, 0.0f)); - return info.type == Format::ComponentType::Float ? EmitPackHalf2x16(ctx.state, pair) - : EmitPackSnorm2x16(ctx.state, pair); + if (bits == 16u && info.type == Format::ComponentType::Float) { + const auto pair = EmitCompositeConstructF32x2( + state, EmitBitCastF32U32(state, data), ConstantF32Value(state, 0.0f)); + return EmitPackHalf2x16(state, pair); } return data; } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9cbfe0229..9ddebe8c1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -28129,6 +28129,105 @@ TestCase BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords() { return test; } +std::vector FormattedStoreConversionCases() { + using F = Prospero::BufferFormat; + using O = ShaderOpcode; + struct ConversionCase { + const char *name[2]; + F format; + u32 components; + std::array values; + u32 expected; + }; + constexpr float nan = std::bit_cast(0x7fc12345u); + constexpr float inf = std::bit_cast(0x7f800000u); + constexpr ConversionCase inputs[] = { + {{"BufferStoreSnorm8Range", "TBufferStoreSnorm8Range"}, + F::k8_8_8_8SNorm, 4, {-2.0f, 0.25f, 0.75f, 2.0f}, 0x7f5f2081u}, + {{"BufferStoreSnorm8Ties", "TBufferStoreSnorm8Ties"}, + F::k8_8SNorm, 2, {0.5f, -0.5f}, 0xffffc040u}, + {{"BufferStoreSnormPackedAlpha", "TBufferStoreSnormPackedAlpha"}, + F::k10_10_10_2SNorm, 4, {-1.0f, -0.5f, 0.5f, 1.0f}, 0xd00c0201u}, + {{"BufferStoreSnormPackedFirstChannel", "TBufferStoreSnormPackedFirstChannel"}, + F::k2_10_10_10SNorm, 4, {1.0f, -1.0f, 0.5f, -0.5f}, 0xc0100807u}, + {{"BufferStoreSnormPackedZeroFillX", "TBufferStoreSnormPackedZeroFillX"}, + F::k10_10_10_2SNorm, 1, {-1.0f}, 0x00000201u}, + {{"BufferStoreSnormPackedZeroFillXy", "TBufferStoreSnormPackedZeroFillXy"}, + F::k10_10_10_2SNorm, 2, {-1.0f, 1.0f}, 0x0007fe01u}, + {{"BufferStoreSnormPackedZeroFillXyz", "TBufferStoreSnormPackedZeroFillXyz"}, + F::k10_10_10_2SNorm, 3, {-1.0f, 1.0f, -1.0f}, 0x2017fe01u}, + {{"BufferStoreSnorm8ZeroFillX", "TBufferStoreSnorm8ZeroFillX"}, + F::k8_8_8_8SNorm, 1, {-1.0f}, 0x00000081u}, + {{"BufferStoreSnorm16ZeroFillX", "TBufferStoreSnorm16ZeroFillX"}, + F::k16_16SNorm, 1, {-1.0f}, 0x00008001u}, + {{"BufferStoreUscaledZeroFillXy", "TBufferStoreUscaledZeroFillXy"}, + F::k8_8_8_8UScaled, 2, {42.0f, 99.0f}, 0x0000632au}, + {{"BufferStoreSscaledZeroFillXyz", "TBufferStoreSscaledZeroFillXyz"}, + F::k8_8_8_8SScaled, 3, {-42.0f, 127.0f, -128.0f}, 0x00807fd6u}, + {{"BufferStoreUscaled8Fraction", "TBufferStoreUscaled8Fraction"}, + F::k8UScaled, 1, {42.875f}, 0xffffff2au}, + {{"BufferStoreSscaled8Fraction", "TBufferStoreSscaled8Fraction"}, + F::k8SScaled, 1, {-42.875f}, 0xffffffd6u}, + {{"BufferStoreUscaled8Range", "TBufferStoreUscaled8Range"}, + F::k8_8_8_8UScaled, 4, {-1.0f, 255.875f, 256.0f, 0.0f}, 0x00ffff00u}, + {{"BufferStoreSscaled8Range", "TBufferStoreSscaled8Range"}, + F::k8_8_8_8SScaled, 4, {-129.0f, -128.75f, 127.875f, 128.0f}, 0x7f7f8080u}, + {{"BufferStoreUscaled16Range", "TBufferStoreUscaled16Range"}, + F::k16_16UScaled, 2, {65536.0f, 42.875f}, 0x002affffu}, + {{"BufferStoreSscaled16Range", "TBufferStoreSscaled16Range"}, + F::k16_16SScaled, 2, {-32769.0f, 32768.0f}, 0x7fff8000u}, + {{"BufferStoreUscaledPacked", "TBufferStoreUscaledPacked"}, + F::k10_10_10_2UScaled, 4, {1024.0f, 3.875f, -1.0f, 4.0f}, 0xc0000fffu}, + {{"BufferStoreSscaledPackedAlpha", "TBufferStoreSscaledPackedAlpha"}, + F::k10_10_10_2SScaled, 4, {-512.75f, 511.875f, -3.875f, 3.875f}, 0xffd7fe00u}, + {{"BufferStoreSscaledPackedFirstChannel", "TBufferStoreSscaledPackedFirstChannel"}, + F::k2_10_10_10SScaled, 4, {3.0f, -512.0f, 511.0f, -1.0f}, 0xffdff803u}, + {{"BufferStoreSnorm8SpecialValues", "TBufferStoreSnorm8SpecialValues"}, + F::k8_8_8_8SNorm, 4, {nan, inf, -inf, -0.0f}, 0x00817f00u}, + {{"BufferStoreUscaled8SpecialValues", "TBufferStoreUscaled8SpecialValues"}, + F::k8_8_8_8UScaled, 4, {nan, inf, -inf, -0.0f}, 0x0000ff00u}, + {{"BufferStoreSscaled8SpecialValues", "TBufferStoreSscaled8SpecialValues"}, + F::k8_8_8_8SScaled, 4, {nan, inf, -inf, -0.0f}, 0x00807f00u}, + }; + constexpr O store_opcodes[2][4] = { + {O::BUFFER_STORE_FORMAT_X, O::BUFFER_STORE_FORMAT_XY, + O::BUFFER_STORE_FORMAT_XYZ, O::BUFFER_STORE_FORMAT_XYZW}, + {O::TBUFFER_STORE_FORMAT_X, O::TBUFFER_STORE_FORMAT_XY, + O::TBUFFER_STORE_FORMAT_XYZ, O::TBUFFER_STORE_FORMAT_XYZW}, + }; + std::vector cases; + for (const auto &input : inputs) { + for (const bool typed : {false, true}) { + TestCase test; + test.name = input.name[typed]; + for (u32 component = 0; component < input.components; ++component) { + AppendVMovLiteral(&test.code, component, + std::bit_cast(input.values[component])); + } + AppendVMovU32(&test.code, 20, 4); + const u32 opcode = 3 + input.components; + if (typed) { + const u32 format = BufferFormat(input.format); + test.code.push_back(EncodeMtbuf0(opcode, format & 0xfu, format >> 4u)); + test.code.push_back(EncodeMtbuf1(opcode, 0, 0, 20)); + } else { + test.code.push_back(EncodeMubuf0(opcode)); + test.code.push_back(EncodeMubuf1(0, 0, 20)); + } + AppendEnd(&test.code); + test.initial = {0xdeadbeefu, 0xffffffffu, 0xcafef00du}; + test.expected = {0xdeadbeefu, input.expected, 0xcafef00du}; + test.user_data = MakeStructuredStorageBufferData( + 0, 12, false, BufferFormat(input.format)); + test.has_user_data = true; + test.opcodes = {O::V_MOV_B32, store_opcodes[typed][input.components - 1], + O::S_ENDPGM}; + cases.push_back(std::move(test)); + } + } + return cases; +} + TestCase BufferLoadFormatXyResource88UintExtractsBytes() { using O = ShaderOpcode; @@ -34526,6 +34625,9 @@ std::vector MakeCases() { AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents); AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors); AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords); + for (auto &test : FormattedStoreConversionCases()) { + cases.push_back(std::move(test)); + } AddCase(BufferLoadFormatXResource8UintZeroExtendsByte); AddCase(BufferLoadFormatXyResource88UintExtractsBytes); AddCase(BufferLoadFormatXyResource8888UnormConvertsFirstTwoComponents); @@ -39717,6 +39819,9 @@ int main(int argc, char **argv) { RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents()); RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors()); RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords()); + for (const auto &test : FormattedStoreConversionCases()) { + RunCase(&vulkan, test); + } RunCase(&vulkan, BufferStoreFormatXResource16UintWritesHalfword()); RunCase(&vulkan, BufferStoreFormatXResource16UintPreservesAdjacentLanes()); RunCase(&vulkan, BufferStoreFormatXyResource88UintWritesBytes());