WIP: perf(vk): the mesh fallback's records line by line, or each vertex once and drawn indexed (RX_VK_MESH_FB_LAYOUT, off by default) #906
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/tooling
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!906
Loading…
Reference in a new issue
No description provided.
Delete branch "zeldaootr/Longshot:fallback-records-indexed"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Type: perf · Risk: medium · Tested on: Steam Deck OLED (RADV, Kokiri at handheld settings), Linux game-free checks and lavapipe · Needs: Steam Deck (native settings, Hyrule Field, plain fps), any other GPU on the compute fallback (
RX_VK_MESH_SHADERS=compute)Why
On a Steam Deck the compute fallback that draws the game's mesh programs (the forest floor, the grass, the trees) is the
largest GPU cost that does not shrink with the render resolution: in Kokiri Forest at handheld settings it takes 17 to 20 ms
a frame, a third of the profiled GPU frame, and 86 % of that is its mesh dispatches. Those dispatches write one 22 to 27
word record per triangle corner, 53 MB a frame, which the vertex stage then reads back: every vertex is written two to six
times, and neighbouring threads write words a record apart. This PR lets the fallback write each vertex once, line by
line, and draw the triangles from indices. Off by default until the remaining Deck measurements are in.
What changed
A switch,
RX_VK_MESH_FB_LAYOUT, chooses how the fallback's mesh stage stores what it hands to its vertex stage:aos(unset: as it always was),
soa(the same records, line by line) orindexed(each vertex once and the triangles as32-bit indices, for triangle programs with no per-primitive outputs; other programs as
soa). Unset, the translation isbyte for byte main's and the recorded commands are main's.
emit_spirv_mesh.cpp):WriteFallbackLineswritessoa's records, line l of record r atH + l * fit + r, the threads taking the records in turn;MeshFallbackIndexedEpiloguecounts a workgroup'svertices (1 + its largest index, a workgroup
OpAtomicUMax), takes its slots from the same allocator (its corners, orits vertices rounded up to whole triangles when they are more), writes its vertices once and its corners' indices in
corner order (the same provoking vertex and winding); slots past its corners hold a triangle of its first vertex,
which has no area.
EmitMeshFallbackVertexBodyreads each layout. The layout,fitand the index line:MeshFallbackLayout,MeshFallbackFit,MeshFallbackIndexed,MeshFallbackIndexWordinemit_spirv.h.emit_spirv.h): unchanged inaosandsoa. Inindexedit is 16 words: words 0-7 as before(the plain draw's count stays 0), the
VkDrawIndexedIndirectCommandat words 11-15. The allocator and the taskstage's dispatch words do not move, so task programs translate the same in every layout.
and names a vertex written; past the area the draw is the first workgroups'.
vk_draw.cpp):fallback_header_fills(main's four fills, and the indexed command's three);fallback_drawdraws the header as before, or binds the program's index line through
bind_index(so the game's next indexed drawbinds its own again) and draws it with
vkCmdDrawIndexedIndirect. Withindexedthe records buffer is also an indexbuffer and a thread's pair keeps a 16-word header for every program, so its programs still share it.
Program::fallback_indexedin the record head (vk_progcache.cpp). The switch is a name underRX_VK_MESH, which the cache's stamp holds: a start with it translates again, a start without it keeps its caches.mesh_rule::fb_layout(mesh_mode.h),mesh_fallback_layout()(vk_shader.cpp),docs/development.md.Measurements
Verdict: the fallback's mesh dispatches take half the time with either new layout on a Steam Deck in Kokiri Forest at
handheld settings: 17.0 ms a frame (
aos, main's layout) to 8.3 ms (soa, -51%) and 8.5 ms (indexed, -50%), two runseach. The gain is the line-by-line layout: indexing adds nothing measurable on top in the mesh stage, and the draw of the
records stays at 4.5-5.1 ms in every arm. Pending: native settings and Hyrule Field, the plain (unprofiled) fps arms, and
a frozen-frame picture comparison.
Hardware: Steam Deck OLED, SteamOS, RADV
Scene: Kokiri Forest, the heavy standing view (~2,200 draws), 120 s standing and three walk cycles; handheld: 1280x720 output, 50 % render, the game's own upscaler
Method: a measuring build (this PR's
gpu/change on the Deck dev base9c9d69ed, plus a probe'sRX_VK_PASS_TIMING=2parts andRX_VK_FALLBACK_STATS=1, not part of this PR); themeshpart ofmesh fallback by part, every 600 presents, the first report after a start ignored; arms interleaved (aos, indexed, soa, indexed, aos, soa), two runs each, the mean; every run from empty program and pipeline caches withRX_VK_PRECOMPILE=0, each arm translating in its own layoutBaseline: the Deck dev base, main @
9c9d69ed, with the switch unset (aos, main's layout), in the same measuring build; the After columns are this PR'sgpu/change applied on that base.How to reproduce:
RX_VK_MESH_FB_LAYOUT=soaorindexedinlongshot_env.txt, withRX_VK_PASS_TIMING=2on a build that has the fallback's parts timed9c9d69ed787025d6787025d643ec86f3/d8e59341/3b8c07f2/89ca750f), first runover)Pictures: the same moments of each arm, looked at full size, show the same ground, grass, bushes, ferns, foliage and
shadows, with no missing, black or stretched geometry. A per-pixel difference cannot prove identity there, as the scene
animates (falling leaves, fairies, swaying grass): across arms it is the same as between two runs of one arm (mean largest
channel difference 9.2-11.9 across, 9.9-10.7 within). Pending: a frozen-frame (pause menu) comparison.
Game-free, Linux, lavapipe (llvmpipe, LLVM 20): the fixture's modules (below) run as the fallback runs them, one
workgroup a dispatch so that the records' order is the workgroups', then drawn into two RGBA32F targets (a smooth and a
flat input, back faces culled):
Flags and settings
One new switch, off by default.
RX_VK_MESH_FB_LAYOUT=aos,soaorindexedaos)aos)soa: line by line;indexed: each vertex once, triangles as indicesTesting
Tested:
aos,soaandindexed, two runs each (Measurements).python -I -B tests/run_all.pyon Linux, before the last rebase: 571 passed, 16 failed, 24 skipped; the 16 fail the same onmainon this PC (test_options_budget_once,test_readback_late,test_vk_pgo,test_bake_portability,test_crash_dialog,test_walk_key,test_missing_nup,test_appimage_player_build,test_lift_cache,test_env_on,test_lift_cleanup_error,test_pipeline_tour,test_make_game_cache,test_linux_preflight,test_mods_size_table,test_vk_occlusion_queries). After it, the 81 checks that read the changed files, once with g++ 13 and once with clang 23: 77 passed, and the 4 that failed (test_bake_portability,test_crash_dialog,test_pipeline_tour,test_vk_occlusion_queries) fail the same on currentmain.test_vk_occlusion_queriesfails onmainbefore it reachesdraw()(the replay's recording-end barrier, its line 809), so its run does not test this PR's change to its draw-command pattern. Checked apart instead: with that one replay check left out, the check passes on this branch with the new pattern, and fails with the old one (occ.issue() with a way out before its draw command).tests/test_mesh_fallback_layout.py(the fixturetests/mesh_fallback_layout_ir.cppthrough the real SPIR-V backend: every module spirv-val clean, SPIR-V 1.6, the vertex stages Vulkan 1.3 too; unset translates byte for byte as
aos; and the modeltests/mesh_fallback_layout_model.cpp: random meshlets, up to 256 vertices and primitives,counts past the most, index bytes past the vertices, degenerate triangles, and past the area);
tests/test_mesh_fallback_draw.py(the fills, the draw of each layout, the index binding it leaves). Updated:
test_fallback_buffer_cleanup,test_program_record_head,test_mesh_fallback,test_vk_occlusion_queries. With g++ 13 and clang 23; the back end is compiledas C++23 (C++2b where the compiler has no
-std=c++23), as the bake's builds do.aosmodules are byte for byte main's (twelve modules: six shapes, mesh and vertex stage).counting slots that did not fit (the overflow rule), slots of the corners only,
soa's vertex stage line stride: bythe model, and by the lavapipe run of the real modules.
main.Not tested: native settings and Hyrule Field on the Deck; unprofiled fps; a pixel-exact picture comparison; any other GPU.
Must be tested:
aos,soa,indexed, expecting the mesh dispatch time to fallas at handheld; plain fps arms without the profiling switches; and a frozen-frame (pause menu) screenshot of each arm at
the same spot, expecting identical pictures (by hand).
RX_VK_MESH_FB_LAYOUT=indexedand without it, expecting the same picture (by hand: same-spot screenshots).Risks
What could break: with
soaorindexed, the fallback's terrain and vegetation (wrong or missing triangles, wrongflat-shaded values) on GPUs using the compute fallback. Unset, nothing: the translation and the commands are main's.
Past the records area,
indexedcan draw a few triangles fewer thanaos(a workgroup with more vertices than cornerstakes more slots); the area is the same 64 MB.
Turning it off: unset
RX_VK_MESH_FB_LAYOUT(the default).Compatibility: nothing changes for a player who does not set it. Setting it, or changing it, translates the programs
again on the next start (the program cache's stamp holds the switch), as
RX_VK_MESH_NOCULLdoes.Also: in
indexed, a workgroup's padding triangles (its slots past its corners, which have no area) shiftgl_PrimitiveIDfor the triangles after them; that only matters to a fragment program that reads the primitive ID.RX_VK_MESH_FB_LAYOUT=aosset explicitly translates as unset does, but its text still changes the cache's stamp, so ittranslates again once. And past the records area,
indexedcan draw a few triangles fewer thanaos(a workgroup withmore vertices than corners takes more slots).
Works with / conflicts with
the area's words) and a 16-word header; this PR leaves words 8-10 unused and puts its indexed command at 11-15, so the
headers fit together. But #707's
fitis read at run time from the area word, while herefitis a compile-timeconstant (
MeshFallbackFit) in three places:soa's line stride in the mesh and vertex stages, the indexed epilogue,and the host's index-line offset (
MeshFallbackIndexWordinfallback_draw); all three would take the area's wordinstead. #707's count draws, a command per record, would need a
VkDrawIndexedIndirectCommandper record for anindexed program.
vkCmdDrawIndexedIndirectat the same draw site. Withindexedthe rotation {2,0,1} would be written into thegenerated indices instead of a static list; with
aosandsoatheir list works as it is.Whichever lands second adapts to the other; I can do that rebase for any of them.
Checklist
Everyone
type(scope): description) of the status line's type.main.python -I -B tests/run_all.pypasses (any failure that also fails onmainis named).third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.docs/tested/for each new measured run (format indocs/tested-configurations.md). (with the remaining runs)tests/that reads changed source updated in this PR, not weakened.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.