Skip to content

fix(agc): read an unnormalized sampler without a mip filter at its base level - #5886

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
ymatrix1:fix/shader-unnormalized-sampler-without-mip-filter
Oct 16, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
ymatrix1:fix/shader-unnormalized-sampler-without-mip-filter

Conversation

@ymatrix1

@ymatrix1 ymatrix1 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

A guest sampler with FORCE_UNNORMALIZED and a mip filter of none reading a multi-level texture is refused: the driver accepts an unnormalized sampler on a view with several levels only when every use is image_sample_lz and the S# has no minimum LOD and no LOD bias. Kena: Bridge of Spirits (PPSA01746) binds such a sampler (implicit LOD, point filters, mip filter none, LOD bias 7, min-reduction filter mode) to a 9-level texture and stops with:

AGC graphics: guest texture sampled with unnormalized coordinates is not a single-layer 1D or 2D view without a minimum LOD clamp, and is neither a single-level view at mip 0 nor read only by image_sample_lz through an S# without minimum LOD or bias ...

With the mip filter off the sampler reads only level 0, so the base-level path the driver already has for unnormalized samplers is exact for it whatever its uses, minimum LOD or bias. A sampler with a point or linear mip filter is unchanged.

Hardware measurement (PS5, gfx1013)

Through the PS5 Vulkan probe harness (mihawk-99/PS5_Vulkan): a 64x64 texture with four mip levels, each level a distinct marker, sampled by a compute shader through a sampler whose descriptor is rewritten to set FORCE_UNNORMALIZED (S# dword 0 bit 15). Coordinates are in texel units. Each row counts which level the 256 output texels came from.

S# mip filter LOD bias access level read texels per level 0/1/2/3
point 0 explicit LOD 0 0 256/0/0/0
point 0 explicit LOD 1 1 0/256/0/0
point 0 explicit LOD 2 2 0/0/256/0
point 0 explicit LOD 3 3 0/0/0/256
point 0 implicit LOD, quad derivative 8 texels 3 0/0/0/256
none 7 explicit LOD 2 0 256/0/0/0
none 7 explicit LOD 3 0 256/0/0/0
none 7 implicit LOD, quad derivative 8 texels 0 256/0/0/0
normalized control, point, implicit 0 same quad derivative 3 0/0/0/256

Also: with a point mip filter the coordinates are read in texel units of the selected level (output texel (5, 2) reads texel (5, 2) at levels 1, 2 and 3 for the same coordinate (5.5, 2.5)); with the mip filter none they are level-0 texels (implicit row: texel (20, 8) for coordinate (20.5, 8.5)). The point-filter rows show the existing rule is exact only when LOD resolves to level 0, so they are not part of this change.

Tested

Windows 11, MinGW-w64 GCC 15.2.0, Release, NVIDIA GeForce RTX 4070 SUPER. agc_shader_memory_tests passes with the change and fails without it on the new assertion ("an image read through an S# without a mip filter was not flagged as exact at its base level"). agc_driver_image_sample_unnormalized_tests, agc_shader_disk_cache_tests and agc_driver_graphics_tests pass. Conventions check passes.

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 under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: PPSA01746 (one of several crashes)
  • GPUs tested: NVIDIA GeForce RTX 4070 SUPER, Windows 11; hardware semantics measured on the PS5 GPU
  • AI-assisted: yes

…se level

A guest sampler with FORCE_UNNORMALIZED and a mip filter of none read a
multi-level texture and the driver refused it: an unnormalized sampler is
accepted on a view with several levels only when every use is image_sample_lz and
the S# has no minimum LOD and no LOD bias. Kena: Bridge of Spirits (PPSA01746)
binds such a sampler (implicit LOD, point filters, mip filter none, LOD bias 7)
to a 9-level texture and stopped with "guest texture sampled with unnormalized
coordinates is not a single-layer 1D or 2D view ...".

With the mip filter off the sampler reads only level 0, so the base-level path
the driver already has for unnormalized samplers is exact for it whatever its
uses, minimum LOD or bias. A sampler with a point or linear mip filter is
unchanged.

Measured on a PS5 (gfx1013) through a sampler descriptor rewritten to force
unnormalized coordinates, a 64x64 texture of four levels with a distinct marker
per level, coordinates in texel units:
- mip filter point, explicit LOD 0, 1, 2, 3: level 0, 1, 2, 3, 256 of 256 texels
  each, coordinates read in texel units of the selected level
- mip filter point, implicit LOD from a quad derivative of 8 texels: level 3
- mip filter none with LOD bias 7: explicit LOD 2, explicit LOD 3 and the implicit
  LOD of 3 each read level 0, 256 of 256 texels

Tested: Windows 11, MinGW-w64 GCC 15.2.0, Release, RTX 4070 SUPER.
agc_shader_memory_tests passes with the change and fails without it on the new
assertion; agc_driver_image_sample_unnormalized_tests, agc_shader_disk_cache_tests
and agc_driver_graphics_tests pass.
@github-actions github-actions Bot added shader core/shader agc libSceAgc* labels Oct 14, 2026
@Dextroid17

Copy link
Copy Markdown
Contributor

Ran this on PR head 766f6de (merge base 3242151, 438 commits behind main ab9a476).

Setup: PR head checked out as-is (not rebased), default CMake build (CMAKE_BUILD_TYPE empty, unoptimized), Linux. The test is CPU-only; no GPU was used.

  • agc_shader_memory_tests on the PR: 3 of 3 passed ("Shader memory capture, strict validation and deterministic replay passed").
  • Mutation check: kept the PR's test, put back the merge-base Optimization/src/DescriptorBindingBuilder.cpp, rebuilt, ran 3 times: 3 of 3 failed: "unnormalized samplers: an image read through an S# without a mip filter was not flagged as exact at its base level".

The test checks the descriptor flag; I did not check rendered output. Main has changed DescriptorBindingBuilder.cpp since the merge base (4 commits), though the old expression is still there, by reading. I did not test the PR rebased onto main. #4943, #5353, #5371, #5609, #5881 and #5958 also touch this file.

Not checked: any title.

AI-assisted: yes

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

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 1341 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 5371 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp
PR 5500 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 5609 core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp
PR 5867 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6177 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6222 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6412 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp
PR 6486 core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp
PR 6503 core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp
PR 6539 core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp

@ymatrix1

Copy link
Copy Markdown
Contributor Author

Thanks @Dextroid17 for running it and for the mutation check. Reverting the rule and getting the new assertion red 3 of 3 is exactly the proof I wanted the test to give.

On the overlaps the bot listed for DescriptorBindingBuilder.cpp: I read the two it flags there. #5371 changes the compare and sRGB specialization block in Prepare (and the image metadata flags further down), and #5881 changes the multisampled image-shape helper near the top of the file. My change is one expression in ProveUnnormalized, the exactness check for images read through an unnormalized sampler, so neither touches it. I have not read #4943, #5353, #5609 or #5958, so I can't say the same for those. main still has the old expression there.

The shared ShaderMemory.cpp hits are my ten added test lines in the existing unnormalized-sampler test, which other PRs also extend; if one of them lands first I'll resolve the conflict.

Not tested here: a rebase onto current main and any game. I verified the sampler semantics on a PS5 (table in the description), and the rule only changes which images the driver binds at their base level. Happy to rebase if a maintainer wants that before review.

@adriadam10 adriadam10 self-assigned this Oct 16, 2026
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on the HW oracle (T# with 4 levels, unnormalized S# with mip filter none): image_sample_lz, explicit LOD and implicit LOD all read level 0 for min LOD 0/2/3 and with a LOD bias; with a point mip filter and min LOD 2 level 2 is read, so the old rule stays for that case. The new assertions pass and fail without the DescriptorBindingBuilder.cpp change; scoped build and tests are green. Merging.

@adriadam10
adriadam10 merged commit aff725b into boykopovar:main Oct 16, 2026
9 of 10 checks passed
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