mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
With the replay on the submitting thread, a present that waits for event-ordered work (vk_backend.c PH) goes out after
the submission that replays the last of that work, at the next present, or after PH_MAX submissions. The game submits
from several threads while one of them presents, so that submission may be another thread's. PH had no lock: two
submitting threads could both pass ph_flush's check of PH.on and put one frame out twice; a thread reading PH's fields
while the presenting thread armed the next frame could put out a job mixed from two frames; and a held frame put out on
a submitting thread could reach the present thread after the presenting thread's next frame, a picture back in time.
runtime/vk_present_held.h now holds PH's job, its arming, putting out and counts behind PH.lock. Submitting threads
only try the lock (ph_flush_try) and go on when another thread holds it: that thread is putting a present out or arming
one, and the next submission, the next present or PH_MAX checks again, the bound there was before. The presenting
thread takes it for every synchronous present while held presents are on (ph_flush_next, ph_present), so the
synchronous present_image calls run one at a time and in the game's order. That is the one new wait, and only the
presenting thread makes it: only while another thread puts a held present out, for at most that present_image. It is
counted (lock_waits, lock_wait_ns, lock_wait_max) and timed as PERF_DRAIN. No path holds the lock across held_resume,
which is a replay. Neither 0.10.0 fresh log has a held present at all ("went out" and "presents that waited" lines),
so the wait is not expected in normal play. present_held = 0 turns held presents off, and with them every use of the
lock.
Not reproduced before the change. Checks that passed: the new tests/present_held_check (one presenting and three
submitting threads over 200,000 frames: every frame out once, in order and never two at once; the unlocked 0.10.1
shape through the same harness gave about 205,000 presents for 200,000 frames, 6,000 to 8,000 out of order or twice and
8,000 to 12,000 at the same time; step-by-step cases; a submitting thread gives up on a held lock at once and the
presenting thread's wait is counted), pass_cc_check on every runtime file, no new -Wall -Wextra warnings in
vk_backend.c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.5 KiB
Python
59 lines
2.5 KiB
Python
"""Build and run tests/present_held_check.c: runtime/vk_present_held.h, the present that waits for held work
|
|
(vk_backend.c "a present that waits for held work"). One presenting thread and three submitting threads over 200,000
|
|
frames: every frame reaches the present exactly once and in order, never two at once, and each reason a held present
|
|
goes out occurs. Step by step: a held present stays while work is held, goes out after the work, at the next present
|
|
or before a later present, and only once. With the lock held elsewhere a submitting thread gives up at once, and the
|
|
presenting thread waits and counts its wait. Then the unlocked 0.10.1 shape runs through the same harness and only
|
|
prints what it saw, to show that the harness catches the race.
|
|
|
|
No GPU, no game files, any Python.
|
|
|
|
Usage: present_held_check.py
|
|
"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
|
|
|
|
|
def compiler():
|
|
"""$CC, else MSYS2 UCRT64's clang, else clang on PATH."""
|
|
if os.environ.get('CC'):
|
|
return os.environ['CC']
|
|
if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists():
|
|
return str(MSYS_BIN / 'clang.exe')
|
|
return shutil.which('clang') or 'clang'
|
|
|
|
|
|
def main():
|
|
work = ROOT / 'build' / 'test' / 'present_held'
|
|
work.mkdir(parents=True, exist_ok=True)
|
|
win = os.name == 'nt'
|
|
exe = work / ('present_held_check.exe' if win else 'present_held_check')
|
|
env = dict(os.environ)
|
|
if win and MSYS_BIN.exists():
|
|
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
|
rt = ROOT / 'runtime'
|
|
plat = [rt / 'platform' / ('win32.c' if win else 'linux.c'), rt / 'platform' / 'common.c']
|
|
libs = ['-lbcrypt', '-lws2_32', '-lpsapi', '-lwinmm', '-ldbghelp', '-lshell32', '-lole32', '-luser32', '-ladvapi32'] if win \
|
|
else ['-lpthread']
|
|
subprocess.check_call([compiler(), '-std=gnu11', '-O1', '-g', '-Wall', '-Wno-unused-function', '-fno-strict-aliasing',
|
|
'-march=x86-64-v3', '-I', str(rt), str(ROOT / 'tests' / 'present_held_check.c'),
|
|
*map(str, plat), '-o', str(exe), '-lm', *libs], env=env)
|
|
try:
|
|
rc = subprocess.run([str(exe)], env=env, timeout=300).returncode
|
|
if rc == 0:
|
|
subprocess.run([str(exe), 'old'], env=env, timeout=300)
|
|
return rc
|
|
except subprocess.TimeoutExpired:
|
|
print('present_held_check: FAIL: the check did not end within 300 s')
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|