Repository navigation
Conversation
… exports CB_COLOR_INFO format 19 with UNORM and COMP_SWAP 2 lists A, B, G, R from the least significant bits. No Vulkan device is required to render into the 4-bit formats and NVIDIA exposes none of them as color attachments, so the target is an R16_UINT image and the pixel shader packs the exports into the guest layout, as the 10_11_11 target does. Writes of fewer than four components, blending, DCC and truncating rounding are refused. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ed words Draws 32_ABGR and FP16_ABGR exports into a linear 4_4_4_4 target and checks every pixel against the guest layout (A in bits 3..0, B in 7..4, G in 11..8, R in 15..12), including NaN, negative, infinite, denormal and rounded values, and the pixels outside the scissor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A CB resolve from a packed color target throws, so a 2-sample 4_4_4_4 target cannot be resolved by the color block. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A CB_TARGET_MASK that writes fewer than four components of a 4_4_4_4 target leaves the other nibbles of each packed word as they were, which an R16_UINT attachment cannot do. Such draws now close the attachment's color write mask, and the pixel shader merges the written nibbles into the target through a storage image: inside a pixel interlock it reads the word, keeps the bits the mask leaves alone and stores it back. The merge (export index, word mask, sample count) is part of the static shader key, request version 40 and shader disk cache format 42; the storage image is appended to the shader's images with a single runtime mode, and its T# is encoded from the target's surface at draw time, so the draw resolves it to the target's resident image, which Draw requires. Merging shaders declare early fragment tests; shaders that kill under late tests, export depth, stencil or coverage, use alpha-to-coverage or a quad sample mask are refused, as are multisampled, mipmapped, volume and array targets and more than one merged target per draw. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…echeck Merged and full writes into one target are now also drawn without a wait between them and checked once at the end, so the render pass that follows a merging draw has to end on the storage write to its attachment. The unit test also passes the partial target masks through DrawRejection. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… depth or stencil A packed 4_4_4_4 partial write merges inside the pixel shader after early depth and stencil tests. Moving the tests ahead of a pixel kill only changes the result when the draw writes depth or stencil, so the target merge now carries whether the draw does (depth write, or stencil with a write mask and a non-keep operation), and the late-test kill refusal applies only then. The flag is part of the static shader key, the serialized request (now version 40) and the pixel input info. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e shader The merge used to write the target at the color export, so a pixel killed later by a valid-mask export still changed its word. The merged word is now kept until the shader returns and written inside the pixel interlock only when the pixel's valid mask is still set. The execution test kills a checkerboard both before and after the merging export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A merging draw read and wrote its packed target as a storage image while the same image stayed a color attachment of the pass, a feedback loop that NVIDIA only tolerates. The merged slot is now VK_ATTACHMENT_UNUSED in the subpass and VK_FORMAT_UNDEFINED in the rendering formats, its view is left out of the render pass and VkFramebuffer, and dynamic rendering binds no view there. The target stays in the draw's tracked and dirty images. Pipelines and continued passes are keyed on the merged slot. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…aw and depth The partial write test now names the word mask of every CB_TARGET_MASK case instead of taking it from PackedWriteMask, draws 16 overlapping layers in one draw whose merged nibble must come from the last layer, and merges behind and in front of a depth target filled by a checkerboard-killing draw: a depth-rejected fragment leaves its word. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ites Occlusion counts are taken at the forced early tests, the refused merge draws and why a late depth write was not used, and the one GPU the merge was measured on. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… image OpBeginInvocationInterlockEXT and OpEndInvocationInterlockEXT order the critical sections of overlapping fragment invocations but perform no availability or visibility operations (Vulkan memory model, fragment shader interlock), so without Coherent a merge could read a word that an earlier overlapping invocation stored but did not make visible. The image variable the merged target is bound through is now decorated Coherent. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A one-sample target under a multisampled rasterizer whose OVERRASTERIZATION_AMOUNT matches (radeonsi line and polygon smoothing) decodes with mergedCoverage and is drawn through mixed-samples coverage reduction onto its one-sample attachments. A merging draw leaves the 4_4_4_4 target out of the attachments, a combination that is not tested, so a partial write of such a draw now throws; the limit is listed in the technical debt file. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… under late tests A partial 4_4_4_4 write merges inside a pixel shader that declares early fragment tests. When the guest runs the shader under late tests, pixels that the depth or stencil test rejects still execute it on the console, so their buffer and image stores, atomics and GDS operations happen there. Under forced early tests they would be dropped. Such a merge now throws, unless the guest asked for early tests itself. The merged target's own image write does not count. A recompile test checks two things for a merging shader that adds to a buffer counter: under late tests it throws, and under the guest's early tests it compiles with its one atomic add beside the merge. The same shader without a merge still compiles under late tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… samples A merged 4_4_4_4 write runs its pixel shader after early depth and stencil tests. Vulkan counts a sample for an occlusion query at those tests, so pixels the guest shader kills under late tests would be counted, and on the console they are not. Once the game has read an occlusion counter the recorder counts samples for every later draw, so from then on such a merge throws. The check runs where the recorded and recipe draw paths prepare their inputs. The state records DB_SHADER_CONTROL.KILL_ENABLE for it, which fits in the existing padding. The execution test starts sample counting and then checks two cases: a merge that kills a checkerboard under late tests is refused, and a merge that kills nothing still draws. A decode test reads KILL_ENABLE. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eject pixels The refusal of a merging shader that writes guest memory under late tests fired in two cases where the early tests change nothing. The first was a draw with no depth, stencil or depth-bounds test. Early tests reject no pixel there, so every write the console makes still happens. The target merge now carries whether the draw has such a test, and the refusal applies only then. This adds one byte to the version 40 merge fields of the recompile request. The second was any GDS binding, including a shader that only reads GDS. The refusal now looks for GDS writes, atomics, appends and consumes. Tests: the depth and stencil decode checks the new flag, and the static shader key depends on it. A counter-adding shader under late tests compiles without a test and throws with one. A late-test merge that reads GDS compiles, and one that adds to GDS throws. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A partial write of a multisampled 4_4_4_4 target now merges per sample: the draw shades per sample, and the pixel shader reads and writes its own sample (gl_SampleID) of the target through a multisampled storage image whose T# describes the target's surface. Draw makes the multisampled target storable before it takes the attachment view, so the image swap of the first storage access cannot leave the framebuffer on the retired image, and it accepts the target among the draw's multisampled sources only as that one merge binding. An execution test draws a full write into a 2-sample target, merges an X write of a triangle that cuts through pixels, and loads every sample: covered samples hold the merged word, uncovered ones the first draw's. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A multisampled target's storage writes are ordered for later draws only through MultisampleTarget::Storage(), which a reused resource build would skip. The merging draw now takes the storage view once more after its resources are resolved, so it is the last access before recording; multisampled draws make no draw recipe. The multisampled test draws a full write right after the merge and checks that it covers the merged samples it overlaps. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The 2-sample test decodes its draw states from registers and checks the word mask, the closed write mask, per-sample shading and the exported merge, and a skip names the missing device feature. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Per-sample shading of a merging draw makes centroid and sample-located inputs and the ancillary sample number follow the invocation's sample (centre-located inputs too on devices without interpolation functions and fragment shader barycentrics), and the 2-sample merge was measured on one GPU only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A partial write of a multisampled 4_4_4_4 target forces per-sample shading after DecodeState checked SAMPLE_COVERAGE against the guest's own iteration, so a pixel-rate shader reading SAMPLE_COVERAGE got only its invocation's sample in gl_SampleMaskIn instead of the whole pixel's coverage. The forced shading now runs the same check and throws its message; a full write of the same target still decodes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rates A partial write of a multisampled 4_4_4_4 target makes the draw shade per sample. For a shader the guest runs per pixel, every invocation of a pixel must then see the same inputs. Otherwise its colours and kill decisions follow each sample, while the console runs one invocation. The decode now throws in two cases when the guest did not ask for per-sample shading. The first is a shader that reads the ancillary word, whose sample number would become the invocation's. The second is a shader that reads centroid, sample-located or pull-model barycentrics, Z, 1/W, or a position at the centroid or a sample. That case is allowed when every sample sits at the pixel centre, as in a draw raised from a one-sample rasterizer. The recompiler throws for centre-located barycentrics on a device without interpolation functions and fragment shader barycentrics, where they would not be rebuilt at the centre. Decode tests run each moving input on a 2-sample rasterizer, where it throws, and with PS_ITER_SAMPLE, where it decodes. The raised draw keeps the located inputs and refuses the ancillary word. Centre and upper-left positions, centre barycentrics, the front face and the fixed-point position decode, and a recompile test covers the missing barycentrics. The debt entry now lists what is kept and what throws. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A merge into a multisampled 4_4_4_4 target runs the pixel shader once per sample. A shader that stores to guest memory, runs atomics or uses GDS would then repeat those writes for every covered sample. An 8-sample pixel would add to a counter eight times. Such a merge now throws. The recompile request carries the merge's sample count, not whether the guest asked for per-sample shading, so this also refuses shaders the guest iterates itself, where the console repeats the writes too. A recompile test checks that a 2-sample merging shader adding to a buffer counter throws, and that the same shader without the merge compiles. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The 2-sample test now draws two merges back to back with no read-back in between: an X merge over one triangle and a W merge over a crossing one. It then loads every sample. Samples inside both triangles must hold both nibbles, so the second merge has to read the word the first one wrote. Each merging draw takes the target's storage view before it records. That view ends any open render pass and records a shader-write barrier, so two merges never share a pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…it at the centre The recompiler refused centre-located barycentrics in a multisampled merge on a device without interpolation functions and fragment shader barycentrics. It did so even when every sample sits at the pixel centre, as in a draw raised from a one-sample rasterizer, where the barycentrics are the centre's anyway. The refusal now applies only when the merge's sample locations are off the centre. The pixel request carries those locations for such a merge. The recompile test runs on two states. On a 2-sample rasterizer, the missing barycentrics throw, and the shader compiles once they are present. On a raised draw, the shader compiles without them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The multisampled merge accepts 2, 4 and 8 samples, but the execution test drew only 2-sample targets. It now runs the same draws on 2, 4 and 8 samples, each count with its own target: a cutting merge under a full write, then an X and a W merge drawn back to back. Every sample is read back through a T# of the target's sample count and checked against the standard sample locations. A partly merged pixel is now any pixel with some, but not all, of its samples merged. A count whose R16_UINT colour attachment with storage use the device does not offer is skipped and printed. The test is skipped when the device offers none of the three. The debt entry on measured hardware now names the 4- and 8-sample merges. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A blended or partial write into a one-sample 11_11_10 float target goes through the pixel-interlock merge: the pixel shader unpacks the stored word, evaluates the CB_BLEND_CONTROL color equation in fp32 and packs the result toward zero before the write mask. Constant color and dual-source factors still throw, and so does a mipmapped, volume, array or multisampled target, now with the target's shape in the message.
A one-sample 4_4_4_4 partial write or 11_11_10 blend into a mip of a mipmapped target, or into a slice range of a 2D array, now goes through the pixel-interlock merge instead of throwing. The merge descriptor names the same whole-array surface as the resident color image at the drawn mip, and the shader addresses the slice as the view's first slice plus gl_Layer, clamped to the view's last slice as the console clamps the render target index to SLICE_MAX. Volume targets and multisampled mips or arrays still throw.
…ed-float-blend
…ed-float-blend # Conflicts: # core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawRegisterKey.cpp # core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp # core/shader/recompiler/CacheKey.hpp # core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp # core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp # core/shader/recompiler/Recompiler.hpp # core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp # core/shader/recompiler/tests/RequestSerializerTests.cpp # docs/dev/debt/libSceAgcDriver.md
CnRJay
left a comment
There was a problem hiding this comment.
Replacing a thrown refusal with a working blend is worth having (GT7 gets past the 11_11_10 stop to its main menu), and building on #8564's interlock merge is reasonable. The cost of that mechanism is not in either PR.
- A blended draw into this target now runs its pixel shader in a fragment-shader-interlock critical section with early fragment tests forced, and for multisampled targets with per-sample shading. Interlock serializes overlapping fragments of the same pixel, and forcing early fragment tests changes when depth is tested relative to the shader's discards; on a blend-heavy pass (particles, UI, fog) that can be much slower than fixed-function blending. The description's only speed figure is "main menu at 4.4 FPS" in GT7 on an RTX 4090 with an earlier state of the commits. Please post the summary lines of the same scene with and without the blend path (the unsupported draw can be skipped with a switch for the comparison), and
Graphics.Drawper frame, so the cost is known before titles depend on it. - State which GPUs support
fragmentShaderPixelInterlockand what happens on those that do not (the previous behavior was a clear throw; the replacement should be too, with a message that names the missing feature), including lavapipe, which the tests run on. - The 2,961 lines include #8563 and #8564 "unchanged". Rebase onto them once they merge so this PR's two commits are what is reviewed; as it stands this PR is marked conflicting and cannot be tested as a unit.
…ed-float-blend
…ost blending (--benchmark-overdraw)
|
@CnRJay thanks, all three points are fair. In order: Cost in GT7. Here is the same scene with and without the blend path, from the same build: Gran Turismo 7 (PPSA01317 01.710.000) idling on its main menu with no input, RTX 4090 (NVIDIA 616.92), Windows 11, Ryzen 7 5800X, 3840x2160 output, warm shader cache. The build is this PR before its
About 12 draws per frame go through the merge here, out of about 1,880, and all of them are this PR's path: blended, layered draws into an 11_11_10 array target, from one pixel shader (0x120af97800). GPU busy per present is the same either way, so in this scene the interlocked merge costs less than the run-to-run spread. But the merged draws are few, and the frame is CPU-bound (queue worker 0 is about 95% busy, For that bound I added a timed overdraw case to the GPU test, aab4fb4:
So when every fragment overlaps 63 others in the same draw, the merge blends at about half the host's rate on this GPU: 2.07x the time per layer, the same in all three runs. At GT7's 12 merged draws per frame that disappears in the noise, as above. A full-screen particle or fog pass with heavy overdraw into an 11_11_10 target would take about twice as long as fixed-function blending. I only measured the RTX 4090. One summary line of each configurationOn the mechanism:
Devices without
So on an AMD Windows driver from before early 2025, GT7 now stops on the interlock message instead of the old 11_11_10 blend message. #8563 and #8564. Agreed. The branch carries them unchanged so that it builds and tests as a whole. This PR's own change is the two commits in the compare link in the description. I merged current AI-assisted: yes (Claude Code) |
|
Ghost of Yōtei (PPSA26344) on plain Your blend path already passes the packing through Would you rather fold 10_11_11 unorm into #9020, or should I open a small follow-up with |
|
@Celegans12 thanks for the Yōtei report. Please open it as a follow-up with Your list matches what I see in #9020's head (aab4fb4):
Two limits of #9020 to keep in mind for Yōtei. First, its blend refuses a multisampled 11_11_10 float target, so a multisampled 10_11_11 unorm target would need that case too. The merge as a whole also refuses volume targets and mipmapped or array multisampled ones. Second, the merge needs Post the Yōtei result here or on your PR, whichever you prefer. I can run your branch on an RTX 4090 if that helps. AI-assisted: yes (Claude Code) |
What
A blended or partial write into a one-sample 11_11_10 float color target used to throw (
AGC graphics: blending into an 11_11_10 float color target is unsupported). No host format holds this target's bits (red in 31..21, blue in 9..0, the mirror ofB10G11R11), so it renders as R32_UINT and fixed-function blending can't apply. This PR blends in the pixel shader instead, through the pixel-interlock merge that JAB-dev's #8564 adds for partial 4_4_4_4 writes. The merge mechanism is theirs; this PR adds blending to it and opens it to mips and 2D array slices.This branch carries #8563 and #8564 unchanged and is merged with current
main. The changes of this PR are its two commits, 9a7a202 (the blend) and 6f240cb (mips and array slices), plus the resolution of the merge commits, which move #8564's serializer fields from version 40 to 43: frankea/AnyPS5@f5edd53...6f240cbDecodeStateturns a blended or partial write into a one-sample 11_11_10 float target into a merge.TargetMergecarries the target's packing and the color equation ofCB_BLEND_CONTROL, and its mask is 32 bits wide;ColorTarget.partialWriteMaskis 32 bits as well, next to a newmergeBlend.TargetMergeSurfacedescribes the resident image of the whole array (ArraySurfaceForTarget, moved fromDraw.cpptoState.cppand shared) at the drawn mip, so the merge's storage image is the same image the target renders into.EncodeTextureResourceencodes a mip range and 2D arrays, and the shader addresses the view's first slice plusgl_Layer, clamped to the view's last slice as the console clamps the render target index toSLICE_MAX, so a guest layer index past the view never reaches outside the image. This applies to one-sample 4_4_4_4 merges too.fragmentShaderPixelInterlock. The refusal now names the target's shape.main'sfloat32DenormPreservestays at 41 and itssampleOrderat 42), shader disk cache 45, pipeline journal 6.Tested
tests/execution/PackedFloatBlend.cpp(new) against a CPU reference, bit for bit on every pixel: 11 equations, a blended and a plain partial write, a back-to-back chain, two same-draw overdraws of 16 alternating layers, and three array merges (mip 0 of all six slices of a 128x128 array of 8 mips, one layer per slice, the shape Gran Turismo 7 renders; mip 1 of slices 1 to 3 of a 256x256 array; one slice drawn without a layer export).agc_driver_packed_float_blend,agc_driver_packed_unorm4444_partial_write,agc_driver_packed_unorm4444_multisample_partial_write,agc_driver_packed_float_color_target,agc_driver_layered_array_color_draw,agc_driver_graphicsandagc_shader_memorypass.request_serializer,agc_shader_disk_cache,agc_shader_memory,agc_shader_control_flow,agc_driver_bda_device,agc_shader_replay_requests,agc_driver_graphics,agc_pipeline_journalandagc_driver_prepared_shaderspass.mainmerges, head e554d27 on the RTX 4090:agc_shader_memory,agc_driver_graphics,agc_driver_layered_array_color_draw,agc_driver_packed_float_blend,agc_driver_packed_float_color_target,agc_driver_packed_unorm4444_partial_writeandagc_driver_packed_unorm4444_multisample_partial_writepass. The same 35 Linux tests pass on 374556c, themainmerge before it.mainmerge and the layer clamp) and fix(agc): raise no end-of-pipe event from flips and carry the release's interrupt context #7978, fix(agc): write a replace blend into a packed color target without blending #7243, feat(agc): render layered draws into a depth view of several slices #7256 and fix(shader): answer image_get_resinfo on any T# dimension #7550 merged locally: past the 11_11_10 blend stop and the cube-map target, the main menu at 4.4 FPS. With scripted input it goes on to the World Map, the Used Cars dealer and a car purchase, then stops in the car delivery scene oncolor targets with fewer fragments than samples (EQAA) are unsupported, which this PR doesn't touch.Checklist
main; no other open PR implements the same functions (the ONE/ZERO/ADD case is left to fix(agc): write a replace blend into a packed color target without blending #7243)NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)