Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
bee1833
split resource tracking into 2 parts
LNDF Jul 29, 2026
42b7bd2
flatten load from buffer if used as sharp source
LNDF Jul 31, 2026
aac91f0
clang-format
LNDF Jul 31, 2026
048268b
fix inmediate sample raw
LNDF Jul 31, 2026
3800d37
Merge branch 'shadps4-emu:main' into buffer-flattening
LNDF Jul 31, 2026
dd46168
defer sharp not found asserts
LNDF Aug 1, 2026
a68eedd
flip asserts
LNDF Aug 1, 2026
4efec91
disable aniso pattern
LNDF Aug 1, 2026
645f373
fix disable aniso
LNDF Aug 1, 2026
2bcc838
some fixes
LNDF Aug 1, 2026
2f527a3
non immediate
LNDF Aug 4, 2026
b932e75
fix PushPtr
LNDF Aug 4, 2026
07450be
fix PushPtr for non immediate
LNDF Aug 4, 2026
d6bc173
don't assert on offset computation failures
LNDF Aug 4, 2026
1f34d19
robust bfs
LNDF Aug 6, 2026
dc69f65
Merge remote-tracking branch 'origin/main' into buffer-flattening
LNDF Aug 7, 2026
4c83593
some xbyak fixes
LNDF Aug 7, 2026
4eb3e36
add bitwise ops
LNDF Aug 7, 2026
5640a7e
umin umax and bit extract
LNDF Aug 7, 2026
d84e79b
correct rshift lshift 0st immediate
LNDF Aug 7, 2026
4b096b3
fix and in memory
LNDF Aug 7, 2026
97abe21
some minor code fixes
LNDF Aug 7, 2026
926bfa9
consider ReadConstBuffer as valid finding
LNDF Aug 8, 2026
8e25815
fix xbyak
LNDF Aug 8, 2026
e8eb5d5
fix magic_enum for opcodes
LNDF Aug 8, 2026
2bdc50c
fix building (yes, i didn't test)
LNDF Aug 8, 2026
0f861dd
don't corrupt stack when bailing out
LNDF Aug 8, 2026
02e8676
ReadConstBuffer compute offset
LNDF Aug 10, 2026
6cbc39d
use canonical inst for readconst non-imm offset
LNDF Aug 10, 2026
54d7bc3
mark all readconstbuffer sharp dwords
LNDF Aug 10, 2026
1dc5c32
consider r128 images
LNDF Aug 10, 2026
cfb141c
FindSharpLocation
LNDF Aug 11, 2026
07602af
handle non-imm offset readconstbuffer
LNDF Aug 12, 2026
0624f6f
Merge branch 'shadps4-emu:main' into buffer-flattening
LNDF Aug 14, 2026
15ab3e9
don't handle immediates when constant folding
LNDF Aug 14, 2026
b849e31
proper check for ReadConst to get flatbuf offset
LNDF Aug 14, 2026
18c62e8
clang-format
LNDF Aug 14, 2026
40a5e85
fix stupidity
LNDF Aug 15, 2026
b7bd91a
invert condition
LNDF Aug 15, 2026
df0d6b4
separate search dominant function
LNDF Aug 15, 2026
8643ae8
handle phis loke other insts in gvn
LNDF Aug 15, 2026
722e2ac
use same logic for phis in gvn than for flatten pass
LNDF Aug 15, 2026
00a0371
apparently idk how logic works
LNDF Aug 15, 2026
630c407
revert back to old logic
LNDF Aug 15, 2026
265d3d3
min/max immediate in constant folding
LNDF Aug 17, 2026
5023544
add offset insts to gvn inst to hash args
LNDF Aug 17, 2026
09818a0
assert that ReadConstsBuffer insts have usage as buffer sharp
LNDF Aug 17, 2026
c6c9028
separate dominance search from bfs
LNDF Aug 21, 2026
a31e940
chang gvn phi handling
LNDF Aug 21, 2026
8ffc057
merge shadps4:main into buffer-flattening
LNDF Aug 23, 2026
f0f0535
remove unused includes
LNDF Aug 23, 2026
5f879a2
fix reuse
LNDF Aug 23, 2026
54d079b
readconst transform add address
LNDF Aug 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,9 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp
src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp
src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
src/shader_recompiler/ir/passes/resource_discover_pass.cpp
src/shader_recompiler/ir/passes/resource_pass.h
src/shader_recompiler/ir/passes/resource_patching_pass.cpp
src/shader_recompiler/ir/passes/ring_access_elimination.cpp
src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp
Expand Down
38 changes: 33 additions & 5 deletions src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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>();

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

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)]);
Expand All @@ -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)]);
Comment thread
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++;
}

Expand Down Expand Up @@ -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 ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

condition can be inverted to reduce nesting

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion src/shader_recompiler/ir/passes/ir_passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include "shader_recompiler/ir/basic_block.h"
#include "shader_recompiler/ir/passes/resource_pass.h"
#include "shader_recompiler/ir/program.h"

namespace Shader {
Expand All @@ -19,7 +20,9 @@ void DeadCodeEliminationPass(IR::Program& program);
void ConstantPropagationPass(IR::BlockList& program);
void FlattenExtendedUserdataPass(IR::Program& program);
void ReadLaneEliminationPass(IR::Program& program);
void ResourceTrackingPass(IR::Program& program, const Profile& profile);
ResourceDiscoveryList ResourceDiscoverPass(IR::Program& program, const Profile& profile);
void ResourcePatchingPass(Shader::Info& info, const ResourceDiscoveryList& sharp_usages,
const Profile& profile);
void CollectShaderInfoPass(IR::Program& program, const Profile& profile);
void LowerBufferFormatToRaw(IR::Program& program);
void LowerFp64ToFp32(IR::Program& program);
Expand Down
229 changes: 229 additions & 0 deletions src/shader_recompiler/ir/passes/resource_discover_pass.cpp
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An assert added, but only for buffer insts.
For image insts the reafctor mentioned earlier to have all sharp parts referenced in image insts (in this pr or another)

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
Loading