Skip to content

feat(agc): render layered draws into 3D color targets - #1077

Closed
alfalfa-star wants to merge 8 commits into
boykopovar:mainfrom
alfalfa-star:feat/agc-layered-volume-targets
Closed

alfalfa-star wants to merge 8 commits into
boykopovar:mainfrom
alfalfa-star:feat/agc-layered-volume-targets

Conversation

@alfalfa-star

@alfalfa-star alfalfa-star commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What

Layered draws into 3D color targets render, where they were rejected with color views of several array slices are unsupported. Titles fill a volume with one instanced draw: CB_COLOR_VIEW covers several depth slices, and the vertex stage picks each primitive's slice through the misc position export (PA_CL_VS_OUT_CNTL USE_VTX_RENDER_TARGET_INDX with VS_OUT_MISC_VEC_ENA).

Builds on #423, which carries PA_CL_VS_OUT_CNTL to the recompiler. Until it merges, the bottom of this branch carries #423 at 577b8d5 squashed into one commit (author kept), with PA_CL_VS_OUT_CNTL read from request version 17 because main now writes 16, followed by @johnmaia's b599cfc (prepare vertex programs with the position export layout), which #423 is taking in. This PR is feat(agc): render layered draws into 3D color targets plus two test commits for main's changes (the legacy vertex info test reads version 16; the layered test passes the new GPU flag to Mutation::Add).

  • Decode: DecodeColorBuffer takes the slice range of a 3D target into ColorTarget::layers, clamped to the surface's last slice. It allows DCC on 3D targets and sizes a tiled 3D target by its whole surface (SurfaceForTarget moves to State.cpp). DecodeState sets State::renderLayers and, for a layered draw, USE_VTX_RENDER_TARGET_INDX (State::layerExports). Targets of different slice counts and layered draws with a depth target throw.
  • Recompiler input: the driver ORs State::layerExports into the PA_CL_VS_OUT_CNTL bits that feat(agc): vertex clip and cull distances #423 passes. feat(agc): vertex clip and cull distances #423 already passes VS_OUT_MISC_VEC_ENA, so only the render target index is added, and only for layered draws. Elsewhere the layer export stays ignored, as before.
  • SPIR-V: a vertex stage writing gl_Layer declares ShaderViewportIndexLayerEXT and SPV_EXT_shader_viewport_index_layer when the device offers them, instead of SPIR-V 1.5 ShaderLayer, which the driver's Vulkan 1.1 target cannot take. The device enables VK_EXT_shader_viewport_index_layer when present. Graphics validation accepts the capability, the extension and a vertex Layer output when it is enabled, and a per-primitive Layer output of a mesh stage.
  • Attachments: the resident target's attachment view is a 2D array view of the slices, and the framebuffer has that many layers.
  • A TechnicalDebt line records what is not modeled.

Tested

