From 213d9fb4a7c13b12ac296d3d07e72d47af942122 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Tue, 29 Sep 2026 17:46:53 -0400 Subject: [PATCH] Just force reads on 8/16bits --- .../backend/spirv/spirv_emit_context.cpp | 49 +++++--- .../ir_opt/collect_shader_info_pass.cpp | 4 + .../ir_opt/constant_propagation_pass.cpp | 16 +++ .../global_memory_to_storage_buffer_pass.cpp | 110 +++++++++++++++--- 4 files changed, 144 insertions(+), 35 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 605de049d6..f174251625 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -962,7 +962,7 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) { OpBranchConditional(cond, then_label, else_label); AddLabel(then_label); const Id word{OpShiftRightLogical(U32[1], offset, Const(2U))}; - const Id result{callback(ssbos[index].U32, word, offset, value)}; + const Id result{callback(ssbos[index], word, offset, value)}; if (returns_value) { OpReturnValue(result); } else { @@ -978,11 +978,22 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) { OpFunctionEnd(); global_memory_funcs.emplace(opcode, func); }}; + const auto vector_pointer{[&](const StorageDefinitions& ssbo, Id word, u32 count) { + if (count == 2) { + return OpAccessChain(storage_types.U32x2.element, ssbo.U32x2, zero, + OpShiftRightLogical(U32[1], word, Const(1U))); + } + return OpAccessChain(storage_types.U32x4.element, ssbo.U32x4, zero, + OpShiftRightLogical(U32[1], word, Const(2U))); + }}; const auto load{[&](Id type, u32 count) { - return [&, type, count](Id ssbo, Id word, Id, Id) { + return [&, type, count](const StorageDefinitions& ssbo, Id word, Id, Id) { + if (count > 1 && profile.support_descriptor_aliasing) { + return OpLoad(type, vector_pointer(ssbo, word, count)); + } std::array words{}; for (u32 element = 0; element < count; ++element) { - words[element] = OpLoad(U32[1], word_pointer(ssbo, word, element)); + words[element] = OpLoad(U32[1], word_pointer(ssbo.U32, word, element)); } if (count == 1) { return words[0]; @@ -991,21 +1002,25 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) { }; }}; const auto store{[&](u32 count) { - return [&, count](Id ssbo, Id word, Id, Id value) { + return [&, count](const StorageDefinitions& ssbo, Id word, Id, Id value) { + if (count > 1 && profile.support_descriptor_aliasing) { + OpStore(vector_pointer(ssbo, word, count), value); + return Id{}; + } if (count == 1) { - OpStore(word_pointer(ssbo, word, 0), value); + OpStore(word_pointer(ssbo.U32, word, 0), value); return Id{}; } for (u32 element = 0; element < count; ++element) { - OpStore(word_pointer(ssbo, word, element), + OpStore(word_pointer(ssbo.U32, word, element), OpCompositeExtract(U32[1], value, element)); } return Id{}; }; }}; const auto extract{[&](bool is_signed, u32 count) { - return [&, is_signed, count](Id ssbo, Id word, Id offset, Id) { - const Id loaded{OpLoad(U32[1], word_pointer(ssbo, word, 0))}; + return [&, is_signed, count](const StorageDefinitions& ssbo, Id word, Id offset, Id) { + const Id loaded{OpLoad(U32[1], word_pointer(ssbo.U32, word, 0))}; if (is_signed) { return OpBitFieldSExtract(U32[1], loaded, bits(offset, count), Const(count)); } @@ -1013,28 +1028,28 @@ void EmitContext::DefineGlobalMemoryFunctions(const IR::Program& program) { }; }}; const auto insert{[&](u32 count) { - return [&, count](Id ssbo, Id word, Id offset, Id value) { - AtomicBitFieldInsert(word_pointer(ssbo, word, 0), value, bits(offset, count), + return [&, count](const StorageDefinitions& ssbo, Id word, Id offset, Id value) { + AtomicBitFieldInsert(word_pointer(ssbo.U32, word, 0), value, bits(offset, count), Const(count)); return Id{}; }; }}; const auto atomic{[&](Id (Sirit::Module::*func)(Id, Id, Id, Id, Id)) { - return [&, func](Id ssbo, Id word, Id, Id value) { - return (this->*func)(U32[1], word_pointer(ssbo, word, 0), scope, zero, value); + return [&, func](const StorageDefinitions& ssbo, Id word, Id, Id value) { + return (this->*func)(U32[1], word_pointer(ssbo.U32, word, 0), scope, zero, value); }; }}; const auto cas{[&](Id type, Id helper) { - return [&, type, helper](Id ssbo, Id word, Id, Id value) { - return OpFunctionCall(type, helper, word, value, ssbo); + return [&, type, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) { + return OpFunctionCall(type, helper, word, value, ssbo.U32); }; }}; const auto packed{[&](bool is_half, Id helper) { - return [&, is_half, helper](Id ssbo, Id word, Id, Id value) { + return [&, is_half, helper](const StorageDefinitions& ssbo, Id word, Id, Id value) { if (is_half) { - return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, word, value, ssbo)); + return OpBitcast(U32[1], OpFunctionCall(F16[2], helper, word, value, ssbo.U32)); } - return OpPackHalf2x16(U32[1], OpFunctionCall(F32[2], helper, word, value, ssbo)); + return OpPackHalf2x16(U32[1], OpFunctionCall(F32[2], helper, word, value, ssbo.U32)); }; }}; for (const IR::Block* const block : program.post_order_blocks) { diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index def242be8f..441058c64d 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -632,6 +632,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::StorageAtomicExchange32: info.used_storage_buffer_types |= IR::Type::U32; break; + case IR::Opcode::LoadGlobal64: + case IR::Opcode::WriteGlobal64: case IR::Opcode::LoadStorage64: case IR::Opcode::WriteStorage64: case IR::Opcode::StorageAtomicIAdd32x2: @@ -645,6 +647,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::StorageAtomicExchange32x2: info.used_storage_buffer_types |= IR::Type::U32x2; break; + case IR::Opcode::LoadGlobal128: + case IR::Opcode::WriteGlobal128: case IR::Opcode::LoadStorage128: case IR::Opcode::WriteStorage128: info.used_storage_buffer_types |= IR::Type::U32x4; diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp index 6285fef16f..279d32d9e4 100644 --- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp @@ -414,6 +414,14 @@ void FoldSelect(IR::Inst& inst) { } } +void FoldAtomicWrap(IR::Inst& inst, IR::Opcode opcode, u32 addend) { + const IR::Value limit{inst.Arg(1)}; + if (limit.IsImmediate() && limit.U32() == 0xFFFFFFFFU) { + inst.ReplaceOpcode(opcode); + inst.SetArg(1, IR::Value{addend}); + } +} + void FoldFPAdd32(IR::Inst& inst) { if (FoldWhenAllImmediates(inst, [](f32 a, f32 b) { return a + b; })) { return; @@ -1104,6 +1112,14 @@ void ConstantPropagation(Environment& env, IR::Block& block, IR::Inst& inst) { IR::Opcode::CompositeInsertF16x4); case IR::Opcode::FSwizzleAdd: return FoldFSwizzleAdd(block, inst); + case IR::Opcode::GlobalAtomicInc32: + return FoldAtomicWrap(inst, IR::Opcode::GlobalAtomicIAdd32, 1U); + case IR::Opcode::GlobalAtomicDec32: + return FoldAtomicWrap(inst, IR::Opcode::GlobalAtomicIAdd32, 0xFFFFFFFFU); + case IR::Opcode::SharedAtomicInc32: + return FoldAtomicWrap(inst, IR::Opcode::SharedAtomicIAdd32, 1U); + case IR::Opcode::SharedAtomicDec32: + return FoldAtomicWrap(inst, IR::Opcode::SharedAtomicIAdd32, 0xFFFFFFFFU); case IR::Opcode::GetCbufF32: case IR::Opcode::GetCbufU32: if (env.HasHLEMacroState()) { diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp index ba05295fab..bf40b0695b 100644 --- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp +++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp @@ -4,14 +4,15 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include +#include #include #include #include "common/alignment.h" #include "shader_recompiler/frontend/ir/basic_block.h" -#include "shader_recompiler/frontend/ir/breadth_first_search.h" #include "shader_recompiler/frontend/ir/ir_emitter.h" #include "shader_recompiler/frontend/ir/value.h" #include "shader_recompiler/host_translate_info.h" @@ -49,6 +50,7 @@ using StorageBufferSet = using StorageInstVector = small_vector; using StorageWritesSet = flat_set, small_vector>; +using LocalStores = std::multimap; struct StorageInfo { StorageBufferSet set; @@ -333,7 +335,7 @@ std::optional TrackLowAddress(IR::Inst* inst) { } /// Tries to track the storage buffer address used by a global memory instruction -std::optional Track(const IR::Value& value, const Bias* bias) { +StorageBufferSet Track(const IR::Value& value, const Bias* bias, const LocalStores& local_stores) { const auto pred{[bias](const IR::Inst* inst) -> std::optional { if (inst->GetOpcode() != IR::Opcode::GetCbufU32 && inst->GetOpcode() != IR::Opcode::GetCbufU32x2) { @@ -366,11 +368,83 @@ std::optional Track(const IR::Value& value, const Bias* bias) } return storage_buffer; }}; - return BreadthFirstSearch(value, pred); + StorageBufferSet result; + std::unordered_set visited; + small_vector pending; + const auto push{[&](const IR::Value& arg) { + if (!arg.IsImmediate() && visited.insert(arg.InstRecursive()).second) { + pending.push_back(arg.InstRecursive()); + } + }}; + push(value); + while (!pending.empty()) { + const IR::Inst* const inst{pending.back()}; + pending.pop_back(); + if (const std::optional storage_buffer{pred(inst)}) { + result.insert(*storage_buffer); + continue; + } + switch (inst->GetOpcode()) { + case IR::Opcode::LoadLocal: + if (inst->Arg(0).IsImmediate()) { + const auto [begin, end]{local_stores.equal_range(inst->Arg(0).U32())}; + for (auto it = begin; it != end; ++it) { + push(it->second); + } + } + continue; + case IR::Opcode::SelectU32: + case IR::Opcode::SelectU64: + push(inst->Arg(1)); + push(inst->Arg(2)); + continue; + case IR::Opcode::GetCbufU8: + case IR::Opcode::GetCbufS8: + case IR::Opcode::GetCbufU16: + case IR::Opcode::GetCbufS16: + case IR::Opcode::GetCbufU32: + case IR::Opcode::GetCbufF32: + case IR::Opcode::GetCbufU32x2: + case IR::Opcode::LoadSharedU8: + case IR::Opcode::LoadSharedS8: + case IR::Opcode::LoadSharedU16: + case IR::Opcode::LoadSharedS16: + case IR::Opcode::LoadSharedU32: + case IR::Opcode::LoadSharedU64: + case IR::Opcode::LoadSharedU128: + continue; + default: + break; + } + if (IsGlobalMemory(*inst) || inst->MayHaveSideEffects()) { + continue; + } + for (size_t arg = 0; arg < inst->NumArgs(); ++arg) { + push(inst->Arg(arg)); + } + } + return result; +} + +LocalStores GatherLocalStores(const IR::Program& program) { + LocalStores stores; + for (IR::Block* const block : program.post_order_blocks) { + for (const IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() != IR::Opcode::WriteLocal) { + continue; + } + if (!inst.Arg(0).IsImmediate()) { + return {}; + } + stores.emplace(inst.Arg(0).U32(), inst.Arg(1)); + } + } + return stores; } /// Collects the storage buffer used by a global memory instruction and the instruction itself -void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info) { +void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info, + const LocalStores& local_stores) { // NVN puts storage buffers in a specific range, we have to bias towards these addresses to // avoid getting false positives static constexpr Bias nvn_bias{ @@ -387,25 +461,24 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info) } // First try to find storage buffers in the NVN address const IR::U32 low_addr{low_addr_info->value}; - std::optional storage_buffer{Track(low_addr, &nvn_bias)}; - if (!storage_buffer) { + StorageBufferSet candidates{Track(low_addr, &nvn_bias, local_stores)}; + if (candidates.empty()) { // If it fails, track without a bias - storage_buffer = Track(low_addr, nullptr); - if (!storage_buffer) { - // If that also fails, use NVN fallbacks - LOG_WARNING(Shader, "Storage buffer failed to track, using global memory fallbacks"); - return; - } - LOG_WARNING(Shader, "Storage buffer tracked without bias, index {} offset {}", - storage_buffer->index, storage_buffer->offset); + candidates = Track(low_addr, nullptr, local_stores); } + if (candidates.size() != 1) { + // If that also fails, use NVN fallbacks + LOG_WARNING(Shader, "Storage buffer failed to track, using global memory fallbacks"); + return; + } + const StorageBufferAddr storage_buffer{*candidates.begin()}; // Collect storage buffer and the instruction if (IsGlobalMemoryWrite(inst)) { - info.writes.insert(*storage_buffer); + info.writes.insert(storage_buffer); } - info.set.insert(*storage_buffer); + info.set.insert(storage_buffer); info.to_replace.push_back(StorageInst{ - .storage_buffer{*storage_buffer}, + .storage_buffer{storage_buffer}, .inst = &inst, .block = &block, }); @@ -525,12 +598,13 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index, void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) { StorageInfo info; + const LocalStores local_stores{GatherLocalStores(program)}; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { if (!IsGlobalMemory(inst)) { continue; } - CollectStorageBuffers(*block, inst, info); + CollectStorageBuffers(*block, inst, info, local_stores); } } for (const StorageBufferAddr& storage_buffer : info.set) {