Skip to content

perf(agc): fill-clear small-float denormal patterns on devices whose clears keep them - #2085

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
orbz22:fix/agc-denormal-clear-colors
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
orbz22:fix/agc-denormal-clear-colors

Conversation

@orbz22

@orbz22 orbz22 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

FillClearColor refuses half and B10G11R11 fill patterns that hold a denormal code, because an implementation may flush denormals in a clear. A fill like that over a resident image goes the CPU way instead: the pattern is written into guest memory and the image is uploaded again.

Astro's Playroom (PPSA01325) fast-clears its 3840x2160 R16G16_SFLOAT target 0x515390000 every frame with a denormal half. That is a 33 MiB guest write plus a re-upload every frame.

This change:

  • ClearKeepsDenormals(context, format) (declared in Texture.hpp) probes the device once per format and caches the answer per device:
    • it clears a 1x1 image of the format with denormal codes (half 0x0001, 0x83ff, 0x0200, 0x8001, one per channel; 11/10-bit 0x001);
    • it reads the texel back and compares the bits.
  • StorageTexture::FillClear tries a refused pattern again with denormals decoded exactly (SmallFloat, FillClearColor), but only when the probe says this device's clears keep them. The clear then stores the guest's bits.
  • NaNs, float32 denormals and the SNORM minimum stay refused, as before. Formats without small floats probe as not keeping.

The probe runs in its own CommandBatch with SubmitAndWait, once per format per device, the same way StorageTexture already does its synchronous uploads.

Tested

Windows 11, WinLibs GCC 15.2, RTX 3060 Laptop. The NVIDIA driver keeps denormal halves in clears; the test prints denormal half clears are kept by this device.

  • agc_driver_recorder_tests has a new case, denormalClearTests. It clears a 64x64 R16G16_SFLOAT resident image with three patterns:
    • a normal half pattern: accepted, bits exact;
    • a denormal pattern 0x83ff0001: cleared exactly when the probe says the device keeps it, and checked bit for bit when cleared;
    • a NaN pattern: refused.
      It also checks that the probe is stable and that R32G32B32A32_SFLOAT probes as not keeping.
  • agc_driver_tests passes, and tools/check_conventions.py --base upstream/main passes.
  • PPSA01325, A/B on the title screen with this commit as the only difference: 8.66 -> 14.30 FPS (115.5 -> 69.9 ms per frame), mean of three runs per side; the per-frame clear of 0x515390000 runs as a GPU clear instead of the CPU write. Current main does not reach the title, so both sides ran on an integration branch; details and per-run numbers are in the comments.

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 (Claude Code)

🤖 Generated with Claude Code

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Same as #2081: main against main plus this PR alone, please. The 85 ms is the cost on main, and the after run had other changes too.

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

orbz22 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean measured as an A/B with only this change between the two sides. Same setup as on #2081: PPSA01325 title screen, frames counted over 111-202 s of each run, three runs per side, A and B alternating.

Current main does not reach the title in PPSA01325; the blockers are listed on #2081. So both sides run on my integration branch (main from 2026-10-08 plus #1836, #1837, #1395, #1902, #1928, #757-#759), with the same build and switches on both sides:

  • A: the branch without this change; its local denormal clear switch is off, so the clear of 0x515390000 takes the CPU write and re-upload, as on main;
  • B: this PR's commit cherry-picked on top (it replaces that switch).
run A (CPU write) B (this PR)
1 8.95 FPS, 111.8 ms 14.70 FPS, 68.0 ms
2 8.78 FPS, 113.9 ms 14.06 FPS, 71.1 ms
3 8.25 FPS, 121.2 ms 14.14 FPS, 70.7 ms
mean 8.66 FPS, 115.5 ms 14.30 FPS, 69.9 ms

About 46 ms per frame. The 85 ms in the description was the CPU path's cost measured on main, not an A/B, so I've replaced it in the description with these numbers.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Conflicts with main in Texture.hpp; please rebase.

…clears keep them

FillClearColor refused half and B10G11R11 patterns with a denormal code,
since an implementation may flush them in a clear, so such fills over a
resident image took the CPU path: PPSA01325 fast-clears its 3840x2160
R16G16_SFLOAT target 0x515390000 with a denormal half every frame, a
33 MiB guest write and re-upload (about 85 ms).

ClearKeepsDenormals probes the device once per format: a 1x1 image
cleared with denormal codes (half 0x0001, 0x83ff, 0x0200, 0x8001;
11/10-bit 0x001) and read back. When the bits survive, FillClear takes
the refused pattern again with SmallFloat decoding denormals exactly.
NaNs, float32 denormals and the SNORM minimum stay refused.

agc_driver_recorder_tests clears an R16G16_SFLOAT resident image with a
normal, a denormal and a NaN pattern: the denormal is cleared exactly
when the probe says the device keeps it, and the NaN is refused.

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

Copy link
Copy Markdown
Collaborator

Rebased onto current main (only conflict: Texture.hpp declaration adjacent to SampledTextureMemory), as @oneandonlydean asked. agc_driver tests (313 in libSceAgcDriver) pass after the rebase; merging on green CI.

@adriadam10
adriadam10 force-pushed the fix/agc-denormal-clear-colors branch from ee7db0f to bafca2f Compare October 10, 2026 08:12
@github-actions github-actions Bot removed needs rebase Conflicts with main, rebase needed labels Oct 10, 2026
@github-actions

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
#2169 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp L37
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L1002

@adriadam10
adriadam10 merged commit 7c33f29 into boykopovar:main Oct 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants