-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
shader_recompiler: split resource tracking and flatten load from buffer for sharp source #4782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
bee1833
42b7bd2
aac91f0
048268b
3800d37
dd46168
a68eedd
4efec91
645f373
2bcc838
2f527a3
b932e75
07450be
d6bc173
1f34d19
dc69f65
4c83593
4eb3e36
5640a7e
d84e79b
4b096b3
97abe21
926bfa9
8e25815
e8eb5d5
2bdc50c
0f861dd
02e8676
6cbc39d
54d7bc3
1dc5c32
cfb141c
07602af
0624f6f
15ab3e9
b849e31
18c62e8
40a5e85
b7bd91a
df0d6b4
8643ae8
722e2ac
00a0371
630c407
265d3d3
5023544
09818a0
c6c9028
a31e940
8ffc057
f0f0535
5f879a2
54d079b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,7 +122,7 @@ using namespace Shader; | |
|
|
||
| struct PassInfo { | ||
| // map offset to inst | ||
| using PtrUserList = boost::container::flat_map<u32, Shader::IR::Inst*>; | ||
| using PtrUserList = boost::container::flat_map<u16, Shader::IR::Inst*>; | ||
|
|
||
| Optimization::SrtGvnTable gvn_table; | ||
| // keys are GetUserData or ReadConst instructions that are used as pointers | ||
|
|
@@ -134,7 +134,7 @@ struct PassInfo { | |
| std::unordered_map<u32, IR::Inst*> vn_to_inst; | ||
|
|
||
| // Bumped during codegen to assign offsets to readconsts | ||
| u32 dst_off_dw; | ||
| u16 dst_off_dw; | ||
|
|
||
| PtrUserList* GetUsesAsPointer(IR::Inst* inst) { | ||
| auto it = pointer_uses.find(inst); | ||
|
|
@@ -158,6 +158,24 @@ namespace Shader::Optimization { | |
|
|
||
| namespace { | ||
|
|
||
| static inline u16 GetFlatbufOffset(const IR::Inst* inst) { | ||
| if (inst->GetOpcode() == IR::Opcode::ReadConstBuffer) { | ||
| auto inst_info = inst->Flags<IR::BufferInstInfo>(); | ||
| return inst_info.flatbuf_off_dw; | ||
| } | ||
| return inst->Flags<u16>(); | ||
| } | ||
|
|
||
| static inline void SetFlatbufOffset(IR::Inst* inst, u16 offset) { | ||
| if (inst->GetOpcode() == IR::Opcode::ReadConstBuffer) { | ||
| auto inst_info = inst->Flags<IR::BufferInstInfo>(); | ||
| inst_info.flatbuf_off_dw.Assign(offset); | ||
| inst->SetFlags(inst_info); | ||
| } else { | ||
| inst->SetFlags(offset); | ||
| } | ||
| } | ||
|
|
||
| static inline void PushPtr(Xbyak::CodeGenerator& c, u32 off_dw) { | ||
| c.push(rdi); | ||
| c.mov(rdi, ptr[rdi + (off_dw << 2)]); | ||
|
|
@@ -184,7 +202,7 @@ static void VisitPointer(u32 off_dw, IR::Inst* subtree, PassInfo& pass_info, | |
| c.mov(r10d, ptr[rdi + (src_off_dw << 2)]); | ||
|
LNDF marked this conversation as resolved.
Outdated
|
||
| c.mov(ptr[rsi + (pass_info.dst_off_dw << 2)], r10d); | ||
|
|
||
| use->SetFlags<u32>(pass_info.dst_off_dw); | ||
| SetFlatbufOffset(use, pass_info.dst_off_dw); | ||
| pass_info.dst_off_dw++; | ||
| } | ||
|
|
||
|
|
@@ -250,7 +268,17 @@ void FlattenExtendedUserdataPass(IR::Program& program) { | |
| r_it++) { | ||
| IR::Block* block = *r_it; | ||
| for (IR::Inst& inst : *block) { | ||
| if (inst.GetOpcode() == IR::Opcode::ReadConst) { | ||
| if (inst.GetOpcode() == IR::Opcode::ReadConst || | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. condition can be inverted to reduce nesting
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| inst.GetOpcode() == IR::Opcode::ReadConstBuffer) { | ||
| if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer) { | ||
| // Only flatten ReadConstBuffer if it was marked as a sharp source in the | ||
| // resource discovery pass | ||
| auto inst_info = inst.Flags<IR::BufferInstInfo>(); | ||
| if (!inst_info.sharp_source) { | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| if (!inst.Arg(1).IsImmediate()) { | ||
| LOG_WARNING(Render_Recompiler, "ReadConst has non-immediate offset"); | ||
| continue; | ||
|
|
@@ -298,7 +326,7 @@ void FlattenExtendedUserdataPass(IR::Program& program) { | |
| for (IR::Inst* readconst : all_readconsts) { | ||
| ASSERT(pass_info.vn_to_inst.contains(pass_info.gvn_table.GetValueNumber(readconst))); | ||
| IR::Inst* original = pass_info.DeduplicateInstruction(readconst); | ||
| readconst->SetFlags<u32>(original->Flags<u32>()); | ||
| SetFlatbufOffset(readconst, GetFlatbufOffset(original)); | ||
| } | ||
|
|
||
| info.RefreshFlatBuf(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include <queue> | ||
| #include "shader_recompiler/frontend/control_flow_graph.h" | ||
| #include "shader_recompiler/ir/passes/ir_passes.h" | ||
| #include "shader_recompiler/ir/passes/resource_pass.h" | ||
| #include "shader_recompiler/ir/program.h" | ||
| #include "shader_recompiler/profile.h" | ||
|
|
||
| namespace Shader::Optimization { | ||
|
|
||
| const IR::Inst* TryDisableAnisoLod0(const IR::Inst* inst) { | ||
| // Find sample source trying to disable anisotropy for lod0. | ||
| // Assuming S# is in UD s[12:15] and T# is in s[4:11] | ||
| // The next pattern: | ||
| // s_bfe_u32 s0, s7, $0x0008000c | ||
| // s_and_b32 s1, s12, $0xfffff1ff | ||
| // s_cmp_eq_u32 s0, 0 | ||
| // s_cselect_b32 s0, s1, s12 | ||
| // is used to disable anisotropy in the sampler if the sampled texture doesn't have mips | ||
|
|
||
| if (inst->GetOpcode() != IR::Opcode::SelectU32) { | ||
| return inst; | ||
| } | ||
|
|
||
| // Select should be based on zero check | ||
| const auto* prod0 = inst->Arg(0).InstRecursive(); | ||
| if (prod0->GetOpcode() != IR::Opcode::IEqual32 || | ||
| !(prod0->Arg(1).IsImmediate() && prod0->Arg(1).U32() == 0u)) { | ||
| return inst; | ||
| } | ||
|
|
||
| // The bitfield extract might be hidden by phi sometimes | ||
| auto* prod0_arg0 = prod0->Arg(0).InstRecursive(); | ||
| if (prod0_arg0->GetOpcode() == IR::Opcode::Phi) { | ||
| auto arg0 = prod0_arg0->Arg(0); | ||
| auto arg1 = prod0_arg0->Arg(1); | ||
| if (!arg0.IsImmediate() && | ||
| arg0.InstRecursive()->GetOpcode() == IR::Opcode::BitFieldUExtract) { | ||
| prod0_arg0 = arg0.InstRecursive(); | ||
| } else if (!arg1.IsImmediate() && | ||
| arg1.InstRecursive()->GetOpcode() == IR::Opcode::BitFieldUExtract) { | ||
| prod0_arg0 = arg1.InstRecursive(); | ||
| } | ||
| } | ||
|
|
||
| // The bits range is for lods (note that constants are changed after constant propagation pass) | ||
| if (prod0_arg0->GetOpcode() != IR::Opcode::BitFieldUExtract || | ||
| !(prod0_arg0->Arg(1).IsImmediate() && prod0_arg0->Arg(1).U32() == 12) || | ||
| !(prod0_arg0->Arg(2).IsImmediate() && prod0_arg0->Arg(2).U32() == 8)) { | ||
| return inst; | ||
| } | ||
|
|
||
| // Make sure mask is masking out anisotropy | ||
| const auto* prod1 = inst->Arg(1).InstRecursive(); | ||
| if (prod1->GetOpcode() != IR::Opcode::BitwiseAnd32 || prod1->Arg(1).U32() != 0xfffff1ff) { | ||
| return inst; | ||
| } | ||
|
|
||
| // We're working on the first dword of s# | ||
| const auto* prod2 = inst->Arg(2).InstRecursive(); | ||
| if (prod2->GetOpcode() != IR::Opcode::GetUserData && | ||
| prod2->GetOpcode() != IR::Opcode::ReadConst && prod2->GetOpcode() != IR::Opcode::Phi) { | ||
| return inst; | ||
| } | ||
|
|
||
| return prod2; | ||
| } | ||
|
|
||
| bool IsSharpSource(const IR::Inst* inst) { | ||
| return inst->GetOpcode() == IR::Opcode::GetUserData || | ||
| inst->GetOpcode() == IR::Opcode::ReadConst || | ||
| inst->GetOpcode() == IR::Opcode::ReadConstBuffer; | ||
| } | ||
|
|
||
| bool IsCfgBlockDominatedBy(const Shader::Gcn::Block* maybe_dominator, | ||
| const Shader::Gcn::Block* block, const Shader::Gcn::Block* dest_block) { | ||
| if (block == maybe_dominator) { | ||
| return true; | ||
| } | ||
|
|
||
| boost::container::small_vector<const Shader::Gcn::Block*, 8> visited; | ||
| std::queue<const Shader::Gcn::Block*> queue; | ||
| queue.push(block); | ||
|
|
||
| while (!queue.empty()) { | ||
| const Shader::Gcn::Block* block{queue.front()}; | ||
| queue.pop(); | ||
| if (block == dest_block) { | ||
| return false; | ||
| } | ||
| if (block == maybe_dominator) { | ||
| continue; | ||
| } | ||
| if (block->branch_false && !std::ranges::contains(visited, block->branch_false)) { | ||
| visited.push_back(block->branch_false); | ||
| queue.push(block->branch_false); | ||
| } | ||
| if (block->branch_true && !std::ranges::contains(visited, block->branch_true)) { | ||
| visited.push_back(block->branch_true); | ||
| queue.push(block->branch_true); | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| const IR::Inst* FindSharpSource(IR::Inst* handle, const IR::Block& current_parent, u32 pc = 0) { | ||
| if (IsSharpSource(handle)) { | ||
| return const_cast<IR::Inst*>(handle); | ||
| } | ||
|
|
||
| boost::container::small_vector<IR::Inst*, 8> visited, sources; | ||
| std::queue<IR::Inst*> queue; | ||
| queue.push(handle); | ||
|
|
||
| while (!queue.empty()) { | ||
| IR::Inst* inst{queue.front()}; | ||
| queue.pop(); | ||
| if (IsSharpSource(inst)) { | ||
| sources.push_back(inst); | ||
| continue; | ||
| } | ||
| if (inst->GetOpcode() != IR::Opcode::Phi) { | ||
| continue; | ||
| } | ||
| for (size_t arg = inst->NumArgs(); arg--;) { | ||
| const IR::Value arg_value = inst->Arg(arg); | ||
| if (arg_value.IsImmediate()) { | ||
| continue; | ||
| } | ||
| IR::Inst* arg_inst = arg_value.InstRecursive(); | ||
| if (std::ranges::find(visited, arg_inst) == visited.end()) { | ||
| visited.push_back(arg_inst); | ||
| queue.push(arg_inst); | ||
| } | ||
| } | ||
| } | ||
| if (sources.empty()) { | ||
| UNREACHABLE_MSG("Unable to find sharp sources pc={:#x}", pc); | ||
| } | ||
|
|
||
| // Perform dominance analysis on found sources and eliminate ones that don't pass | ||
| // If a sharp source is dominated by another, the former can be eliminated. | ||
| size_t num_sources = sources.size(); | ||
| for (s32 i = 0; i < num_sources;) { | ||
| const IR::Block* block = sources[i]->GetParent(); | ||
| ASSERT(block->cfg_block); | ||
| bool was_removed = false; | ||
| for (s32 j = 0; j < num_sources;) { | ||
| const IR::Block* dominator = sources[j]->GetParent(); | ||
| ASSERT(dominator->cfg_block); | ||
| if (i != j && IsCfgBlockDominatedBy(dominator->cfg_block, block->cfg_block, | ||
| current_parent.cfg_block)) { | ||
| std::swap(sources[i], sources[num_sources - 1]); | ||
| --num_sources; | ||
| sources.pop_back(); | ||
| was_removed = true; | ||
| break; | ||
| } else { | ||
| ++j; | ||
| } | ||
| } | ||
| if (!was_removed) { | ||
| ++i; | ||
| } | ||
| } | ||
|
|
||
| ASSERT_MSG(sources.size() == 1, "Unable to deduce sharp source"); | ||
|
|
||
| IR::Inst* sharp_source = sources[0]; | ||
| if (sharp_source->GetOpcode() == IR::Opcode::ReadConstBuffer) { | ||
| // Set flag so that the flattening pass knows to flatten this instruction. | ||
| auto flags = sharp_source->Flags<IR::BufferInstInfo>(); | ||
| flags.sharp_source.Assign(1u); | ||
| sharp_source->SetFlags(flags); | ||
| } | ||
|
|
||
| return sharp_source; | ||
| } | ||
|
|
||
| void DiscoverBufferSharp(IR::Block& block, IR::Inst& inst, ResourceDiscoveryList& sharp_usages) { | ||
| IR::Inst* handle = inst.Arg(0).InstRecursive(); | ||
| if (handle->AreAllArgsImmediates()) { | ||
| // For inmediates, add a sharp usage with null sharp source. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor spelling mistake :kek: |
||
| sharp_usages.emplace_back(ResourceDiscovery{&inst, &block, nullptr}); | ||
| } else { | ||
| IR::Inst* buffer_handle = handle->Arg(0).InstRecursive(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For buffers handle is a composite that contains all 4 dwords of V#, can just call FindSharpSource for each, store them in an array (as mentioned in other review comment) and in resource patching verify offsets of flattened instructions are contiguous. If they are not, additional logic could be introduced (for example using gather intrinsic like _mm_i32gather_epi32 with a slim abstraction could be suitable)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this is in scope for this PR or out of scope? IMO its a better idea to do this in another PR where we also include the refactor of image instructions to include both the hish and low vectors.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this is in scope for this PR or out of scope? IMO its a better idea to do this in another PR where we also include the refactor of image instructions to include both the hish and low vectors.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure it could be possible. You should add an assertion at least that the ReadConstBuffer insts you find have a use in the composite handle of the buffer opcode
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An assert added, but only for buffer insts. |
||
| const auto inst_info = inst.Flags<IR::BufferInstInfo>(); | ||
| const IR::Inst* sharp_source = FindSharpSource(buffer_handle, block, inst_info.pc); | ||
| sharp_usages.emplace_back(ResourceDiscovery{&inst, &block, sharp_source}); | ||
| } | ||
| } | ||
|
|
||
| void DiscoverImageSharp(IR::Block& block, IR::Inst& inst, ResourceDiscoveryList& sharp_usages) { | ||
| IR::Inst* image_handle = inst.Arg(0).InstRecursive(); | ||
| const auto inst_info = inst.Flags<IR::TextureInstInfo>(); | ||
| const IR::Inst* sharp_source = FindSharpSource(image_handle, block, inst_info.pc); | ||
| const IR::Inst* sampler_sharp_source = nullptr; | ||
|
|
||
| if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) { | ||
| const IR::Inst* sampler = inst.Arg(1).InstRecursive(); | ||
| sampler_sharp_source = | ||
| FindSharpSource(sampler->Arg(0).InstRecursive(), block, inst_info.pc); | ||
| } | ||
|
|
||
| sharp_usages.emplace_back(ResourceDiscovery{&inst, &block, sharp_source, sampler_sharp_source}); | ||
| } | ||
|
|
||
| ResourceDiscoveryList ResourceDiscoverPass(IR::Program& program, const Profile& profile) { | ||
| ResourceDiscoveryList sharp_usages; | ||
|
|
||
| for (IR::Block* const block : program.blocks) { | ||
| for (IR::Inst& inst : block->Instructions()) { | ||
| if (IsBufferInstruction(inst)) { | ||
| DiscoverBufferSharp(*block, inst, sharp_usages); | ||
| } else if (IsImageInstruction(inst)) { | ||
| DiscoverImageSharp(*block, inst, sharp_usages); | ||
| } else if (IsDataRingInstruction(inst)) { | ||
| sharp_usages.emplace_back(ResourceDiscovery{&inst, block, nullptr}); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return sharp_usages; | ||
| } | ||
|
|
||
| } // namespace Shader::Optimization | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for good measure check if its IR::Opcode::ReadConst and add an UNREACHABLE at the end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done