Skip to content

fix(agc): draw each face with its own depth bias - #1924

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
fisherjoey:fix/depth-bias-per-face
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
fisherjoey:fix/depth-bias-per-face

Conversation

@fisherjoey

@fisherjoey fisherjoey commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

decodeDepthBias rejected a draw that rasterizes both faces when the front and back depth bias differ (depth bias differing between front and back faces). Vulkan has one depth bias per draw, and the cull mode is baked into the pipeline.

Such a draw is now recorded twice in the same render pass:

  • DecodeState keeps the front values in depthBias*, puts the back values in the new backDepthBiasConstant/backDepthBiasSlope, and sets depthBiasPerFace. A face whose offset enable bit (PA_SU_SC_MODE_CNTL bit 11 or 12) is clear gets bias 0, which is the same as no bias.
  • Pipeline creates a second VkPipeline for that state. The first one culls back faces, the second culls front faces, and depthBiasPerFace is part of the pipeline key.
  • Both draw paths in Draw.cpp record the draw with the first pipeline and then call ContinueBackFaces (bind the second pipeline, set the back bias) and record it again. Both pipelines share one layout, so descriptor sets and push constants stay bound.
  • When one face is culled, nothing changes: the decoder already used the remaining face's values.

The TechnicalDebt entry records the difference from hardware: the vertex stages run twice, and all front faces land before all back faces instead of in primitive order. That only matters for blending or stencil updates that depend on the order, or for a vertex shader that writes memory.

Closes #1923

Tested

Nobara 44 x86-64 (Linux 7.2), GCC 16.2, RTX 5070 Ti, driver 595.91.07. First tested on main f145139; rebased onto main 0011248 and retested (see the end of this section).

  • agc_driver_graphics: the depth-bias block now decodes differing front/back bias with no culling, front-only and back-only bias, and cull-front with the back bias. On main it fails, because the decode throws.
  • New GPU test agc_driver_depth_bias_per_face (tests/execution/DepthBiasPerFace.cpp): a front-facing and a back-facing triangle at depth 0.5, a LESS test against a surface cleared to 0.5, opposite biases on the two faces. Only the face that is pulled closer passes, in both directions. Equal biases and front-only bias are checked too. With a single pipeline that draws both faces with the front bias, it fails (the back-facing triangle at (20, 4) is 255, expected depth-rejected).
  • ctest -j1 --timeout 60: 494 of 495 pass. The one failure, guest_dynamic_loader, also fails here on a branch without this change. tools/check_conventions.py and tools/check_test_registration.py pass.
  • After the rebase onto 0011248 (built with ANYPS5_ENABLE_SPIRV_TOOLS=ON): agc_driver_depth_bias_per_face and agc_driver_graphics pass, ctest -R agc_ -j1 passes 279 of 280. The one failure, agc_driver_dcc_keys_first_write, fails on plain main 0011248 on this GPU too.
  • Demon's Souls PPSA01341 v1.07: not run with this branch. In the tutorial level about 4.3k shadow-caster draws per 10 s hit this rejection (PA_SU_SC_MODE_CNTL 0x01001a48, front slope -17.6, back slope -480000). A local stopgap that drew both faces with the front bias removed those rejections with no new errors.

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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@klosixgaming-arch klosixgaming-arch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is well done and I can approve it.

  • The double-record-in-same-render-pass approach is the correct way to give two faces different depth bias under Vulkan's one-bias-per-draw constraint, and depthBiasPerFace in the pipeline key means you don't accidentally reuse a single-face pipeline for a per-face state.
  • The Require(!state.depthBiasPerFace || (depthBias && state.cullMode == VK_CULL_MODE_NONE)) guard is exactly right — per-face only makes sense when both faces actually rasterize.
  • ContinueBackFaces rebinds viewport/scissor/depth-bounds after the pipeline switch, which is the detail people forget when binding a second VkPipeline mid-pass. Good.
  • The decode math in decodeDepthBias correctly zeroes a face's bias when its offset-enable bit (PA_SU_SC_MODE_CNTL 11/12) is clear, and the tests cover front-only, back-only, equal, and culled-face cases.
  • The APS5_REJECT_PER_FACE_DEPTH_BIAS escape hatch preserves the old behaviour — nice.
  • Real GPU test (DepthBiasPerFace.cpp) with opposite biases and a LESS test against a 0.5 clear, in both directions. Solid.
  • The debt entry honestly states the two real deviations (vertex stages run twice; all front faces land before all back faces). That's the right way to handle a known-different semantic.

Nothing blocking. The vertex-shader-writes-memory-twice case is the one thing a pathological title could in theory observe, and you've disclosed it. Good.

Written with AI assistance.

@popcoder565

Copy link
Copy Markdown
Contributor

Native Windows run, since no Windows results were posted. Setup: Windows 10 (19045), RTX 3060 Laptop (NVIDIA 616.92), MinGW-w64 GCC 15.2.0 (WinLibs r7), Release. Head 75af5a0b, merged onto main d70b899 in one build with 33 other AGC/shader PRs that had no Windows run.

No title was run.

🤖 Addressed by Claude Code

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 9, 2026
@fisherjoey
fisherjoey force-pushed the fix/depth-bias-per-face branch from 75af5a0 to 9884ab0 Compare October 9, 2026 22:19
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 9, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 9, 2026
@fisherjoey
fisherjoey force-pushed the fix/depth-bias-per-face branch from 9884ab0 to 2a64f54 Compare October 10, 2026 02:22
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

This conflicts with main in Pipeline.cpp and tests/Graphics.cpp after today's merges; could you rebase? Please also drop APS5_REJECT_PER_FACE_DEPTH_BIAS (State.cpp:265) and its TechnicalDebt sentence: a switch that turns the fix off isn't wanted here. It passed testing otherwise.

@fisherjoey
fisherjoey force-pushed the fix/depth-bias-per-face branch from 2a64f54 to 75239c9 Compare October 10, 2026 05:52
@fisherjoey

Copy link
Copy Markdown
Contributor Author

Rebased onto main 0011248 and the switch is gone. New head: 75239c9.

  • APS5_REJECT_PER_FACE_DEPTH_BIAS is removed from State.cpp, along with its sentence in the TechnicalDebt entry. A draw with different front and back bias is always drawn per face now.
  • Pipeline.cpp: main now destroys the shader modules right after vkCreateGraphicsPipelines. The back-face pipeline is created before that, since it uses the same modules.
  • tests/Graphics.cpp: kept both sides. The per-face checks come first, then main's new point/line offset enable and polygon mode checks.

Tested on an RTX 5070 Ti (NVIDIA 595.91.07), built with ANYPS5_ENABLE_SPIRV_TOOLS=ON: agc_driver_depth_bias_per_face and agc_driver_graphics pass, and ctest -R agc_ -j1 passes 279 of 280. The one failure, agc_driver_dcc_keys_first_write, also fails on plain main 0011248 on this GPU.

AI-assisted: yes

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
A draw that rasterizes both faces with different front and back depth
bias was rejected, since Vulkan has one depth bias per draw and the
cull mode is pipeline state. Such a draw is now recorded twice in the
same pass: front faces with the back faces culled and the front bias,
then back faces with the front faces culled and the back bias. A face
whose offset is disabled gets no bias.
@adriadam10
adriadam10 force-pushed the fix/depth-bias-per-face branch from 75239c9 to e0084a5 Compare October 10, 2026 07:11
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (the only conflict was a TechnicalDebt line next to the direct memory entry; both kept). Switch removal from the previous push confirmed, no APS5_REJECT_PER_FACE_DEPTH_BIAS left. Built and ran agc_driver_graphics and agc_driver_depth_bias_per_face on RADV: both pass. Merging once CI is green.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@adriadam10
adriadam10 merged commit eba8525 into boykopovar:main Oct 10, 2026
14 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…-face

fix(agc): draw each face with its own depth bias
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AGC: draws with different front and back depth bias are rejected

5 participants