diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index a1decba73..440aba8a3 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -514,31 +514,6 @@ void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem) }); } -void FormattedStorePrepared(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem, - uint32_t component, const MemoryResourceAccess& resource, - uint32_t data) { - const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem)); - if (info.type == Format::ComponentType::Unknown) { - StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, component), resource, data); - return; - } - if (component >= info.component_count) return; - const auto bits = info.component_bits[component]; - const auto component_mem = RebaseFormattedComponent(mem, info, component); - if (bits == 8u || bits == 16u) { - StoreSubwordPrepared(ctx, inst, component_mem, resource, bits, data); - } else { - StoreWordPrepared(ctx, inst, component_mem, resource, data); - } -} - -void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) { - EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() { - const auto resource = PrepareMemoryResourceAccess(ctx.state, mem); - FormattedStorePrepared(ctx, inst, mem, 0u, resource, ctx.Arg(inst, inst.NumArgs() - 2)); - }); -} - spv::Op SpirvAtomicOpcode(IR::ValueOpcode opcode) { switch (opcode) { case IR::ValueOpcode::BufferAtomicCmpSwap32: return spv::OpAtomicCompareExchange; @@ -729,6 +704,11 @@ 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) { + const auto value = EmitBitCastF32U32(ctx.state, data); + data = EmitPackSnorm2x16( + ctx.state, EmitCompositeConstructF32x2(ctx.state, value, ConstantF32Value(ctx.state, 0.0f))); + } if (bits == 8u || bits == 16u) { StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component], plan.indices[component], bits, data); @@ -737,6 +717,23 @@ void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem, } } +void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) { + EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() { + const auto resource = PrepareMemoryResourceAccess(ctx.state, mem); + const auto data = ctx.Arg(inst, inst.NumArgs() - 2); + const auto info = Format::GetFormatInfo(BufferFormat(ctx, mem)); + if (info.type == Format::ComponentType::Unknown) { + StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data); + return; + } + const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, 1u, + FormattedAccess::Store); + EmitIfCondition(ctx.state, plan.in_bounds, [&]() { + StoreFormattedInBounds(ctx, mem, plan, 0u, data); + }); + }); +} + uint32_t LoadWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t components) { auto& state = ctx.state; return EmitValueOrDefaultIfCondition( diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ffe85b57b..73b8c8aa7 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -22382,6 +22382,63 @@ TestCase BufferStoreFormatXResource16UintPreservesAdjacentLanes() { return test; } +TestCase BufferStoreFormatXyzwSnorm16CapturedSkinningVectors() { + using O = ShaderOpcode; + + // Normal and tangent from the character skinning dispatch (eadfd178c07feebd). + constexpr std::array values = { + 0x3d98f7c5u, 0xbedae712u, 0xbf66a19eu, 0x3f800000u, + 0x3f7e6baeu, 0xbd339a7du, 0x3dd0a2e2u, 0xbf800000u}; + std::vector code; + for (u32 record = 0; record < 2; record++) { + for (u32 component = 0; component < 4; component++) { + AppendVMovLiteral(&code, component, 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 = "BufferStoreFormatXyzwSnorm16CapturedSkinningVectors"; + test.code = std::move(code); + test.initial = std::vector(4, 0xdeadbeefu); + test.expected = {0xc947098fu, 0x7fff8cb0u, 0xfa637f35u, 0x80010d0au}; + test.user_data = MakeStructuredStorageBufferData( + 8, 2, false, BufferFormat(Prospero::BufferFormat::k16_16_16_16SNorm)); + test.has_user_data = true; + test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM}; + return test; +} + +TestCase BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords() { + using O = ShaderOpcode; + + constexpr std::array values = { + -2.0f, -1.0f, -0.75f, -0.25f, 0.0f, 0.25f, 0.75f, 1.0f, 2.0f}; + std::vector code; + for (u32 i = 0; i < values.size(); i++) { + AppendVMovLiteral(&code, 0, std::bit_cast(values[i])); + AppendVMovU32(&code, 20, i * 2 + 2); + code.push_back(EncodeMubuf0(0x04u)); + code.push_back(EncodeMubuf1(0, 0, 20)); + } + AppendEnd(&code); + + TestCase test; + test.name = "BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords"; + test.code = std::move(code); + test.initial = std::vector(6, 0xdeadbeefu); + test.expected = {0x8001beefu, 0xa0018001u, 0x0000e000u, + 0x5fff2000u, 0x7fff7fffu, 0xdeadbeefu}; + test.user_data = MakeStructuredStorageBufferData( + 0, 24, false, BufferFormat(Prospero::BufferFormat::k16SNorm)); + test.has_user_data = true; + test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM}; + return test; +} + TestCase BufferLoadFormatXyResource88UintExtractsBytes() { using O = ShaderOpcode; @@ -26901,6 +26958,8 @@ std::vector MakeCases() { AddCase(BufferFormatStoreVariants); AddCase(BufferStoreFormatXResource16UintWritesHalfword); AddCase(BufferStoreFormatXResource16UintPreservesAdjacentLanes); + AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors); + AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords); AddCase(BufferLoadFormatXResource8UintZeroExtendsByte); AddCase(BufferLoadFormatXyResource88UintExtractsBytes); AddCase(BufferLoadFormatXyResource8888UnormConvertsFirstTwoComponents); @@ -31512,6 +31571,18 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--buffer-snorm-store-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors()); + RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords()); + RunCase(&vulkan, BufferStoreFormatXResource16UintWritesHalfword()); + RunCase(&vulkan, BufferStoreFormatXResource16UintPreservesAdjacentLanes()); + RunCase(&vulkan, BufferStoreFormatXyResource88UintWritesBytes()); + RunCase(&vulkan, BufferStoreFormatXyzwDropsPartialRecord()); + RunCase(&vulkan, BufferStoreFormatXChecksOnlyTransferredComponent()); + RunCase(&vulkan, BufferFormatStoreVariants()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--s-ashr-i64-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ScalarAshrI64Edges(false));