Repository navigation
Conversation
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
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>
c1df0df to
f5edd53
Compare
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
@JAB-dev two notes on this head (d201de3):
AI-assisted: yes (Claude Code) |
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… merge main took format 45 for its barycentric metadata, so this branch takes the next number. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rtial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…/agc-4-4-4-4-partial-writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Taken, thanks: the shader disk cache format is 46 on this branch (main is on 45), and I merged current Thanks for the 4090 runs. Stacking the 11_11_10 blend on top of this PR works for me; I'll take the next free numbers when I update, so please follow whatever main holds then. |
adriadam10
left a comment
There was a problem hiding this comment.
Reviewed the full diff (driver, recompiler, serializer/cache/journal versions, refusals and their tests); the Build run on this head is green. One thing blocks the merge:
docs/dev/debt/libSceAgcDriver.md, "Packed 16-bit textures" entry: merged with currentmain, this branch deletes the sentences "Storage images use compatible R16_UINT views with the buffer UNORM store conversion ...; quantization uses nearest even and maps NaN to zero. Rounding ties and NaN handling for packed stores have not been checked on AMD or PS5 hardware." They are onmain(from #8563) and are still true, and this PR does not change them. Restore them frommain(the diff againstmainshould then touch that entry not at all).
Nothing else to change. Once that line is back and CI is green on the new head this can merge.
The merge resolution had dropped its storage image sentences, which main keeps and this branch does not change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Thanks. My merge resolution had kept this branch's side of that conflict and dropped the sentences. The "Packed 16-bit textures" entry is now |
What
A draw that writes only some channels of a 4_4_4_4 colour target no longer throws. The write is now merged into the stored word, for single-sample targets and for 2-, 4- and 8-sample targets. This PR depends on the previous PR, which adds the 4_4_4_4 target.
The pixel shader reads the word through a driver-supplied storage image of the target under pixel interlock and writes
(old & ~mask) | (packed & mask), only for pixels that survive the shader. The image is decoratedCoherent: the interlock orders the critical sections, but by itself it makes no write available or visible (Vulkan memory model). The merged target is left out of the draw's attachments, and early fragment tests are forced.For a multisampled target:
Draw.cppline 2531 on this branch).The merge is part of the static shader key. Request serializer version is 40 (after
main's 39), shader disk cache format 42 (aftermain's 41) and pipeline journal format 5 (aftermain's 4). The journal carries the merged target's write mask, so a replayed pipeline is keyed as the merging draw's. Draws the merge cannot reproduce exactly throw, and the new entries indocs/dev/debt/libSceAgcDriver.mdanddocs/dev/debt/shader.mdlist them. Among them:Overlaps with open PRs:
sameDecodeline inDrawRegisterKey.cppand adds a truncated-coverage mode. This PR refuses partial 4_4_4_4 writes under merged coverage only, so whichever lands second must extend that refusal to truncated coverage or test the merge there.Format4_4_4_4UNormcases at the same anchors; whichever lands second drops those lines; no behaviour conflict) is not touched here.Apart from the packed-16 storage fix's two buffer-format cases, no other open PR implements the same functions.
Arcade Game Zone (PPSA19015) on
mainbf2c63f (committed 2026-10-19 20:52 UTC) with the previous PR stops after 14.5 s withAGC graphics: partial writes of a 4_4_4_4 unorm color target are unsupported. With this PR it stops after 13.7 s, at its first 2-sample merge draw, withAGC graphics: partial writes of a multisampled 4_4_4_4 unorm color target run the pixel shader once per sample, so a shader the guest runs once per pixel cannot read the ancillary word or, unless every sample sits at the pixel centre, centroid, sample-located or pull-model barycentrics, Z, 1/W or a position at the centroid or a sample: they would follow the invocation's sample (SPI_PS_INPUT_ENA 0x402, SPI_PS_INPUT_ADDR 0x402, SPI_BARYC_CNTL 0x1000000, sample 1 location 0xc4). That draw's pixel shader runs once per pixel and readsPERSP_CENTERandPOS_Z_FLOATon a 2-sample rasterizer. Under forced per-sample shading its Z would follow each sample. Where the console evaluates Z for a pixel-rate shader under MSAA has not been measured, so the draw throws. That measurement, or an exact rule for that Z, is what the title needs to get further here.Tested
Windows 11 x64, GCC 15.2 (WinLibs), NVIDIA GeForce RTX 4070 SUPER, driver 610.88.
agc_driver_packed_unorm4444_partial_write_tests(single sample):KILL_ENABLE.agc_driver_packed_unorm4444_multisample_partial_write_testsruns on 2-, 4- and 8-sample targets. The draw states come fromDecodeState. For each sample count:Mutation checks on that test:
Each merging draw takes the target's storage view before it records, which ends an open render pass and records a shader-write barrier, so two merges never share a pass. Removing that call or that barrier does not make the test fail on this GPU.
Unit tests in
agc_driver_graphicscover:Coherentimage variableSAMPLE_COVERAGE, the ancillary word, or (on a 2-sample rasterizer) a centroid, sample, pull-model, Z or 1/W input or a centroid or sample positionPS_ITER_SAMPLEand, apart from the ancillary word, in a draw raised from a one-sample rasterizerEach refusal check fails with its refusal removed. On
maineach of these tests fails at decode, where the 4_4_4_4 target is refused.agc_shader_memorychecks that the shader key and the serialized pixel info carry the merge, including its depth/stencil write and test flags.A version 39 request (
main's current version) replays with no merge, andmain's replay tests of older versions still pass.The execution tests are skipped on a device without fragment shader pixel interlock, which is likely the case on lavapipe in CI. The multisample test is also skipped without multisampled storage images or sample-rate shading. It skips each sample count the device cannot use as an R16_UINT storage colour target.
Full
ctest -j4on this branch (with the previous PR below it), before the last rebase ontomain: 1690 of 1691 pass.agc_driver_pass_input_hazardfails, and it fails onmain3 of 3 on this machine too.Vulkan validation: I ran the same focused tests on
mainand on this branch, on the same machine and GPU, throughctest -VwithVK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation,ANYPS5_NO_SHADER_CACHE=1andANYPS5_REQUIRE_VULKAN=1. Neither log contains "was not found but was requested by env var". The 23 existing tests report the same VUID ids with the same counts on both:agc_driver_graphics,agc_shader_memory,request_serializerandagc_multisample_guest_accessThe new execution tests report only:
main(the layer is the Vulkan SDK 1.3.236 build, older than the 1.4 driver, and does not know the newer structures in those chains)VUID-VkGraphicsPipelineCreateInfo-renderPass-06053, with the same message thatmain's native depth, pass-order and sample-mask tests reportEarlier, on the render-pass path, the layer with synchronization validation reported 57 feedback-loop and 38 hazard messages before the merged target was left out of the attachments, and none after. It does not check feedback loops under dynamic rendering.
Linux not tested.
Checklist
main; no other open PR implements the same functionsNotImplemented_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)🤖 Generated with Claude Code