Skip to content

Upstream PR batch 1 U8b: Metal depth, MSAA and fill-kernel correctness - #484

Merged
RINNECODER merged 4 commits into
mainfrom
upr1/depth-msaa-fill
Oct 14, 2026
Merged

RINNECODER merged 4 commits into
mainfrom
upr1/depth-msaa-fill

Conversation

@RINNECODER

@RINNECODER RINNECODER commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Upstream PR batch 1, unit U8b: Metal depth, MSAA and fill-kernel correctness. Three of the eighteen upstream PRs are ported, one commit each; the other fifteen are skipped, each with a reason below. A fourth commit adds only a TechnicalDebt entry.

No performance claims: not measured.

Refs upstream-pr-batch-1

Ported

Upstream Commit What changes on our path
boykopovar#5149 fix(agc): sample 10_10_10_2 SNorm and SInt textures Cherry-picked. Formats 51/55 are now in the shared TextureFormat.cpp and RdnaDescriptorFormat.cpp tables. Metal has no signed RGB10A2 pixel format, so MetalTexture still refuses them, now naming the format instead of failing the shared lookup. Added to TechnicalDebt.
boykopovar#4683 fix(agc): share a depth plane's contents across its stencil pairings on Metal MetalDepthSurfaceCache records which surface last bound each depth plane address and each stencil plane address. Binding a single-sample surface (no DB_DEPTH_VIEW slices) copies a plane from that surface when the format and extent match. For Depth32Float_Stencil8 the copy takes only that plane's aspect, through a buffer. Multisampled surfaces and depth-array slices keep their own planes.
#454 fix(agc): retire a Metal depth surface once another writer covers its memory When MetalDraw binds a color target, it drops from the draw's resident depth list every depth surface whose planes the target overlaps. Binding a depth target moves that surface to the end of the list and drops the other surfaces its planes overlap. The cache keeps them, so binding the target again brings the surface back. Upstream's storage-image rule and CPU/driver-store rule are not ported: our path has no storage view of a depth plane and no write tracking for this cache. Added to TechnicalDebt.

Skipped

Upstream Reason
boykopovar#4722, boykopovar#4873 Both fix the staging-buffer layout (padded rows and slices) of the Vulkan DepthPlaneCopy. Metal has no such copy. It samples a resident plane through a texture view, and a depth array through GatherDepthSlices (layer views or blits), so no guest linear layout is involved.
boykopovar#5054 Relaxes the guest mip-footprint check in the Vulkan DepthPlaneCopy gather. Metal has no such check. Note: Metal cannot sample a depth cube over resident surfaces at all (residentView throws for any non-2D view). That is a separate, larger gap.
boykopovar#4646 Performance only: an address filter in front of the Vulkan depth-surface scan. Metal scans only the draw's own resident list. Not measured, so not ported.
boykopovar#5113 Copies a storage image written over a depth plane back into the surface. On Metal, a storage image at a resident depth plane's address throws in MetalDraw::residentView, so there is no store-back to fix.
boykopovar#5446 Seeds a new depth surface from storage writes over its plane. On Metal the seed would only help until the surface exists: after that, the same storage write throws (see boykopovar#5113). Without storage views of depth planes (upstream #425/boykopovar#4500), the port would not fix the pattern.
boykopovar#2053 Builds on #374's HTILE fast-clear model, which our Metal path does not have (no HTILE handling in Graphics/Metal or Execution/Metal).
boykopovar#4465, boykopovar#4603 Change the fill-kernel HLE in Execution/src/Driver/Memory/BufferFill.cpp, which the Rosetta/Metal build does not compile. Metal runs these kernels as real compute dispatches, so their stores land in guest memory. Upstream needs them recognized for its HTILE/DCC-key fast paths, which Metal does not model.
boykopovar#4747 Performance only (BC surface copied from its storage view on the GPU). On Metal, the BC texture misses the image cache and is uploaded from guest memory after the storage write's copy-back, which is correct. Not measured.
boykopovar#4767 Materializes pending CMASK fast clears on the Vulkan MultisampleTarget for FMASK decompress. Neither MultisampleTarget nor a CMASK model exists on our path. Treating the pass as a no-op on Metal would hide a missing CMASK clear, so the pass still goes through the existing unsupported-mode path.
boykopovar#4790 Builds direct FMASK image loads from a guest FMASK/CMASK snapshot. Metal multisampled color surfaces stay resident and uncompressed, and their fragments never reach guest memory. The fixed FMASK mode the recompiler emits (each sample is its own fragment) matches them. A guest-memory snapshot would read bytes Metal never writes.
boykopovar#5445 Seeds a never-drawn MSAA texture from uniform guest color memory, on top of the MultisampleTarget chain (boykopovar#4820, boykopovar#2132) that our tree does not have. Metal already serves such a texture from a zero-cleared resident surface (documented in MetalDraw::residentTexture) and does not model the guest MSAA footprint that reading the memory would need.
boykopovar#5039 Lets a 2D view share a 2D-array target's image. On Metal the 2D texture gets its own backing, uploaded from guest memory after the array target's copy-back, so it is not stale. Sharing the backing would only save an upload: not measured.
boykopovar#4935 Performance only, in Vulkan presentation and StorageTexture pending-slice tracking. Metal presentation is separate. Not measured.

