WIP: fix(macos): the Mac halves of indirect quads and mesh count draws #851
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!851
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/topic-draw-path-quads-mesh"
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: fix · Risk: medium · Tested on: macOS 26.7.1, Apple M4 Max (arm64; x86_64 under Rosetta) · Needs: Windows and Linux, any GPU
Why
Two every-platform fixes draw what LS left out: indirect quad draws (#705) and the mesh fallback's count draws (#707). On a Mac
each needs one more step, because there the first vertex of a triangle provokes (MoltenVK 1.4.2 offers the last vertex only with
Metal's private API), and draw-path (the PR before this one) draws the Mac's triangles with their last vertex first. Without this
PR a quad's two triangles both take their flat values from the quad's first corner on the Mac, where every other device takes the
third and fourth; and the mesh fallback's rotation list, made for single draws in its first records area, would be read from the
wrong place by a count draw and fall short of a grown area. This PR rotates each quad triangle on Apple so the same corners provoke
as everywhere else, and makes the fallback's rotation follow the count draws and the growing area. It needs #705 and #707, separate
every-platform PRs, which it carries at its base. It is part of the macOS port. See #420.
How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max, MoltenVK 1.4.2
Settings and switches: defaults
Steps: the members' standalone MoltenVK probes: QD-01's
prov_unrotatedstep (quads coloured flat by the provoking vertex's id;reports/2026-11-14-indirect-quads.md§6) and MP-01'sRAWarms (the mesh fallback's single and count draws of a program with a flat input, areas grown past the first;reports/2026-11-14-mesh-provoking.md§4)Expected: the quad halves show corners 2 and 3, as on every other device; every fallback pixel takes its triangle's last corner (NVN's vertex), as a rotated single draw in the first area does
Actual: both quad halves show corner 0; the fallback's count draws and grown areas take the first corner
What changed
On Apple, where the device provokes the first vertex, each quad triangle's corners are rotated with its winding and edges kept,
for direct quads and #705's indirect ones; and the mesh fallback's rotation list covers the count draws' commands and the area as
it grows. Elsewhere nothing changes. Its base merges #705 and #707 (restacked on draw-path), with their hand resolutions. One
commit per original PR, in order:
fix(macos): quads provoke the same corners where the first vertex provokes (gpu/vk/quad_draws.hkFirstVertexandrotated(),quad_rotated()invk_draw.cpp, the quad report's triangles field). Was:#706
fix(macos): the mesh fallback's rotation follows the count draws and the growing records area (gpu/vk/provoking.hfallback_corners,FallbackRotationandprovoking_fallbackinvk_draw.cpp). Was:#708
c2cfee0fand brace67fd90bc, shader-backend's70cc5ba1, #705'sa27f8f47: the indirect quads' count with the provoking-vertex and fan censuses, its checks to draw-path's shape, and #707's19d6d2d4: test_drawid_direct pins #707's DrawIndex condition); one conflict, intest_provoking_first's comment, resolved tothe base's six sites.
fix(macos): log_frame_stats' MeshCountDraws report block closed again (d99221c4): the base merge of #707 had dropped theblock's closing brace in
vk_replay.cpp, which stopped compiling from there; #707's restack and Integration 41 have it.Measurements
Verdict: both fixed where measured: in QD-01 the rotated quads took corners 2 and 3 on LS's indirect path and the direct path
alike (unrotated: 0 and 0), and in MP-01 every step's image equalled the reference byte for byte, the cut steps cutting exactly the
predicted corners, with no per-draw cost at that size.
Baseline: main does not start on macOS. Each probe compares, in one standalone MoltenVK 1.4.2 process, what the PRs this one
needs draw without it (#705's unrotated quads, #707's and draw-path's unrotated fallback draws) with this PR's code at the
member's head (
fc8dd951for #706,3f872b0cfor #708).Hardware: macOS 26.7.1, Apple M4 Max (40-core GPU), MoltenVK 1.4.2, 16-core CPU, 64 GB
Scene: QD-01's
prov_rotatedandprov_unrotatedsteps; MP-01's 22 steps (task + mesh and mesh-only, counts 0, 1, 16 and past, 1 and 3 commands a call, grown areas, the allowance cutting, single draws)Method: QD-01
reports/IndirectQuads-files/qd01/runs-qd01-arm64/result.json(271fb491…); MP-01reports/MeshProvoking-files/mp01/runs-mp01-arm64/result.json(3d518854…); each released once on arm64How to reproduce: QD-01's
runcommand (reports/2026-11-14-indirect-quads.md§6); MP-01's (reports/2026-11-14-mesh-provoking.md§4)aa95d35f(with #705 and #707: unrotated; main does not start on macOS)90ab8ea1(each member's code, measured at its head)Start #32 (I38, with #708 at
3f872b0c, before its area fix): 53,047 rotated fallback draws logged as cut (69 error lines, each10-16 words off the 16,777,216-word area) while the census of corners past the area stayed 0: false cuts, 0 corners lost; #708's
head stops counting and logging them, and what is drawn is unchanged.
Flags and settings
None: no switch. On Apple, on a device without the last provoking vertex, only.
Testing
Tested: on macOS 26.7.1, Apple M4 Max, game-free, at this PR's head
90ab8ea1:90ab8ea1: 19 test files, 35 runs, all exit 0. 16 ran on arm64 and on x86_64 under Rosetta separately;test_mesh_census,test_mesh_fallbackand the crash-record source contract once (no x86_64 form). #705's:test_quad_draws(#706's too),test_draw_binds,test_seg_gathers; #707's:test_mesh_area,test_mesh_census,test_count_draws,test_mesh_count_fallback,test_mesh_drawid,test_mesh_fallback,test_seg_xfer(area, count fallback and xfer #708's too); #708's:test_mesh_provoking,test_provoking_first,test_fan_key, the crash-record contract; and the base'stest_index8_bind,test_fan_indirect,test_drawid_direct,test_vendor_fixes,test_subgroup_vertex.python -I -B tests/run_all.py --jobs 6) on this Mac against main's (543 passed, 33 failed, 17 skipped): 617 passed, 15 failed, 17 skipped; no check that passes on main fails here. Against the draw-path topic's suite (611, 15, 17) the only differences are six new tests passing:test_quad_draws(#705),test_mesh_area,test_mesh_census,test_mesh_count_fallback,test_mesh_drawid(#707) andtest_mesh_provoking(#708). The 15 failures are main's own on this Mac, the same 15 as draw-path's.mkwvk::name onegpu/vkfile declares and another defines; each defining file compiled with the tree's own flags, arm64 and x86_64, and every symbol an object leaves undefined defined by one of them): 0 of 639 declared names without a definition, 66 of 66 objects compiled, 1,254 name/arch checks with 0 failures, and on each arch 583 cross-file symbols all defined.vk_draw.cppandvk_replay.cppalso compiled whole with nx2/build.py's own macOS commands: 4 of 4 exit 0.unifdef -U__APPLE__of every changed C/C++ file against the base):quad_draws.h(kFirstVertexandrotated(apple, provokes_last), false off Apple),provoking.h(fallback_corners),vk_draw.cpp(quad_rotated()returns false off Apple;FallbackRotationandprovoking_fallbackreached only whereprovoking_first()holds, never off Apple; the fallback's header carries the area's own words as #707's does) andvk_replay.cpp(the one closing brace that makes it compile again). What Windows and Linux see run differently: the quad report line ends with its triangles' corners ({0,1,2, 0,2,3}there), a log field.78ec1c47(arm64, MoltenVK main + argbuf-slots): 1,197 s held in play, 12 of 12 must-shows, a clean end, 0 skipped draws; run #34 on I383f872b0c(x86_64 under Rosetta): 16 m 52 s, every draw drawn, 19 of 19 checks, a clean exit.Not tested: Windows and Linux (no PC here): off Apple both members leave what is drawn as #705 and #707 draw it, but nobody
has built or started this stack there.
Must be tested:
90ab8ea1) and main (aa95d35f) the same way and runpython -I -B tests/run_all.pyon each, then start each to the title, expecting this PR's tests to pass, no other check to change status against main, and thevk:lines in longshot_log.txt as main's except #705's and #707's new quad and mesh lines (the quad line ending in triangles{0,1,2, 0,2,3}), with novk: provoking vertexline (Test Farm: @Beanstalk windows and linux: build unchanged, run_all, start to the title)Risks
What could break: on Apple only: the order of each quad triangle's corners (edges, winding and split unchanged); the mesh
fallback's rotated count draws take one fill each, and a CPU-filled rotation list grows with the area (4 bytes a corner; a 128 MB
area at 31 words is a 16 MB list), made once a growth and kept; where the allowance holds the list back, the corners past it are
cut (logged and counted), not drawn unrotated. Elsewhere: the quad report's new field only.
Turning it off: revert the PR (no switch).
Compatibility: Nothing changes: no save, options key, cache or vk ABI change.
Issues
See #420. The macOS topic PRs, merged in this order; "topic N" in each description means these: 1 #845 (macos-host), 2 #846 (vulkan-on-metal, on 1), 3 #847 (mvk-descriptor-limits), 4 #848 (shader-backend, on 2, 3 and #431), 5 #849 (precompile-metal, on 4), 6 #850 (draw-path, on 4), 7 #851 (draw-path-quads-mesh, on 6, #705 and #707), 8 #852 (mac-perf, on 7).
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.mainand passing here.tests/that reads changed source updated in this PR, not weakened.Every scan down vk_memory.cpp's import table by start (pool_lookup's covering import and merge plan, bg_drain, prefetch_chunks, import_in_background, memory_note_gpu_write and memory_guest_imported) stopped once an import ended more than 1 GiB below its address, on the belief that no import is larger than that. pool_lookup's own merge escape leaves an import above 1 GiB over the smaller ones inside it ("left overlapping"), and the device allows imports up to max_import_size: a scan stopped at a small import inside a large one, missed the large one, and the same bytes were imported again, two memories over the same bytes. import_reach.h counts the live imports by span class; its bound is above every live span, within twice the largest. imported_add and imported_take, now the table's only ways in and out, keep the count. A scan stops only where no import further down can reach its address, so it also stops sooner than before when the imports are small. The covering-import checks in pool_lookup and import_in_background look past the last import at or below the range for the same reason. Tests: test_import_reach.py runs the actual scans with a 4 GiB import over a 16 MiB chunk, left by the merge escape (imports are recorded ranges: no payload, no Vulkan, no game). Its lookup, query, note, drain, background and prefetch cases fail on main5e072fc8and pass here, and a source check keeps every change to the table in the two helpers. test_import_windows runs pool_lookup with the helpers; test_mirror_gpu_only names the new publication point. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>This conflicts with
mainand is built on top of the macOS series (it carries the commits of #845 and others below it).nx2/build.py,nx2/make_game.pyanddocs/development.mdconflict with #577 (the WebGPU renderer removed from the Windows build). Please rebase onto currentmainonce its base PR #845 lands or is rebased; I didn't touch it because a rebase here would have to be redone after that.View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.