diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index 1fd56400a..d28e01866 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -12,6 +12,7 @@ ### Functional +- The [resource tracker](../../../core/shader/recompiler/Optimization/src/ResourceTracker.cpp) plans a [descriptor source](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) used only behind a conditional branch as inactive when the branch condition reads only clean (specialization) memory and so is evaluable at capture time; an inactive source is bound as zero words and not validated. Other conditions (EXEC, VCC, SCC set by vector or non-clean data, loop headers) leave every source active, as before, and a branch on a value that changes between dispatches is a specialization key like any other clean slot. PPSA15246 has a copy kernel that selects a buffer or an image path from an SRT word and reads the same address as a V# or a T#; before, the untaken path's descriptor was validated and threw. Whether the console would also accept an invalid descriptor in a path it does not take is assumed, not measured - [MaskedSelectEliminator](../../../core/shader/recompiler/Optimization/src/MaskedSelectEliminator.cpp) can discard inactive source lanes through `ReadFirstLane` only when its read mask implies the write mask and its result is unobserved outside the read mask. A loop-local read mask requires result uses in the same block without phi transport. The empty-EXEC lane-zero fallback follows AMD's [Vega ISA, section 12.8, V_READFIRSTLANE_B32, page 127](https://www.amd.com/content/dam/amd/en/documents/radeon-tech-docs/instruction-set-architectures/vega-shader-instruction-set-architecture.pdf) and the existing SPIR-V emitter. The optimization is covered by synthetic IR tests but has not been compared with console hardware; a proof that needs a read mask of a kind that is already being solved is refused. - [Shader modules built at registration](../../../core/shader/recompiler/Recompiler.cpp) (shader recompiler): on devices with graphics pipeline libraries a vertex or pixel module reads buffer V# words, image modes, swizzles and mip ranges, depth-compare state, export mappings and vertex component selectors at run time instead of specializing on them per draw, so its libraries can be built when the shader is registered. A module that would exceed `SpirvTarget::registrationWordLimit` (200000 SPIR-V words) is emitted specialized as before: on RADV (Radeon 660M, Mesa 26.2.4) a sample of 60 Astro's Playroom pixel shaders compiled 1.9 to 2.2 times slower in registration form below 141k words and 3.8 to 5.7 times slower above 280k words (none in between), so the limit sits in that gap; it was not measured on other drivers. Texel-offset folding, the format word of a buffer accessed with the descriptor's format (`buffer_load_format_*`; as runtime values every format's decoder is compiled, which made CI's lavapipe JIT ten times slower) and the vertex attribute numeric class stay specialized, so shaders with the first two, and pixel shaders whose image needs a multisampled descriptor, get no registration result; these, and every draw whose fixed-function state differs from the prebuild's, still build their libraries at the draw. Compute and the other stages stay specialized: in registration form the `agc_driver_dword_pattern_fill` compute shader hung CI's lavapipe (Mesa 25.2.8) when built with `ANYPS5_ENABLE_SPIRV_TOOLS=ON`, not on RADV, and the cause was not found. - [SPIRV-Tools optimization](../../../core/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp) skips CCP and Simplification for modules with storage-only 8- or 16-bit types because the pinned folder produces illegal narrow constants. This also disables unrelated constant folding; the restricted full pipeline mirrors the pinned performance recipe. @@ -36,7 +37,7 @@ - [Shader MODE register access](../../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) reads the initial rounding fields from `GuestContext::floatMode`; missing metadata retains the legacy round-to-nearest-even assumption. Immediate rounding writes are accepted only when they preserve that state. State-changing or SGPR-sourced MODE writes throw until runtime mode tracking and affected arithmetic are implemented. Non-rounding register reads still throw; `s_setreg_b32` writes to other hardware registers are still ignored. Tests follow the public RDNA2/LLVM contract, not console measurements. - A [flat SRT read](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) through a null or unmapped pointer loaded from memory raises a [BDA fault](../../../core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp) only if the program executes it, then stops or reads zero like a BDA read. - Repeated `s_load`s of one [flat SRT](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) dword through a null or unmapped pointer report one of their pcs, and an `s_load` whose inaccessible dwords are all unused, or only address repeated loads, reports no fault of its own. -- A value read through a null or unmapped pointer still throws at record time if a [branch condition, uniform fill or table base](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) needs it, or if the pointer comes from user data and is not null, even if the console skips the read. +- A value read through a null or unmapped pointer still throws at record time if a [uniform fill or table base](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) needs it, or if the pointer comes from user data and is not null, even if the console skips the read. - A [V# or flat SRT word](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp) read through a null pointer taken from user data is zero and counts as a [poisoned SRT read](../../../core/shader/recompiler/Recompiler.cpp), so a dispatch that reads it is not cached. The console faults if the program executes the read; PPSA28416 branches around it with `s_cbranch_execz`. The fault is not modeled. - A [V#, T# or S#](../../../core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp) loaded through a null or unmapped pointer read from memory is zero when the work is recorded, and the capture throws if no flat SRT slot records that read. - [2D accesses through a 1D T#](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) read the texel the 1D addressing selects and ignore the second coordinate, as simonbbby measured on RDNA2 (gfx1036) in #2237 for `image_sample_lz` and mip-less `image_load` with point filtering and wrap, clamp and border addressing; the PS5's gfx1013 is not measured. A plain `image_sample` (implicit LOD) through such a pair takes its LOD from the derivatives of the first coordinate alone, measured on RDNA2 (gfx1036) with the [hardware oracle](../HW_ORACLE.md) on the quads of a compute kernel (wave32 and wave64, IEEE off, DX10 clamp on, denormals preserved, round to nearest even): on a 16-texel, 5-mip 32_UINT T# with point filters it picks the same mip and texel as DIM 1D for first-coordinate steps of 0, 1, 2, 4 and 8 texels per lane along x or along y, whatever the second coordinate does (normalised steps up to 16 per lane), while the same rows on a 16x16 2D T# move the mip with the second coordinate. Outside the pixel stage a plain `image_sample` is emitted with LOD 0 for every image type, so this pair reads mip 0 there, where the kernel measurement shows the quad's derivatives being used. Other 2D accesses through a 1D T# (storage, depth, packed, BY loads, mips, explicit LOD, bias, offsets, derivatives, comparison) still throw.