TechnicalDebt (docs/dev/TechnicalDebt.md): boykopovar#5149 and #454 each add a Metal entry. #454's upstream entry describes Vulkan behaviour, so it is replaced by the Metal one. The fourth commit, docs(agc): record the Metal depth-plane views and storage writes not modeled, records the gap behind the skipped boykopovar#4722, boykopovar#4873, boykopovar#5054, boykopovar#5113 and boykopovar#5446. A resident Metal depth surface is served only as a single-level 2D view or a 2D array of its extent. In a dispatch, a storage image over its plane is bound to guest memory, and a new surface starts cleared. The upstream docs/dev/debt/* hunks of boykopovar#5446, boykopovar#5039, boykopovar#4603 and boykopovar#4790 belong to skipped PRs and are not carried.

Evidence

  • metal_utility_tests (arm64, -DANYPS5_METAL_SHADER_BRIDGE=ON -DANYPS5_METAL_GUEST_REPLAY=ON) passes. It has new cases:
    • DepthResource.mm sharedPlanes, for D16S8 and D32S8: second stencil pairing, the same pairing again, back to the first, the stencil plane with another depth plane, a pairing of another extent. Without the fix(agc): share a depth plane's contents across its stencil pairings boykopovar/AnyPS5#4683 change it fails with "Sampled depth plane returned incorrect GPU values".
    • DepthResource.mm retiredDepth: overlap edges, the surface being bound is kept, the cache keeps a retired surface, a surface of another extent at the address.
    • TextureResource.mm: formats 51 and 55 resolve, are 4 bytes and not block compressed, and a native texture refuses them by name.
  • Each port's test fails with its fix removed from the working tree:
  • Full arm64 Metal set (-DANYPS5_METAL_UTILITIES_ONLY=ON -DANYPS5_METAL_SHADER_BRIDGE=ON -DANYPS5_BUILD_METAL_SHADER_BRIDGE_TESTS=ON -DANYPS5_METAL_GUEST_REPLAY=ON): nice -n 10 ctest --parallel 4 passed all 50 tests on e7af80f. The branch is up to date with origin/main 2b4606b.
  • Also pass: anyps5_metal_guest_draw_replay, anyps5_metal_msaa_depth, anyps5_metal_color_{format_view,eqaa,msaa_color,msaa_read,cb_resolve}.
  • The MetalDraw wiring for Upstream sync 7: kernel, libc and small libs #454 (bind order, color and depth retirement) has no draw-level test. Only the rule itself (RetireDepthSurfaces) is tested directly.
  • The x86_64 Rosetta build (rosetta_libs rosetta_engine) passes rosetta_direct_graphics and metal_utility_tests.
  • Title smoke (tools/smoke.sh, 300 s with --map, on AC power). It reached the same screens as main: main menu at 162 s or earlier, leader select by 178 s, map generation by 235 s. There was no fault and no uncaught exception, and the first unimplemented import is the same as on main (an Ajm Opus initialize). The last frames were 97% non-black. No log lines mention incompatible depth views, unsupported state or exceptions, the same as main's baseline run.
  • The timings are slower than main's baseline (main menu by 58 s) because the Mac was shared: load average about 20 during the run. They are not a measurement of this change. An earlier 130 s run under the same load did not reach the main menu, but showed no errors.

🤖 Generated with Claude Code

xXPBSXx and others added 4 commits October 13, 2026 21:51
Guest texture formats 51 (SNorm) and 55 (SInt) of the 10-bit packed group
were missing from the texture lookup and the descriptor format table, so a
descriptor using them threw 'unsupported guest texture format' while the
UNorm (50) and UInt (54) siblings worked. Map them to
A2B10G10R10_SNORM_PACK32 / A2B10G10R10_SINT_PACK32, classify them for
sampling (float / signed integer), and list SINT among the integer
formats. TextureFormat.cpp and RdnaDescriptorFormat.cpp are shared with
our Metal path.

Metal has no signed RGB10A2 pixel format, so MetalTexture still refuses
these two formats, now by name ("no supported pixel format for guest
format 51") instead of failing the shared lookup. Recorded in
TechnicalDebt.

Test: metal_utility_tests (TextureResource.mm) checks that 51 and 55
resolve to the signed packings, are four bytes and not block compressed,
and that a native texture refuses them by name. The upstream
RunTextureFormatTests cases (tests/TextureFormat.cpp) are carried too;
they build in the Vulkan graphics tests, not on macOS.

Ported from upstream PR boykopovar#5149
(cherry picked from commit c6a4223)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
…on Metal

A title can bind one depth plane with several stencil planes (or one
stencil plane with several depth planes). MetalDepthSurfaceCache keys a
surface by both plane addresses, so each pairing got its own textures,
cleared on creation: depth written through one pairing never reached the
others, and later draws tested against stale depth.

The cache now records, per depth and per stencil plane address, the
surface that bound it last. Binding a single-sample surface without
DB_DEPTH_VIEW slices takes each of its planes from that surface when both
have the same format and extent: a blit copies the texture layer, or, for
a combined Depth32Float_Stencil8 texture, only that plane's aspect through
a buffer. The blit is committed on the backend's queue, after the passes
that wrote the other surface and before the draw that binds this one.
Multisampled surfaces and depth-array slices keep their own planes, as
before. Clear() forgets the owners.

Upstream does this in the Vulkan DepthSurface.cpp (vkCmdCopyImage, plus a
pending fast clear carried over); our Metal path has no HTILE fast-clear
state, so only the copy applies.

Test: metal_utility_tests (DepthResource.mm, sharedPlanes) binds a depth
plane with a second stencil plane, binds the same pairing again, goes
back to the first pairing, binds the stencil plane with another depth
plane and binds a pairing of another extent, for D16S8 and D32S8. It
fails without this change ("Sampled depth plane returned incorrect GPU
values").

Ported from upstream PR boykopovar#4683

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
… memory

A depth surface lives only in its Metal textures: nothing writes it back
to guest memory. When a color target or another depth target is later
bound over its memory, the surface still answered every view of that
address, so a texture of another layout there threw ("resident depth
view is incompatible") and a matching one read the dead depth.

The surface is now retired by the last writer:
- binding a color target drops the depth surfaces whose depth or stencil
  plane it overlaps from the draw's resident surfaces (the ones views
  and depth-array gathers search);
- binding a depth target moves its surface last and retires the other
  surfaces its planes overlap: another stencil pairing of its depth
  plane, or a surface of another extent at its address. The lookup then
  finds the surface bound last, not the one bound first.
The cache keeps a retired surface; binding its target again brings it
back with the texels it held (with boykopovar#4683's change, a plane another
pairing wrote since is copied over first).

Not ported: upstream also retires a surface on a written storage image
over its planes and, with write watching, on CPU or driver stores. Our
Metal path has no storage view of a depth plane (a storage image at its
address throws) and no write tracking for this cache, so those cases keep
throwing. Recorded in TechnicalDebt.

Test: metal_utility_tests (DepthResource.mm, retiredDepth): writes next
to the planes retire nothing; the surface being bound is kept; a writer
inside the depth or stencil plane retires it; the cache still returns it;
binding a depth target of another extent at the address retires the
other surface.

Ported from upstream PR #454

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
…modeled

Upstream boykopovar#4722, boykopovar#4873, boykopovar#5054, boykopovar#5113 and boykopovar#5446 fix the Vulkan depth-plane
copy (padded detiled rows and slices, a cube with a mip chain) and the
storage writes over a depth plane. Our Metal path has neither: a resident
depth surface is served only as a single-level 2D view or a 2D array of
its extent, and a storage image over its plane is bound to guest memory.
Record that in TechnicalDebt instead of porting the fixes.

Refs upstream-pr-batch-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
@RINNECODER RINNECODER added the upstream-sync Upstream boykopovar/AnyPS5 intake label Oct 14, 2026
@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 05407154-4aec-4e1d-9831-bc70da3c6ee4
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7af80f366

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

depthSurfaces.push_back(depth);
}
// A color target over a depth surface's memory writes where its planes were.
for (const auto& color : state.colors) RetireDepthSurfaces(depthSurfaces, color.address, color.bytes, depth.get());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include all MSAA fragments in the color overlap range

When a multisampled color target overlaps a resident depth plane only in its second or later fragment allocation, this range covers just one fragment because ColorTarget::bytes is computed from width, height, and element size without samples. The same file's MetalColorSurface::OverlapsMappings correctly uses target.bytes * target.samples; use that full logical footprint here too, otherwise the depth surface remains resident and later texture reads can return stale depth data.

Useful? React with 👍 / 👎.

Comment on lines +681 to +684
RetireDepthSurfaces(depthSurfaces, target.address, Graphics::DepthSliceBytes(target.extent, d16 ? 2u : 4u) * target.samples);
if (target.stencilAddress != 0) {
RetireDepthSurfaces(depthSurfaces, target.stencilAddress, Graphics::DepthSliceBytes(target.extent, 1u) * target.samples);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retire surfaces overlapped by every layered depth slice

For a layered depth draw, this only retires surfaces overlapping the first slice. The later-slice loop at lines 696–703 removes surfaces only when their depth address exactly equals that slice's address, so a previously resident target whose plane begins inside a later slice (or overlaps only its stencil plane) survives and can subsequently serve stale texels. Apply the same depth/stencil range retirement to each generated slice before adding it.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T03:15:43.539188Z e7af80f PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@RINNECODER
RINNECODER merged commit 29a0d8e into main Oct 14, 2026
9 of 10 checks passed
@RINNECODER
RINNECODER deleted the upr1/depth-msaa-fill branch October 14, 2026 03:40
RINNECODER added a commit that referenced this pull request Oct 14, 2026
…es, Metal depth retirement, null degamma images) (#498)

* fix(relinker): decode scalar compares behind address-size and segment prefixes

A scalar compare carrying an address-size override (67 C5 FA C2 ..., 67 F3 0F C2 ...)
or with F2/F3 on either side of another legacy prefix was not decoded, so it stayed in
its Rosetta block and could still lose the later load of its destination. Legacy
prefixes are now read in any order, the last F2/F3 being the mandatory one; F2/F3
before a VEX escape is still not a compare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agc): retire depth surfaces over every MSAA fragment and every layered slice on Metal

A multisampled color target now retires the depth surfaces over all of its fragments
(bytes * samples), as MetalColorSurface::OverlapsMappings already measures it. Each
extra slice of a layered depth draw retires the surfaces over its depth and stencil
planes, not only one at exactly its depth address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(recompiler): skip null images before FORCE_DEGAMMA validation

A null T# (TYPE=0 or zero base) reads zero whatever its other words say, so a null
image left with an RGBA8_UINT format under a FORCE_DEGAMMA sampler no longer fails
the one-slice-volume check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(agc): pin a swizzled T# sampled through the FORCE_DEGAMMA sRGB view

An R/B-swapped UNORM and sRGB texture sampled under FORCE_DEGAMMA keeps its mapping
once. Measured: a Metal view of a view takes its swizzle relative to the root texture,
so passing the parent's swizzle is right and an identity swizzle drops the mapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(agc): keep a stencil-only target's absent depth plane absent in its layered slices

The extra slices of a layered draw offset the depth address even when it is zero (a
stencil-only target), and the range retirement then treated that fabricated address as
a depth plane. DepthArraySlice leaves an absent plane at zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RINNECODER added a commit that referenced this pull request Oct 15, 2026
A multisampled depth or stencil plane was taken to span
DepthSliceBytes(extent) * samples: the single-sample 128x128 (or
256x128, 256x256) block padding times the sample count. A 64 KiB
swizzle block holds 64 KiB / (bytes per texel * samples) pixels, so a
4x 1920x1080 D32 plane pads to 64x64 blocks: 33.4 MB, not 35.4 MB.

Since depth surfaces are retired when another depth target is bound
over their planes (#484), the overstated extent reached a single-sample
depth target the title allocates right after its 4x scene depth.
Binding that target retired the 4x scene depth surface, so later passes
that sample scene depth (fog of war, water, atmosphere) read stale guest
memory and washed the 3D map out in white and pale blue.

DepthPlaneBytes(extent, bytes per texel, samples) gives the plane size
with the sample count's block dimensions (equal to DepthSliceBytes for a
single sample) and replaces DepthSliceBytes * samples in the depth
surface overlap, retirement and mapping checks.

Test: metal_utility_tests (DepthResource.mm, multisampledDepthPlane)
checks the plane sizes and that a single-sample target 0x1FF0000 bytes
after a 4x 1920x1080 one retires neither; it fails without this change
("Binding a depth target after a multisampled one retired it").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
RINNECODER added a commit that referenced this pull request Oct 15, 2026
A multisampled depth or stencil plane was taken to span
DepthSliceBytes(extent) * samples: the single-sample 128x128 (or
256x128, 256x256) block padding times the sample count. A 64 KiB
swizzle block holds 64 KiB / (bytes per texel * samples) pixels, so a
4x 1920x1080 D32 plane pads to 64x64 blocks: 33.4 MB, not 35.4 MB.

Since depth surfaces are retired when another depth target is bound
over their planes (#484), the overstated extent reached a single-sample
depth target the title allocates right after its 4x scene depth.
Binding that target retired the 4x scene depth surface, so later passes
that sample scene depth (fog of war, water, atmosphere) read stale guest
memory and washed the 3D map out in white and pale blue.

DepthPlaneBytes(extent, bytes per texel, samples) gives the plane size
with the sample count's block dimensions (equal to DepthSliceBytes for a
single sample) and replaces DepthSliceBytes * samples in the depth
surface overlap, retirement and mapping checks.

Test: metal_utility_tests (DepthResource.mm, multisampledDepthPlane)
checks the plane sizes and that a single-sample target 0x1FF0000 bytes
after a 4x 1920x1080 one retires neither; it fails without this change
("Binding a depth target after a multisampled one retired it").


Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

upstream-sync Upstream boykopovar/AnyPS5 intake

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants