Skip to content

shader_recompiler: split resource tracking and flatten load from buffer for sharp source - #4782

Open
LNDF wants to merge 44 commits into
shadps4-emu:mainfrom
LNDF:buffer-flattening
Open

shader_recompiler: split resource tracking and flatten load from buffer for sharp source#4782
LNDF wants to merge 44 commits into
shadps4-emu:mainfrom
LNDF:buffer-flattening

Conversation

@LNDF

@LNDF LNDF commented Jul 31, 2026

Copy link
Copy Markdown
Member

This splits resource tracking pass into resource discovery and patching passes. Then we put flattening pass in the middle.

That way:

  1. We discover all resources first, accepting ReadConstBuffer as source and marking it for flattening.
  2. Flattening pass flattens all ReadConsts + marked ReadConstBuffers
  3. Patch resource instructions, also accepting ReadConstBuffer as source (since it was flattened)

@@ -185,7 +202,7 @@ static void VisitPointer(u32 off_dw, IR::Inst* subtree, PassInfo& pass_info,
c.mov(r10d, ptr[rdi + (src_off_dw << 2)]);

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 i need to handle buffer stride etc here if ReadConstBuffer?

@LNDF
LNDF requested a review from raphaelthegreat July 31, 2026 17:04
@brad0demx

Copy link
Copy Markdown

On this PR, Call of Duty Cold War crashes in roughly the same point with this new critical
[Debug] <Critical> (shadPS4:GpuComm) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code! Unable to find sharp sources pc=0x70a8
CUSA24267.log

@brad0demx

brad0demx commented Jul 31, 2026

Copy link
Copy Markdown

Killzone Shadow Fall still crashes while loading into offline match, now crashing with

[Debug] <Critical> (shadPS4:GpuComm) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code!
Unable to find sharp sources pc=0x1484

CUSA00008.log

@bigol83

bigol83 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Skyrim immediately crashes with this PR
CUSA05486.log

[Debug] <Critical> (shadPS4:GpuCommandProcessor) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code!
Unable to find sharp sources pc=0x80

@StevenMiller123

Copy link
Copy Markdown
Collaborator

Current push fixes the Skyrim regression. Now the game behaves like main again.

@LNDF

LNDF commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Skyrim immediately crashes with this PR CUSA05486.log

[Debug] <Critical> (shadPS4:GpuCommandProcessor) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code!
Unable to find sharp sources pc=0x80

Should be fixed

Killzone Shadow Fall still crashes while loading into offline match, now crashing with

[Debug] <Critical> (shadPS4:GpuComm) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code!
Unable to find sharp sources pc=0x1484

CUSA00008.log

Try latest commit

On this PR, Call of Duty Cold War crashes in roughly the same point with this new critical [Debug] <Critical> (shadPS4:GpuComm) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code! Unable to find sharp sources pc=0x70a8 CUSA24267.log

Try latest commit

@bigol83

bigol83 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Skyrim immediately crashes with this PR CUSA05486.log

[Debug] <Critical> (shadPS4:GpuCommandProcessor) resource_discover_pass.cpp:141 FindSharpSource: Unreachable code!
Unable to find sharp sources pc=0x80

Should be fixed

It is fixed for me

@StevenMiller123

Copy link
Copy Markdown
Collaborator

This PR causes regressions in Uncharted: The Nathan Drake Collection and Bloodborne. I've reported these directly to @LNDF, just mentioning them here so people know these issues are already known.

@bigol83

bigol83 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Far Cry 5 shows this on this PR

[Render.Vulkan] <Info> (shadPS4:GpuCommandProcessor) vk_pipeline_cache.cpp:602 CompileModule: Compiling fs shader 0xc42b4153
[Debug] <Critical> (shadPS4:GpuCommandProcessor) breadth_first_search.h:139 DominatingBreadthFirstSearch: Assertion Failed!
Unable to deduce correct finding

Main build

[Render.Vulkan] <Info> (shadPS4:GpuCommandProcessor) vk_pipeline_cache.cpp:602 CompileModule: Compiling fs shader 0xc42b4153
[Debug] <Critical> (shadPS4:GpuCommandProcessor) resource_tracking_pass.cpp:489 TrackSharp: Assertion Failed!
Unable to deduce sharp source

crashes on both cases without even showing a logo

Final Fantasy XV crashes with

[Render.Vulkan] <Info> (shadPS4:GpuCommandProcessor) vk_pipeline_cache.cpp:602 CompileModule: Compiling cs shader 0x66f5202
[Debug] <Critical> (shadPS4:GpuCommandProcessor) ir_emitter.cpp:770 CompositeConstruct: Unreachable code!
Composite construct with 0 elements, only 2-4 are supported

Main

[Kernel.Vmm] <Info> (LuminousTask1) memory.cpp:246 sceKernelMapNamedDirectMemory: out_addr = 0x33e1e0000
[Debug] <Critical> (shadPS4:GpuCommandProcessor) resource_tracking_pass.cpp:411 FindSharpSources: Unreachable code!
Bindless sharp access detected pc=0x584

@brad0demx

Copy link
Copy Markdown

COD Cold War now hangs rather than crashing.

[Render.Recompiler] <Error> (shadPS4:GpuComm) flatten_extended_userdata_pass.cpp:546 ComputeOffset: Unexpected instruction for offset computation, BitCastU32F32
[Render.Recompiler] <Error> (shadPS4:GpuComm) flatten_extended_userdata_pass.cpp:589 VisitPointer: Failed to compute offset for SRT walker
[Lib.Http] <Info> (bdHTTPWorker(PSN)) http.cpp:1915 sceHttpWaitRequest: epoll id=2 returned 0 events after wait
[Lib.Http] <Info> (bdHTTPWorker(PSN)) http.cpp:1915 sceHttpWaitRequest: epoll id=2 returned 0 events after wait
[Lib.Http] <Info> (bdHTTPWorker(PSN)) http.cpp:1915 sceHttpWaitRequest: epoll id=2 returned 0 events after wait

CUSA24267.log


static bool EmitComputeOffsetIAdd32(Xbyak::CodeGenerator& c, Xbyak::Reg32 reg, PassInfo& pass_info,
IR::Inst* inst) {
if (inst->AreAllArgsImmediates()) {

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.

I feel this check might not be needed as constant propagation has already run before this and should have constant folded the operation, have you noticed this in some code?

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.

Tho min and max afaik are not constant folded so i left them

u32 vn;

switch (inst->GetOpcode()) {
case IR::Opcode::Phi: {

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.

I feel this also suffers from the logic bug in main. If a shader has something like that

addr = ...
read_const(addr)
...
addr += 4
read_const(addr)

then the GVN might consider them identical since it skips over the intermediate add sees same addr. I have not confirmed this so worth checking. Another question is if we really need GVN pass, how much de-duplication actually happens in practice

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.

For other insts that are not GetUserData, CompositeConstructU32x2, ReadConst and ReadConstBuffer GVN will associate an incrementing number to the instruction itself. For those 4 instructions it will insteed assign an incrementing number to a hash of the numbers given to the argument instructions.

Because of this, if you have

%10 = ReadConst %9 ...
%11 = IAdd32 %10 ...
%12 = ReadConst %11 ...

Both readconsts should have different number since the argument is different.

I figured out this by reading the gvn table .h file so i might have understood something wrong. Is there a game that is affected by this in main?

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.

I think the example given was incorrect, what about this

addr = user_data()
if (cond0)
  addr0 = addr + 4;
else
  addr0 = addr;
read_const(addr0);

if (cond1)
   addr1 = addr + 8
else
   addr1 = addr;
read_const(addr1);

Each ReadConst is gonna have a phi as address where 1 argument is the same SSA value directly from user data and another is an IAdd. The bfs is gonna return the addr SSA in both cases resulting in same value number

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. We now handle phis as any other inst (so assugn a new gvn value for each phi)

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.

Actualy, just updated it to use the same logic as in flattening pass


// Perform dominance analysis on findings and eliminate ones that don't pass
// If a finding is dominated by another, the former can be eliminated.
size_t num_findings = findings.size();

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.

I think this should be a separate function in dominance.h that takes the findings array by reference and prunes it

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.

Yes. But is dominance.h the correct place? That is for frontend and what we are trying to separate is ir stuff.

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.

Well this code prunes sources based on dominance analysis I feel it matches better in dominance than a what is supposed to be a generic BFS algorithm header impl

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

}

template <typename Instruction, typename Pred>
auto DominatingBreadthFirstSearch(Instruction* inst, const IR::Block& current_parent, bool deep,

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.

The BFS header is getting crowded so worth doing a more holistic review.
The normal BFS

  • Only finds 1 instruction its looking for and stops
  • Keeps searching until it finds it without any way to stop it

This BFS

  • Can find multiple sources if presents
  • Is more selective where it searches and deep option stops it from going thru phis

A review of all BFS usages in recompiler could be useful to deduce if its possible to make a unified function that handles everything (also could use std::expected as predicate return to allow user to specify if they want to stop search on the current path or entirely etc)

}

boost::container::small_vector<IR::Inst*, 8> visited, findings;
std::queue<IR::Inst*> queue;

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.

Small optimization; since we spam this function a lot in resource discover might be useful to either pass this as argument in function so resource discover can maintain a queue or make this static and clear it on function entry, to avoid reconstructing the object on every call and associated heap allocations

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

// For inmediates, add a sharp usage with null sharp source.
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


if (sharp_source && sharp_source->GetOpcode() == IR::Opcode::ReadConstBuffer) {
const auto texture_flags = inst.Flags<IR::TextureInstInfo>();
const auto is_r128 = texture_flags.is_r128.Value();

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.

Mentioning here too, image sample ir op should be updated to hold 2 composites for the low and high part of the T# and do the same as buffers. The r128 check should be entirely a frontend thing and leave the high composite null

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.

Answered in the previous comment

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

if (!inst.Arg(1).IsImmediate()) {
LOG_WARNING(Render_Recompiler, "ReadConst has non-immediate offset");
continue;
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

IR::Inst* inst{queue.front()};
queue.pop();

if (inst->GetOpcode() == IR::Opcode::ReadConstBuffer) {

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.

Should this stop when it encounters a Phi? iirc the dynamic offset emit cannot handle them atm though not sure what the plan is to add them in the future. Also this is another BFS variant that searches the entire tree without stopping, would be good to unify it with the rest

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.

Currently will go through all possible previous blocks. Then this will log an error when generating the x64 code.

@DarkBoy08

This comment was marked as abuse.

@LNDF

LNDF commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Regression on Uncharted The Nathan Drake Collection. It doesn't matter whether DMA is enabled or not, it crashes at the shader compilation stage. But I decided to launch it without DMA. Main Uploading Main-log! Without DMA.txt… Freezes while compiling shaders PR PR log Without DMA.txt The game doesn't start AT ALL.

Thanks for the report. This regression is already know and will be delt with before merging. There are more games thatare affected.

First will apply the suggested changes on the review though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants