Skip to content

Upstream sync 9 (slice C): AGC driver onto Metal - #490

Merged
RINNECODER merged 30 commits into
mainfrom
sync9/agc
Oct 14, 2026
Merged

RINNECODER merged 30 commits into
mainfrom
sync9/agc

Conversation

@RINNECODER

Copy link
Copy Markdown
Owner

Upstream sync 9, slice C: AGC driver onto Metal. Range 18c438b10..upstream/main, scopes agc, agc-driver, libSceAgc, libSceAgcDriver (65 commits). 26 upstream commits are cherry-picked with -x, one each. Four fork commits follow: a debt-doc move, a Metal test, and two Metal adaptations. Commit messages that named games or title IDs were reworded.

Ported

On the Metal/Rosetta path (shared State.cpp, QueueState.hpp, Pm4.cpp, libSceAgc, or adapted in Metal code):

  • 3960519b1 make host imports over writable pages on macOS (the must-have). Resolved onto the fork's ImportWatched, which does not have upstream's later staysWatched/WindowCopy path, so only the GuestArena::HostWrite around import() is added. No Metal change is needed: no Metal-path source uses write watching (GuestArenaWriteWatched/BeginHostWrite appear only in the Vulkan driver and its tests), so the Metal no-copy aliases never cover read-only watched pages.
  • 15955c089 the sample location registers start at their clear-state value 0 (shared QueueState.hpp). Test list conflict resolved.
  • acbc57321 skip CMASK decode on single-sample views with viewMip > 0 (shared State.cpp). Applied onto the fork's !multisampled check. The debt note goes into TechnicalDebt.md. The Vulkan test uses cmaskAddress, because the fork has no cmaskFastClear field.
  • dd3c13ffd execute INDEX_ATTRIBUTES_INDIRECT (PM4 0x91) (shared Pm4.cpp). On Metal the packet takes the default path in MetalPacketExecution.mm: it is not register-only, so prior GPU work completes before Pm4::Execute reads the record. MetalPacketExecution.mm is unchanged. The debt note moves to TechnicalDebt.md (ea6808d62).
  • 7370aaf6d decode GS_FAST_LAUNCH geometry with one point per subgroup (shared State.cpp). Metal adaptation 05a477abd: the Metal mesh path refuses indexed fast-launch draws, as upstream's Vulkan Draw does. This is one line in MetalDraw.mm.
  • ff3eae6c9 sample non-sRGB textures through FORCE_DEGAMMA samplers as sRGB views. The upstream test conflict is resolved by dropping its ANISO_OVERRIDE line (that helper is not in the fork) and using the 3-argument RequireSame. Metal adaptation 0a8dc74ff in MetalShaderResources.mm: the bound view is re-viewed with the sRGB twin of its pixel format (R8, RG8, RGBA8, BGRA8, BC1/2/3/7), keeping its levels, slices and swizzle. A format without a twin is refused.
  • d6f67d1be keep fused geometry programs out of the caller's memory (libSceAgc ShaderFusion.cpp and the Vulkan ShaderRegistry.cpp; the registry helpers are added beside the fork's). The fused code now lives in libSceAgc-owned host memory. On the Rosetta direct path the CPU reads it through the host page check (HostAccessible), not the GPU ranges.
  • fbf619ef0 sceAgcDcbEventWrite ignores the address on non-sample-dump events (libSceAgc). docs/dev/debt/libSceAgc.md does not exist in the fork, so the note goes into TechnicalDebt.md with the title name and ID dropped.
  • 0ba462f8e, af19dbfab skip an indexed draw with no indices or instances. The Vulkan Draw.cpp keeps the fork's IndexReadCount. The test keeps the fork's rect-list rejection and adds the two zero-count draws. Metal already returns early for these draws (MetalDraw.mm).

Vulkan driver side, carried to stay close to upstream (not built on macOS):

  • 3bc3e35a0 fill kernel that reads its pattern from a buffer
  • 090058619 APS5_SKIP_SHADER (Vulkan draws and dispatches only; resolved onto DecodeComputeProgramAddress)
  • abc997b0d release the cached address space on device teardown (test main: only the teardown case added)
  • 12d07b8ed, a141b8eb6 R8/R8G8 sRGB twins
  • e2ee9430b, 65eb466dd, a296b9cac, 3f314030b, 0612c181f CPU snapshots on demand and their fixups
  • 0adbdcf45 draw key cache
  • fce8aa8c7, 9a1ac6a23 queued-write groups and their ctest entry (only agc_queued_write_groups registered; the other upstream entries need tests the fork lacks)
  • 71999169a, faef399e8, 7262b10ae layers of an array surface, slice-0 array target sampled as 2D

Skipped

Evidence

  • arm64 guest replay config (-DANYPS5_METAL_UTILITIES_ONLY=ON -DANYPS5_METAL_SHADER_BRIDGE=ON -DANYPS5_BUILD_METAL_SHADER_BRIDGE_TESTS=ON -DANYPS5_METAL_GUEST_REPLAY=ON): full build, then ctest excluding the GUI anyps5_metal_presentation_replay. Before 0a8dc74ff: 57/58 passed. The failure was anyps5_metal_driver_semantics_imagesampleforcedegamma, which runs the ported upstream execution test on the Metal driver: "guest format 56, which is not sRGB, ... not implemented". After 0a8dc74ff, the affected set (driver_semantics*, render_state) passed 10/10.

  • anyps5_metal_render_state gains Sync9SharedDecode. It covers the sample-location defaults, CMASK at view mip 1 versus mip 0, and INDEX_ATTRIBUTES_INDIRECT loading and refusal. With the QueueState.hpp line removed, it fails with "a sample location register does not start at its clear-state value 0".

  • Rosetta x86_64 (-DANYPS5_ROSETTA_LIBS=ON): rosetta_libs rosetta_engine metal_utility_tests rosetta_direct_graphics_tests built, and ctest metal_utility_tests|rosetta_direct_graphics passed 2/2.

  • Vulkan-device targets (tests/Graphics.cpp, tests/Recorder.cpp, tests/Pm4.cpp, most tests/execution/*) are not built on macOS. Their hunks are carried but not compiled or run here.

  • Game smokes (tools/smoke.sh <build> 300 --map, eboot lock, private image clone), same build dir:

    head main menu leader select map generation fault skipped draws/dispatches
    main 304c53c5d <= 47 s <= 56 s <= 77 s none 4 lines: 1 draw, 1 dispatch with unresolved DCC metadata; 1 + x10 dispatches with mismatched texture dimensions
    05a477abd (before the Metal degamma commit) <= 51 s <= 64 s <= 83 s none same 4 lines
    0a8dc74ff (final) <= 51 s <= 63 s <= 83 s none same 4 lines

    All three runs reached the 3D map and ran to the 300 s wall (exit 0 on SIGTERM), with 0 unresolved imports. The only unimplemented call is the same Ajm Opus mode in each. The timings are asset-open times from a shared machine running other agents. They are not a performance comparison.

  • No performance claims; nothing was measured for speed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

marcio-ch and others added 30 commits October 14, 2026 01:41
The 16-byte fill kernel has a variant that loads its pattern with
s_buffer_load_dwordx4 through the V# in s[4:7] instead of taking it from
the user SGPRs. A title clears its UI canvas HTILE
with it every frame; run as a shader, the depth surface never cleared and
every UI draw failed its GEQUAL depth test, leaving the screen black.

(cherry picked from commit 3bc3e35)

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

A sampler with FORCE_DEGAMMA decodes the texels of a non-sRGB 8-bit texture
as sRGB. Such a binding was refused; it now samples an sRGB view of the same
image. Snapshot images whose format has an sRGB twin (R8, R8G8, RGBA8, BGRA8,
ABGR8 packed, BC1/2/3/7 UNORM) are created with MUTABLE_FORMAT, and storage
images are already mutable. The view is made on first use for the full,
first-layer or base-level range, with sampled usage only and the descriptor's
minimum LOD. A format without an sRGB twin is still refused.

(cherry picked from commit ff3eae6)

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

APS5_SKIP_SHADER=<code address, hex> drops every draw that runs a program
at that address and every dispatch of a compute program at that address, as
if the packet had run. Skipping one pass at a time shows which pass first
writes a wrong value into an image: One title's green sky
light was bisected this way to the copy into its sky cube (boykopovar#4551).

(cherry picked from commit 0900586)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit abc997b)

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

SrgbTwinFormat mapped R8_UNORM and R8G8_UNORM to R8_SRGB and R8G8_SRGB
without asking the device. Where 8_SRGB or 8_8_SRGB is decoded in the
shader (context.srgbDecodeFormats: the device cannot sample the sRGB
format, or APS5_SRGB_SHADER_DECODE=1), the forced view would have been
created with a format the device cannot sample. Those two formats now
get no twin there, so a FORCE_DEGAMMA sampler on them keeps the refusal.
The image is no longer created MUTABLE_FORMAT either.

(cherry picked from commit 12d07b8)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit a141b8e)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit e2ee943)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit 65eb466)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit a296b9c)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit 3f31403)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
Create uploads before publishing snapshot validity. Keep allocation
edits separate from the per-unit validity changes in boykopovar#5024.

(cherry picked from commit 0612c18)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit 0ba462f)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit af19dbf)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
A draw whose key has no entry captured its stages, built a variant per
stage (a copy of every captured region) and inserted a draw entry. A key
that never comes back paid for the variants and the entry for nothing,
and its entry pushed a live one out of the cache. Draws whose user data
changes every frame are such keys.

The driver now remembers the keys it has seen once, in a table of
four-way buckets holding four times the cache's size in keys. A key gets
its entry at its second sight; a key that already has an entry still
takes new variants at once. A full bucket replaces its oldest key, so
keys that share a bucket do not push each other out until more than four
of them come between two sights of one.

Test: agc_driver_draw_cache_reuse draws three times. The first two draws
find no entry, one entry is inserted, and the third draw hits it. A
sighting table of one bucket keeps two keys drawn in turn and replaces
its oldest key when full.

(cherry picked from commit 0adbdcf)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1 (mesh-style NGG, as RADV programs
it on gfx10.3) was refused. With a point list and GE_CNTL groups of one
point, it launches one subgroup per input point and the first thread
reads that point's index in the ES vertex id VGPR, which the mesh
prologue already provides. The subgroup's primitive limit comes from
GE_NGG_SUBGRP_CNTL.PRIM_AMP_FACTOR, and the GS and ES VGPR counts are
not required to be full.

Fast launch 2, other group sizes or inputs, and indexed fast-launch
draws still throw.

(cherry picked from commit 7370aaf)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
A draw with PA_SC_AA_CONFIG.MSAA_NUM_SAMPLES > 0 whose context never
wrote PA_SC_AA_SAMPLE_LOCS_PIXEL_* (context 0x2fe-0x30d) was rejected
with "missing register in context bank at DWORD 0x2fe", because the
registers were absent from the initial context (a title stops on it
after two frames). The MSAA_ENABLE single-sample path read them the
same way.

The sixteen registers now start at 0, both initially and after
CLEAR_STATE. That is the value in libSceAgc's own register defaults
(sceAgcGetRegisterDefaults2, every public table that holds context
registers, the tables the initial context already follows for
PA_SU_VTX_CNTL and PA_SC_CONSERVATIVE_RASTERIZATION_CNTL), in amdgpu's
GFX10 clear-state table (clearstate_gfx10.h, gfx10_SECT_CONTEXT_def_8,
loaded by gfx_v10_0_cp_gfx_start before CLEAR_STATE) and the GC 10.3
register reset value (gc_10_3_0_default.h). RADV, radeonsi and PAL
always write the locations themselves and do not settle the unwritten
value.

0 puts every sample at the pixel center. That is not the standard
pattern for any sample count, so such a draw decodes as custom
locations at the center, as a single-sample scan into a multisampled
target already does.

(cherry picked from commit 15955c0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit 3960519)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
sceAgcFuseShaderHalves copied both halves' code, the fused header and its
tables into the caller's memory and registered the program at that
address. A title fuses different halves into the same memory a few
calls apart, while draws that use the earlier result are still queued.
The registry keeps one program per code address, so those draws were
compiled from the later program: its code read a user-data word the
bound layout does not have, and the device reported "BDA access failed"
for the mesh stage.

The PS5 5.02 module writes only the back half's register list into that
memory (sceAgcGetFusedShaderSize reports eight bytes per register,
alignment 4) and leaves the code where the halves are.

The fused code, header and tables now live in memory libSceAgc owns, one
program per distinct pair of halves, registered once. The caller's
memory receives the fused register list and the size query reports that
list. A second fusion of the same halves returns the same program; halves
whose contents changed get a new one.

The driver looked a header up by comparing its fields with the
registered header, including the register list's address. A header whose
register list is a copy elsewhere with the same contents now matches
too, which is what the title passes after a fusion.

(cherry picked from commit d6f67d1)

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

(cherry picked from commit acbc573)

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

(cherry picked from commit fbf619e)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
The driver had no handler for the PFP INDEX_ATTRIBUTES_INDIRECT packet,
so a submission with it threw "opcode is not known in the reference".
The packet now reads the 16-byte record at attribute_base +
attribute_index * 16 when it executes and loads INDEX_BASE,
INDEX_BUFFER_SIZE and INDEX_TYPE from it as the direct packets do.
The packet encoding follows PAL's BuildIndexAttributesIndirect and the
record layout PAL's IndexAttribIndirect (gfx9Chip.h, c5e80007).
indirect_mode 1, reserved bits and record index types above 3 throw.

(cherry picked from commit dd3c13f)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
A queued store run records each group's plain stores first and its
occlusion counter dumps after them. A store started a new group only
when it overlapped an earlier dump, so a label the title writes after
its dumps was recorded before them. A title that polls that label then
reads counters from before the dumps land. Any store after a dump now
starts a new group.

A title read stale or zero occlusion results
every other frame and left visible geometry undrawn on alternate frames.

(cherry picked from commit fce8aa8)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
(cherry picked from commit 9a1ac6a)

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

(cherry picked from commit 7199916)

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

(cherry picked from commit faef399)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
SampleSliceZero cleared the cached textures while its draw's results
were still pending, so the storage image was torn down with them, which
ClearCachedTextures only expects at device teardown. On some runs the
next Run then read the first pass's slice 0 after filling it with the
initial value (3 of 20 on an RTX 4070, reported on the PR). It now
flushes and waits first, as Run does before its own clear.

(cherry picked from commit 7262b10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
Upstream 23eb459 put its debt note in docs/dev/debt/libSceAgcDriver.md;
the fork keeps the AGC driver notes in docs/dev/TechnicalDebt.md, so the
note moves there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
anyps5_metal_render_state_tests builds the State.cpp, QueueState.hpp and
Pm4.cpp the Metal backend runs. Sync9SharedDecode checks three of this
batch's shared fixes there: the sample location registers start at 0, so
a 4x draw that never wrote them decodes as custom locations instead of
throwing for a missing register; a single-sample view of mip 1 of a
fast-clear target decodes no CMASK, while mip 0 of a mipmapped target is
still refused; and INDEX_ATTRIBUTES_INDIRECT loads the index base, size
and type from its record and refuses an index type above 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
The shared State decode now accepts VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH 1
with one point per subgroup (upstream 7370aaf), and upstream's Vulkan
Draw refuses such a draw when it is indexed. The Metal mesh path takes
the same decoded state, so it refuses indexed fast-launch draws the same
way instead of running them through the indexed mesh prologue.

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

Upstream ff3eae6 samples a texture whose format has an sRGB twin
through an sRGB view of the same image when a sampler forces sRGB
decoding (S# FORCE_DEGAMMA), and refuses only formats without one. The
Metal binding refused every non-sRGB format. It now does the same as the
Vulkan driver: the bound view (sampled or resident) is re-viewed with the
sRGB twin of its pixel format (R8, RG8, RGBA8, BGRA8, BC1, BC2, BC3,
BC7), keeping its levels, slices and swizzle; a format without a twin
throws "which is not sRGB and has no sRGB view".

Test: anyps5_metal_driver_semantics_imagesampleforcedegamma (the
upstream execution test run on the Metal driver) fails on the previous
commit with "guest format 56, which is not sRGB, ... not implemented" and
passes here: 8_8_8_8_UNORM through FORCE_DEGAMMA samples the decoded
values of 8_8_8_8_SRGB, point and bilinear, and 32_FLOAT is refused.

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
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

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-14T08:15:24.399667Z 0a8dc74 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.

@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: d7148766-4b60-4bb8-8fbe-cfa6d7e0db9b
  • 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: 0a8dc74ff3

ℹ️ 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".

Comment on lines +684 to +685
id<MTLTexture> result = [view newTextureViewWithPixelFormat:format textureType:type levels:NSMakeRange(0, view.mipmapLevelCount)
slices:NSMakeRange(0, slices) swizzle:view.swizzle];

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 Avoid applying the parent texture swizzle twice

When FORCE_DEGAMMA is used with a non-sRGB texture whose guest descriptor has a non-identity channel mapping, view is already a swizzled child created by MetalTexture::createViews, and passing view.swizzle while creating another child composes that same mapping a second time. Metal explicitly combines a child view's swizzle with its parent's (Apple documentation), so mappings such as R/B swaps can be undone and produce incorrect sampled colors; create the sRGB child with an identity-relative swizzle so it preserves the parent's effective mapping.

Useful? React with 👍 / 👎.

@RINNECODER
RINNECODER merged commit 59064c9 into main Oct 14, 2026
6 checks passed
@RINNECODER
RINNECODER deleted the sync9/agc branch October 14, 2026 08:15
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>
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.