Linux 7.2.9 (Arch), Mesa 26.2.4 RADV, RX 9070 XT.

  • New execution test agc_driver_layered_volume_draw. It draws into a 4-slice, 64 KiB-tiled RGBA8 volume through a view of slices 1..3, three bands with one per layer (the layer comes from the misc export), and reads every slice back with the R_X swizzle equation (its slice input included). Slice 0 stays untouched and slice 1 + L holds only band L. With paClVsOutCntl left at 0, it fails as expected (layer 1's band lands in slice 1). It skips without VK_EXT_shader_viewport_index_layer, which lavapipe has.
    • Mesh case: it recompiles a mesh program with the layer export (Mesh.cpp's geometry program plus an exp pos1), checks that it declares a Layer output, and validates it against the layered state. Without the mesh Layer rule in the validator, this case fails. It does not draw, because lavapipe's LLVM backend aborts while compiling that mesh pipeline (Cannot emit physreg copy instruction), which a test cannot catch. On RADV the same mesh draw renders the bands into the right slices.
  • agc_vertex_layer_export recompiles a vertex shader with a misc layer export. It declares the extension's capability under SPIR-V 1.3 when it is offered, ShaderLayer with SPIR-V 1.5 when it is not, and no gl_Layer without USE_VTX_RENDER_TARGET_INDX.
  • agc_driver_graphics covers the slice-range decode (layers, clamping, refusals, a DCC 3D target, the export bit) and the validation of a vertex Layer output with and without the extension.
  • PPSA14632 (LEGO Horizon Adventures) renders its 64x64x64 RGBA16F translucency lighting volumes this way: two DCC targets, 64 instances, a mesh (NGG) vertex stage. About 270 such draws per 150 s were rejected. With this PR the slice range decodes and the draws record. Their pixel shaders also read the layer from the packed pixel ancillary (fix(shader): packed pixel ancillary as SampleId and Layer, gl_Layer via Geometry, Z export formats #295, merged, and fix(agc): fragment SampleId, Layer, FragDepth and SampleMask interfaces #375 through feat(agc): vertex clip and cull distances #423). About half of the draws then stop at a separate limit (writable guest memory overlaps an immutable snapshot), which is not part of this PR.
  • ctest on feat(agc): vertex clip and cull distances #423 + this PR: 397 of 397 pass; 468 of 468 after the rebase onto feat(agc): vertex clip and cull distances #423's current head.
  • Rebased onto main 90c152e (2026-10-11): full ctest 818/818 pass (4 video_out_flip_* skipped), RX 9070 XT, RADV. agc_driver_layered_volume_draw draws (not skipped). tools/check_conventions.py passes.
  • PPSA14632 on that main (with scripted pad input only): plain main aborts after about 35 s at color views of several array slices are unsupported on its 64x64x64 lighting volumes. With this branch it records those draws and next stops after about 38 s at AGC: storage image access to depth/stencil surface 0x201a800000 is not implemented #1409 (storage image access to depth/stencil surface ... is not implemented).

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: feat(agc): vertex clip and cull distances #423; its commits are included here until it merges
  • AI-assisted: yes

🤖 Generated with Claude Code

@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. Build and ctest are green merged onto current main (396/396). Before this can go in, part of it needs to come out and a test needs to go in:

  • Overlap with #423: please drop that part. #423 (open since 2026-10-02) already adds ShaderVertexStageInfo::paClVsOutCntl and plumbs it through Recompiler.hpp, CacheKey.hpp, RequestSerializer.cpp (version 9), DrawLookup.cpp, sameVertexInfo and ShaderInputInfoBuilder.cpp, with its round-trip test in ShaderMemory.cpp. This PR adds the same plumbing again. CONTRIBUTING asks that no two open PRs implement the same thing. The Draw.cpp:139 assignment also collides: #423 stores the clip/cull bits in that field and this PR stores graphics.layerExports, so whichever lands second drops the other's bits. Please remove the duplicated plumbing, list #423 under Depends on, and add the layer bits (USE_VTX_RENDER_TARGET_INDX, VS_OUT_MISC_VEC_ENA) to #423's mask for layered draws. The rest of the PR stays as it is: the CB_COLOR_VIEW slice range, the 2D-array attachment views, the framebuffer layers, the VK_EXT_shader_viewport_index_layer path and the validation.
  • No test of the layered draw itself. The new tests cover the register decode, the serializer and which capability the SPIR-V declares, but nothing renders. The 2D-array attachment view of a 3D image (Texture.cpp:998-1001), the framebuffer layer count (Pipeline.cpp:73-74) and gl_Layer selecting the slice relative to SLICE_START are never run. An execution test that draws into a 4-slice 3D target with a view of slices 1..3 (one primitive per layer via the misc export), then reads every slice back, would pin it. Slice 0 should stay untouched and slices 1-3 should hold their primitive's value. lavapipe exposes VK_EXT_shader_viewport_index_layer, so it runs on CI. tests/Mesh.cpp and tests/execution/Pixel*.cpp already have the draw harness.
  • Minor: ShaderValidation.cpp:112 now accepts a per-primitive Layer output in mesh stages, but validationTests only covers the vertex case. A mesh case would cover it.

We couldn't check the slice mapping on hardware: our oracle only runs compute, not draws. The TechnicalDebt line covering that is fine.

@alfalfa-star
alfalfa-star force-pushed the feat/agc-layered-volume-targets branch from b9f3f02 to 6017bd9 Compare October 7, 2026 13:02
@alfalfa-star

Copy link
Copy Markdown
Contributor Author

@adriadam10 Thanks. All three points are done in 6017bd9, and the branch is now stacked on #423.

  • feat(agc): vertex clip and cull distances #423 overlap: the duplicated plumbing is gone: the paClVsOutCntl field, the version 9 serializer, the cache key, the draw lookup hash, sameVertexInfo, ShaderInputInfoBuilder and the ShaderMemory round trip. feat(agc): vertex clip and cull distances #423's commits sit under this one (rebased onto current main) and are listed under Depends on. At Draw.cpp:139 the line is now feat(agc): vertex clip and cull distances #423's mask, OR'd with graphics.layerExports, so neither PR drops the other's bits. feat(agc): vertex clip and cull distances #423's mask already includes VS_OUT_MISC_VEC_ENA (bit 21), which also shifts the clip/cull vectors, so layerExports only adds USE_VTX_RENDER_TARGET_INDX (bit 18), and only when the color view covers several slices.
  • Layered draw test: new agc_driver_layered_volume_draw, as you described. It renders into a 4-slice 64 KiB-tiled RGBA8 volume through a view of slices 1..3. Each primitive's layer comes from the misc export (one band per layer). Every slice is read back through the R_X swizzle equation with its slice input, which matters: the XOR pattern differs per slice, so a 2D detile reads the wrong texels. Slice 0 must stay untouched and slice 1 + L must hold only band L. With paClVsOutCntl forced to 0 it fails (layer 1's band lands in slice 1). It skips without VK_EXT_shader_viewport_index_layer, which lavapipe has.
  • Mesh case: the same test also draws through the mesh path. It uses Mesh.cpp's geometry program with one exp pos1 added for the layer, so the per-primitive Layer output goes through ValidateShaders in the draw and onto the target. It also fails with the export off. I covered it there rather than in validationTests, because a mesh module that passes the rest of the mesh validation (modes, push block, output arrays, primitive indices) by hand would mostly test the hand-built module.

ctest: 397/397 on Linux (RADV, RX 9070 XT).

@alfalfa-star

Copy link
Copy Markdown
Contributor Author

CI fix in d86365c. The Windows build lacked std::aligned_alloc; the test now allocates the volume like the other execution tests (VirtualAlloc on Windows). On lavapipe, the mesh case aborted inside LLVM while compiling the mesh pipeline (Cannot emit physreg copy instruction), so it now recompiles and validates the layered mesh program without drawing it. The description has the details.

@alfalfa-star

Copy link
Copy Markdown
Contributor Author

Status update.

NVIDIA routing (@adriadam10's review): @orishamir found the cause: the dynamic-rendering path that devices with pipeline libraries take sets VkRenderingInfo::layerCount = 1, so every layer lands in the first slice. @AlxcBTW's bdd55e0 passes the framebuffer's layer count there and fixes the layered tests for NVIDIA (subgroup size in the mesh case, clean shutdown). I'll take that commit, with authorship kept.

Waiting on #423: this PR needs #423's PA_CL_VS_OUT_CNTL plumbing and carries a squashed copy of it until #423 merges. #423 was rebuilt on main today. Rather than rebase this branch onto every new #423 head, I'll rebase once #423 lands. Then this PR is only its own commits plus the fix above.

Testing it now: #3763 (@eljackjoestar-cmd) carries this PR rebased onto a recent main, with the same layerCount fix, so that's the easiest branch to test titles on until then.

AI-assisted: yes (Claude Code)

@github-actions

github-actions Bot commented Oct 13, 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
#448 core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp
core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDraw.cpp

@fma-rc

fma-rc commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Kena (PPSA01746) needs the DCC part of this. On main 2a2a3dc (with #423 merged) it runs to 101 s and stops with AGC graphics: mipmapped or DCC 3D color targets are unsupported.

I printed the decoded target in front of that check: slot 0, 64 slices, max mip 0, DCC enabled (CB_COLOR_ATTRIB3 0x4ec6c03f, info word 0x10040730). That is the DCC case this PR lifts; the surface has no mips.

I could not run the title on this branch, it conflicts with main since #423 merged. I will run it after a rebase and report where it gets to.

@oneandonlydean oneandonlydean self-assigned this Oct 13, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Closing as merged through #3763: your commits are on main with your authorship (1055617751c render layered draws into 3D color targets, the layered volume tests, the layer-export reporting fix and the DCC key extent note), with the dynamic-rendering layer count on top. Thanks for the work on this.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@fma-rc #3763 is merged with these commits, and mipmapped or DCC 3D color targets are unsupported is gone from main. Does PPSA01746 get past 101 s on current main now? If it stops again, the next log line would help.

@fma-rc

fma-rc commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Yes, it gets past that check. On main b1328fc PPSA01746 creates the two 200x113 targets with 64 slices and no longer throws on them. Two runs then stop a little later in the same place (77 s and 86 s into the run, the difference is shader cache warm-up):

[gpu] layer/viewport index vertex exports are ignored (PA_CL_VS_OUT_CNTL=0x01240000)
terminate called after throwing an instance of 'std::runtime_error'
  what():  AGC driver: guest memory is not readable at 0xdfac00000008 (range 0xdfac00000008+0x4)

The layer/viewport index line is printed once per run, right before the stop. I have not looked at which read hits 0xdfac00000008 yet, so I can't say whether the two lines are related. I'll get a backtrace for it and open an issue with that.

@alfalfa-star

Copy link
Copy Markdown
Contributor Author

@fma-rc I hit the same 0xdfac00000008 stop with Kena on PPSA01802, so here is what I found, in case it saves you the backtrace.

  • What it is: the address is not a real pointer. It is words 2-3 of a buffer descriptor (V#) read as a 64-bit address: word 2 (NUM_RECORDS, here 8) in the low half and word 3 in the high half, cut to 48 bits. Kena's V# word 3 looks like 0x0004dfac, which gives 0xdfac00000008 (I also saw 0x9fac...). The SRT walker follows that "pointer" while evaluating a shader's descriptor sources, and CheckRange throws on the unreadable range.
  • fix(recompiler): leave out descriptor sources behind a branch the capture decides #3952 alone doesn't cover it: your main b1328fc already has fix(recompiler): leave out descriptor sources behind a branch the capture decides #3952 (branch-decided descriptor sources) and still stops, which matches what I saw.
  • What got Kena past it locally: outside flat SRTs, an inaccessible read in EvaluateRuntimeSourcesImpl (SrtDescriptorEvaluation.cpp) is reported the same way the flat-SRT path already does it (ReportInaccessibleReads), so that descriptor becomes a null descriptor instead of ending the title. That stand-in was part of the local stack on which Kena reached gameplay. I haven't proposed it as a PR yet, because it hides the read instead of explaining why the shader's source resolves to V# words. A backtrace from your side (APS5_TRACE_UNREADABLE=1 prints the caller's offset) would show whether it's the same walker path.

The [gpu] layer/viewport index vertex exports are ignored (PA_CL_VS_OUT_CNTL=0x01240000) line is most likely unrelated. It is printed because bit 24 is outside layerExports even though the layered draw is routed (@queeeef noted the same on #2102).

AI-assisted: yes (Claude Code)

@alfalfa-star
alfalfa-star deleted the feat/agc-layered-volume-targets branch October 15, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build docs needs rebase Conflicts with main, rebase needed shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants