Skip to content

fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE - #9533

Open
EagleBearerYS wants to merge 26 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-msaa-d32s8-separate-stencil
Open

EagleBearerYS wants to merge 26 commits into
boykopovar:mainfrom
EagleBearerYS:fix/agc-msaa-d32s8-separate-stencil

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What

Stacked on #6300 (Gashrod1), which imports cleared native multisample D32S8 targets whose stencil lives in HTILE, and takes whole HTILE fills as clears. This branch carries #6300's two commits unchanged, merges main into them, and adds fourteen commits for a shape #6300 doesn't cover: a D32S8 target whose stencil is a separate plane and whose HTILE is depth-only.

Titles with this shape, all stopping on main at the native depth import:

  • The Grinch: Christmas Adventures (PPSA07656) stops about 2 s in, before its first present, with native multisample stencil or depth format import is not implemented (BunnyFl's report).
    • The target is two-sample D32S8 at 2560x1440, with a separate stencil plane of 0x780000 bytes and a pipe-aligned, depth-only HTILE.
    • It samples the depth through a descriptor with no metadata.
  • Ghost of Tsushima Director's Cut (PPSA02225) stops on the same format refusal, and on feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300 at native multisample stencil import requires HTILE stencil metadata (milescoviello on feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300: 1, 2).
    • The target is two-sample D32S8 at 1920x2160, with a depth-only HTILE that is neither pipe-aligned nor ITERATE_256.
    • Two whole-range fills of 0 come before the sample: the HTILE (0x50000 bytes) and the stencil plane (0x870000).
    • On 5daa8cb it got past both refusals and stopped at a CPU read of the stencil plane, whose memory a 2-sample colour target also covers (milescoviello and xXPBSXx, below). 2d4c1da and 14e5620 handle that read.
    • On 514643a that read is gone, and it stops at the depth plane, which another host-only colour target covers (next stops).
  • Spirit of the Island (PPSA13934) aborts at its first gameplay frame after New Game on the same format refusal (JJenkx on #6300).
    • The target is eight-sample D32S8 at 512x512, with a separate stencil plane and a depth-only HTILE that isn't pipe-aligned.
    • Thanks to JJenkx for printing the target's fields.

The changes:

  • 8eba29a, merge of main into feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300's head c11074b. Three conflicts, each resolved by keeping both sides:
  • 7d6e968, the separate stencil plane.
    • A D32S8 target with a separate plane and a depth-only HTILE is imported when the depth's HTILE proves its clear, and every byte of the plane equals DB_STENCIL_CLEAR. The plane is read once, between taking the ranges' generations and the final check, so a racing write still refuses.
    • The host image is created cleared to both values.
    • A whole depth-only HTILE fill now clears only the depth.
    • A plane that isn't all clear refuses: "native multisample stencil plane does not hold its clear value".
  • 5be005e, sampling without HTILE. The sampled descriptor may now name no metadata at all (dccAddress 0, not pipe-aligned), as well as the target's HTILE, when address, format, XOR and ITERATE_256 match. Any other metadata still refuses.
    • The host image holds decompressed depth. On hardware, a read without metadata returns that only after the guest has expanded the plane in place, so 439557b accepts such a descriptor only after an observed expand.
  • 770a499, a depth-only HTILE that isn't pipe-aligned is imported when every word over HtileMetadataBytes(extent) is the same word and proves the clear.
    • Pipe alignment only moves words between tiles, so one repeated word proves the same in any order.
    • One whose words differ refuses: "native multisample depth HTILE that is not pipe-aligned holds more than one word".
    • An HTILE that holds stencil keeps the alignment refusal.
  • 90d652a, a whole fill of the separate stencil plane with one byte value becomes a stencil clear. It's proven at the next sample the way feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300 proves a whole HTILE fill: no CPU write since, complete tracked stores (or the fill is the plane's newest queued GPU write), and every plane byte equal to the fill byte once it has landed.
    • Partial fills, multi-byte patterns, unwatched memory and a second fill before the first is proven each refuse with their own text.
  • 0e17d12 and 5daa8cb, the order of Ghost's fills.
    • A DMA_DATA fill is noted before its GPU write is recorded, so the first sample saw the queued fill as a foreign write. The sample now takes the note's pattern from the one write recorded over the range since the note, when that write is a word fill of the whole range with that pattern.
    • A compute memory-value fill is noted twice, before and after the fill, and the second note refused as overlapping. A second note of a pending fill now counts as the same fill under the same conditions.
    • A later queued write, a different word, or a partial queued fill still refuses.
  • 2d4c1da, a stencil plane under a host-only colour target. Ghost of Tsushima Director's Cut renders a 2-sample R8 colour target over the stencil plane's memory, so reading the plane hit that target's guard.
    • MultisampleTarget gains a query that doesn't throw: the active colour target whose guarded range overlaps a range, with its format, samples, footprint and newest content. A fill the target absorbed, a CMASK fast clear, or a clear with no draw since is a known value; a draw, a storage write or refused metadata is not.
    • prepareDepthSampling asks it for the depth, HTILE and plane ranges before reading guest memory. An R8 UNORM or UINT target with the depth's sample count over exactly the plane, holding a known value, gives the plane's stencil.
    • CheckCurrent runs the same query: a plane fill over an aliased plane is proven from the colour target's value, not by reading the plane.
  • 439557b, sampling without metadata only after an in-place expand. A draw over the target with DB_RENDER_CONTROL's depth compress-disable bit (bit 6), and the stencil one (bit 5) when the target has a stencil plane, marks the surface expanded, as radeonsi's in-place decompress sets them. A clear draw, an HTILE clear or fill, and a plane fill clear the mark.
    • CheckResource takes a descriptor without metadata only while the surface is marked. Otherwise it refuses with "native multisample depth sampled metadata or XOR mismatch" and prints the last DB_RENDER_CONTROL seen, at most eight times.
    • The draw precheck hands the register to NoteDepthRenderControl before an expand draw, which has no depth test and no colour writes, is dropped as drawing nothing.
  • 14e5620, a plane under a colour target that drew, or holds something unknown. It was refused at import, so sampling the depth failed too. The plane is now imported unproven: the host stencil isn't taken from it, depth sampling and depth-only draws go on, and a draw with a stencil test or a sample of the plane refuses with the alias text and a line naming the colour target's state.
    • A later whole-plane fill proves the plane from the fill byte without reading guest memory, if a colour target still over the plane absorbed that fill as the same R8 byte at the same sample count.
    • 8ba6321's debt line names what taking a drawn plane would need: a GPU copy of the colour target's R8 samples into the depth image's stencil aspect.
  • 514643a, the sample shadow's barrier. A D32S8 depth's sample shadow went from UNDEFINED to GENERAL with only the depth aspect, which breaks VUID-VkImageMemoryBarrier-image-03320 without separateDepthStencilLayouts. The transition now names the stencil aspect too when the host format has one. JJenkx found the same on an RX 6650 XT.
  • 6bc1899d50 and dd7ad4638a, a queued DMA_DATA fill's metadata note (both Co-authored-by: JJenkx). enqueueDmaPacket's large-fill branch queued storeBufferFill(..., false, false), whose last argument is noteMetadata, so a fill over 64 KiB never noted HTILE or colour metadata, and the next sample took this PR's fill transition for an external GPU write. dd7ad46 notes it, as the synchronous and small queued paths do. 6bc1899 adds a test seam, friend struct DriverTestAccess; (as VulkanDeviceTestAccess does), so the test runs native multisample depth through the driver's own device, following JJenkx's approach. On main a metadata fill over a live native multisample target's HTILE refuses anyway, so the gap shows only with this PR's fill transition.
  • 1e07a65, merge of main 8b3a6ba: clean. The PR's changed lines are the same as before the merge (git diff -U0 patch-id); only their context moved.
  • 8b2b308, merge of main 3ca07d0, which brings fix(agc): separate possible writes from complete coverage #7233 (MarcZX10): possible writes, an HTILE clear's note, arm and complete steps, and the drain in Driver::WaitIdle. Each conflict keeps both sides:
    • CheckCurrent: main's "native multisample depth clear has not been recorded" check comes first, then this PR's checks, unchanged.
    • NoteHtileDepthClear: this PR's refusals for a stencil surface and an unproven target come first, then main's overlap check and pending flag, which now also set this PR's clear aspects. main's ArmHtileDepthClear takes the clear's generation, in place of this PR's immediate one.
    • The test file takes main's ClearFault cases and DriverTestAccess::GetDevice beside this PR's ClearSource and UseDevice, with one CheckDepthTarget signature.
    • Two test adaptations are in the merge itself, since without them it fails its own tests:
      • The expand-gate case arms its HTILE mask clear, as main's model needs. Without it: "native multisample depth clear has not been recorded".
      • --driver-queued-fill waits only for the driver queue, through DriverTestAccess::WaitQueue. main's 71decdc makes Driver::WaitIdle also wait for the device, so the case no longer sampled while its fill was queued. Without it: "CPU writes raced native multisample depth clear".
    • So the PR's changed lines differ from before this merge (the git diff -U0 patch-id changes), by these resolutions and adaptations.
  • a6b6274, a metadata fill over a pending HTILE clear dispatch. Under main's model a dispatch clear is pending from its note until it's armed. A whole-HTILE fill noted in between took the clear as its own, and the arm kept the fill's pattern, so the dispatch clear was proven as a fill. Such a fill now refuses: "native multisample depth metadata fill over a pending HTILE clear dispatch is not implemented". It's the refusal from the composition with fix(agc): settle queued HTILE clears before native depth use #7371 (5e8a1cd), which Amoenus asked to keep with this PR's fill support (comment).
  • 229d718, merge of main 8b3e398, which brings fix(agc): resolve and render a multisampled target through its sRGB twin format #8847 (JJenkx's sRGB twin). One conflict, in MultisampleTargetView: each overload keeps this PR's drawn mark and returns main's View(target.format). The PR's changed lines are otherwise the same, less one that main now makes too (auto& acquired in place of const auto&).
  • 1d16d50, merge of main b7d1d47, which brings fix(agc): settle queued HTILE clears before native depth use #7371 (Amoenus's queued HTILE clear handoff). In DepthSurface.cpp, fix(agc): settle queued HTILE clears before native depth use #7371's clearCompletionQueued field and its reset sit beside this PR's clear fields, both kept. The PR's changed lines are the same as before this merge (git diff -U0 patch-id), and the merged DepthSurface.cpp is byte for byte the composition posted in fix(agc): settle queued HTILE clears before native depth use #7371's thread (5e8a1cdb83).
  • a45dff8, merge of main d6e7ab4, which brings fix(agc): retire an unwritten native multisample depth surface on guest access #6727 (simonbronk). In Draw.cpp each DepthSurfaceView call keeps this PR's stencilTest and gains fix(agc): retire an unwritten native multisample depth surface on guest access #6727's NoteDepthSurfaceWrite, and in the debt entry fix(agc): retire an unwritten native multisample depth surface on guest access #6727's sentence follows this PR's. fix(agc): retire an unwritten native multisample depth surface on guest access #6727 retires an unwritten native multisample depth surface on a guest read instead of refusing it (#6727), and its own fixtures note a host write with NoteDepthSurfaceWrite before they expect a refusal. CheckStencilTarget now does the same before it reads the stencil plane, so --stencil still checks that a written D32S8 target refuses. Apart from that line, the PR's changed lines are the same as before the merge, with the stencilTest arguments now inside fix(agc): retire an unwritten native multisample depth surface on guest access #6727's blocks.
  • 1582cf6 (test): a queued DMA_DATA fill of more than 64 KiB is stored through a host import, and Windows compares its direct host imports instead of keeping them write-watched, so the fill's HTILE is unwatched after the store and the fill refuses. On Windows, agc_driver_native_depth_driver_queued_fill now skips with that reason when PrepareImportWatch reports compared imports. Linux still forces watched imports, so the case keeps running there. The debt entry records that such a fill is refused wherever host imports are compared.
  • The native multisampled depth sampling debt entry lists the new cases and refusals.

Overlap:

Tested

On Linux with lavapipe (llvmpipe, LLVM 20.1.2), which samples D32S8 only at four samples, each case below runs at four samples and the two- and eight-sample cases report unavailable. The RTX 3070 run covers two and eight samples.

  • New ctest agc_driver_native_depth_separate_stencil (--separate-stencil). Each red below reverts that change's driver hunk and keeps the test:

    case red
    import with a separate plane "native multisample stencil import requires HTILE stencil metadata"
    one stencil byte not clear the refusal "did not match native multisample stencil plane does not hold its clear value"
    a depth-only fill keeps the stencil (only the clearAspects line reverted) "a sampled fetch lost a depth sample or a stencil test outcome"
    sample through a descriptor without HTILE "native multisample depth sampled metadata or XOR mismatch"
    a non-pipe-aligned depth-only HTILE "native multisample depth initial import requires pipe-aligned HTILE without VRS"
    a whole stencil plane fill "guest writes over native multisample depth are not implemented"
    a partial plane fill the refusal "did not match native multisample stencil plane partial fill transition is not implemented"
    Ghost's fills queued after their note "external GPU writes over native multisample depth are not implemented"
    a fill noted, queued and noted again "overlapping native multisample depth clear transitions are not implemented"
    • Every case passes 20/20 on its commit.
    • Ghost's sequence (creation, a whole HTILE fill of 0, a whole plane fill of 0, then a sample) passes in four orders: stored, queued after the note, queued before it, and noted twice.
    • The guards hold: a later queued stencil write, a different HTILE word, a partial HTILE fill, a plane fill noted twice with no write between, and a target refused at creation that keeps its creation reason after both fills.
  • After every commit: ctest -R 'native_multisample|native_depth|depth|htile|stencil' 40/40, with 2 skipped as on main (agc_driver_native_depth_bda_clear and agc_driver_sample_order_recipe_depth_locations).

  • Linux, as CI runs it, at 5daa8cb (Ubuntu 24.04, GCC 13.3, unprivileged user):

    • 1,824 tests: 1,738 passed, 83 skipped, 3 failed. guest_socket_stream_options, guest_socket_address_length and guest_tcp fail the same way on main there, because that machine has no usable sockets.
    • check_test_registration.py: none new. check_skips.py: guest_sce_net, for the same reason.
  • GitHub's runners at 5daa8cb (run): on Linux, agc_driver_native_depth_separate_stencil and agc_driver_native_depth_stencil pass ×3. On Windows the build succeeds and both skip, since that runner has no Vulkan device. The libc and libkernel suites pass on both.

  • check_conventions.py at 514643a: no errors and no comment lines added. One warning, debt-length, on the native multisampled depth entry: 3,192 characters, against 1,403 on main.

  • New ctests for 2d4c1da and 439557b:

    • agc_driver_native_depth_aliased_stencil (2d4c1da): a 4-sample R8 target over the plane with an absorbed fill imports, and the next stencil test passes the fill value and no other. A second absorbed fill is proven and taken, and a colour draw after it refuses. A target drawn after its fill, and an R8G8 target over the plane, refuse with the new reason. The guest plane keeps its 0xa5 bytes throughout. With the product change reverted the cleared case throws the guard text (1 of 1); with it the mode passes 5 of 5.
    • agc_driver_native_depth_expand_gate (439557b), for D32 and for D32S8 with a separate plane: a bare sample before any expand is refused, and on D32S8 also after only the depth bit. After the expand draw it gets the cleared and then the drawn depth. An HTILE fill, a clear draw and a plane fill (an HTILE mask clear on D32) each refuse it again until the next expand. With the gate reverted the first bare sample is accepted (1 of 1); with it the mode passes 5 of 5. The separate-stencil test now draws an expand before its bare sample.
  • NVIDIA GeForce RTX 3070 Laptop GPU at 514643a (driver 617.42, the only device enumerated, Windows 11, WinLibs GCC 15.2.0, Release, ANYPS5_REQUIRE_VULKAN=1; GPU clocks weren't held, pass/fail only): the eight native depth tests, agc_driver_native_multisample_depth and agc_driver_native_depth_{retirement,clears,clear_refusals,stencil,separate_stencil,aliased_stencil,expand_gate}, pass 5 runs in a row, with their 2-, 4- and 8-sample cases.

  • Validation layer, main against this PR (CONTRIBUTING's AGC rule), on one GitHub Linux runner (run). Both runs used VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1 and ctest -V, with VK_LOADER_DEBUG=layer to show the layer loading. The device was llvmpipe (LLVM 20.1.2) on Mesa 25.2.8, Ubuntu 24.04.5, Khronos validation layers 1.3.275.

    • Tests: agc_driver_native_multisample_depth, agc_driver_native_depth_{retirement,clears,clear_refusals} and agc_driver_msaa_dcc_clear_4 on both trees, plus this PR's agc_driver_native_depth_{stencil,separate_stencil,aliased_stencil,expand_gate}, which main doesn't have. main's tree (a6dcea9) ran its five once and this head's nine three times.
    • The loader inserted VK_LAYER_KHRONOS_validation 9 times on main and 51 with this PR, and neither log has was not found but was requested by env var VK_INSTANCE_LAYERS.
    • Both logs report the same single message, VUID-VkDeviceCreateInfo-pNext-pNext, for a device-creation structure newer than that layer version. This PR adds no VUID- message.
    • The same run builds the nine on Windows, where they skip without Vulkan. The suites' two failures are timing tests this change doesn't reach: precise_wait (libc, Linux) and guest_raise_in_system_call (libkernel, Windows), which other PRs' runs on those runners have hit too.
  • Validation layer, the previous head against this one, on one GitHub Linux runner (run), with the same settings and device. The previous head's 6 native depth tests log VUID-VkImageMemoryBarrier-image-03320 39 times, in agc_driver_native_depth_stencil: vkCmdPipelineBarrier() on a VK_FORMAT_D32_SFLOAT_S8_UINT image with only VK_IMAGE_ASPECT_DEPTH_BIT, the sample shadow's barrier. This head's 8 don't log it, and add no VUID- message.

  • Title runs, on 5daa8cb:

    • The Grinch: Christmas Adventures (PPSA07656), BunnyFl, Windows 11, AMD Radeon RX 9070 XT (driver 32.0.32015.7029), merged into main d6eafa5, the title relinked with the same build (1, 2). On main it aborts about 2 s in, before its first frame. With this PR it doesn't stop:
      • 240 s with no driver failure and no AGC refusal; frame dumps show the logos, the publisher video, the autosave notice, loading and the storybook intro;
      • with scripted input it reaches the first level and plays there: walking, collecting presents and moving between rooms, 365 s, no AGC refusal.
    • Ghost of Tsushima Director's Cut (PPSA02225): milescoviello (Linux, RTX 4070 Ti, NVIDIA 615.71.09, three runs; comment) and xXPBSXx (Windows, AMD Radeon RX 6400, all of main's libs rebuilt for both, 600 s each; comment). On main it stops at native multisample stencil or depth format import is not implemented. With this PR no native multisample refusal appears, and the run stops later at the CPU read of the stencil plane that a 2-sample colour target covers. 514643a doesn't make that read.
    • PPSA03208, oneandonlydean, on main plus other open PRs it needs (comment): both of its stops clear, the format refusal and the HTILE stencil requirement, and its two whole fills are taken as clears. The next stop is a per-sample R8_UINT read of the stencil plane, refused by CheckResource, which feat(agc): sample the stencil plane of a native multisampled D32S8 target #9717, stacked on this PR, covers.
  • Title run, on 514643a: Ghost of Tsushima Director's Cut (PPSA02225), milescoviello, Linux, RTX 4070 Ti, NVIDIA 615.71.09, warm and cold, with a pad script cycling through the menu buttons (comment).

    • The CPU read of the stencil plane is gone.
    • Both runs stop on the depth plane of the same target, warm at 17 s and cold at 52 s: native multisample depth 0x1544ed0000+0x1fe0000 aliases multisampled colour target 0x1544ed0000+0x1fe0000 (vk format 100, 2 samples), newest clear 0x00000000 ..., then native multisample depth or HTILE aliases a multisampled colour target whose samples are only on the host.
  • Ghost of Tsushima Director's Cut (PPSA02225) with the stacked fix(agc): accept a colour alias over a native multisample depth plane when HTILE proves its clear #9742 and feat(agc): sample the stencil plane of a native multisampled D32S8 target #9717, and milescoviello's feat(agc): draw one-sample color targets beside a multisampled depth target with sample 0's coverage #8115 and fix(agc): draw a PS_ITER_SAMPLE program once per pixel into a one-sample color target #8121, on the test branch test/ghost-9742-9717 (3ec696f, on 514643a) with those two merged on top (comment): milescoviello, Linux, RTX 4070 Ti, NVIDIA 615.71.09, 20 GiB cap. Warm and cold both reach the 600 s limit with no refusal and no crash. In their words, the game shows its brightness calibration screen, with the mask rendered correctly, and then its intro. The only native depth line left is fix(agc): accept a colour alias over a native multisample depth plane when HTILE proves its clear #9742's alias line, printed once and accepted.

  • Focused tests on other GPUs, on 5daa8cb:

    • xXPBSXx, Windows, AMD Radeon RX 6400 (comment): agc_driver_native_multisample_depth passes 3 of 3, and fails 3 of 3 with DepthSurface.cpp from the merge-base.
    • JJenkx, Linux, AMD Radeon RX 6650 XT (RADV NAVI23), Khronos validation at instance and device creation, merged into main d6eafa5 (comment): the 7 focused tests pass, 5 of them on main too. This PR's stencil tests logged VUID-VkImageMemoryBarrier-image-03320, which 514643a removes. Their production-DMA diagnostic found a queued DMA fill over native multisample depth that the next sample refuses. dd7ad46 takes their fix, with them as co-author: on main a metadata fill over a live native multisample target's HTILE refuses anyway, so the gap shows only with this PR's fill transition.
      • On 5daa8cb with their own two fixes, their 12 D32S8 cases (2, 4 and 8 samples, clear 0 and 1, compressed and uncompressed HTILE, stencil checked) pass with validation on and log no VUID- (comment).
  • 6bc1899 and dd7ad46, the queued DMA fill, on GitHub's runners (run), against 6bc1899, so that only dd7ad46's driver hunk is reverted:

    • agc_driver_native_depth_driver_queued_fill fails 3 of 3 without it, with "AGC graphics: external GPU writes over native multisample depth are not implemented", and passes 10 of 10 with it, for clears 0 and 1. With fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533's native depth tests, agc_driver_native_depth_driver_path and agc_driver_pipelined_dma, 11 tests pass 10 of 10 on Linux. On Windows they build and pass, with the Vulkan tests skipping.
    • On a cloud Linux machine, 6bc1899 builds on its own and its agc_driver_native_depth_driver_path passes, and the two new tests pass until-fail:20.
    • The case does one 128 KiB fill, then samples. On lavapipe it samples once, while the fill is still queued, since llvmpipe's CPU stores look like guest writes there.
    • Validation, 514643a against dd7ad46 (run), every test named: the layer loads in both runs, with no "was not found" line. Both log only VUID-VkDeviceCreateInfo-pNext-pNext, and the two commits add no VUID- message.
  • 1e07a65, main 8b3a6ba merged in (run): the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10 on Linux, none skipped. On Windows they build and pass, with the 12 Vulkan tests skipping. The libc and libkernel suites pass on both.

    • Validation layer, main against this head, in the same run: one GitHub Linux runner, Ubuntu 24.04.5, llvmpipe (Mesa 25.2.8, LLVM 20.1.2), with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, VK_LAYER_KHRONOS_validation and ctest -V. main's arm runs the 7 of the 13 tests it has, and this head's arm runs all 13. The layer loads in both runs, with no "was not found" line. Both log only VUID-VkDeviceCreateInfo-pNext-pNext, and this head adds no VUID- message.
    • check_conventions.py on the merged head: no errors and no comment lines added. The one warning is the same debt-length on the native multisampled depth entry, at 3,192 characters.
  • 8b2b308 and a6b6274, on a cloud Linux machine (CI's configure flags, lavapipe on Mesa 25.2.8, unprivileged user): both build, and the 14 native depth, DCC clear and pipelined DMA tests pass; on a6b6274 they pass --repeat until-fail:10.

    • Each adaptation's red above was taken on 8b2b308 with only that adaptation reverted.
    • a6b6274's red: with its DepthSurface.cpp hunk reverted and the test kept, agc_driver_native_depth_clear_refusals accepts the fill ("multisample target refusal did not match metadata fill over a pending HTILE clear dispatch").
    • The full ^agc set, once each: 880 of 881 on a6b6274 and 875 of 875 on main 3ca07d0, with 60 skipped on both. The six extra tests are this PR's. The one failure, agc_driver_sgpr_pattern_fill_keys ("the dispatch completed 17 times without a pending write over the keys"), passed 20 of 20 alone and 881 of 881 in a second sweep, failed the same way on main in a third sweep, and isn't in a file this merge touches.
  • 229d718 (run): the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10 on Linux, none skipped. On Windows they build and pass, with the 12 Vulkan tests skipping. Validation against main in the same run: the layer loads in both runs, with no "was not found" line, and both log only VUID-VkDeviceCreateInfo-pNext-pNext. In that run's libc suite, precise_wait failed on the Linux runner with this head, under its strict median bounds; main's arm passed it.

  • 1d16d50, main b7d1d47 merged in (run): the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10 on Linux, none skipped. On Windows they build and pass, with the 12 Vulkan tests skipping. Validation against main in the same run, on the same Linux runner: main's arm runs the 7 of the 13 tests it has, and this head's arm runs all 13. The layer loads in both runs, with no "was not found" line. Both log only VUID-VkDeviceCreateInfo-pNext-pNext, and this head adds no VUID- message. The libc and libkernel suites pass on both runners. The libc and libkernel suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • check_conventions.py on 1d16d50: no errors and no comment lines added. The one warning is debt-length on the native multisampled depth entry, now 3,308 characters with a6b6274's clause.

  • 9afaa90, main 11f976b merged in (clean; the PR's own diff is unchanged) (run): the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10 on Linux, none skipped. On Windows they build and pass, with the 12 Vulkan tests skipping. In the same run, validation against main: main's arm runs the 7 of the 13 tests it has, the layer loads in both runs with no "was not found" line, and both log only VUID-VkDeviceCreateInfo-pNext-pNext, so this head adds no VUID- message. The libc and libkernel suites pass on both runners. The libc and libkernel suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • a45dff8, main d6e7ab4 merged in (run): on GitHub's Linux runner, the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10, none skipped. Without the fixture line, agc_driver_native_depth_stencil failed on this merge with an empty refusal where it expects "sample-preserving guest publication" (run). In the same run, validation against main: main's arm runs the 7 of the 13 tests it has, the layer loads in both runs (11 and 210 times) with no "was not found" line, and both log only VUID-VkDeviceCreateInfo-pNext-pNext, so this head adds no VUID- message. On GitHub's Windows runner they build and the 12 Vulkan tests skip, so its lines are the suites only. The libc and libkernel suites pass on both runners. The libc and libkernel suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

  • NVIDIA GeForce RTX 3070 Laptop GPU at 082ef25, the fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533, fix(agc): accept a colour alias over a native multisample depth plane when HTILE proves its clear #9742 and feat(agc): sample the stencil plane of a native multisampled D32S8 target #9717 stack before the fix(agc): retire an unwritten native multisample depth surface on guest access #6727 merge (driver 617.42, Windows 11, WinLibs GCC 15.2.0, Release, ANYPS5_REQUIRE_VULKAN=1; GPU clocks weren't held, pass/fail only): 11 of the 12 native depth tests pass 5 of 5 at 2, 4 and 8 samples. agc_driver_native_depth_driver_queued_fill failed 4 of 4 with "AGC graphics: native multisample depth metadata fill over unwatched memory is not implemented". On Windows the queued fill still refuses, as on main; 1582cf6 makes the test skip there with that reason. A run of 1582cf6 on that GPU is pending.

  • 1582cf6 (run): on GitHub's Linux runner, the 13 native depth, DCC clear and pipelined DMA tests pass 10 of 10, none skipped, agc_driver_native_depth_driver_queued_fill among them with watched imports. In the same run, validation against main: main's arm runs the 7 of the 13 tests it has, the layer loads in both runs (11 and 210 times) with no "was not found" line, and both log only VUID-VkDeviceCreateInfo-pNext-pNext, so this head adds no VUID- message. On GitHub's Windows runner the change builds and the 12 Vulkan tests skip, so its lines are the suites only. The libc and libkernel suites pass on both runners. The libc and libkernel suites ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners. tools/check_skips.py accepts the new skip line on the Windows job, where any agc* skip is expected; on Linux the case can't skip, since it forces watched imports. check_conventions.py: no errors and no comment lines added; the one warning is debt-length on the native multisampled depth entry, now 3,990 characters.

  • 5388641, after @jotitadev's macOS run of 1582cf6 (review): on an Apple M2 Max with MoltenVK 1.4.2, on feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658's macOS port, the eight native depth tests and agc_driver_native_multisample_depth pass. agc_driver_native_depth_driver_path and agc_driver_native_depth_driver_queued_fill pass every case and then abort at exit with recursive_mutex lock failed: Invalid argument. The test handed its device to the driver and never took it back, so the driver singleton released it at exit, after statics its teardown locks had been destroyed.

    • 5388641 (test only) gives the driver's device back before each of the two tests returns.
    • On GitHub's runners (run), ^agc_driver_native_depth and agc_driver_native_multisample_depth: on Linux 10 tests pass 10 of 10, and agc_driver_native_depth_bda_clear skips as on main. On Windows the 11 build and skip, since that runner has no Vulkan device. The libc and libkernel suites pass on both runners. They ran with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.
    • These runners show no red: glibc and Windows tolerate locking the destroyed mutex, and the abort is macOS's. Not yet rerun on macOS.
  • Not tested: Spirit of the Island (PPSA13934), and The Grinch and PPSA03208 on 514643a and later.

Next stops to expect:

  • An HTILE mask clear of a separate-stencil target still refuses, under feat(agc): import cleared native multisample D32S8 targets and whole HTILE fills #6300's rule.
  • If Ghost of Tsushima creates its target before the two fills, while the memory still holds older contents, creation refuses with its own reason, and the fills can't help. The run would then print that reason.
  • A plane under a colour target that drew is imported unproven, so its first stencil test or stencil sample refuses until a whole-plane fill proves it.
  • Ghost of Tsushima's depth plane: a host-only 2-sample R32_SFLOAT colour target cleared to 0 covers exactly the depth range, so the depth import refuses with the alias text (milescoviello's run above). fix(agc): accept a colour alias over a native multisample depth plane when HTILE proves its clear #9742, stacked on this PR, covers it. As JJenkx's source review notes, the colour clear alone doesn't show that the depth agrees with it: that needs the target's clearDepth and its HTILE.
  • A per-sample R8_UINT read of the stencil plane refuses (PPSA03208); feat(agc): sample the stencil plane of a native multisampled D32S8 target #9717, stacked on this PR, covers it.
  • With APS5_PIPELINE_DMA set, a large queued DMA fill over the depth's metadata isn't noted, so the next sample refused it as an external write (JJenkx). Fixed in this PR by dd7ad46 (Co-authored-by: JJenkx) where host imports stay write-watched, as on Linux in our runs. The queued fill is stored through a host import, and where host imports are compared rather than watched, as Windows' direct imports always are, the fill still refuses, as on main: on an NVIDIA GeForce RTX 3070 Laptop GPU, agc_driver_native_depth_driver_queued_fill failed 4 of 4 with "native multisample depth metadata fill over unwatched memory is not implemented". 1582cf6 makes that case skip on Windows with the reason, and a run of it on the RTX 3070 is pending. Back-to-back HTILE fills of one pattern still refuse as overlapping clear transitions, queued or not.
  • de4cee5, a merge of main 0db47b2. The only conflict was DrawPrecheck.cpp's include block: main added DeferredLabels.hpp (perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946) and this branch DepthSurface.hpp, and both are kept. On GitHub's runners (run), the agc_driver_native_depth* and agc_driver_native_multisample_depth* tests pass 10 runs in a row on Linux and on Windows. The libc and libkernel suites pass on both, run with APS5_SKIP_TIMING_TESTS=1, which relaxes precise_wait's median bounds on GitHub's hosted runners.

Checklist

Gashrod1 and others added 9 commits October 15, 2026 15:22
…HTILE fills

A 2, 4 or 8 sample D32_SFLOAT_S8_UINT target with its stencil in HTILE is
imported natively when every HTILE word proves both planes cleared: the
depth range as before, and SMem 0 for the stencil. The stencil plane joins
the watched, guarded and generation-checked ranges. A depth-only HTILE,
an unaligned stencil plane, or a word failing either proof is refused with
its own reason.

A fill covering the whole HTILE of a sampled native target is a clear
transition, proven at the next sample like the HTILE mask clear: complete
tracked stores since the fill, no CPU or queued writes, and every word
proving the depth and, with a stencil plane, the stencil clear value. A
partial fill and an HTILE mask clear of a stencil target stay refused.
…attern

A whole HTILE fill recorded on the GPU is still pending when the title
binds or samples the depth in the same frame, so the HTILE words in guest
memory are not the fill's yet and the queued write was refused as an
external GPU write. The fill's write note is kept with its pattern: while
it is still the newest queued write over the HTILE, the clear is proven
from the pattern, and the completed tracked store check is not needed for
a GPU fill. A later queued write, a CPU write or a pattern that does not
prove the clear is refused as before.

AI-assisted: yes
Brings upstream main a6dcea9 under boykopovar#6300's two commits.

Conflicts:
- DepthSurface.cpp prepareDepthSampling: boykopovar#6300's stencil checks are kept
  and main's 752cca1 drops the ITERATE_256 requirement, so the HTILE
  check is main's line ("requires pipe-aligned HTILE without VRS").
- DepthSurface.cpp NoteHtileDepthClear and NoteDepthMetadataFill: boykopovar#6300's
  clear and fill proofs are kept, written with main's first-refusal-wins
  DepthSamplingState::Refuse. After the proven whole-fill branch, the
  remaining fills take main's ForgetClear/NoteHtileClear and single-sample
  plane-fill paths.
- NativeMultisampleDepth.cpp CheckDepthTarget takes both boykopovar#6300's
  ClearSource and main's iterate256, in that order; main's ITERATE_256
  call passes ClearSource::HtileDispatch.
- docs/dev/debt/libSceAgcDriver.md: one entry with boykopovar#6300's stencil and
  fill text and main's ITERATE_256 text.

Adapted to main: main samples a native multisampled depth target
through a shadow copy that is refreshed when the sampled view is looked
up (1912045, 5a0a00f). CheckStencilTarget recorded its raster and
the fetch in one batch, so the fetch read the shadow from before the
raster ("native depth-stencil sampled fetch lost a depth sample or a
stencil test outcome"). It now submits the raster, looks the view up
again and syncs before the fetch, as main's CheckDepthTarget does.
…ared stencil plane

The Grinch: Christmas Adventures (PPSA07656) binds a two-sample
D32_SFLOAT_S8_UINT target whose stencil is a plane of its own and whose
HTILE is depth-only (DB_STENCIL_INFO.TILE_STENCIL_DISABLE), so HTILE
holds no stencil state. Every byte of that plane is the clear stencil
when the target is first bound. The import refused it ("native
multisample stencil import requires HTILE stencil metadata") and the
title stopped before its first present.

With a depth-only HTILE the stencil is uncompressed in its plane, so the
plane is its own proof: the import scans it once and accepts the target
when every byte equals DB_STENCIL_CLEAR. The depth keeps its HTILE clear
proof. The plane is already one of the import's ranges, accessible,
watched, generation-checked, guarded from guest publication and kept
apart from the depth and HTILE ranges, so a later CPU or GPU write to it
refuses sampling like a write to the depth. The host image is created
cleared to the clear stencil. The plane's base keeps the 64 KiB
alignment of the depth plane: both use SW_64KB_Z_X, the decode takes
the pipe/bank XOR from the depth base's low 16 bits and masks both bases.

A whole HTILE fill of such a target is proven for the depth only and
clears only the host depth: the stencil plane is untouched by the fill
and still proven unchanged since the import.

A stencil plane that is not all clear, a stencil in neither HTILE nor
its own plane, D16S8 and HTILE words that do not prove a clear keep
their refusals.

Tested: agc_driver_native_depth_separate_stencil imports a four-sample
target (two and eight where the device has them), samples the clear
depth, stencil-tests the clear stencil, and refills a depth-only HTILE
keeping a replaced stencil; one stencil byte not clear, a later stencil
write and an unwatched plane are refused. Without the import change the
target refuses "native multisample stencil import requires HTILE
stencil metadata"; without the fill change the refilled target loses
its stencil.
…or without HTILE

Once its D32S8 target imports, The Grinch: Christmas Adventures
(PPSA07656) samples the depth plane through a T# with no metadata
(META_DATA_ADDRESS 0, not pipe-aligned) while the target has its HTILE.
The pipe/bank XOR and ITERATE_256 match. CheckResource required the
descriptor's metadata to be the target's HTILE and stopped the title
with "native multisample depth sampled metadata or XOR mismatch".

The host image keeps the depth decompressed, so a descriptor that reads
the plane without metadata reads the values a metadata-aware one does.
CheckResource now accepts a descriptor with no metadata at all beside
one naming the target's HTILE, when its address, format, XOR and
ITERATE_256 match. A descriptor with any other metadata keeps refusing.

Assumption: on hardware such a read returns the decompressed depth only
once the guest has expanded the plane (an in-place expand with
compression disabled). Main draws an in-place expand as an ordinary draw
that writes the depth attachment (DB_RENDER_CONTROL compress-disable bits
are not refused), over a host image that already holds the decompressed
depth, and has no out-of-place expand for multisampled planes. Nothing
here observes whether the guest expanded first; a title that samples the
plane unexpanded gets the decompressed values instead of the raw plane.

Tested: agc_driver_native_depth_separate_stencil samples the separate
stencil target through a descriptor without metadata, before and after
raster writes and after a depth-only HTILE fill, and gets the depth.
The same descriptor with another XOR and one naming other metadata are
refused. Without the change the first sample refuses "native
multisample depth sampled metadata or XOR mismatch".
…th-only HTILE when every word proves the same clear

Ghost of Tsushima (PPSA02225) samples a two-sample D32S8 1920x2160
target with a separate stencil plane and a depth-only HTILE that is
neither pipe-aligned nor ITERATE_256. The import required a pipe-aligned
HTILE.

Pipe alignment only decides where each tile's word sits in the HTILE.
When every word is the same and proves the clear, their order cannot
change what they prove, so a depth-only HTILE that is not pipe-aligned
is imported when all its words are one word proving the clear value.
A non-pipe-aligned HTILE whose words differ is refused ("native
multisample depth HTILE that is not pipe-aligned holds more than one
word"), and one that holds stencil state keeps the alignment refusal.
The range stays HtileMetadataBytes(extent): Ghost's whole-HTILE fill
covers exactly that, 0x50000 bytes for 1920x2160. ITERATE_256 is
already independent of the import (752cca1).

Tested: agc_driver_native_depth_separate_stencil runs every separate
stencil case on a depth-only HTILE that is not pipe-aligned or
ITERATE_256 too, sampled through a descriptor naming that HTILE and one
without metadata; one differing word is refused. Without the change the
target refuses "native multisample depth initial import requires
pipe-aligned HTILE without VRS".
…encil clear

Ghost of Tsushima (PPSA02225) fills the separate stencil plane of its
two-sample D32S8 target with pattern 0 (0x870000) before it samples the
target. Any write over the plane was refused ("guest writes over native
multisample depth are not implemented").

A DMA or compute fill that covers the whole plane of a target whose
depth-only HTILE leaves the stencil in the plane is now recorded like a
whole-HTILE fill. At the next sample it is proven the way a whole-HTILE
fill is: no CPU write since the fill, complete tracked stores since the
fill unless the fill is the plane's newest queued GPU write, and every
plane byte equal to the fill byte once it has landed. The host image's
stencil aspect is then cleared to that byte. A fill that covers part of
the plane is refused ("native multisample stencil plane partial fill
transition is not implemented"), and so is a pattern that is not one
byte, a fill over unwatched memory and a second fill before the plane
was proven.

Tested: agc_driver_native_depth_separate_stencil fills the HTILE, the
stencil plane, or both, after a draw replaced the stencil, on pipe-aligned
and non-pipe-aligned depth-only HTILE, and checks the sampled depth and
which stencil reference the next draw passes. Partial, two-byte-pattern,
unstored and CPU-raced plane fills are refused. Without the change the
first plane fill refuses "guest writes over native multisample depth are
not implemented", and the partial fill refuses with that text instead of
the partial fill one.
…te is recorded after the fill is noted

Ghost of Tsushima (PPSA02225) fills the HTILE (0x50000 bytes) and the
separate stencil plane (0x870000 bytes) of its two-sample D32S8 target
with pattern 0 before it samples the target. Both are DMA_DATA fills
larger than 64 KiB. PacketMemory notes a DMA_DATA fill before
storeBufferFill records it, so when the range has a host import the
fill's GPU write is recorded after NoteDepthMetadataFill took
LastWriteNote. The note was then 0, the fill did not count as the
queued fill, and the sample that followed in the same batch refused
"external GPU writes over native multisample depth are not implemented".
A compute fill is noted after its write is recorded and was not
affected.

When the noted fill had no write note yet, the sample now looks at the
writes recorded over the HTILE or the stencil plane since the fill was
noted. It takes the one write as the fill's note when that write is the
only one, covers the whole range, and is a word fill of the noted
pattern. A second write since, a different word or a fill of part of
the range keeps the refusal. Everything else is proven as before:
complete tracked stores, no CPU write since the fill, and the plane or
HTILE words holding the fill once it has landed.

The "native multisample depth metadata fill transition is not
implemented" stop reported on boykopovar#6300 is set by a fill over the HTILE
that does not cover all of it, or by any HTILE fill when the target's
sampling never activated. Ghost's fill covers the whole HTILE, so its
target was most likely refused at creation, and on boykopovar#6300 the fill's
message replaced that refusal. Refusals now keep the first one, so a
target refused at creation reports its creation reason after the fills.

Tested: agc_driver_native_depth_separate_stencil runs Ghost's sequence
(creation of a depth-only HTILE that is neither pipe-aligned nor
ITERATE_256 with a separate stencil plane, clear 0 and stencil 0, a
whole-HTILE fill of 0, a whole stencil plane fill of 0, then the first
sample) with the fill stored, queued after it is noted, and queued
before it is noted. Each one samples the cleared depth and passes only
the stencil reference 0. A later queued stencil write, a queued HTILE
fill of another word and a queued fill of part of the HTILE are
refused, and a target refused at creation keeps its refusal after the
fills. Without the change the fill queued after it is noted refuses
"external GPU writes over native multisample depth are not
implemented".
…nce its GPU write is recorded

A compute dispatch of the memory-value fill kernel is noted twice: the
dispatch path notes it from MatchMemoryValueFill before the fill runs
(Dispatch.cpp), and storeBufferFill notes it again after FillBuffer has
recorded its GPU write. On a native multisample depth target the second
note refused "overlapping native multisample depth clear transitions
are not implemented" for the HTILE, and the same refusal for the
separate stencil plane, so a compute fill of either one never reached
the sample.

A second note of a pending fill is now taken as the same fill when it
has the same pattern, the first note had no write note, and the one
write recorded over the range since the first note is a word fill of
the whole range with that pattern. That write's note becomes the fill's
note, and the first note's write generation is kept, so CPU writes
between the two notes are still refused. Two notes without that write
between them still refuse as overlapping (agc_driver_native_depth_stencil's
FillOverlap case, and a new stencil plane case). A memory-value fill
that falls back to CPU stores, with no host import of the range, is
still refused as overlapping.

Tested: agc_driver_native_depth_separate_stencil runs Ghost's sequence
with each fill noted, queued on the GPU and noted again, and samples
the cleared depth and stencil 0. A stencil plane fill noted twice
without a write between is refused. Without the change the second
note refuses "overlapping native multisample depth clear transitions
are not implemented".
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 22, 2026

@MarcZX10 MarcZX10 left a comment

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 implementation and regression report are detailed. The exact PR head is based on current main, required eligibility/conventions/intake/labels/report checks pass, and the reported lavapipe regressions include negative controls. The hosted Linux/Windows product and relinker jobs were skipped; I did not run local tests.

I’m requesting changes for these remaining correctness and contribution-rule items:

  1. Required AGC validation comparison is missing from the report. CONTRIBUTING.md requires the same focused tests on main and this change, on the same machine and GPU, using ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, and ctest -V. Please report both runs, confirm the validation layer was actually found, compare the logs for new VUID- errors, and give the GPU vendor/model/driver/OS. The current lavapipe report does not establish those required comparison details.

  2. The no-metadata sampling path accepts an unproven state. CheckResource accepts dccAddress == 0 for this target, while this PR says it cannot observe whether the guest performed the in-place expansion first and that an unexpanded sample will still receive the decompressed host image. The new fixture checks clear samples, not the title sequence that establishes this assumption. Please either provide evidence that the affected title expands before this descriptor is used and test that sequence, or keep refusing this descriptor until the guest expansion state is established. Otherwise the emulator can silently return different values from the guest memory the descriptor names.

  3. The Unblocks checklist is incomplete. The repository requires each title ID to be paired with its Game bring-up discussion. The Grinch has #8606, but the Ghost of Tsushima and Spirit of the Island entries currently link to PR comments rather than Game bring-up discussions. Please add those discussion links, or narrow the checklist to titles with a qualifying discussion and mark the others none.

  4. Please close the overlap coordination loop. The live overlap scan reports shared DepthSurface.cpp/test regions with open #7233 and #7371, also identified in this PR description. The repository asks authors to agree merge order for these overlaps and declare Depends on when one must land first. Please record the coordination/order and update the checklist if either PR is a prerequisite.

I have not found a reason to dispute the reported lavapipe regression results; the requests above concern the required validation evidence, the unverified no-metadata behavior, and the repository’s title/overlap checklist rules.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

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.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6987 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L131, L335
PR 6989 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L131, L335
PR 6995 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L131, L335
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L131, L335
PR 8105 core/libs/prx/libSceAgcDriver/CMakeLists.txt L2515, L2518
core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp L675, L846-849, L851-862, L1343
PR 9186 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L718-728
PR 9849 core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp L864-865, L1297
PR 10049 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L1292

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.

Pull request Shared files not compared
PR 1191 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 4072 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4449 docs/dev/debt/libSceAgcDriver.md
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5431 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6169 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6202 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6235 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6741 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7369 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 7705 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8246 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 8365 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8395 core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8527 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8552 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8608 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8672 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8998 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 9176 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9243 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9248 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9287 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9289 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9356 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 9387 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9412 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9431 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9489 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
PR 9508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9521 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9673 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9674 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9675 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9676 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9685 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9856 docs/dev/debt/libSceAgcDriver.md
PR 9879 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9891 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9892 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9893 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9897 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 9898 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9965 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp
PR 10026 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS Ghost of Tsushima (PPSA02225) on this PR's head 5daa8cb7a9, as I offered on #6300. Release libs, Linux, RTX 4070 Ti, NVIDIA 615.71.09. The memory cap was 18 GB: the host didn't have room for 20, and it didn't matter, since the runs stop early.

  • Warm (shader cache from earlier runs): stops at 15 s.
  • Cold (no cache): stops at 55 s.
  • A third warm run, to keep a core: stops at 16 s.

All three end with the same message, and none prints a native multisample refusal, so the earlier stops on main and on #6300 are passed:

AGC graphics: a CPU read of 0x1546eb0000+0x870000 overlaps multisampled color target 0x1546eb0000+0x870000 (2 samples), whose samples are only on the host: guest access to it is not implemented

What I can tell from it:

  • 0x870000 is this target's separate stencil plane (MultisampledDepthBytes of 1920x2160 at 2 samples, the plane size you listed for Ghost).
  • The same range is in MultisampleTarget.cpp's guarded list as a 2-sample color target. So the game also renders into the stencil plane's memory as a color target, and those samples are only in the host image.
  • The guard (GuestMemory::SetReadGuard(&guardReads)) refuses any CPU read of that range. The read covers exactly the plane, which matches the new check in DepthSurface.cpp at line 158, GuestMemory::Read(target.stencilAddress, ...). That check compares every byte of the plane with DB_STENCIL_CLEAR.
  • I couldn't confirm the reader from the core. The exception is caught on the GPU thread and rethrown by the present loop, so the core only shows VideoOutDriver::presentLoop calling std::terminate. If you want the throw site itself, I can rerun with a __cxa_throw hook.

If that's the reader, then for Ghost the plane's current contents are in the host color image, not in guest memory. The clear proof would need to come from that target's last clear, or the import would need to take the plane from the color target's image.

AI-assisted: yes (Claude Code)

@MarcZX10 MarcZX10 left a comment

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.

Thanks for rebasing to the current main and updating the report with three Ghost of Tsushima runs on the PR head. The earlier native multisample refusal is now passed, but the run reaches a new guest access refusal over the same 0x870000 stencil-plane range. The reported overlap with a host-only multisampled color target also raises a concrete issue with the new CPU read used to prove the plane clear.

I’m keeping the request for changes for these remaining items:

  1. The separate-plane clear proof must account for the overlapping color target. The report says the plane range is also guarded as a multisampled color target and its samples are host-only; the title run now stops on a CPU read of exactly that range. Please trace the read to its source and establish how the implementation can prove the stencil plane’s clear without reading guest memory that is guarded or stale relative to the host color image. Add a regression for the relevant alias/guard path and update the game result after the fix. Until then, the reported title run does not establish this path as working.
  2. The no-metadata descriptor still accepts an unproven expansion state. The report says it cannot observe whether the guest expanded the plane before sampling and would return the decompressed host image even if it did not. Please provide evidence and a regression covering the guest sequence, or retain the refusal until that state is established.
  3. Required AGC validation comparison is still missing. Please report the same focused tests on main and this head using ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation with ctest -V on the same machine and GPU; confirm the layer was found, compare for newly introduced VUID- messages, and name the GPU vendor/model/driver/OS. The existing lavapipe results do not include this required comparison.
  4. Complete Unblocks according to the repository checklist. The Ghost discussion is #4461. The Grinch is #8606. Spirit of the Island has no Game bring-up discussion yet, so please omit it from Unblocks for now (or add a qualifying discussion first).
  5. Record overlap coordination and required ordering. The description identifies shared hunks with #7233 and #7371. Please record the authors’ agreed integration order and list the required PR number(s) on Depends on if this branch needs them first.

The updated hosted eligibility, intake, conventions, labels and report checks pass; Linux/Windows product and relinker jobs are skipped, and I did not run local tests. I have not found grounds to dispute the reported lavapipe fixture results; the items above concern an observed guarded read on the title path, an explicitly unverified sampling assumption, required validation evidence, and the repository’s checklist/overlap rules.

@MarcZX10

Copy link
Copy Markdown
Contributor

Thanks for the new Ghost run and for documenting the exact next stop. The updated trace reaches a CPU read of the separate stencil plane, which the report also says overlaps a host-only multisampled color target. I have refreshed my review with a request to trace that read and prove the plane clear without relying on guarded or stale guest memory. I also noted that the no-metadata sampling assumption, required same-GPU Vulkan validation comparison, Spirit of the Island Unblocks entry, and #7233/#7371 ordering still need resolution. The existing lavapipe fixture results remain useful; I did not run them locally.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@milescoviello thanks for the three Ghost of Tsushima Director's Cut (PPSA02225) runs and for tracing the stop this far. The range in the guard is exactly the stencil plane, and the only CPU read of exactly that range is this PR's clear check in prepareDepthSampling (DepthSurface.cpp L158), so the __cxa_throw hook isn't needed. As you read it, the game uses the plane's memory as a 2-sample colour target as well; with 64 KiB tiles, an R8 target at 1920x2160 has the same footprint.

@MarcZX10, thanks. The next commits, by point:

  1. Before reading anything, the import asks whether a host-only multisampled colour target covers its ranges, without reading guarded memory. If an R8 target with the same samples and footprint covers the plane and its newest content is a known clear, the import takes that value as the stencil. Otherwise it refuses with a named reason and logs that target's format, samples and newest operation. A regression puts a colour target over the plane memory (cleared, drawn after its clear, and another format) and checks that no guarded read happens.
  2. A sample through a descriptor without metadata is accepted only after a draw with DB_RENDER_CONTROL's compress-disable bits, the guest's in-place expand, has marked the surface expanded. A clear or fill resets the mark. Without an observed expand it refuses as before, and a regression covers both. Whether The Grinch: Christmas Adventures (PPSA07656) passes that step then depends on its expanding first, which needs a run to show.
  3. The validation comparison runs the same focused tests on main and on the new head under ctest -V, with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1 and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, on lavapipe on Ubuntu 24.04. It includes the loader's output showing the layer and a diff of the VUID- messages.

Points 4 and 5 are in the description now.

@milescoviello, once the new head is up, would you run Ghost of Tsushima on it? Its new log line says whether the game clears or draws that colour target, which decides whether the next step is a copy of its samples into the stencil.

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS yes, I'll run Ghost of Tsushima (PPSA02225) on the new head when it's up, warm and cold, and post the log lines about that color target.

The __cxa_throw hook run had already started when your comment came in, and it confirms the reader. Throw stack, frames resolved with addr2line against the run's libSceAgcDriver.prx:

AgcDriver::Graphics::RequireNoMultisampleColorTarget(unsigned long, unsigned long, char const*) [clone .cold]
(guest-memory read guard)
AgcDriver::Graphics::(anonymous namespace)::prepareDepthSampling(Context const&, DepthTarget const&)
... (three draw-path frames)
std::_Function_handler<void (), AgcDriver::DriverDetail::Driver::draw(...)::...>

It's the same stop at 18 s.

AI-assisted: yes (Claude Code)

…rom that target

Ghost of Tsushima Director's Cut (PPSA02225) renders a 2-sample R8
colour target over the memory of a D32S8 target's separate stencil
plane. The native import proved the plane by reading it, and that read
hit the colour target's guard: "a CPU read of ... overlaps multisampled
color target ..., whose samples are only on the host".

MultisampleTarget gains a query that does not throw: the active colour
target whose guarded range overlaps a range, with its format, samples,
footprint and newest content. A pending fill the target absorbed, a
CMASK fast clear, or a clear it recorded with no draw since is a known
uniform value; a draw, a storage write or any refused metadata is not.
The target records its clears and whether it was drawn since.

prepareDepthSampling asks it for the depth, HTILE and stencil plane
ranges before reading guest memory. An R8 UNORM or UINT target with the
depth's sample count over exactly the plane, holding a known value,
gives the plane's initial stencil, which the first host clear uses. Any
other overlap refuses sampling ("native multisample stencil plane
aliases a multisampled colour target whose samples are only on the
host", or the depth or HTILE form) and prints the colour target's
address, format, samples and newest operation, at most eight times.
CheckCurrent runs the same query: a plane fill over an aliased plane is
proven from the colour target's value, not by reading the plane, and a
colour target that drew, changed value or appeared since the last proof
refuses sampling.

Tested: agc_driver_native_depth_aliased_stencil. A 4-sample R8 target
over the plane with an absorbed fill imports and the next stencil test
passes the fill value and not another; a second absorbed fill is proven
and taken; a colour draw after it refuses. The target drawn after its
fill, and an R8G8 target over the plane, refuse with the new reason. The
guest plane keeps its 0xa5 bytes throughout. Two and eight samples, and
another colour sample count, are not available on lavapipe. With the
product change reverted the cleared case throws the guard text above
(1 of 1); with it the mode passes 5 of 5.
… an in-place expand

5be005e let a descriptor with no metadata (META_DATA_ADDRESS 0, not
pipe-aligned) sample a native multisample depth target from its
decompressed host image, assuming the guest had expanded the plane. On
hardware such a read sees decompressed depth only after an in-place
expand, a draw with DB_RENDER_CONTROL DEPTH_COMPRESS_DISABLE (bit 6)
and STENCIL_COMPRESS_DISABLE (bit 5) set, as radeonsi's in-place
decompress sets them.

DecodeDepthRenderControl reads DB_RENDER_CONTROL, DB_Z_INFO's format and
sample count and DB_Z_READ_BASE for a draw with clear or compress-disable
bits. The draw precheck hands it to NoteDepthRenderControl before the
draw is dropped as drawing nothing (an expand draw has no depth test and
no colour writes), draining the draw pipeline first for an expand, or
for a clear over an existing depth surface. A draw over the target with
the depth bit, and the stencil bit when the target has a stencil plane,
marks the surface expanded; a clear draw, an HTILE clear or fill
(NoteHtileDepthClear, NoteDepthMetadataFill, a cleared HTILE at
DepthSurfaceView) and a plane fill clear the mark. CheckResource takes a
descriptor without metadata only while the surface is marked, and
otherwise refuses with "native multisample depth sampled metadata or XOR
mismatch", printing the last DB_RENDER_CONTROL seen at most eight times.

Tested: agc_driver_native_depth_expand_gate, for D32 and D32S8 with a
separate stencil plane: a bare sample before any expand is refused, and
after only the depth bit on D32S8; after the expand draw it gets the
cleared and then the drawn depth; an HTILE fill, a clear draw and a
plane fill (an HTILE mask clear on D32) each refuse it again until the
next expand. The separate-stencil test now draws an expand before its
bare sample. With the gate reverted the first bare sample is accepted
(1 of 1); with it the mode passes 5 of 5.
A native D32S8 target whose separate stencil plane a multisampled R8
colour target covers is refused for sampling once that colour target
has drawn. Taking it would need a GPU copy of the colour target's R8
samples into the depth image's stencil aspect; the debt file now names
that gap and that the console's stencil reads of such a plane are not
measured.
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@EagleBearerYS I ran #9533 (5daa8cb) on PPSA03208 (current main plus a few other open PRs this title needs to get here, a local colour-guard retire like #8223's, and diagnostic prints). It clears both stops it names for this title: the format refusal and the HTILE stencil requirement. The two whole fills (HTILE, then the separate stencil plane) are taken as clears with no refusal, and the non-pipe-aligned HTILE check passes. Thanks.

The next stop is at the first dispatch of a compute program, in CheckResource:

native multisample depth sampled identity or format mismatch

I printed the descriptor at that check. The program samples the surface three times:

read base format (vk) samples dim / tile metadata result
1, 2 depth plane 0x1544ed0000 22 (D32) 2 2D MSAA / 4 none (dcc=0) passes
3 stencil plane 0x1546eb0000 5 (R8_UINT) 2 2D MSAA / 4 none fails: base and format

T# of the failing read: 1546eb00 c0500000 021bc1df e1810924 00000000 00700010 00000000 00000000. The surface is 1920x2160, two samples, D32S8, HTILE depth-only, with the stencil plane registered at 0x1546eb0000.

Here is how it gets there, as far as I can see. DepthSurfaceTexture finds the surface for a reader whose base is either the depth plane or the separate stencil plane (with a one-byte format). For a multisampled reader, it then calls CheckCurrent and CheckResource, and CheckResource only accepts the depth plane in format 22. SampledAccepts already knows a stencil view (R8_UINT at stencilAddress), but the multisampled path never reaches it.

So the title reads the stencil of the native D32S8 target per sample, through a plain R8_UINT 2D MSAA descriptor with no metadata, right after reading the depth the same way. Would a stencil-aspect sampled view of the native image fit in this PR? Or would you rather keep it for a follow-up? If it helps, I can run a build of yours on the title and post the result.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@oneandonlydean thanks for running it on PPSA03208 and for printing the descriptors, and good to see both of its stops clear. Your third read samples the stencil plane per sample through a plain R8_UINT 2D MSAA descriptor, which this PR doesn't handle. It'll come as a follow-up stacked on this PR. Sampling the stencil plane needs a stencil-aspect view of the native image, the R8_UINT T# accepted at the stencil address, and the sample shadow carrying stencil too, which is more than this PR's import change. We'd be glad to take you up on the offer to run it once it's up.

…plane fill

A native D32S8 target whose separate stencil plane a multisampled colour
target covers with a draw, unknown content, or another format or sample
count was refused at import, so sampling its depth failed as well. The
plane is now imported unproven: its host stencil is not taken from it,
depth sampling and depth-only draws continue, and a draw with a stencil
test or a sample of the plane refuses with the stencil alias text and a
line naming the colour target's state.

A later whole-plane fill proves the plane from the fill byte without
reading guest memory and clears the host stencil to it, provided a
colour target still over the plane absorbed that fill as the same R8
byte at the same sample count; otherwise the fill is refused.
The sample shadow of a native multisampled depth target was moved from
UNDEFINED to GENERAL with only the depth aspect. For a host format with
stencil and without separateDepthStencilLayouts that breaks
VUID-VkImageMemoryBarrier-image-03320, which the validation layer
reported for every D32S8 shadow. The transition now names the stencil
aspect too when the host format has one; the shadow still copies only
the depth aspect.
@xXPBSXx

xXPBSXx commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Data point (Windows, AMD RX 6400), PR head 5daa8cb, merge-base a6dcea9 (merges cleanly into upstream/main).

Target: agc_driver_native_multisample_depth (the PR's NativeMultisampleDepth.cpp).

  • With the PR (head): 3/3 pass.
  • Same test, DepthSurface.cpp reset to the merge-base (test file kept): 3/3 fail.
  • GPU: Selected GPU name=AMD Radeon RX 6400 vendor=0x1002 device=0x743f (from the test log).

Only this one execution test was run; no title run and no other GPU. Logs are available as file names only: data_9533_rx6400_green1-3.log, data_9533_rx6400_red1-3.log.

AI-assisted (Claude Code).

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@EagleBearerYS thanks, that matches what I saw: both stops clear with this PR, and the next one is the per-sample R8_UINT read of the stencil plane. A follow-up stacked on this one makes sense. Mention me when it's up and I'll run it on PPSA03208.

@xXPBSXx

xXPBSXx commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Title data point (Windows, AMD Radeon RX 6400), PPSA02225, 600 s runs, one run each.

  • GPU, both runs: Selected GPU name=AMD Radeon RX 6400 vendor=0x1002 device=0x743f subgroup=64 (run_*.out line 24).
  • Without the PR (libs from main 17f5335562, all 360 libs rebuilt; run_072452): stops with compute shader 0x20001ef700: AGC graphics: native multisample stencil or depth format import is not implemented (.err line 252-258, then abort).
  • With the PR (the same main plus fix(agc): import native multisample D32S8 targets with a separate stencil plane and a depth-only HTILE #9533 merged locally, all 360 libs rebuilt; run_080153): that message does not appear (0 matches in the 3544-line log). The run now stops later with AGC graphics: a CPU read of 0x1546eb0000+0x870000 overlaps multisampled color target 0x1546eb0000+0x870000 (2 samples), whose samples are only on the host: guest access to it is not implemented (.err line 233-237, then abort).

So on this title the PR moves the stop past the depth/stencil import; the next stop is a different refusal (CPU read of a multisampled color target). One run per state, so this says nothing about how often either stop occurs. No other title and no other GPU were run. Run folders on request (file names only: PPSA02225_rNT/run_072452, run_080153).

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 22, 2026
@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS Ghost of Tsushima (PPSA02225) on the new head 1e07a659ed, the same way as before: release libs at that commit, Linux, RTX 4070 Ti, NVIDIA 615.71.09, 16 GB cap, warm and cold. Both runs stop at the same place as on 514643ab16, warm at 53 s and cold at 48 s, with the same lines:

[gpu] native multisample depth 0x1544ed0000+0x1fe0000 aliases multisampled colour target 0x1544ed0000+0x1fe0000 (vk format 100, 2 samples), newest clear 0x00000000 0x00000000 0x00000000 0x00000000
compute shader 0x20001ef700: AGC graphics: native multisample depth or HTILE aliases a multisampled colour target whose samples are only on the host

So the new commits don't change this title's path, which fits the depth-plane alias being left for your follow-up. I'll run that one when it's up.

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@milescoviello thanks for confirming the stop is unchanged on 1e07a65. That depth-plane alias is #9742's, stacked on this PR, and it's up now. With a depth-only HTILE whose every word proves DB_DEPTH_CLEAR, its import accepts one colour target over the depth plane with the target's sample count, like the title's 2-sample R32_SFLOAT one: depth reads the clear value there, never the plane's bytes. A draw or clear into that colour target afterwards, or a second one over the plane, still refuses at the next sample. If you can run #9742's head, 219e925, the same way, warm and cold, we'd like to know where it stops next. It's on this PR's previous head, which takes this title's path the same way.

…ear dispatch

With main's pending clear model an HTILE mask clear dispatch is noted
pending when its packet is decoded and takes its generation only when
the dispatch is recorded. A whole-HTILE fill noted in between found no
generation, took the clear as its own, and the arm that followed kept
the fill's pattern, so the dispatch clear was proven as a fill. A fill
now refuses while a dispatch clear is pending, from its notification
until a sample takes it.

agc_driver_native_depth_clear_refusals notes a fill between the clear's
notification and its arm and expects the refusal.
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
# Conflicts:
#	core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
Main's boykopovar#6727 retires an unwritten native multisample depth surface on a
guest read instead of refusing it, and its own fixtures note a draw's
write with NoteDepthSurfaceWrite before they expect a refusal. The
D32S8 stencil fixture now does the same before it reads the stencil
plane, so --stencil still checks that a written surface refuses.
A queued DMA_DATA fill of more than 64 KiB is stored through a host
import. Windows compares its direct host imports instead of keeping them
write-watched, so after the store the HTILE is no longer watched and the
fill's note refuses with "native multisample depth metadata fill over
unwatched memory is not implemented". On an NVIDIA GeForce RTX 3070
Laptop GPU, agc_driver_native_depth_driver_queued_fill failed that way
in 4 of 4 runs. Noting the fill before the store only moves the refusal
to the next sample, because the clear proof needs a write-watched HTILE.

On Windows the queued mode now skips, and says why, when
PrepareImportWatch reports compared imports. Linux still forces watched
imports, so the case keeps running there. The debt entry records that
such a fill is refused wherever host imports are compared.

@jotitadev jotitadev left a comment

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.

Apple GPU / MoltenVK run of 1582cf6cc0. The two new driver-path tests check every case and pass it. Then the process aborts at exit on macOS, when the driver singleton tears down the device the test handed it.

Machine: Apple M2 Max, macOS 27.2, x86_64 under Rosetta 2. Vulkan loader + MoltenVK 1.4.2 (Vulkan SDK 1.4.363.0), and again against MoltenVK main + SPIRV-Cross #2704. Release, BUILD_TESTING=ON.

Base/method: PR #8658 feat/macos-port @ c5fa39620a (current main plus the macOS port), not main, because main does not build on macOS. This head was merged onto it with git merge-tree, with no conflicts, and the ':!docs' diff applied. Full rebuild first. This was done both on its own (with #9717's test added) and together with #9717, #9443 and #7705.

Results on both drivers, every run:

  • agc_driver_native_depth_retirement, _clears, _clear_refusals, _bda_clear, _stencil, _separate_stencil, _aliased_stencil and _expand_gate pass, and so does agc_driver_native_multisample_depth.
  • agc_driver_native_depth_driver_path and agc_driver_native_depth_driver_queued_fill end as Subprocess aborted, after their summary lines:
Native depth on the driver's device cases checked=4 unavailable=2
libc++abi: terminating due to uncaught exception of type std::__1::system_error: recursive_mutex lock failed: Invalid argument

The macOS crash report has the stack:

exit -> __cxa_finalize_ranges
  -> AgcDriver::DriverDetail::Driver::~Driver -> Driver::stop -> DevicePointer::Reset
  -> AgcDriver::VulkanDevice::~VulkanDevice -> VulkanDevice::State::~State -> std::terminate

CheckDriverPath passes its device to the driver with DriverTestAccess::UseDevice and never takes it back. So the last reference goes away in the driver singleton's destructor at exit, and ~State runs after function-local statics it locks have already been destroyed. glibc lets you lock a destroyed mutex; macOS's pthread returns EINVAL. This is the same kind of failure as #8658's a036c56b8 (GuestMemory::GpuMutex). As a probe, I changed surfacesMutex() in DepthSurface.cpp to a mutex that is never freed. The abort then moved to mutex lock failed: Invalid argument, a plain std::mutex, in the same ~State. So more than one static is involved. A fix in the test, for example releasing the driver's device before CheckDriverPath returns, might avoid all of them. I haven't tried that.

Queued fill on macOS: the probe reports host imports resolve write protection: no ... imported ranges stay watched, and this file forces Watch outside Windows. So the queued case does not skip here. All its cases run and pass, at 2 and 4 samples (samples=2/4 clear=0/1: cleared by a queued DMA_DATA fill of 128 KiB from its HTILE through the driver and sampled). The abort comes only after that.

Full ^agc suite (877 tests, this PR with #9717, #9443 and #7705), both drivers: the failures from the #8658 base, plus these two, plus #7705's depth_plane_sampling_evicting, which I reported there.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2; and MoltenVK main + SPIRV-Cross #2704).

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

…return

agc_driver_native_depth_driver_path and
agc_driver_native_depth_driver_queued_fill hand their Vulkan device to the
driver singleton and never take it back. The device's last reference
therefore died in Driver::~Driver at static destruction, after
function-local statics that the device's state locks were already
destroyed. glibc and Windows let a destroyed mutex be locked; macOS
returns EINVAL, so both tests aborted after their summary lines with
"recursive_mutex lock failed: Invalid argument" (Driver::~Driver ->
Driver::stop -> DevicePointer::Reset -> VulkanDevice::~VulkanDevice ->
std::terminate).

jotitadev found this running the tests on an Apple M2 Max (macOS 27.2
under Rosetta 2, MoltenVK 1.4.2):
boykopovar#9533 (review)

CheckDriverPath now runs its cases in a lambda, drops its own reference
to the device and stops the driver through a new DriverTestAccess::Stop.
The driver then releases the device inside Driver::stop, under the GPU
lock as at a normal shutdown, before main returns. The lambda ends first
because the Context it copies holds the device's buffer pool: releasing
the device while that copy is alive leaves the pool's buffers and memory
to be freed after vkDestroyDevice. Both returns after UseDevice, the
Windows queued-fill skip and the pass, go through the release. A failing
Require still throws to main and leaves the device to static
destruction, as before; the test has failed by then.
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

@jotitadev thank you for the macOS run and the stack. Your reading was right: the two driver-path tests handed their device to the driver and never took it back, so the driver singleton released it at exit, after the statics its teardown locks were gone.

5388641 (test only) gives the driver's device back before agc_driver_native_depth_driver_path and agc_driver_native_depth_driver_queued_fill return. On GitHub's runners the native depth set passes 10 of 10 on Linux, and Windows builds it and skips without a Vulkan device. Neither shows the abort, since glibc and Windows tolerate the destroyed mutex.

Could you rerun those two tests on your M2 Max with 5388641? A run with MoltenVK 1.4.2 alone is enough.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
Keeps both includes in DrawPrecheck.cpp: main's DeferredLabels.hpp and this branch's DepthSurface.hpp.
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026

This branch has not been deployed

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

Labels

agc libSceAgc* build docs unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants