Repository navigation
AGC: run mesh-path draws on devices without mesh shaders through a compute pass and an indexed draw #6528
Description
Mesh-path draws (NGG geometry and fast-launch programs that need a workgroup) only run where the device has VK_EXT_mesh_shader. That leaves out GPUs and drivers that have none: AMD RDNA1 on Windows (an RX 5700 XT reports mesh=0), MoltenVK on macOS (#1), and older or integrated GPUs. Sackboy: A Big Adventure (PPSA01289) hits this on an RX 5700 XT about 50 s in. #6523 makes the stop say device does not support mesh shaders instead of a subgroup error, but the draw still cannot run.
The PS5 has no mesh shaders either. The guest uses NGG, and a Vulkan mesh shader is how AnyPS5 runs it on the host, because it is the only graphics stage with a workgroup, shared memory and explicit primitive output. A compute shader has the first two, so I'd like to propose a fallback for devices without mesh shaders and ask whether you would take it before I build it out.
Proposed fallback
Run a mesh-path draw in two passes:
- Compute pass. The recompiler emits the same program as a GLCompute shader with the mesh shader's workgroup size and dispatch. The guest function,
MeshAllocateand the per-lane vertex and primitive data stay as they are. Only the final emit (EmitMeshEntryPoint) changes: instead ofOpSetMeshOutputsEXTand the mesh output arrays, each group stores into storage buffers:- its vertex outputs at
group * maxVertices + vertex(position, parameter exports, clip and cull distances); - its triangles as absolute indices at
group * maxPrimitives + primitive, with culled and unallocated primitives written as a degenerate triangle; - the per-primitive layer, where one is exported.
- its vertex outputs at
- Draw. An indexed draw of
groups * maxPrimitives * 3indices from that buffer, through a generated vertex shader that reads the vertex outputs bygl_VertexIndexand passes them on with the same locations and interpolation as the mesh outputs. The fragment shader is unchanged.
The draw records the dispatch, a compute-to-vertex-input/index barrier, then the draw. The scratch buffers are sized from the draw's group count and the mesh configuration (maxVertices, maxPrimitives, output count). The mesh draw parameters, now push data, would be the same push data for the compute pass. Indirect mesh draws would need the group count from the indirect arguments, through an indirect dispatch plus an indirect draw whose count a small step computes.
Questions
- Would you accept this as the path when
meshShaderis false, with the mesh shader kept as the path everywhere else? - Is there a preferred place for the scratch buffers (the recorder's transient allocations, or a per-draw buffer)?
- Per-primitive layer output needs
VK_EXT_shader_viewport_index_layerin the vertex shader. Is it acceptable to throw where that is missing? - Any part of the mesh path you plan to change soon that this would collide with?
I'm starting a prototype on a branch to test it against Sackboy, and I'll link it here. I won't open a PR before you've had a chance to answer.
Setup where the stop was seen: Windows 11, AMD Radeon RX 5700 XT (driver 32.0.21045.5002), main b7cedd900 plus #6236.
AI-assisted (Claude Code).
Activity
jotitadev commentedon Oct 15, 2026
macOS data point for the questions above, from
vulkaninfoon an Apple M2 Max (macOS 27.2) with two MoltenVK builds: the Vulkan SDK's MoltenVK 1.4.2 and MoltenVKmain(1.4.3).main)VK_EXT_mesh_shaderVK_EXT_shader_viewport_index_layer/shaderOutputLayervertexPipelineStoresAndAtomicsmaxComputeWorkGroupInvocations,maxComputeWorkGroupSizemaxComputeSharedMemorySizesubgroupSize(subgroupSizeControl, min/max)drawIndirectCountSo on Apple GPUs the mesh path is never available, and the per-primitive layer through the vertex shader would not need the throw. Two limits matter for the fallback here:
ldsSizeDwords * 4againstmaxMeshSharedMemorySize(Graphics/src/Pipeline.cpp:319), and the equivalent check againstmaxComputeSharedMemorySizewould refuse NGG programs with more than 32 KiB of LDS on this device.drawIndirectCount, so for indirect mesh draws, a step that writes plain indirect draw arguments would work on more drivers than a count-buffer draw.maindoes not build in full on macOS yet (#1), so I could not run the mesh path itself here; this is only what the device reports.Written with AI assistance (Claude Code). I ran the check above on my machine.
YossiYad commentedon Oct 16, 2026
@billythemenace2 I'd like to help with this. Data from a device where the fallback would be the only path, and an offer below.
Linux (Ubuntu 24.04), Intel UHD Graphics 620 (CML GT2), Mesa 25.2.8 ANV, Vulkan 1.4.318, read with a small
vkGetPhysicalDevice*2query:VK_EXT_mesh_shaderVK_EXT_shader_viewport_index_layer/shaderOutputLayervertexPipelineStoresAndAtomicsdrawIndirectCountmaxComputeWorkGroupInvocationsmaxComputeSharedMemorySizesubgroupSizeControl, min/max)maxStorageBufferRangePipeline.cpp:317already halvesthreadsPerGroupfor a wave64 program on a subgroup-32 device, so the compute pass would need the same split.main5d9dcc42dhere,agc_driver_meshskips on both devices: on ANV for the missing extension, and on llvmpipe for the explicitllvmpipeskip intests/Mesh.cpp. So CI never runs the mesh fixtures today. If the fallback can be forced on a device that has mesh shaders (an environment switch or a test-only flag), the existingMesh.cppfixtures could run through it on lavapipe in CI and be compared against the mesh path on GPUs that have one.Offer:
ctest. A full build andctestalready pass here onmain(1269 tests, 19 skipped).Mesh.cpp's fixtures through the fallback and comparing pixels with the mesh path) or the indirect mesh draw step that writes plain indirect draw arguments, as @jotitadev suggested for drivers withoutdrawIndirectCount. Tell me which part, if any, you'd rather keep yourself, and I'll build on your branch.billythemenace2 commentedon Oct 16, 2026
@jotitadev @YossiYad thank you, this is exactly the data the design needed.
Where the prototype stands, on an AMD Radeon RX 5700 XT (no
VK_EXT_mesh_shader), rebased onmainaff725bf6:tests/Mesh.cpppasses through the compute pass and the indexed draw: indexed and non-indexed triangle lists with wave32 and wave64 subgroups, fast launch, indirect records (including empty and past-the-end ones), strips and fans with restarts, and the new line-strip output.On your points:
ldsSizeDwords * 4againstmaxComputeSharedMemorySize, so on Apple GPUs a program with more LDS is refused with a clear message instead of being built.vkCmdDrawIndirectfrom a header the compute pass writes, so it does not needdrawIndirectCount.Mesh.cppthrough both routes.@YossiYad I'll post the branch link here as soon as the Sackboy crash is understood, and I'd welcome a run on the UHD 620.
AI-assisted (Claude Code).
Pacuka commentedon Oct 16, 2026
Data point for the fallback, from a device that has mesh shaders: on RDNA2 the mesh path also loses layers.
AMD RX 6650 XT (gfx10.3, Linux, Mesa 26.2.4 RADV) reports
maxMeshOutputLayers = 8. RADV packs the per-primitive layer into bits 19..17 of the primitive export on GFX10.3 (ac_nir_lower_ngg_mesh.c), so a mesh draw that writesgl_Layer≥ 8 silently drops those primitives' writes. AnyPS5 does not checkmaxMeshOutputLayerstoday.Where it shows: The Smurfs – Dreams (PPSA21607, UE). Its 32×32×32 color-grading LUT is built by one mesh-path draw (
vkCmdDrawMeshTasksEXT1×32×1,gl_Layer0..31). In a RenderDoc capture onmain82ddae75b, slices 0–7 of the LUT hold the expected values and slices 8–31 stay 0. The tonemapper then maps almost every color to black, and only low-blue foliage stays visible. Other UE titles that build the volume LUT this way should hit the same problem on RDNA2.Repro without a game: in
tests/execution/LayeredVolumeDraw.cpp, setSlices = 32,FirstSlice = 0,Width = 64, and let the mesh case draw andChecklike the vertex case does. The vertex draw passes; the mesh draw fails withmesh draw: slice 8 texel (16, 0) channel 0 is 64, expected 255 (the band of layer 8).So the compute + vertex fallback (layer from the provoking vertex through
VK_EXT_shader_viewport_index_layer) could also be the route when a draw's layer count exceedsmaxMeshOutputLayers, not only whenmeshShaderis false. I can test your branch on the RX 6650 XT with Smurfs and the 32-slice test once it's up.billythemenace2 commentedon Oct 16, 2026
The prototype is up:
billythemenace2/AnyPS5@feat/agc-mesh-compute-fallback(f89a0df02, one commit onmain452494439). It is not a pull request yet. @YossiYad @Pacuka, runs on your machines would be very welcome.Where it is on the RX 5700 XT:
agc_driver_meshpasses through the fallback, andagc_driver_graphicspasses.vkCmdDrawMeshTasksEXT-shaped 1x32x1 withgl_Layer0..31, a 192-thread program with 192 vertices and 64 primitives. That looks like the same volume-LUT build @Pacuka describes for The Smurfs. The title still sits on its loading screen, which I'm tracking separately in Sackboy: A Big Adventure (PPSA01289) #6803.@Pacuka thanks, that's a good catch. Routing a draw whose layer count can exceed
maxMeshOutputLayersthrough the same pass fits this design, since the layer goes out through the vertex shader. A mesh-path draw doesn't declare its layer count up front, though, so the choice would have to come from the target's layer count or a draw's instance count. I haven't added that yet. A run of your 32-sliceLayeredVolumeDrawcase through the fallback on the RX 6650 XT would show whether the vertex route keeps all 32 slices there.Still to do: the switch that forces the fallback on a device with mesh shaders, for lavapipe and comparison runs. Two limits are noted but not handled yet. For indirect draws the output buffer is sized for at most 4 instances per draw, and each draw allocates its own output buffer.
AI-assisted (Claude Code).
odntht commentedon Oct 16, 2026
@billythemenace2 I ran the branch (
86729c6a1) on an Intel HD Graphics 520, a device where the fallback is the only path. Three findings.Setup: Intel HD Graphics 520 (Skylake GT2,
0x1916), Mesa 25.2.8 ANV, Linux 6.12, Release build, GCC 13.3. NoVK_EXT_mesh_shader, noVK_KHR_fragment_shader_barycentric;geometryShader,shaderOutputLayer,vertexPipelineStoresAndAtomicsanddrawIndirectCounttrue, 64 KiB compute shared memory, subgroup 8 to 32. Baseline ismain82ddae75b.1. The branch doesn't build on Linux. The two size checks in
ShaderDiskCache.cpponly apply on Linux x86_64 with libstdc++, andmeshRecordLayoutgrows both structs:The encoder and decoder already carry the new field, so I only changed the two sizes locally (232 to 240, 464 to 472) to run the rest.
2.
agc_driver_meshruns through the fallback here and fails its first case. Onmainit skips (the device has no VK_EXT_mesh_shader). On the branch it runs and stops at:So the case that passes on your RX 5700 XT draws nothing for triangle 0 on ANV.
agc_driver_graphicsfails on this GPU on bothmainand the branch, with the sameVOP2 opcode is not implemented, so that one isn't from your change.3. Barycentric emulation still only attaches to the vertex path. Devices without mesh shaders usually also lack
VK_KHR_fragment_shader_barycentric. InExecution/src/Driver/Draw/Draw.cpp:453, a pixel shader withbarycentricEmulation.activerequiresgraphics.stages.path == ShaderPath::Vertex, andBuildBarycentricGeometryShaderis built fromprogramResults[0], the guest vertex program. The branch doesn't touch that file, so ccleste (PPSA59322) stops at the same point onmainand on the branch:That is stop 2 of #5079. It is what 11 of 68 catalog homebrews hit on an Arrow Lake iGPU (milescoviello) and 10 of 86 on lavapipe (overlordxrz-source), including ccleste, Mario 64 and Wind Waker HD. The #5079 fix only covers devices with
meshShader, so on these devices your fallback is the only route. For them to run, the draw would need three stages: the compute pass,MeshReadbackVertexShader, and the barycentric geometry stage built against the readback shader's outputs instead of the guest vertex program.I can rerun
agc_driver_meshand ccleste on this GPU when you push a fix.AI-assisted: yes (Claude Code).
hashaam1217 commentedon Oct 16, 2026
@billythemenace2 Results for the prototype on a device without
VK_EXT_mesh_shader: Intel HD Graphics 530 (Skylake GT2, Gen9), Mesa 25.2.8 ANV, Ubuntu 24.04, GCC 13, Release, configured like CI with-DANYPS5_ENABLE_SPIRV_TOOLS=ON. Head86729c6a1, run withANYPS5_GPU="HD Graphics 530" ANYPS5_REQUIRE_VULKAN=1. Onmain, every test below passes or skips on this GPU.Build on Linux. The prototype does not compile with libstdc++. The size checks in
ShaderDiskCache.cppare guarded by__linux__ && __x86_64__ && __GLIBCXX__, so they do not fire on Windows:encodeArtifact/decodeArtifactalready carrymeshRecordLayout, so only the numbers are stale:sizeof(CompiledShaderArtifact)is 240 (was 232) andsizeof(RecompileResult)is 472 (was 464). I changed those two locally to run the tests.Results (
ctest -R '^agc_': 619 of 624 pass):agc_driver_meshSPIRV-Tools: module version exceeds requested SPIR-V targetagc_driver_layered_volume_drawagc_driver_prepared_graphicsa mesh program run as compute must export one position(stage 7)agc_driver_graphicsShaderRecompiler::Recompile: VOP2 opcode is not implementedagc_shader_memorynew requests did not use serialization version 31(the prototype bumps the cache version; the test still expects 31)agc_driver_ngg_vertex_wave_info,_reduction,layered_volume_submit,_vertex,_vertex_no_gplagc_driver_mesh_index_cacheThe SPIR-V version error is probably hidden on your side if your build does not enable SPIRV-Tools validation. CI's Linux job does. I can rerun on this GPU whenever you push an update.
AI-assisted: yes.
Pacuka commentedon Oct 16, 2026
@billythemenace2 Ran your branch (
86729c6a1) on the RX 6650 XT (RADV, Mesa 26.2.4,maxMeshOutputLayers = 8). Locally I forced the fallback with a one-line switch (state->meshShader = … && !getenv("APS5_NO_MESH_SHADER")) and bumped the twoShaderDiskCache.cppsize asserts (240/472) like hashaam1217 did.LayeredVolumeDrawwith 32 slices (Width 64,Slices 32,FirstSlice 0), same binary:slice 8 texel (16, 0) channel 0 is 64, expected 255(as before)agc_driver_meshalso passes through the forced fallback.The Smurfs - Dreams with the fallback forced:
Outside the village everything I tried looks right: the Astral Map, the dream select screen and Chef Smurf's Dream all render with correct colors. Only Smurfs' Village itself is still washed out (pale, near-white surfaces), so that looks like a separate, scene-specific issue. Separately, moving Smurfs leave flat grey silhouettes behind that never clear (most visible in Chef Smurf's Dream), probably a stale render target, unrelated to this fallback.
So routing draws whose target layer count exceeds
maxMeshOutputLayersthrough the fallback would fix this title on RDNA2. Happy to retest once that's in.AI-assisted (Claude Code).
feat(agc): run mesh-path draws through a compute pass on devices with…
billythemenace2 commentedon Oct 16, 2026
@odntht @hashaam1217 @Pacuka thank you, these runs found real problems. The branch is updated:
5e9032eae, rebased onmainbc3c0fc13.Fixed from your reports:
ShaderDiskCache.cppsize checks now say 248 and 480. Currentmaingrew both structs again, and the new field adds 8 bytes on top.module version exceeds requested SPIR-V target(hashaam1217, SPIRV-Tools on): a mesh program run as compute started from the mesh stage's SPIR-V 1.4 while the device target is 1.3. It now starts at 1.3. I can't run SPIRV-Tools here, so this one is untested on my side.a mesh program run as compute must export one position(agc_driver_prepared_graphics): a mesh program without a position export now gets a zero position instead of failing.agc_shader_memoryandrequest_serializer:maintook serialization version 32 for spanned waves in the meantime. The mesh flag now shares that byte, as version 33, and both tests cover it.agc_driver_meshon the HD 520 or 530 would tell us whether that was it.APS5_NO_MESH_SHADER=1now does what @Pacuka's one-liner did, so lavapipe and RDNA2 can run the mesh tests through the fallback.On my RX 5700 XT these pass:
agc_driver_mesh,agc_driver_graphics,agc_driver_layered_volume_draw,agc_driver_prepared_graphics(all four variants),agc_shader_memoryandrequest_serializer. I haven't run the full suite.@Pacuka the Smurfs result is great news: 32 slices through the fallback, and the village lit. Routing only draws whose target has more layers than
maxMeshOutputLayersneeds both variants of the mesh program and a per-draw choice. That's the next piece, and I'll post when it's in.AI-assisted (Claude Code).
Pacuka commentedon Oct 16, 2026
@billythemenace2 Thanks for the update! A note in case you missed it: I edited my comment above after first posting it, so the version you read may not have had this part yet.
With the fallback forced, the Astral Map, the dream select screen and Chef Smurf's Dream all render with correct colors. Only Smurfs' Village itself is still washed out (pale, near-white surfaces), so that looks like a separate, scene-specific issue rather than something in the fallback.
Separately, moving Smurfs leave flat grey silhouettes behind that never clear (most visible in Chef Smurf's Dream). That's probably a stale render target and also unrelated to this fallback.
Neither one needs anything from your branch. I'll retest 5e9032e and the layer-count routing once it's in.
AI-assisted (Claude Code).
Alexius0815 commentedon Oct 17, 2026
@billythemenace2 first macOS run of the prototype, on a device where the fallback is the only path.
Setup: Apple M4 (16 GB), macOS 27.2 (26B5112a), MoltenVK 1.4.2 (
mesh=0, no geometry shaders, tessellation available), x86-64 under Rosetta 2, Release. Branch5e9032eaewith @mugurc's macOS branches merged on top (theci/macos-seriesset: libc/libkernel/agc/audio), plus #6124, #6448, #6580, #6928, #6668, #6667 and #6235 so that it builds and runs on macOS.ShaderDiskCache.cppsize checks are Linux-only).agc_driver_mesh_tests: passes through the fallback ("Mesh tests passed", exit 0).a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it). On MoltenVK the extension is off on purpose (PerVertexKHR) and there are no geometry shaders, so neither path exists there. With the fallback the draw becomes an indexed triangle list behind a generated vertex shader, so a tessellation-based emulation (3-control-point patches, tessellation level 1,gl_TessCoordas the weights) might be one way to close that gap on macOS. That is a separate step from this PR.AI-assisted: yes
Alexius0815 commentedon Oct 17, 2026
@billythemenace2 follow-up from the Apple M4 (macOS 27.2, MoltenVK 1.4.2): with your fallback (
5e9032eae) ccleste (PPSA59322) now reaches its title screen on macOS.The remaining gap was barycentric emulation: MoltenVK has no geometry shaders, so the generated geometry stage cannot be used. Locally we emulate it with tessellation instead: a triangle list is drawn as three-vertex patches at tessellation level 1, and the evaluation stage writes
gl_TessCoordas the weights and the three values of each per-vertex parameter. For mesh-path draws this goes behind your compute pass and readback vertex shader (compute + readback VS + TCS + TES + FS, patch list with three control points; triangle output only, clip/cull distances and layer refused).On the M4, with SPIR-V validation on:
agc_driver_pixel_barycentric_emulation_testspasses through the patches (932 perspective and 932 linear pixels, raw per-vertex values, host interpolation), plus a new back/front-face culling comparison with the plain draw for the winding;agc_driver_mesh_testspasses, including new indexed and non-indexed per-vertex barycentric cases through your fallback;This is local and builds on your branch plus the macOS series, so it is a follow-up to your PR rather than part of it. Happy to share the patch if it helps.
AI-assisted: yes
billythemenace2 commentedon Oct 17, 2026
@Alexius0815 thank you, a macOS data point is exactly what this needed:
agc_driver_meshpassing through the fallback on MoltenVK, and ccleste reaching its title screen once barycentrics are emulated with tessellation. Please do open your tessellation barycentric emulation as its own pull request, saying it builds on #6528. It's a separate topic from this PR, and having it reviewed on its own would be cleaner than folding it in here. I'll link it from the design issue once it's up.@Pacuka thanks for the clarification. Noted that the washed-out village and the grey silhouettes look unrelated to the fallback. Routing by
maxMeshOutputLayersis still next on my side.AI-assisted (Claude Code).
Alexius0815 commentedon Oct 17, 2026
@billythemenace2 thanks, will do. I'll open it on Monday. I asked krishalivaibhav in #1 whether their local TCS/TES fallback is going to be published and want to give them until then. I'll also compare it with yripper's BaryCoordKHR built-in approach (508a894) on the same titles.
One detail for the link: the tessellation emulation itself is based on
mainand does not depend on #6528. It depends on #7124 (gl_ClipDistance forwarding through generated tessellation stages). Routing the mesh-fallback readback draws through it is a small follow-up on top of #6528, and I'll open that separately once both are in.AI-assisted: yes
3 remaining items
Alexius0815 commentedon Oct 18, 2026
@jotitadev thanks for the heads-up. We checked the overlap: #7308 replaces our planned standalone tessellation-barycentric PR, so we will not open a duplicate.
The remaining local change is narrower: route the compute mesh fallback's generated readback vertex shader through #7308's tessellation stages when the fragment shader needs emulated barycentrics. It is on our macOS integration stack, based on billythemenace2's
5e9032eaeprototype, not on standalonemain.For the local follow-up at
4443edf4e, our saved Apple M4 run from October 18 includesMesh fallback barycentric pixels and front/back culling passedinagc_driver_mesh. The recorded ccleste smoke run also gets past the previous "needs a vertex shader before it" stop. This is a limited synthetic-fixture/smoke result, not full gameplay or interpolation-equivalence validation.Setup: macOS 27.2, MoltenVK, x86-64 under Rosetta.
Separately, we have incorporated the ClipDistance interface corrections from #7330 and #7334 into our local test stack. The affected test executables build successfully, but runtime validation with
VK_LAYER_KHRONOS_validationon the M4 is still pending. Those corrections were not part of the earlier4443edf4erun.vkQuake remains quarantined; no new run is included here.
AI-assisted: yes
feat(agc): run mesh-path draws through a compute pass on devices with…
billythemenace2 commentedon Oct 18, 2026
@Pacuka the
maxMeshOutputLayersrouting is in.billythemenace2/AnyPS5@feat/agc-mesh-compute-fallbackis now168eaf3e9, rebased onmaine5f18ea99, with one new commit on top.MeshDrawLayerstakes the largest layer count of the bound targets: a color target'slayersor 3Ddepth, or the depth target's slice range. On a device with mesh shaders,MeshDrawRunsAsComputesends a mesh-path draw through the compute fallback when that count is overmaxMeshOutputLayers. Draws within the limit stay on the native mesh stage.meshAsComputeset, which the cache keys already separate. A program compiled for the mesh stage that reaches the compute pass is refused with an error, not drawn.APS5_NO_MESH_SHADER=1still forces every mesh draw through the fallback, and it's now listed indocs/dev/ENVIRONMENT.md.mainreached 37 in the meantime.On the RX 5700 XT, which has no mesh shaders,
agc_driver_graphicspasses, including a new unit test for the routing decision: 32 volume slices and 16 depth slices route with an 8-layer limit, and 8 layers don't. So doagc_driver_mesh,agc_driver_layered_volume_draw/_submit/_vertex,agc_driver_prepared_graphics,agc_shader_memoryandrequest_serializer.agc_driver_layered_volume_vertex_no_gplfails here, but it fails the same way on plainmain.I can't exercise the routed path itself, because it needs a device that has mesh shaders. Could you run your 32-slice
LayeredVolumeDrawedit and Smurfs Dreams withoutAPS5_NO_MESH_SHADERon the RX 6650 XT? Both should now pass and light the village through the fallback, while the other mesh draws stay native.AI-assisted (Claude Code).
Pacuka commentedon Oct 18, 2026
@billythemenace2 Tested
168eaf3e9on the RX 6650 XT (maxMeshOutputLayers = 8), RADV Mesa 26.2.4, CachyOS, Release, withoutAPS5_NO_MESH_SHADER:LayeredVolumeDraw: my edit adapted to the current test (64x16, 32 slices from slice 0, the mesh case draws and checks every slice; the mesh program is compiled withmeshAsComputewhen the layer count is overMaxMeshOutputLayers(), asDrawCapturedoes). It printsmaxMeshOutputLayers 8, layers 32and passes; every slice is written. Onmainthe same edit failed at slice 8.agc_driver_graphics,agc_driver_mesh,agc_driver_mesh_draw_relocation/_index_cache/_provoking_vertex,agc_driver_layered_volume_draw/_submit/_vertex/_vertex_no_gpl,agc_driver_prepared_graphics(+ prebuild variants),agc_shader_memory,request_serializer: 15 of 15 pass, including_vertex_no_gpl.main), and the Smurfs' Village has no black areas any more. No crash, clean exit. The village is still washed out because this build doesn't include fix(agc): store linear compute writes behind their dispatch #6948; with fix(agc): store linear compute writes behind their dispatch #6948 it renders correctly (as with the earlier prototype).FYI, #6959 (JJenkx) targets the same limit differently: it splits the layered draw into attachment windows within
maxMeshOutputLayersand keeps the native mesh stage. I tested it on Smurfs today too, with the same visible result: comment. Happy to run more checks on either.AI-assisted (Claude Code).
root-over commentedon Oct 19, 2026
@billythemenace2 a run of
168eaf3e9on a GCN 5 device with no mesh shaders, where the fallback is the only path: AMD Ryzen 5 5625U iGPU (Vega, RADV RENOIR,mesh=0), Mesa 26.0.8, Linux (Ubuntu, kernel 7.0), GCC 15.2.0, Release. Unlike MoltenVK, this device hasgeometryShaderbut noVK_KHR_fragment_shader_barycentric.Tests, with
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1:agc_driver_mesh,agc_driver_graphics,agc_driver_layered_volume_draw/_submit/_vertex/_vertex_no_gpl,agc_driver_prepared_graphics(+ the three prebuild variants),agc_shader_memoryandrequest_serializerpass.agc_driver_mesh_draw_relocation,_index_cacheand_provoking_vertexskip, as they need the native mesh stage._vertex_no_gplpasses here, unlike on the RX 5700 XT.Catalog titles, 45 s each, branch head as is (based before #6610, so the Linux allocator regression is not involved):
mainbefore (8816fd772with #6610's guard)168eaf3e9device does not support mesh shadersneeds a vertex shader before itneeds a vertex shader before itneeds a vertex shader before itSo on GCN 5 the barycentric step is what remains for these three, as on macOS. The difference:
Draw.cppwould chooseBuildBarycentricGeometryShaderhere, sinceGeometryLimits()is set, not #7308's tessellation stages. @Alexius0815, when the readback routing goes up, I can run it on this device to cover the geometry-shader branch behind the readback vertex shader.root-over commentedon Oct 19, 2026
Follow-up to my run above: with the geometry-shader branch routed behind the readback vertex shader, ccleste, VLC and PS5SX2 all reach their UI on this Vega.
The change is one commit on top of
168eaf3e9(11 lines in 3 files):root-over/AnyPS5@feat/mesh-fallback-geometry-barycentrics.Execution/.../Draw.cppaccepts the mesh path when the program was compiled as compute (meshRecordLayout != 0) and insertsBuildBarycentricGeometryShaderafter it. The mesh program stands in for the vertex result there: the readback vertex shader writes exactly itsparameterExportslocations. Without geometry shaders it still throws, leaving the tessellation route to @Alexius0815's follow-up.MeshComputeStageskeeps that geometry stage: compute, readback vertex, geometry, fragment.ValidateShadersaccepts that four-stage order only when the fragment shader emulates barycentrics.Same device (Ryzen 5 5625U Vega, RADV RENOIR, Mesa 26.0.8,
mesh=0,geometryShader=1, nofragmentShaderBarycentric), Release:168eaf3e9needs a vertex shader before itneeds a vertex shader before itneeds a vertex shader before itThe 17 tests from my previous comment plus
barycentricpass withANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1; the three native-mesh tests still skip.One caveat on lavapipe (Mesa 26.0.8, with
APS5_NO_MESH_SHADER=1): VLC runs past 90 s through the same route, but most of its UI quads lose one of their two triangles. On RADV the same frames are complete, so I haven't treated it as a defect of this route yet. I haven't run clip or cull distances or layered targets through it: the generated geometry stage forwards only position and parameters, so a mesh program that writes them would lose them here.root-over commentedon Oct 19, 2026
Four more catalog titles from a wider sweep on the same Vega (RADV RENOIR,
mesh=0, nofragmentShaderBarycentric) reach the same stop on currentmainand get past it with168eaf3e9plus the geometry-stage commit (eff3479e9).mainhere is676269473with only babyhuey's #6610 heap guard applied locally; 40 s per title.main676269473168eaf3e9+eff3479e9needs a vertex shader before it, 3 s inWith the earlier three (ccleste, VLC, PS5SX2), that makes seven titles on this device that stop at this check on
mainand start with both changes. Of the other catalog titles I ran, eight behave the same on both builds, and four stop because the user has to supply game data or a jailbroken console.billythemenace2 commentedon Oct 19, 2026
@root-over thank you, this is the first full run on a GCN device where the fallback is the only path. It's good to see
agc_driver_layered_volume_vertex_no_gplpassing there, since it fails on the RX 5700 XT on plainmaintoo.eff3479e9looks right to me. Using the mesh program'sparameterExportsas the vertex result is exactly what the readback shader guarantees. Seven titles past the same stop is a clear case for it. Please open it as its own pull request once the fallback is one, saying it builds on it, the same way @Alexius0815 is doing for the tessellation route. Your two caveats belong in its description: clip, cull distances and layers aren't forwarded through the generated geometry stage, and the missing triangles on lavapipe.AI-assisted (Claude Code).
root-over commentedon Oct 20, 2026
@billythemenace2 thanks. Before writing up the lavapipe caveat for the follow-up, I looked into it. The missing triangles seem to come from the fallback itself, not from the geometry stage: lavapipe's compute subgroups are narrower than the guest wave. Same machine,
168eaf3e9pluseff3479e9,APS5_NO_MESH_SHADER=1 ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1:agc_driver_meshskips on llvmpipe whether or not the draw runs as compute: the device-name check comes after themeshAsComputecheck. With that skip lifted locally (&& !target.meshAsCompute), it fails at the first case, before any barycentric emulation is involved:indexed triangle list, one-wave subgroups: triangle 0 was not rendered in its color, pixel (16, 24, 40, 255). That is with lavapipe's default 8-lane subgroups, and the same withLP_NATIVE_VECTOR_WIDTH=128(4 lanes). The 16-lane setting crashes lavapipe on this CPU, which has no AVX-512, so I couldn't test a wider one.agc_driver_layered_volume_drawon lavapipe fails in your new case:mesh draw run as compute: slice 1 texel (0, 0) channel 0 is 64, expected 255 (the band of layer 0). On RADV (64-lane subgroups) it passes.agc_driver_meshpasses through the fallback, as in my first run.So the compute pass seems to assume a host subgroup at least as wide as the guest wave.
ComputeTargetgives the mesh program the device's subgroup size (8 on lavapipe), andPipelineasks for a required size only whencomputeWave32is set, so on such a device the draw runs and gives wrong output instead of being refused. Either refusing a mesh-as-compute draw when the compute subgroup is narrower than the wave, or emulating the wave as the plain compute path does, would cover it. In the follow-up's description I'll describe the lavapipe result as this, not as an issue of the geometry stage.billythemenace2 commentedon Oct 20, 2026
@root-over thank you for chasing that down. You're right, it's the fallback, not your geometry stage: the compute pass assumed subgroups at least as wide as the guest wave.
08346eb8fnow refuses a mesh draw run as compute when the subgroup size the pass will actually run at is narrower than the program's wave, witha mesh-path draw run as compute needs compute subgroups at least as wide as its wave. It computes that size the same wayPipelinepins it, so on lavapipe it now stops cleanly instead of drawing wrong triangles. A unit test inagc_driver_graphicscovers it with an 8-lane device and a wave-32 program. On the RX 5700 XTagc_driver_graphics,agc_driver_meshand the layered volume tests still pass.Emulating the wave over narrower subgroups, as the plain compute path does, would be the full fix. It belongs in a later change, not in the first fallback PR.
AI-assisted (Claude Code).
billythemenace2 commentedon Oct 20, 2026
The Smurfs – Dreams (PPSA21607) now runs on a device with neither mesh shaders nor
VK_KHR_fragment_shader_barycentric: AMD Radeon RX 5700 XT (RDNA1), AMD driver 32.0.21045.5002, Windows 11. It loads, reaches the lit Smurfs' Village and the level selection, and plays the first level. In-level frame rate is low, 2-13 FPS from the title's counter.The build is
maind073fe6a1, with the fallback merged in (08346eb8f) and three local changes on top. They aren't pushed yet, because the merge also needs its serializer tests reworked:BuildBarycentricGeometryShaderbehind the readback vertex shader when the mesh program was compiled as compute. That's @root-over's approach fromeff3479e9, written separately here.MeshComputeStageskeeps the stage order compute, readback vertex, geometry, fragment, andValidateShadersaccepts it when the mesh program outputs triangles.main's newmeshBarycentricPixelis now set only when the target has mesh shaders. Otherwise the pixel shader declaredMeshShadingEXT(capability 5283) and was refused on this device.main's newdevice does not support mesh shaderscheck inValidateShadersnow lets the compute-pass stages through.A draw that is routed through the fallback on a device that does have mesh shaders still compiles its pixel shader in mesh mode. That combination needs a follow-up.
I haven't profiled the low frame rate yet, so I can't say yet whether the fallback's compute pass is the cost.
AI-assisted (Claude Code).
Alexius0815 commentedon Oct 20, 2026
@billythemenace2 an M4 data point on
08346eb8f, from merging the prototype into mugurc's current macOS series (ci/macos-series-2, onmain3fd1cc3). Apple M4, macOS 27.2, MoltenVK, x86-64 under Rosetta.Two checks stop the fallback on Apple GPUs:
lanes >= state.stages.vertexWaveSizeinMeshComputeStages. Apple compute subgroups are always 32 lanes, so every wave-64 mesh program is refused (a mesh-path draw run as compute needs compute subgroups at least as wide as its wave). Before this commit,agc_driver_meshpassed here, because the fallback already pairs lanes for wave 64 on 32-lane subgroups (thethreadsPerGroup / 2uinvocation count inPipeline.cpp). Locally I allowed exactly that case and kept the refusal for narrower subgroups such as lavapipe's:Require(!mesh || meshShader, "device does not support mesh shaders")inValidateShaders, from60a2aa47donmain, which the prototype doesn't have yet. Once merged, it refuses the compute pipeline too. Locally I let it through when the first stage is the compute pass.With both,
agc_driver_meshpasses on the M4 again, along with the barycentric tests through the tessellation route. I haven't checked whether the 64-on-32 pairing is fully correct for every subgroup operation; this only restores the previous behaviour on Apple.AI-assisted: yes
tezzgod74 commentedon Oct 21, 2026
Windows runs on two more AMD architectures, on AMD's driver: an RDNA 2 iGPU where the fallback is the only path, and an RDNA 4 card with mesh shaders. Branch
08346eb8f+ @root-over'seff3479e9(cherry-picked cleanly), compared against its basee5f18ea99.168eaf3e9+eff3479e9gave the same results. Windows 11 Pro 10.0.26300, MinGW-w64 GCC 15.2.0 (WinLibs r7), Release,-DANYPS5_ENABLE_SPIRV_TOOLS=ON, allagc_*tests (718):1002:164e, 32.0.21045.5002):mesh=0, SPIR-V 1.3 target, so the fallback is the only path.1002:7590, 32.0.32015.2008):mesh=1. Run as is, and withAPS5_NO_MESH_SHADER=1.e5f18ea9908346eb8f+eff3479e9agc_driver_meshagc_driver_meshAPS5_NO_MESH_SHADER=1,agc_driver_meshWith the branch, all three configurations pass
agc_driver_graphics,agc_driver_layered_volume_draw,_submitand_vertex,agc_driver_prepared_graphicsandagc_shader_memory, andrequest_serializerpasses. With SPIRV-Tools on and the iGPU's SPIR-V 1.3 target, no test reportsmodule version exceeds requested SPIR-V target, so starting the mesh-as-compute module at 1.3 holds here. The subgroup check from08346eb8fdoesn't refuse any of these draws on either device.Failures that occur on the base as well, so not from the branch:
agc_driver_layered_volume_vertex_no_gpleverywhere, as on your RX 5700 XTagc_driver_lds_mixed_width_atomicsandagc_driver_lds_lock_budget(AGC: 64-bit LDS atomics under the workgroup lock never finish in workgroups over 64 threads on AMD RDNA 2 (Windows) #6666 / AGC: AMD device-backed LDS lock-budget case returns half of expected sum #7344, fixed onmainafter this base)agc_driver_quad_pixel_maskagc_driver_mesh_index_cache,agc_driver_mesh_draw_relocationandagc_driver_mesh_provoking_vertexare skipped whenever the fallback is in use, on the iGPU and on the RX 9060 XT withAPS5_NO_MESH_SHADER=1. So on these devicesagc_driver_meshis the only test that runs through the fallback.AI-assisted: yes
DavidSamir commentedon Oct 21, 2026
Catalog title runs on an RDNA 2 iGPU with no mesh shaders, where the fallback is the only path. @tezzgod74 covered the unit tests on a similar device, so this adds titles.
Setup: Windows 11 Pro 26200, AMD Radeon 680M (
1002:1681, Ryzen 7 6800H, 12 GB RAM), AMD driver 32.0.21030.2001,mesh=0. WinLibs GCC 15.2.0 r7, Release. Homebrew catalog titles relinked with--windows --registry, 60 s each, one at a time.main1ff5c8ac5.08346eb8f, as is, on its basee5f18ea99. It doesn't merge cleanly into currentmain(15 files conflict), so the libraries are the branch's own build. The relinker ismain's for both sides.Before: all 20 stop within 9 s with
AGC graphics: device does not support mesh shaders.After: 15 of 20 run the full 60 s.
main)initial multisampled DCC contents require watched guest memory(3 s)inverted scissor rectangle(8-14 s, both runs)malloc_stats_fast: implemented only for an application allocator that provides it(12 s, both runs)unresolved ELF import RZA5RZawY04 from libSceLibcInternal.prxat startThe last five stops are past the mesh path or before any draw, so none of them is a fallback failure. Some may come from the branch's older base: the unresolved import, for example, may be an export added to
mainaftere5f18ea99. I didn't test them against currentmainwith the fallback merged in, because of the conflicts.Unit tests on the same device, with
VK_LAYER_KHRONOS_validation(SDK 1.4.363),ANYPS5_REQUIRE_VULKAN=1andANYPS5_NO_SHADER_CACHE=1:agc_driver_mesh: skipped onmain(no VK_EXT_mesh_shader); passes on the branch.agc_driver_graphicsandagc_driver_layered_volume_{draw,submit,vertex}pass on both.VUID-lines on either side.agc_driver_layered_volume_vertex_no_gplfails on the branch, but its basee5f18ea99fails identically, 3 of 3 (slice 1 texel (0, 0) channel 0 is 64, expected 255), andmainpasses it. So it comes from the base, not the fallback.AI-assisted: yes
billythemenace2 commentedon Oct 21, 2026
The fallback is now a pull request: #9356, one commit on
main1698d1ea6. Thank you all, every point in it came from your runs. The description lists each.Since
08346eb8f:main, at serialization version 43 and disk cache format 45.ValidateShaders' newdevice does not support mesh shaderscheck lets the compute pass through.@tezzgod74, @DavidSamir: thank you for the RDNA2 and RDNA4 runs and the 20-title table. They're cited in the PR.
@root-over, @Alexius0815: the barycentric routes (geometry and tessellation) aren't in #9356, so each can go in as its own PR on top of it. A note for the geometry route:
main's newmeshBarycentricPixelcompiles the pixel shader for the mesh stage, which declaresMeshShadingEXT. On a device without mesh shaders it has to stay off (&& request.target.mesh.has_value()inRecompiler.cpp), or the pixel shader is refused. With that and your stage on top, The Smurfs – Dreams reaches its first level on my RX 5700 XT.AI-assisted (Claude Code).