Skip to content

feat(agc): fold samples of views whose channels all select constants - #1417

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
gjonn:feat/agc-fold-constant-swizzle-samples
Oct 10, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
gjonn:feat/agc-fold-constant-swizzle-samples

Conversation

@gjonn

@gjonn gjonn commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

When every dst_sel of a sampled view selects constant 0 or 1, a sample or gather returns that constant whatever the texel, filter, LOD or coordinates. Titles sample such placeholders through views the static runtime image interface has no mode for: a 1x1x1 3D R8 texture under a 2D sample with an unnormalized sampler, or a 1D texture under a 2D gather. Both stop at runtime image mode selection ("image descriptor is incompatible with the static runtime image interface"), and the 3D one would then fail the unnormalized proof ("samples a 1D-array, 2D-array, 3D, cube or multisampled image").

  • RuntimeImageModes appends three constant modes (float, uint, sint) to a direct sampled image whose accesses are all non-compare samples or gathers (constantSwizzleCompatible, tracked by ResourceTracker). Storage, depth-compare, packed and table images get none.
  • RuntimeImageMode selects the constant mode of the view's numeric class when all four dst_sel are 0 or 1, before dimensions are matched. FMASK, depth-bits, converted and sRGB-decoded views keep their existing modes.
  • In a constant mode the emitter returns each channel's constant for a sample, and the dmask channel's constant in all four texels for a gather: 1.0f for float views, 1 for integer views. The selectors come from the image's existing swizzle specialization constants; no coordinate or sampler code is emitted and the image is left unbound.
  • ProveUnnormalized skips a constant mode only after the run-time-selected check, so bindless table roots still throw.
  • The two new ImageResource fields are serialized and FormatVersion goes from 20 to 21.

Tested

  • Windows 11, MinGW-w64 GCC 15.2.0, RTX 5090: agc_driver_image_sample_unnormalized has new cases, which fail without the change:
    • a 3D view with dst_sel (1, 0, 1, 0) sampled with image_sample_lz, image_sample_l and image_sample through all five unnormalized samplers returns exactly (1.0f, 0, 1.0f, 0);
    • image_sample_lz dmask 0x6, image_gather4_lz dmask 0x2 and image_gather4_lz dmask 0x4 under a 2D access return (0, 1.0f), all 0 and all 1.0f on a 3D UNORM view selecting (1, 0, 1, 0); (0, 1), all 0 and all 1 on 32_UINT and 32_SINT views; and zeros on a 1D view selecting (0, 0, 0, 0). These are the values measured on gfx1035.
  • agc_shader_memory: a 3D view selecting constants passes the unnormalized proof and is not bound; a table root with the same descriptor still fails with "samples an image selected at run time".
  • Full ctest: 507/507 pass (one skipped as usual).

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Measured on gfx1035 (HW oracle): with all four dst_sel constant, image_sample_lz returns 1.0f/0 for float and unorm formats and integer 1/0 for 32_UINT/32_SINT; image_gather4_lz returns the dst_sel of the dmask component in all four texels; dmask 0x6 packs channels 1 and 2; a 3D T# sampled with a 2D dim and an unnormalized sampler gives the same. That matches what the emitter produces. Build + ctest pass and the new case fails without the change. Three things before it can go in:

  • DescriptorBindingBuilder.cpp:221: the constantSwizzle skip runs before the indirectRoot check, but EmitSamplingOp only folds direct images (SpirvImageEmitter.cpp:1308, access.slot == 0). A bindless table root whose snapshot descriptor has a constant swizzle used to throw "samples an image selected at run time"; now it passes the proof and gets sampled through the table path with an unnormalized sampler, and the other entries of the table don't have to be constant. Skip only direct images (image.constantSwizzle != 0u && image.indirectRoot == ImageResource::NoIndirectImage), so tables keep throwing.
  • The test only covers float formats with dmask 0xf. The gather branch (ImageGatherComponent(EffectiveDmask(...))), the uint/sint constants and a partial dmask aren't exercised. Please add cases pinning them, for example image_gather4_lz with dmask 0x2 and 0x4 on dst_sel (1, 0, 1, 0) (expected all 0 and all 1.0f), a 32_UINT or 32_SINT view (expected integer 1, not 0x3f800000), and image_sample_lz with dmask 0x6 (expected 0, 1.0f). These are the values measured on hardware above.
  • Conflicts with main: ShaderDiskCache.hpp is already at FormatVersion = 9, so this needs 10 after rebasing.

@Pipodi

Pipodi commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

After #639, PPSA26344 (Ghost of Yōtei) hits the case this PR covers, at runtime image mode selection rather than in DescriptorBindingBuilder.

A compute program gathers (DIM 2D) through a 1D T# whose four dst_sels all select constant 0: 50091c00 03800000 00000000 80000000 00000000 00700000 00000000 00000000. No runtime mode matches a 1D T# under a 2D access, so it throws "image descriptor is incompatible with the static runtime image interface". Every read through that view returns 0 whatever the texel, so the view's own dimension doesn't matter.

Locally I treat a sampled, non-comparison T# whose dst_sel is all zero (word3 & 0xfff == 0) like a null descriptor: mode 0 in TryRuntimeImageMode, and no texture in ShaderResources. That's the all-zero subset of what this PR does. It gets the title past this stop, and with robustness2's nullDescriptor the reads return zeros.

This PR's base predates #639 and now conflicts with main in ResourceMaterializer, DescriptorBindingBuilder and the disk cache. Are you planning to rebase it? If not, I can open the all-zero runtime-mode version separately, but I didn't want to compete with this one.

@gjonn

gjonn commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

After #639, PPSA26344 (Ghost of Yōtei) hits the case this PR covers, at runtime image mode selection rather than in DescriptorBindingBuilder.

A compute program gathers (DIM 2D) through a 1D T# whose four dst_sels all select constant 0: 50091c00 03800000 00000000 80000000 00000000 00700000 00000000 00000000. No runtime mode matches a 1D T# under a 2D access, so it throws "image descriptor is incompatible with the static runtime image interface". Every read through that view returns 0 whatever the texel, so the view's own dimension doesn't matter.

Locally I treat a sampled, non-comparison T# whose dst_sel is all zero (word3 & 0xfff == 0) like a null descriptor: mode 0 in TryRuntimeImageMode, and no texture in ShaderResources. That's the all-zero subset of what this PR does. It gets the title past this stop, and with robustness2's nullDescriptor the reads return zeros.

This PR's base predates #639 and now conflicts with main in ResourceMaterializer, DescriptorBindingBuilder and the disk cache. Are you planning to rebase it? If not, I can open the all-zero runtime-mode version separately, but I didn't want to compete with this one.

Lost track of this one. I'll get it rebased now.

@gjonn
gjonn force-pushed the feat/agc-fold-constant-swizzle-samples branch from 1dffbf1 to 829944a Compare October 9, 2026 16:27
@gjonn
gjonn requested a review from adriadam10 October 9, 2026 16:27
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 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
#757 core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp L189, L297-298
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp L705-706, L729
#758 core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp L189, L297-298
core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp L705-706, L729
#822 core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleUnnormalized.cpp L45-46, L131, L145
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp L85
core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp L187-197
core/shader/recompiler/ShaderDiskCache.cpp L476, L553, L577
core/shader/recompiler/ShaderDiskCache.hpp L15
#2133 core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp L85
core/shader/recompiler/Optimization/src/ResourceTracker.cpp L1063
core/shader/recompiler/ShaderDiskCache.cpp L476, L577
#2282 core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp L729

gjonn and others added 2 commits October 10, 2026 01:22
When every dst_sel of a sampled view selects constant 0 or 1, a sample
or gather returns that constant whatever the texel, filter, LOD or
coordinates. Such placeholder views can have a dimension the static
runtime image interface has no mode for (a 3D view under a 2D sample,
a 1D view under a 2D gather), and a 3D one paired with an unnormalized
sampler fails the unnormalized proof.

A direct sampled image whose accesses are all non-compare samples or
gathers gets three constant runtime modes (float, uint, sint). The mode
is selected when all four dst_sel are 0 or 1, unless the view is FMASK,
depth bits, converted or sRGB-decoded. In it the emitter returns each
channel's constant (the dmask channel's, in all four texels, for a
gather) from the image's swizzle specialization constants, and the
image is left unbound. ProveUnnormalized skips the mode after the
run-time-selected check, so table roots still throw.
… fields

The ImageResource layout change needs its own version: main is already at 21.
@adriadam10
adriadam10 force-pushed the feat/agc-fold-constant-swizzle-samples branch from 829944a to 995de0c Compare October 9, 2026 23:26

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rebase. The three points from the earlier review are done: the constant-swizzle skip now runs after the indirectRoot check (table roots still throw), and the tests pin gather dmask 0x2/0x4, dmask 0x6 and the 32_UINT/32_SINT constants.

Checked on current main: build and ctest pass, and agc_shader_memory and agc_driver_image_sample_unnormalized fail without the change. The sample/gather results match what the gfx1035 hardware returns.

Added one commit on top: FormatVersion is 21 on main already, so the 20 → 21 bump disappeared in the rebase; it is now 22 since ImageResource gained two serialized fields.

Merging once CI is green.

@adriadam10
adriadam10 merged commit 5a014b8 into boykopovar:main Oct 10, 2026
8 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…swizzle-samples

feat(agc): fold samples of views whose channels all select constants
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants