fix(macos): pin Vulkan host imports until their last owner, and unwind failed startups #491
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 @ 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!491
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/renderer-correctness"
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 (game-free checks, arm64 and Rosetta x86_64; no game run on this branch) · Needs: Windows and Linux, AMD or NVIDIA GPU
Why
On macOS the renderer imports guest memory into Vulkan, and the guest pages behind an import must not be replaced or
discarded while any Vulkan object still uses them; nothing guaranteed that. On every platform a renderer whose startup
fails part-way left behind what it had made. This is part of the macOS port (#420).
guest-memory code can replace a page's backing (a discard maps fresh memory there) while the renderer still has a
buffer over it; the GPU would then read pages the game no longer sees. Nothing told the executable which ranges the
renderer was importing, or for how long.
Poolwas copied into the import table and the background-import queue(
imported.emplace(lo, p),bg_ready.push_back({.., p})). Any owner the copy carried would be released twice, or bythe wrong copy.
replay_startreturnedfalseon a failed create and left behind what it had made (fences,command pools, semaphores, the parallel init pools, the VMA allocator). Several creates were not checked at all
(
vkAllocateCommandBuffers, the frame fence and semaphore).waveOutOpenreached SDL through a weakhos_sdl_audio_openimport, which astrict two-level macOS link (
-undefined error) refuses.MessageBoxAlooked for SDL under its Linux names.How to reproduce
Seen on: macOS 26.7.1, Apple M4 Max (arm64 and x86_64 under Rosetta), in the game-free renderer and audio tests.
The startup rollback and the import copies are in code every platform runs (
vk_replay.cpp,vk_vma.cpp,vk_memory.cpp); the pins and the audio route are macOS only.Settings and switches: defaults;
RX_VK_RECORDER=deferorthreadfor the recorder boundary.Steps: on a Mac,
python -I -B tests/test_darwin_renderer.py --cxx clang++ --headers <Vulkan-Headers/include> --arch arm64 --arch x86_64 --source <a tree of main>andpython -I -B tests/test_darwin_audio_service.py --cc clang --arch arm64 --arch x86_64 --source <a tree of main>; and readreplay_startinmain'svk_replay.cpp.Expected: each pin released once, after its buffer is destroyed; retirement waiting for completion; each failure
point of the startup unwinding exactly what it made; the audio shim linking strictly.
Actual: on
main(and on the dependencies alone) both tests stop: nostruct DarwinImportBackingand noWaveAudioOpenFnservice route to cut (before the rebase, at5e072fc8).main'sreplay_startreturnsfalseon afailed create and leaves its fences, command pools, semaphores, parallel init pools and VMA allocator behind, and does
not check
vkAllocateCommandBuffers, the frame fence or its semaphore. On macOS nothing pins an import's pages.What changed
Every macOS import pins its whole backing in the executable (through two new host callbacks) until the last Vulkan
owner is destroyed, and imports are move-only so an owner is never copied or dropped early. A renderer whose startup
fails part-way gives back what it made, and the recorder drains before that rollback destroys the allocator: those
startup and ownership changes apply on every platform (listed under "Changes on Windows and Linux").
DarwinImportBackingandDarwinImportViewinvk_memory.cpp, thenreplay_start_rollbackinvk_replay.cpp.This branch carries their commits unchanged: guest-16k's head
afa7490c, then #438's head0f2262a3merged(
4975b01a); its own commits start at7d168a5e. It needs guest-16k for the executable'shos_posix_guest_import_pin/unpin, which the new callbacks call, and #438 forMkwVkHost's Apple callbacks itappends to.
4975b01a..7bf15899(seven commits); the commits before it are the pull requests this one dependson.
7d168a5eis the change (one conflict with #250's includes, kept both);1e538827moves three fixtures onto theWindows/Linux
Poollayout;3bdfb929adds thevmaDestroyAllocatorboundary (beside #250'svmaFreeMemory);2764a8a6follows #324 and #250 in the new fixture;f78ae3edfollows the movedPoolintest_mirror_gpu_only.py;0f54963dmakes the import pin callbacks macOS host API 24 (below);7bf15899makesvk_memory.cppcompile for macOS beside main's #324 (below). The renderer-core PR later rewritestest_mirror_gpu_only.py's line for itsimported_add. The measurements name the first five by their old SHAs:107fda29,1f805dac,05213268,91f9a58d,c1f9f884.macOS (inside
#if defined(__APPLE__))gpu/vk/vk_api.h:MkwVkHostgainsguest_import_pin(lo, hi)andguest_import_unpin(lo, hi)at its end, onApple only, where
MKWVK_API_VERSIONbecomes 25 (24 after #438). Elsewhere it staysmain's 23 and the structureis unchanged.
host/rx_backend.cfills them with guest-16k'shos_posix_guest_import_pin/unpin.gpu/vk/vk_memory.cpp: each macOS import has oneDarwinImportBacking: the whole outward-rounded range, pinned inthe executable before the import is prepared and unpinned only in its destructor, after its device memory is freed.
A
Pool's buffer is a move-onlyDarwinImportViewthat holds the backing; the buffer is always destroyed before thelast backing owner unpins. Retirement keeps the existing completion gates (
retired_value,dead_imports): a viewleaves only when the GPU is past it. A failed or unpublished import unwinds its view and pin at once. The published
tables live in process-owned containers that are never destroyed at exit, as the recorder's are, so no Vulkan call
runs from static teardown.
darwin_guest_importedreports which guest ranges have live owners, from the existingtables (no second registry).
gpu/vk/darwin_import.h(new) holds the range arithmetic.host/posix/win32_posix.c:waveOutOpenresolves the executable'shos_posix_audio_openonce throughdlsym(RTLD_MAIN_ONLY, …)instead of a weak import, so the renderer image links strictly and never starts a secondSDL.
host/hos_sdl.cexports that service.MessageBoxAlooks forlibSDL3.0.dylibandlibSDL2-2.0.0.dylib.Changes on Windows and Linux
gpu/vk/vk_replay.cpp,gpu/vk/vk_vma.cpp, every platform): every fallible create inreplay_startis checked, and a failure calls the newreplay_start_rollback(), which destroys the parallel initpools, each recording's fence and command pools, each frame's command pool, fence and semaphore, the three timeline
semaphores, and then the VMA allocator (
vma_startup_rollback()), before returningfalseas before.vma_initpublishes the allocator only once
vmaCreateAllocatorsucceeded. A startup that succeeds runs exactly as before.gpu/vk/vk_recorder.h,vk_work.h, every platform):vmaDestroyAllocatoris a drain-firstboundary, next to main's
vmaFreeMemory, so withRX_VK_RECORDER=deferorthreadthe recorder drains before therollback destroys the allocator. With the recorder off (the default) the boundary is a relaxed load and a branch.
tests/test_recorder_sync.pycaught the missing boundary.gpu/vk/vk_memory.cpp, every platform):Poolvalues are moved, not copied, into the importtable and the background queue; the queue slot for a retired or evicted pool (and a
DeadImport) is allocatedbefore the pool leaves the live table, so a failed allocation leaves its owner where it was; the three-line buffer and
memory release is one
destroy_import()helper with the same calls in the same order. Log lines read the moved pool.host/posix/win32_posix.c(Linux):MessageBoxAcloses the extra reference itsRTLD_NOLOADdlopentakes;waveOutOpenchecks itscallocandpthread_mutex_initand returnsMMSYSERR_ERRORinstead of crashing.6c9edffevk_api.his not in the stamp (since #370), so nothing rebuilds. Its preprocessedtext off Apple is unchanged and the API stays
main's 22.How this was checked: every changed C/C++ file, preprocessed with
unifdeffor Linux (-U__APPLE__ -D__linux__) andfor Windows (
-U__APPLE__ -D_WIN32), compared with the branch before this PR's commits.vk_api.h,hos_sdl.handrx_backend.care identical; the differences in the others are exactly the ones listed (hos_sdl.cby one blank line).That is text, not a Windows or Linux build.
Rebase onto
5e072fc8and6c9edffe, and notes for reviewersvmaFreeMemoryboundary and this PR'svmaDestroyAllocatorare both kept, inRX_REC_BOUNDARIESandvk_work.h; itsvk_memory.cppincludes and this PR's are both kept. The renderer fixturegains an empty
sparse_wantedstand-in, which #250'sretire_poolnow erases from.imports_give_backends; the cut follows the new words. Samecode cut. Both in commit
2764a8a6(was91f9a58d).tests/test_darwin_audio_service.pydeclares a skip for Windows and Linux. Without the run-all PR (#454), the runnerlabels macOS as
linuxand skips it inrun_all.py; run directly, or with #454, it runs and passes (above).the series.
6c9edffe: the carried heads are the rebased PRs' heads: #480'safa7490c(with #478'scd383fcband#436's
861d9aba) and #438's0f2262a3. The merge4975b01a(wasb5da98ba) was made again, with the same includeblock.
7d168a5e(was107fda29) met #438's end commit intests/test_vk_darwin_admission.py: the offsets arecombined (Apple callbacks at 360/368/376/384, size 392). The end commit
0f54963dmakes the import pin/unpincallbacks macOS host API 24 in
vk_api.h, since #438 is now 23 on Apple (#51 took 22 everywhere); the admissioncontract and
darwin_renderer_startup_test.cpppin 24/392, and the renderer test's PASS line saysApple24. If #68lands first and takes 23, #438 becomes 24 and this PR 25.
7bf15899, the macOS compile fix: main's #324 (a4c77174) declaresextern std::deque<…> evicted_pools;abovememory_guest_imported, and this PR defines it on Apple as a process-owned reference (auto& evicted_pools = *new …), sovk_memory.cppdid not compile for macOS (redefinition of 'evicted_pools' with a different type; the sametwo declarations are already at
c1f9f884, on5e072fc8). The game-free fixtures cut functions out of the file andnever compile it whole. The
externis now#if !defined(__APPLE__): on macOSmemory_guest_importedanswersthrough
darwin_guest_imported, after the definition.unifdeffor Windows and for Linux gives the same text as at0f54963d. The same change is444e05c1in the renderer-core PR, which merges this one.Tests
tests/test_darwin_renderer.py(new) withdarwin_import_lifetime_test.cppanddarwin_renderer_startup_test.cpp:cuts the real import, view, pin, retirement and eviction code and the real
replay_start, its rollback,vma_initandvma_startup_rollbackfrom the source and runs them against recording stand-ins for Vulkan and the host pins. Itchecks that every pin has one unpin after the buffer's destruction, that retirement waits for completion, that each
failure point unwinds exactly what was made, the host callback table's positions, and
MessageBoxAon bothplatforms.
--legacy-sourcerunsmain's import and retirement topology through the same consumer.tests/test_darwin_audio_service.py(new): links the real DarwinwaveOutshim strictly (-undefined error) andchecks it reaches only the executable's audio service, never a renderer-local lookalike, with every
MMRESULTpath.test_vk_darwin_admission.py(Apple API 25, the two callbacks at 384 and 392, size 400),test_parallel_init_order.py(the init pools are made last and destroyed by the rollback),test_sdl_audio_library.py,test_import_buffer_binding.pyandtest_texel_buffer_ownership.py(they keep testing the Windows/Linux factories ona Mac; the Apple owner has its own test), and
test_direct_buffer_writes.py,test_parallel_rb_refresh.pyandnx2/test_import_windows.py(they cutPool's andimport_pool's Windows/Linux layout, as before), andtest_mirror_gpu_only.py(its cut ofpool_lookupnow ends at the movedimported.emplace(p.lo, std::move(p))).Measurements
Verdict: fixed game-free on both Mac routes, with every platform's startup rollback checked at each failure point:
at this head the two new tests pass on arm64 and x86_64 under Rosetta (
PASS: 133262 startup/ABI/resource checks),and the full suite after the rebase has no check that passes on
mainfailing. Before the rebase both tests failed onmainand onmainwith the carried pull requests, and passed here, two interleaved runs each; five existing checksthat pass on
mainand that this PR's first commit broke are shown failing before their repair and passing at thehead. No timing: the rollback runs only after a failed startup.
What is measured: two new game-free tests compile the real import, retirement, startup and audio code against
recording stand-ins, on arm64 and on x86_64 under Rosetta; and the full game-free suite on an Apple M4 Max (macOS
26.7.1), compared by test name with
main. Not measured: a game run on this branch; a real Vulkan device; Windows andLinux runs; macOS 14.4; an Intel Mac.
Commits: main
6c9edffe5d(2026-11-14); this PRmac/renderer-correctness@7bf1589934(its parent0f54963ddiffers only by the 2-line macOS compile guard).Before the rebase: main
5e072fc8c2(2026-11-14); depsb5da98ba(mainplus the pullrequests this one carries, the branch just before its own commits); this PR @
c1f9f88499.Hardware: Apple M4 Max, 16 cores, 64 GiB, macOS 26.7.1; Apple clang 21.0.0; Vulkan-Headers 1.4.345; Python 3.14.10. No GPU, no game.
Scene: none. Game-free.
Method: after the rebase, the two new tests and the admission test on this head (and at
0f54963d/45bd1541), wholefiles compiled for macOS, and the full suite at
45bd1541againstmain@6c9edffe(Testing). Before the rebase: thetwo new tests with
--arch arm64 --arch x86_64, on this PR, onmainand on deps (through--source), interleaved:PR, main, deps, PR, main, deps. Then the fixtures at each of this PR's commits (
git archive), throughtests/run_all.py. Every run throughtools/slot.py; x86_64 executables under Rosetta.How to reproduce:
python -I -B tests/test_darwin_renderer.py --cxx clang++ --headers <Vulkan-Headers/include> --arch arm64 --arch x86_64 [--source <tree>];python -I -B tests/test_darwin_audio_service.py --cc clang --arch arm64 --arch x86_64 [--source <tree>].6c9edffe7bf15899test_darwin_renderer.pyon the Mac, arm64 and x86_64main; not re-run againstmainafter the rebase (exit 1 at5e072fc8, below)0f54963d: 10 PASS lines, endingPASS: 133262 startup/ABI/resource checks)test_darwin_audio_service.pyon the Mac, arm64 and x86_64main; not re-run againstmainafter the rebase (exit 1 at5e072fc8, below)test_vk_darwin_admission.pyon the Mac, arm64, then x86_64 (#438's, updated here)mainApple version 24, size 392, typed callbacks at 360/368/376/384(non-Apple version 22, size 360, all 45 named offsets; that line at45bd1541)tests/run_all.py --jobs 6)45bd1541(0f54963dbut for a PASS label): 537 passed, 31 failed, 18 skipped in 175 s; no check that passes onmainfailsBefore the rebase (history at the commits the columns name):
5e072fc8b5da98bac1f9f884tests/test_darwin_renderer.py, arm64 + x86_64, runs 1 and 2 (and 3, at the head)struct DarwinImportBackingto cuttests/test_darwin_audio_service.py, arm64 + x86_64, runs 1 and 2 (and 3, at the head)WaveAudioOpenFnservice route to cuttests/test_mirror_gpu_only.py91f9a58d, before its last commit:substring not found)Runs 1 and 2 of this PR's side were at
91f9a58d, which differs from the head only intests/test_mirror_gpu_only.py;run 3 is at the head.
This PR's own commits (
git archiveof each, the same 11 checks throughtests/run_all.py):107fda29(the change)1f805dac(+ fixtures)05213268(+ boundary)91f9a58d(+ fixture follows main)test_direct_buffer_writes,test_parallel_rb_refreshunknown type name 'DarwinImportView'nx2/test_import_windowsdarwin_import_poolundeclaredtest_recorder_syncvk_vma.cpp calls ['vmaDestroyAllocator'], which vk_work.h does not drain beforetest_darwin_rendererThe checks in the first three rows pass on
main: this PR's first commit broke them, and its next two commits repairthem.
91f9a58dmakes the new fixture followmain. The full suite at91f9a58dthen found one more check the firstcommit broke,
tests/test_mirror_gpu_only.py(it passes onmain); the next commit,c1f9f884(nowf78ae3ed),repairs it. With the merge's
gpu_onlymark removed from a throwaway copy, that test fails (a pool that takes others in does not keep their gpu_only and gpu_written marks), so it still checks what it did.Screenshots: none; nothing on screen.
Flags and settings
No new switch.
MKWVK_API_VERSIONbecomes 25 on macOS only;main's 23 elsewhere. If #68 lands first and takes 24,#438's becomes 25 and this PR's 26. The other switches the diff touches come from the pull requests it carries.
RX_VK_RECORDER=defer/thread(existing)vmaDestroyAllocatoris now a drain-first boundaryRX_VK_LIBRARY(carried from #438)HOS_ZERO_ALL_PAGES,HOS_KEEP_FREED_PAGES,RX_NVN_WATCH_TEXELS_SRC,RX_NVN_WATCH_AFTER,RX_NVN_WATCH_ALL(carried from #480)Testing
Tested:
Re-tested after the rebase (head
0f54963don6c9edffe):python -I -B tests/test_darwin_renderer.py --cxx clang++ --headers <Vulkan-Headers/include> --arch arm64 --arch x86_64: 10 PASS lines, endingPASS: 133262 startup/ABI/resource checks.python -I -B tests/test_darwin_audio_service.py --cc clang --arch arm64 --arch x86_64:PASS.
python -I -B tests/test_vk_darwin_admission.py --cxx clang++ --headers <Vulkan-Headers/include> --arch arm64,and with
--arch x86_64:Apple version 24, size 392, typed callbacks at 360/368/376/384(non-Apple version 22, size360, all 45 named offsets). All exit 0.
unifdef -U__APPLE__of every changed C/C++ file, branch againstmain: thenon-Apple text differs only where it did before the rebase, with the same content. The audio and admission runs and
the
unifdefcomparison were at45bd1541, which differs from0f54963donly by the renderer test's PASS label (nowApple24 typed host callback wiring).Whole files on macOS, and re-tested at
7bf15899: every C/C++ file this PR changes (4 renderer, 13 host) wascompiled whole, as an object, for arm64 and x86_64, with
nx2/build.py's macOS commands (#453'srenderer_flags()andhost_flags();host/posixfiles in both the executable's and the renderer's job; the lift's runtime headers madewithout a game, as
tests/test_arm64_host.pymakes them). At0f54963d,gpu/vk/vk_memory.cppfailed on both(
redefinition of 'evicted_pools' with a different type, see Reviewer notes); at7bf15899it compiles.host/posix/win32_posix.cstops atmain's#include <linux/futex.h>, as onmainand in #480, #438, #478 and #436;with the darwin-host PR (#490, which carries #437's guard) merged in (one include-block conflict, #490's block kept),
all 17 files compile whole on both. The other 71 renderer and host files of the macOS build (86 of the 88 include a
header this PR changes) compile, or fail with exactly
main's errors (vk_draw.cpp,vk_menu.cpp,vk_progcache.cpp,vk_programs.cpp,vk_shader.cpp,control.c,hos_crash_record.c,hos_report_run.c, and on arm64hos_setjmp.cand
hos_thread.c, which other pull requests port). At7bf15899the two new tests (arm64 and x86_64), the admissiontest (each) and 12 named checks through
tests/run_all.pypass.The two new tests and the per-commit checks (Measurements).
Mutants (throwaway copies of this PR):
vk_work.hwithout thevmaDestroyAllocatorboundary makestests/test_recorder_sync.pyfail (vk_work.h's boundary macros and RX_REC_BOUNDARIES differ: … only in vk_recorder.h ['vmaDestroyAllocator']);vma_startup_rollbackthat leaves the allocator alive makestests/test_darwin_renderer.pyfail (FAIL: failed startup leaked a resource).The Windows and Linux text, as described under "Changes on Windows and Linux" (
unifdef).The whole suite after the rebase,
tests/run_all.py --jobs 6, on the same Mac, compared by test name with a fullmainrun at6c9edffe(run at45bd1541, which differs from0f54963donly by the renderer test's PASS label):tests/test_darwin_renderer.py(new) passes;tests/test_darwin_audio_service.py(new) is skipped, because without the run-all PR (#454) the runner labels macOS
linux(it passes run directly,above).
test_darwin_fault_context,test_darwin_guest_memory,test_vk_darwin_admissionandtest_vk_darwin_sdl(new) pass;test_collect_widegoes from FAIL to pass.mainfails here (test_gpu_hang_watch, which failed under load before the rebase,passes). The 31 failures all fail on
maintoo:test_appimage_package_retry,test_appimage_player_build,test_control,test_cpm_discovery,test_crash_dialog,test_crash_record,test_env_on,test_game_layout,test_lift_cleanup_error,test_linux_preflight,test_longshot_ctl,test_low_memory_notice,test_make_game_cache,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_oot_state_tap,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_pipeline_tour,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant,test_readback_late,test_report_run,test_split_giants,test_vk_pgo,test_vram_budget_row,test_walk_key.Before the rebase (the runner labels macOS
linux):main@5e072fc8had 525 passed, 19 failed, 17 skipped;this PR @
c1f9f884had 530 passed, 19 failed, 18 skipped in 194 s.tests/test_darwin_renderer.py(new) passed;tests/test_darwin_audio_service.py(new) was skipped, because without the run-all PR (#454) the runner labels macOS
linux(it passes run directly,above).
test_darwin_fault_context,test_darwin_guest_memory,test_vk_darwin_admissionandtest_vk_darwin_sdl(new) passed;test_collect_widewent from FAIL to pass.mainfailed there:tests/test_gpu_hang_watch.py, a timing check (exit 3, 3.5 sagainst its bound of 3.4 s) on a loaded machine (load average 10 to 16). The code it cuts,
gpu_hang_watch_start,was the same there as on
main, byte for byte. Of two reruns of it alone at that head, the first failed the sameway and the second passed.
maintoo:test_appimage_package_retry,test_control,test_framegen_resources,test_game_layout,test_low_memory_notice,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_module_ids,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant. Onmain@6c9edffe,test_framegen_resources,test_module_idsandtest_pad_layoutpass; its 32 failures are the other 15,test_collect_wideand 16 new since5e072fc8.History, not rerun on this branch: these commits were first made on the macOS integration branch (
044635ea,6cf83191,605ddf99) with the same checks; integration builds that carry them have since built and started the gameon macOS (see #420). That speaks for those builds, not for this branch alone.
After the rebase onto main
aa95d35f, at695f6859(7bf15899before the rebases ontoe090cc48andaa95d35f):Sweep3's checks of this head (the C/C++ it changes compiled, and its own tests run, on arm64 and x86_64 where they
apply) gave the same results as before, but the ABI numbers main's #729 moved: the admission test's line now reads
version 23, size 368, all 46 named offsets; Apple version 25, size 400, typed callbacks at 368/376/384/392and therenderer test's
Apple25 typed host callback wiring;python -I -B tests/run_all.pyon wave B's mergedb0e2e16,which carries this head: 569/32/18 (passed/failed/skipped) against main aa95d35f's 543/33/17 on this Mac, no new
failure (
reports/2026-11-15-rebase-sweep-3.md).Not tested:
fail), and the recorder modes (
RX_VK_RECORDER=defer/thread) through such a failure.owners and release calls; checked only by the fixtures and
unifdeftext).vk_api.hafterunifdef -U__APPLE__ismain's byte for byte (version 23, the sameMkwVkHost), so they pair as before; not run.tests/test_darwin_renderer.pyon Windows or Linux: it has noRUN_ALL_SKIP, sotests/run_all.pyruns it there(only its
--archis macOS-only); it ran only on the Mac.Must be tested:
695f6859) and mainaa95d35fthe same way,start each, play a minute in a busy outdoor area and quit, then run
python -I -B tests/run_all.pyon each, expectingthe same start, picture and frame rate as main, no new error lines in
longshot_log.txt, no crash at quit, no programtranslated again, the new
test_darwin_rendererto pass (test_darwin_audio_serviceskips), and no check that passeson main to fail (by hand: test request T-K3Q5).
Risks
What could break:
and the same release calls. The new recorder boundary costs a relaxed load and a branch with the recorder off. These
touch the renderer's import path, which every platform runs; nobody has run them in a game on Windows or Linux.
MessageBoxAnow drops its extra SDL reference;waveOutOpenfails cleanly where it would have crashed.VK_ERROR_MEMORY_MAP_FAILED) rather than importingunprotected pages.
becomes 25.
one.
0030e14a(imported_add(Pool&&)and thetest_import_reach/test_darwin_rendererfixtures) makes #431 and #491 build together on macOS.Turning it off: revert this PR's seven commits (
4975b01a..7bf15899).Compatibility: no shader cache rebuilds: on
6c9edffevk_api.his not hashed intoLONGSHOT_SHADER_STAMP(since#370;
nx2/translator_files.pynames what is), and none of the changed renderer files is either, so nothing isretranslated. The vk ABI:
MKWVK_API_VERSIONis 25 on macOS only (two more callbacks inMkwVkHost); on Windows andLinux it stays main's 23 with the same structure. Saves and
longshot_options.iniare unchanged. The builder compiles thehost's objects again once (
main'snx2/build.pykeys them ongpu/vk/vk_api.hand everyhost/*.h).Issues
See #420.
See #432 (the every-platform guest-memory follow-up; this PR's carried guest-16k code stays Apple-only).
Checklist
Everyone
type(scope): description) of the status line's type.main: onaa95d35f(#761, 2026-11-15), head695f6859, on #438681e3708, on #480657b7803, without a new conflict; with #438's ABI move for main's #729, the import pin callbacks are macOS host APIversion 25 (Apple table 400 bytes, callbacks at 368/376/384/392); this PR's own commits, old → new:
4975b01a→08304713,7d168a5e→db82a0f2,1e538827→6cee76eb,3bdfb929→18c05be5,2764a8a6→c099f5da,f78ae3ed→165bf7e6,0f54963d→fa691b55,7bf15899→695f6859; the commit names elsewhere in thisdescription are from before the rebase.
python -I -B tests/run_all.pypasses (any failure that also fails onmainis named). On macOS, after therebase: 537 passed, 18 skipped, and 31 failures that also fail on
main(531 passed, 32 failed, 17 skipped at6c9edffe), named under Testing. Run at45bd1541, which differs from0f54963donly by a test's PASS label. Afterthe rebase onto main
aa95d35f(head695f6859): wave B's mergedb0e2e16, 569/32/18 against main aa95d35f's543/33/17, no new failure.
third_party/untouched (a new folder with aPROVENANCE.mdinstead).When it applies
Assisted-by:/Co-Authored-By:trailers.new switch here; the carried ones are documented by #438 (
RX_VK_LIBRARYin the switch table) and #480 (the macOSguest-memory section), all unset.
MKWVK_API_VERSIONbumped (gpu/vk/vk_api.h'sMkwVkHostorMkwVkRendererchanged). Only the Appleversion: 25 on Apple, where
MkwVkHostchanged;main's 23 elsewhere, where it did not.mainand passing here.tests/that reads changed source updated in this PR, not weakened.044635ea added vma_startup_rollback (vk_vma.cpp, run after a failed replay_start), which calls vmaDestroyAllocator. vk_vma.cpp includes vk_work.h last so that RX_VK_RECORDER=defer/thread drains before its destructions, and every such boundary call in a vk_work.h file must be in RX_REC_BOUNDARIES with a drain-first macro. This one was not, and test_recorder_sync caught it ("vk_vma.cpp calls ['vmaDestroyAllocator'], which vk_work.h does not drain before"): it passes at 58c5c5aa (044635ea's parent) and fails at 044635ea. Add it to the list and to vk_work.h, next to vmaDestroyBuffer and vmaDestroyImage. With the recorder off (the default) boundary() is a relaxed load and a branch. Rollback runs before any image or buffer exists, so nothing should be held there today, but the allocator is no longer destroyed while a recorder could still hold packets. The test is unchanged (sources: 42 boundaries drained, was 41). Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>c1f9f884990f54963d80WIP: fix(macos): pin Vulkan host imports until their last owner, and unwind failed startupsto fix(macos): pin Vulkan host imports until their last owner, and unwind failed startups7bf1589934f66d0a87b8f66d0a87b8695f6859dcFolded into #846 (feat(macos): Vulkan on Metal: pinned host imports, the unified-memory budget, Metal's cache folder and KosmicKrisp)
Pull request closed