Skip to content

AGC: run mesh-path draws on devices without mesh shaders through a compute pass and an indexed draw #6528

Open

Description

@billythemenace2

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:

  1. Compute pass. The recompiler emits the same program as a GLCompute shader with the mesh shader's workgroup size and dispatch. The guest function, MeshAllocate and the per-lane vertex and primitive data stay as they are. Only the final emit (EmitMeshEntryPoint) changes: instead of OpSetMeshOutputsEXT and 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.
  2. Draw. An indexed draw of groups * maxPrimitives * 3 indices from that buffer, through a generated vertex shader that reads the vertex outputs by gl_VertexIndex and 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 meshShader is 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_layer in 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

  1. jotitadev commented on Oct 15, 2026

    @jotitadev
    Contributor

    macOS data point for the questions above, from vulkaninfo on an Apple M2 Max (macOS 27.2) with two MoltenVK builds: the Vulkan SDK's MoltenVK 1.4.2 and MoltenVK main (1.4.3).

    MoltenVK 1.4.2 MoltenVK 1.4.3 (main)
    VK_EXT_mesh_shader absent absent
    VK_EXT_shader_viewport_index_layer / shaderOutputLayer yes / true yes / true
    vertexPipelineStoresAndAtomics true true
    maxComputeWorkGroupInvocations, maxComputeWorkGroupSize 1024, 1024x1024x1024 same
    maxComputeSharedMemorySize 32768 32768
    subgroupSize (subgroupSizeControl, min/max) 32 (true, 4/32) same
    drawIndirectCount false true

    So 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:

    • Compute shared memory is 32 KiB. The mesh path checks ldsSizeDwords * 4 against maxMeshSharedMemorySize (Graphics/src/Pipeline.cpp:319), and the equivalent check against maxComputeSharedMemorySize would refuse NGG programs with more than 32 KiB of LDS on this device.
    • The SDK's MoltenVK has no drawIndirectCount, so for indirect mesh draws, a step that writes plain indirect draw arguments would work on more drivers than a count-buffer draw.

    main does 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.

  2. YossiYad commented on Oct 16, 2026

    @YossiYad
    Contributor

    @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*2 query:

    Intel UHD 620 (ANV) llvmpipe (Mesa 25.2.8)
    VK_EXT_mesh_shader absent present
    VK_EXT_shader_viewport_index_layer / shaderOutputLayer yes / true yes / true
    vertexPipelineStoresAndAtomics true true
    drawIndirectCount true true
    maxComputeWorkGroupInvocations 1024 1024
    maxComputeSharedMemorySize 65536 32768
    subgroup size (subgroupSizeControl, min/max) 32 (true, 8/32) 8 (true, 8/8)
    maxStorageBufferRange 4 GiB - 1 128 MiB
    • No wave64: Pipeline.cpp:317 already halves threadsPerGroup for a wave64 program on a subgroup-32 device, so the compute pass would need the same split.
    • On main 5d9dcc42d here, agc_driver_mesh skips on both devices: on ANV for the missing extension, and on llvmpipe for the explicit llvmpipe skip in tests/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 existing Mesh.cpp fixtures could run through it on lavapipe in CI and be compared against the mesh path on GPUs that have one.

    Offer:

    • I can run your prototype branch on this machine as soon as it's linked, using the mesh fixtures and the full ctest. A full build and ctest already pass here on main (1269 tests, 19 skipped).
    • If it helps to split the work, I could take a separate part so we don't collide, for example the test side (running 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 without drawIndirectCount. Tell me which part, if any, you'd rather keep yourself, and I'll build on your branch.
  3. billythemenace2 commented on Oct 16, 2026

    @billythemenace2
    Author

    @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 on main aff725bf6:

    • With the fallback forced on, the whole of tests/Mesh.cpp passes 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.
    • The compute variant also carries clip and cull distances and the per-primitive layer. The readback vertex shader writes the layer from the provoking vertex, so the layer case does not need to throw, as @jotitadev expected.
    • Sackboy: A Big Adventure (PPSA01289) is the title I'm testing it on. It now gets past its first mesh draws but loses the device on the first layered pass that goes through the fallback. That happens even with an empty compute shader in that spot, so the cause is in where I record the compute pass inside a recorded batch, not in the generated shaders. I'm working on that before I push the branch.

    On your points:

    • 32 KiB shared memory: the fallback checks ldsSizeDwords * 4 against maxComputeSharedMemorySize, so on Apple GPUs a program with more LDS is refused with a clear message instead of being built.
    • Indirect draws: the readback draw is a plain vkCmdDrawIndirect from a header the compute pass writes, so it does not need drawIndirectCount.
    • Wave64 on subgroup-32 devices: the compute pipeline uses the same halved local size as the mesh path.
    • Running it in CI: agreed. I'll add a switch that forces the fallback on a device that has mesh shaders, so lavapipe can run Mesh.cpp through 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).

  4. Pacuka commented on Oct 16, 2026

    @Pacuka
    Contributor

    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 writes gl_Layer ≥ 8 silently drops those primitives' writes. AnyPS5 does not check maxMeshOutputLayers today.

    Where it shows: The Smurfs – Dreams (PPSA21607, UE). Its 32×32×32 color-grading LUT is built by one mesh-path draw (vkCmdDrawMeshTasksEXT 1×32×1, gl_Layer 0..31). In a RenderDoc capture on main 82ddae75b, 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, set Slices = 32, FirstSlice = 0, Width = 64, and let the mesh case draw and Check like the vertex case does. The vertex draw passes; the mesh draw fails with mesh 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 exceeds maxMeshOutputLayers, not only when meshShader is false. I can test your branch on the RX 6650 XT with Smurfs and the 32-slice test once it's up.

  5. billythemenace2 commented on Oct 16, 2026

    @billythemenace2
    Author

    The prototype is up: billythemenace2/AnyPS5@feat/agc-mesh-compute-fallback (f89a0df02, one commit on main 452494439). 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_mesh passes through the fallback, and agc_driver_graphics passes.
    • The crash in Sackboy was the compute pass using the draw's independent-sets pipeline layout. Even an empty compute shader lost the device in that spot. The pass now has its own layout with the same set layouts and a compute push range.
    • Sackboy now runs without a crash. Its only mesh-path draw is a layered full-screen pass, vkCmdDrawMeshTasksEXT-shaped 1x32x1 with gl_Layer 0..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 maxMeshOutputLayers through 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-slice LayeredVolumeDraw case 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).

  6. odntht commented on Oct 16, 2026

    @odntht

    @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. No VK_EXT_mesh_shader, no VK_KHR_fragment_shader_barycentric; geometryShader, shaderOutputLayer, vertexPipelineStoresAndAtomics and drawIndirectCount true, 64 KiB compute shared memory, subgroup 8 to 32. Baseline is main 82ddae75b.

    1. The branch doesn't build on Linux. The two size checks in ShaderDiskCache.cpp only apply on Linux x86_64 with libstdc++, and meshRecordLayout grows both structs:

    ShaderDiskCache.cpp:87: static assertion failed: CompiledShaderArtifact changed: update the artifact encoder (240 == 232)
    ShaderDiskCache.cpp:89: static assertion failed: RecompileResult changed: update EncodeResult and DecodeResult (472 == 464)
    

    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_mesh runs through the fallback here and fails its first case. On main it skips (the device has no VK_EXT_mesh_shader). On the branch it runs and stops at:

    AGC graphics: indexed triangle list, one-wave subgroups: triangle 0 was not rendered in its color, pixel (16, 24, 40, 255)
    

    So the case that passes on your RX 5700 XT draws nothing for triangle 0 on ANV. agc_driver_graphics fails on this GPU on both main and the branch, with the same VOP2 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. In Execution/src/Driver/Draw/Draw.cpp:453, a pixel shader with barycentricEmulation.active requires graphics.stages.path == ShaderPath::Vertex, and BuildBarycentricGeometryShader is built from programResults[0], the guest vertex program. The branch doesn't touch that file, so ccleste (PPSA59322) stops at the same point on main and on the branch:

    AGC driver: a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it
    

    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_mesh and ccleste on this GPU when you push a fix.

    AI-assisted: yes (Claude Code).

  7. hashaam1217 commented on Oct 16, 2026

    @hashaam1217
    Contributor

    @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. Head 86729c6a1, run with ANYPS5_GPU="HD Graphics 530" ANYPS5_REQUIRE_VULKAN=1. On main, every test below passes or skips on this GPU.

    Build on Linux. The prototype does not compile with libstdc++. The size checks in ShaderDiskCache.cpp are guarded by __linux__ && __x86_64__ && __GLIBCXX__, so they do not fire on Windows:

    ShaderDiskCache.cpp:87: static assertion failed: CompiledShaderArtifact changed: update the artifact encoder
    ShaderDiskCache.cpp:89: static assertion failed: RecompileResult changed: update EncodeResult and DecodeResult
    

    encodeArtifact/decodeArtifact already carry meshRecordLayout, so only the numbers are stale: sizeof(CompiledShaderArtifact) is 240 (was 232) and sizeof(RecompileResult) is 472 (was 464). I changed those two locally to run the tests.

    Results (ctest -R '^agc_': 619 of 624 pass):

    Test Result on HD 530
    agc_driver_mesh fails: SPIRV-Tools: module version exceeds requested SPIR-V target
    agc_driver_layered_volume_draw fails: same SPIR-V version error
    agc_driver_prepared_graphics fails: a mesh program run as compute must export one position (stage 7)
    agc_driver_graphics fails: ShaderRecompiler::Recompile: VOP2 opcode is not implemented
    agc_shader_memory fails: new 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_gpl pass
    agc_driver_mesh_index_cache skips

    The 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.

  8. Pacuka commented on Oct 16, 2026

    @Pacuka
    Contributor

    @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 two ShaderDiskCache.cpp size asserts (240/472) like hashaam1217 did.

    LayeredVolumeDraw with 32 slices (Width 64, Slices 32, FirstSlice 0), same binary:

    • native mesh path: slice 8 texel (16, 0) channel 0 is 64, expected 255 (as before)
    • forced compute fallback: all 32 slices pass

    agc_driver_mesh also passes through the forced fallback.

    The Smurfs - Dreams with the fallback forced:

    • the main menu background (sky and clouds) now renders correctly
    • Smurfs' Village is lit. On the native mesh path it was dark, with only emissive foliage visible
    • no crash, and it exits cleanly

    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 maxMeshOutputLayers through the fallback would fix this title on RDNA2. Happy to retest once that's in.

    AI-assisted (Claude Code).

  9. billythemenace2 commented on Oct 16, 2026

    @billythemenace2
    Author

    @odntht @hashaam1217 @Pacuka thank you, these runs found real problems. The branch is updated: 5e9032eae, rebased on main bc3c0fc13.

    Fixed from your reports:

    • Linux build: the two ShaderDiskCache.cpp size checks now say 248 and 480. Current main grew 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_memory and request_serializer: main took serialization version 32 for spanned waves in the meantime. The mesh flag now shares that byte, as version 33, and both tests cover it.
    • Triangle 0 missing on ANV (odntht): my best guess is the compute pass running at a different subgroup size than the program was compiled for, since ANV can pick SIMD8/16/32 for compute. The pass now pins the program's subgroup size the same way compute dispatches do. A rerun of agc_driver_mesh on the HD 520 or 530 would tell us whether that was it.
    • Forcing the fallback: APS5_NO_MESH_SHADER=1 now 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_memory and request_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 maxMeshOutputLayers needs 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).

  10. Pacuka commented on Oct 16, 2026

    @Pacuka
    Contributor

    @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).

  11. Alexius0815 commented on Oct 17, 2026

    @Alexius0815
    Contributor

    @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. Branch 5e9032eae with @mugurc's macOS branches merged on top (the ci/macos-series set: libc/libkernel/agc/audio), plus #6124, #6448, #6580, #6928, #6668, #6667 and #6235 so that it builds and runs on macOS.

    • Build: compiles with Apple clang 21 / libc++, no changes to the fallback needed (the ShaderDiskCache.cpp size checks are Linux-only).
    • agc_driver_mesh_tests: passes through the fallback ("Mesh tests passed", exit 0).
    • ccleste (PPSA59322, catalog 1.00): boots, creates the device and swapchain, plays audio, runs its rect-list and compute work, then stops at its first mesh-path draw, before the fallback runs: the pixel shader needs barycentric emulation (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_TessCoord as the weights) might be one way to close that gap on macOS. That is a separate step from this PR.

    AI-assisted: yes

  12. Alexius0815 commented on Oct 17, 2026

    @Alexius0815
    Contributor

    @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_TessCoord as 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_tests passes 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_tests passes, including new indexed and non-indexed per-vertex barycentric cases through your fallback;
    • ccleste renders its title screen at 1920x1080 (captured frames). Not yet a gameplay, soak or clean-exit result.

    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

  13. billythemenace2 commented on Oct 17, 2026

    @billythemenace2
    Author

    @Alexius0815 thank you, a macOS data point is exactly what this needed: agc_driver_mesh passing 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 maxMeshOutputLayers is still next on my side.

    AI-assisted (Claude Code).

  14. Alexius0815 commented on Oct 17, 2026

    @Alexius0815
    Contributor

    @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 main and 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

  15. 3 remaining items

  16. Alexius0815 commented on Oct 18, 2026

    @Alexius0815
    Contributor

    @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 5e9032eae prototype, not on standalone main.

    For the local follow-up at 4443edf4e, our saved Apple M4 run from October 18 includes Mesh fallback barycentric pixels and front/back culling passed in agc_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_validation on the M4 is still pending. Those corrections were not part of the earlier 4443edf4e run.

    vkQuake remains quarantined; no new run is included here.

    AI-assisted: yes

  17. billythemenace2 commented on Oct 18, 2026

    @billythemenace2
    Author

    @Pacuka the maxMeshOutputLayers routing is in. billythemenace2/AnyPS5@feat/agc-mesh-compute-fallback is now 168eaf3e9, rebased on main e5f18ea99, with one new commit on top.

    • MeshDrawLayers takes the largest layer count of the bound targets: a color target's layers or 3D depth, or the depth target's slice range. On a device with mesh shaders, MeshDrawRunsAsCompute sends a mesh-path draw through the compute fallback when that count is over maxMeshOutputLayers. Draws within the limit stay on the native mesh stage.
    • The draw stage then asks for the mesh program with meshAsCompute set, 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=1 still forces every mesh draw through the fallback, and it's now listed in docs/dev/ENVIRONMENT.md.
    • The shader disk cache format is 38, since main reached 37 in the meantime.

    On the RX 5700 XT, which has no mesh shaders, agc_driver_graphics passes, 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 do agc_driver_mesh, agc_driver_layered_volume_draw/_submit/_vertex, agc_driver_prepared_graphics, agc_shader_memory and request_serializer. agc_driver_layered_volume_vertex_no_gpl fails here, but it fails the same way on plain main.

    I can't exercise the routed path itself, because it needs a device that has mesh shaders. Could you run your 32-slice LayeredVolumeDraw edit and Smurfs Dreams without APS5_NO_MESH_SHADER on 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).

  18. Pacuka commented on Oct 18, 2026

    @Pacuka
    Contributor

    @billythemenace2 Tested 168eaf3e9 on the RX 6650 XT (maxMeshOutputLayers = 8), RADV Mesa 26.2.4, CachyOS, Release, without APS5_NO_MESH_SHADER:

    • 32-slice 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 with meshAsCompute when the layer count is over MaxMeshOutputLayers(), as DrawCapture does). It prints maxMeshOutputLayers 8, layers 32 and passes; every slice is written. On main the same edit failed at slice 8.
    • Tests on this GPU: 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.
    • Smurfs Dreams, fresh save, no env: the title screen and character select now show the cloud / starfield background (black on 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 maxMeshOutputLayers and 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).

  19. root-over commented on Oct 19, 2026

    @root-over

    @billythemenace2 a run of 168eaf3e9 on 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 has geometryShader but no VK_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_memory and request_serializer pass. agc_driver_mesh_draw_relocation, _index_cache and _provoking_vertex skip, as they need the native mesh stage. _vertex_no_gpl passes 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):

    Title main before (8816fd772 with #6610's guard) 168eaf3e9
    PS5SX2 (PPSA99203) device does not support mesh shaders gets past it, stops 3 s in at needs a vertex shader before it
    ccleste (PPSA59322) needs a vertex shader before it same, 4 s in
    VLC (PPSA85300) needs a vertex shader before it same, 3 s in
    DriftCheck (PPSA98766), DOOM (PPSA99666) run 45 s, frames drawn same, no new messages

    So on GCN 5 the barycentric step is what remains for these three, as on macOS. The difference: Draw.cpp would choose BuildBarycentricGeometryShader here, since GeometryLimits() 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.

  20. root-over commented on Oct 19, 2026

    @root-over

    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.cpp accepts the mesh path when the program was compiled as compute (meshRecordLayout != 0) and inserts BuildBarycentricGeometryShader after it. The mesh program stands in for the vertex result there: the readback vertex shader writes exactly its parameterExports locations. Without geometry shaders it still throws, leaving the tessellation route to @Alexius0815's follow-up.
    • MeshComputeStages keeps that geometry stage: compute, readback vertex, geometry, fragment.
    • ValidateShaders accepts 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, no fragmentShaderBarycentric), Release:

    Title 168eaf3e9 + this commit
    ccleste (PPSA59322) needs a vertex shader before it title screen, alive at 45 s
    PS5SX2 (PPSA99203) needs a vertex shader before it game list UI, alive at 45 s
    VLC (PPSA85300) needs a vertex shader before it home screen, alive at 90 s
    DriftCheck (PPSA98766), DOOM (PPSA99666) run run, unchanged

    The 17 tests from my previous comment plus barycentric pass with ANYPS5_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.

  21. root-over commented on Oct 19, 2026

    @root-over

    Four more catalog titles from a wider sweep on the same Vega (RADV RENOIR, mesh=0, no fragmentShaderBarycentric) reach the same stop on current main and get past it with 168eaf3e9 plus the geometry-stage commit (eff3479e9). main here is 676269473 with only babyhuey's #6610 heap guard applied locally; 40 s per title.

    Title main 676269473 168eaf3e9 + eff3479e9
    PSXS5 (PPSA97510) needs a vertex shader before it, 3 s in library screen ("Your shelf is empty"), alive at 40 s
    SwanStationPS5 (PPSA98510) same, 3 s in library screen, alive at 40 s
    PSFlyCast (PPSA99247) same, 3 s in library screen, alive at 40 s
    Mario 64 (PPSA99310) same, 3 s in runs 5 s, then exits with status 0 through its own shell request, most likely for lack of a ROM

    With the earlier three (ccleste, VLC, PS5SX2), that makes seven titles on this device that stop at this check on main and 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.

  22. billythemenace2 commented on Oct 19, 2026

    @billythemenace2
    Author

    @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_gpl passing there, since it fails on the RX 5700 XT on plain main too.

    eff3479e9 looks right to me. Using the mesh program's parameterExports as 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).

  23. root-over commented on Oct 20, 2026

    @root-over

    @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, 168eaf3e9 plus eff3479e9, APS5_NO_MESH_SHADER=1 ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1:

    • agc_driver_mesh skips on llvmpipe whether or not the draw runs as compute: the device-name check comes after the meshAsCompute check. 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 with LP_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_draw on 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.
    • On RADV agc_driver_mesh passes 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. ComputeTarget gives the mesh program the device's subgroup size (8 on lavapipe), and Pipeline asks for a required size only when computeWave32 is 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.

  24. billythemenace2 commented on Oct 20, 2026

    @billythemenace2
    Author

    @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. 08346eb8f now refuses a mesh draw run as compute when the subgroup size the pass will actually run at is narrower than the program's wave, with a mesh-path draw run as compute needs compute subgroups at least as wide as its wave. It computes that size the same way Pipeline pins it, so on lavapipe it now stops cleanly instead of drawing wrong triangles. A unit test in agc_driver_graphics covers it with an 8-lane device and a wave-32 program. On the RX 5700 XT agc_driver_graphics, agc_driver_mesh and 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).

  25. billythemenace2 commented on Oct 20, 2026

    @billythemenace2
    Author

    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.

    First level

    The build is main d073fe6a1, 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:

    • Barycentrics: the title's mesh-path pixel shaders read barycentrics, so the draw stage now emulates them with BuildBarycentricGeometryShader behind the readback vertex shader when the mesh program was compiled as compute. That's @root-over's approach from eff3479e9, written separately here. MeshComputeStages keeps the stage order compute, readback vertex, geometry, fragment, and ValidateShaders accepts it when the mesh program outputs triangles.
    • Pixel shader mode: main's new meshBarycentricPixel is now set only when the target has mesh shaders. Otherwise the pixel shader declared MeshShadingEXT (capability 5283) and was refused on this device.
    • Mesh support check: main's new device does not support mesh shaders check in ValidateShaders now lets the compute-pass stages through.
    • Merge: the fallback's flag is a separate field at serialization version 40, and the disk cache format is 42.

    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).

  26. Alexius0815 commented on Oct 20, 2026

    @Alexius0815
    Contributor

    @billythemenace2 an M4 data point on 08346eb8f, from merging the prototype into mugurc's current macOS series (ci/macos-series-2, on main 3fd1cc3). Apple M4, macOS 27.2, MoltenVK, x86-64 under Rosetta.

    Two checks stop the fallback on Apple GPUs:

    1. lanes >= state.stages.vertexWaveSize in MeshComputeStages. 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_mesh passed here, because the fallback already pairs lanes for wave 64 on 32-lane subgroups (the threadsPerGroup / 2u invocation count in Pipeline.cpp). Locally I allowed exactly that case and kept the refusal for narrower subgroups such as lavapipe's:
      Require(lanes >= state.stages.vertexWaveSize || (state.stages.vertexWaveSize == 64u && lanes == 32u && context.subgroup.subgroupSize == 32u), ...);
    2. Require(!mesh || meshShader, "device does not support mesh shaders") in ValidateShaders, from 60a2aa47d on main, 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_mesh passes 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

  27. tezzgod74 commented on Oct 21, 2026

    @tezzgod74
    Contributor

    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's eff3479e9 (cherry-picked cleanly), compared against its base e5f18ea99. 168eaf3e9 + eff3479e9 gave the same results. Windows 11 Pro 10.0.26300, MinGW-w64 GCC 15.2.0 (WinLibs r7), Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON, all agc_* tests (718):

    • Ryzen 9 7950X iGPU (RDNA 2, 1002:164e, 32.0.21045.5002): mesh=0, SPIR-V 1.3 target, so the fallback is the only path.
    • RX 9060 XT (RDNA 4, 1002:7590, 32.0.32015.2008): mesh=1. Run as is, and with APS5_NO_MESH_SHADER=1.
    base e5f18ea99 08346eb8f + eff3479e9
    iGPU, agc_driver_mesh skipped (no mesh shaders) passes through the fallback
    RX 9060 XT, agc_driver_mesh passes passes
    RX 9060 XT + APS5_NO_MESH_SHADER=1, agc_driver_mesh passes (natively, the switch isn't on the base) passes through the fallback
    failed tests, each configuration same set as the base

    With the branch, all three configurations pass agc_driver_graphics, agc_driver_layered_volume_draw, _submit and _vertex, agc_driver_prepared_graphics and agc_shader_memory, and request_serializer passes. With SPIRV-Tools on and the iGPU's SPIR-V 1.3 target, no test reports module version exceeds requested SPIR-V target, so starting the mesh-as-compute module at 1.3 holds here. The subgroup check from 08346eb8f doesn't refuse any of these draws on either device.

    Failures that occur on the base as well, so not from the branch:

    agc_driver_mesh_index_cache, agc_driver_mesh_draw_relocation and agc_driver_mesh_provoking_vertex are skipped whenever the fallback is in use, on the iGPU and on the RX 9060 XT with APS5_NO_MESH_SHADER=1. So on these devices agc_driver_mesh is the only test that runs through the fallback.

    AI-assisted: yes

  28. DavidSamir commented on Oct 21, 2026

    @DavidSamir
    Contributor

    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.

    • Before: main 1ff5c8ac5.
    • After: branch head 08346eb8f, as is, on its base e5f18ea99. It doesn't merge cleanly into current main (15 files conflict), so the libraries are the branch's own build. The relinker is main'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.

    Title Before (main) After (branch)
    PPSA59322 ccleste 1.00 mesh stop 60 s
    PPSA76677 RommPS 1.2.1 mesh stop 60 s
    PPSA97510 PSXS5 2.2.0 mesh stop 60 s
    PPSA98510 SwanStationPS5 1.4.2 mesh stop 60 s, 2 of 2 runs
    PPSA98511 ReKHCoM ps5-v1.0.6 mesh stop 60 s, 2 of 2 runs
    PPSA99004 ProsperoAI 01.006.000 mesh stop 60 s, 2 of 2 runs
    PPSA99141 Mortalfin 1.0.0-beta.5 mesh stop 60 s in 1 of 2 runs; the other exited by itself, code 0, after 14 s
    PPSA99142 Seedee5 1.22 mesh stop 60 s in 1 of 2 runs; the other exited by itself, code 0, after 14 s
    PPSA99203 PS5SX2 vk-285-177 mesh stop 60 s, 2 of 2 runs
    PPSA99247 PSFlyCast 1.3.0 mesh stop 60 s, 2 of 2 runs
    PPSA99305 Dusklight 1.0.0 mesh stop 60 s, 2 of 2 runs
    PPSA99310 Mario 64 1.0.1 mesh stop 60 s, 2 of 2 runs
    PPSA99314 Eternal Darkness 0.9.5 mesh stop 60 s, 2 of 2 runs
    PPSA99641 Wind Waker HD ps5-v0.1.2 mesh stop 60 s, 2 of 2 runs
    PPSA99764 Porpoise 3.0 mesh stop 60 s, 2 of 2 runs
    PPSA69423 Rush 2 Recompiled 00.003.000 mesh stop past the mesh path, then initial multisampled DCC contents require watched guest memory (3 s)
    PPSA69424 Wave Race 64 Recompiled 00.001.000 mesh stop the same as Rush 2 (3 s)
    PPSA99248 PSSwanStation build24 mesh stop past the mesh path, then inverted scissor rectangle (8-14 s, both runs)
    PPSA99320 Ship of Harkinian (Vulkan) 01.000.000 mesh stop past the mesh path, then malloc_stats_fast: implemented only for an application allocator that provides it (12 s, both runs)
    PPSA99670 Link's Awakening v0.1.0 mesh stop unresolved ELF import RZA5RZawY04 from libSceLibcInternal.prx at start

    The 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 main after e5f18ea99. I didn't test them against current main with 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=1 and ANYPS5_NO_SHADER_CACHE=1:

    • agc_driver_mesh: skipped on main (no VK_EXT_mesh_shader); passes on the branch.
    • agc_driver_graphics and agc_driver_layered_volume_{draw,submit,vertex} pass on both.
    • 0 VUID- lines on either side.
    • agc_driver_layered_volume_vertex_no_gpl fails on the branch, but its base e5f18ea99 fails identically, 3 of 3 (slice 1 texel (0, 0) channel 0 is 64, expected 255), and main passes it. So it comes from the base, not the fallback.

    AI-assisted: yes

  29. billythemenace2 commented on Oct 21, 2026

    @billythemenace2
    Author

    The fallback is now a pull request: #9356, one commit on main 1698d1ea6. Thank you all, every point in it came from your runs. The description lists each.

    Since 08346eb8f:

    • Rebase: it's rebased onto current main, at serialization version 43 and disk cache format 45.
    • @Alexius0815's Apple case: a wave-64 program on 32-lane subgroups is allowed again, in the paired-lane mode the pipeline already sets up. Narrower subgroups, as on lavapipe, are still refused.
    • Mesh support check: ValidateShaders' new device does not support mesh shaders check 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 new meshBarycentricPixel compiles the pixel shader for the mesh stage, which declares MeshShadingEXT. On a device without mesh shaders it has to stay off (&& request.target.mesh.has_value() in Recompiler.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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions