WIP: fix(vk): the render thread may leave its core when another program takes it: no 30-40 ms stalls in Castle Town's market #838
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
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!838
Loading…
Reference in a new issue
No description provided.
Delete branch "nyannoying/Longshot:fix/replay-thread-free-to-move"
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: Windows 11, Arc B580, i5-13600K · Needs: Windows with an AMD or NVIDIA GPU, and a CPU of one core kind (a Ryzen)
Why
With Threaded rendering on (Auto turns it on with 12 or more logical processors, so most desktop PCs), the game's render thread was tied to one CPU core: the core the game keeps free of its own threads. Other programs' threads are not kept off it. On an i5-13600K with an Arc B580, Windows had parked that core's second logical processor, and a priority-15 thread of Intel's PresentMon service (which the Intel Graphics Software overlay runs) held the one left for 30-40 ms at a time. The render thread waited, ready to run, and the frame came 40 ms or more late: regular hitches in Castle Town's market, up to one a second.
See #565 and #597 (periodic stutter and rough frame times on other PCs): whether a render thread held off its one core causes those is not known.
How to reproduce
Seen on: Windows 11 Pro 10.0.29704 (Insider), Intel Arc B580 12 GB, driver 32.0.101.9154, Core i5-13600K, 64 GB; Intel Graphics Software installed (its PresentMon service running)
Settings and switches: Threaded rendering Auto (On here: 20 logical processors); 1280x720 window, render 75% with Lanczos, frame generation off;
HOS_FPS_MAX=240(the frame rate not capped) for the numbers belowSteps: load a save, go to Castle Town's market by day as child Link (the control bridge's teleport
Market_Child), stand at the entrance for a minute and watch the frame times (RX_VK_FRAME_LOG=<file>writes one line a frame). The log saysvk: the replay thread runs on logical processors 0xc00, kept from the game's threads. Intel's service takes the core only some of the time (every run of one session, none of another), so the program below does the same on demand: it holds the core's two logical processors (10 and 11 on an i5-13600K; the log's mask) at priority 15 for 35 ms of every second. Runcore_hog 10,11 35 1000 200just before loading the saveExpected: steady frame times, about 17 ms here
Actual: a frame of 50-58 ms every second with the program; with Intel's service, 14-37 frames over 40 ms in 40 s
core_hog.c (build: clang -O2 -o core_hog.exe core_hog.c -lwinmm)
What changed
On Windows the render thread keeps its core as its ideal processor, so it runs there whenever it can, and may now run on the other cores of the fastest kind when another program's thread holds that core. The game's own threads are still kept off the core, as before.
RX_VK_RT_STRICT_PIN=1keeps the render thread to the core alone, as before.replay_thread_main()ingpu/vk/vk_replay.cppsets the affinity to the reserved core plusfastest_kind_cpus()within the process's affinity, and the ideal processor to the reserved core's last logical processor. The log addsvk: the replay thread may run on logical processors 0xfff(the i5-13600K's performance cores)fastest_kind_cpus()(new, Windows only, same file): the logical processors of the cores with the highestEfficiencyClassin the thread's processor group, fromGetLogicalProcessorInformationEx. A hybrid CPU's performance cores; every core on a CPU of one kindRX_VK_RT_STRICT_PINindocs/development.mdRX_VK_REPLAY_PIN)Measurements
Verdict: it helps when another program takes the core and costs a little when none does. With a program holding the core 35 ms of every second, the frames over 40 ms go from 40 a run to 0, the average from 55.2 to 57.4 fps and the 1% low from 18.8 to 41.9 fps. With nothing taking it, there were no hitches either way, and the average is 0.5 fps lower (57.5 to 57.0; the 1% low and p99 are unchanged). With a program that wakes on the core every millisecond, it is worse (one run each, see Risks).
Hardware: Windows 11 Pro 10.0.29704, Intel Arc B580 12 GB (driver 32.0.101.9154), Core i5-13600K, 64 GB
Scene: Castle Town's market as child by day, standing at the entrance; 1280x720 window, render 75% with Lanczos, frame generation off, V-Sync Auto, the frame rate not capped (
HOS_FPS_MAX=240)Method:
RX_VK_FRAME_LOGframe times, 40 s measured 40 s after the teleport, each run from a fresh start; 3 runs each way, interleaved (before, after, after, before, before, after); the median of the runs. Frames over 40 ms counted per runHow to reproduce: the steps above;
core_hog 10,11 35 1000 200started with the game for the first rowsBaseline: the renderer this PC's game folder loads, built from 690d8446 (main
2b1d38c6with #443, #446, #459 and #500 merged) with #761's change applied, against the same with this PR's diff applied (built from59389332, the same diff asf89a017eafter the rebase onto main0557080f). Main's own renderer (aa95d35f,MKWVK_API_VERSION23) needs a newer game folder than this one (22): built from main it fell back to WebGPU here, and a new game folder means preparing every shader again on this PC. The diff applies to both unchangedf89a017e, on the same baseEarlier, with Intel's service taking the core (a development build: 690d8446 with experiments not in this PR; the same placement set by switches), 3 runs each: frames over 40 ms 15, 37 and 14 before, 0, 0 and 0 after; average 56.5 and 57.9 fps (medians).
Flags and settings
One new A/B switch, off.
RX_VK_RT_STRICT_PIN=1: the hitches above come back where another program's thread takes the core; 0.5 fps more where none does=1helps only where a program wakes on the core very often (the last row)Testing
Tested:
python -I -B tests/run_all.pywith the toolchain's llvm-mingw onPATH: 504 passed, 18 failed, 72 skipped, and the same 18 fail on main0557080f(tools this PC lacks, a CRLF pipe check, andtest_env_on.pynamingRX_VK_PRESENT_AFTER_SIGNAL, all on main too). In the game: Windows 11, Arc B580, driver 32.0.101.9154, i5-13600K, the market as above, 14 runs (Measurements); no device lost; the log lines above.Not tested: AMD and NVIDIA GPUs; a CPU of one core kind (every core then counts as fastest); Linux (no change); Threaded rendering Off (no change).
Must be tested:
longshot_vk.so) from this branch and run the market by day for 60 s with Threaded rendering On, expecting it to build and the log linevk: the replay thread runs on logical processors ..., kept from the game's threadswith nomay runline (Test Farm: @Beanstalk linux, build and the market)RX_VK_FRAME_LOG, this PR againstRX_VK_RT_STRICT_PIN=1, expecting the log linevk: the replay thread may run on logical processorswith every core, and no lower average fps and no more frames over 40 ms than with the switch (Test Farm: @Beanstalk windows, amd or nvidia, a Ryzen, the market against RX_VK_RT_STRICT_PIN=1)Risks
What could break: when another program's thread runs on the core, the render thread now goes to another performance core, where the game's threads run, instead of waiting. A program that holds the core for a while (Intel's service,
core_hogabove) costs nothing then. One that wakes on the core very often at a high priority moves the render thread off its core again and again, and there it ran slower than it waited: 44.6 against 54.3 fps with a priority-15 thread waking every millisecond (one run each; the after run also had one 500 ms frame). With nothing on the core it costs 0.5 fps here (57.5 to 57.0). A CPU of one kind: the render thread may go to any core.Turning it off:
RX_VK_RT_STRICT_PIN=1Compatibility: Nothing changes.
Issues
See #565, #597
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.#ifdef _WIN32the change is one variable and two log lines around theSetThreadAffinityMaskcall main already makes there. Asked of the Test Farm (Must be tested).docs/tested/for each new measured run (format indocs/tested-configurations.md).🤖 Generated with Claude Code
Measured: 3 interleaved runs each way in the market on the base this PC's game folder loads (690d8446 + #761; main's renderer needs a newer game folder). With a program holding the core 35 ms a second: 40 frames over 40 ms a run before, 0 after; 55.2 to 57.4 fps. Nothing started: no hitches either way, 57.5 to 57.0 fps. A program waking on the core every 1 ms at priority 15: worse after (44.6 against 54.3 fps, one run each), now in Risks. 14 docs/tested files added; risk raised to medium.
ef8beb6701e5f1e40c95Rebased onto main
0557080f(no conflicts; main's three new commits touch none of these files). python -I -B tests/run_all.py: 504 passed, 18 failed, 72 skipped, the same 18 failing on main0557080f.Measurements' columns now name their commits (Before: the base @ 690d8446, After: this PR @
f89a017e). Added the Linux build box, not ticked: no Linux toolchain on this PC, so a Linux build and run is asked of the Test Farm; the PR stays WIP until then.@Beanstalk two runs for this PR, please:
vk: the replay thread runs on logical processors ..., kept from the game's threadswith nomay runline after it (Linux keeps the old placement).RX_VK_FRAME_LOG=<file>, once with this PR as it is and once withRX_VK_RT_STRICT_PIN=1. Expected: the log linevk: the replay thread may run on logical processorslisting every core; with this PR, an average fps no lower and no more frames over 40 ms than with the switch.@nyannoying only members of
BigBeanscan start the farm. One of them can approve this request by reacting 👍 to it.Beanstalk test farm
Commit
8015c9a· tier reviewed · approved by futondestroyer · dashboardWhat will be tested · AI plan (
gpt-6.1-terra)The plan compares main and the PR in child Link’s Castle Town Market for 60 seconds after warm-up, with threaded rendering enabled. It measures mean FPS, median and p99 frame time, hitches over 50 ms, crashes and placement logs; device loss, a missing reserved-core log, a Linux “may run” log or a substantial performance regression fails. The Linux target is currently offline and will wait for a tester. This farm schema skips Windows gameplay, so the requested Windows runs and direct default-versus-strict comparison cannot be represented; it also cannot count frames over 40 ms or guarantee daytime. Ryzen coverage remains outstanding, and a quiet run would not prove the contention fix. No tester online now matches linux: that target waits for one.
RX_VK_ASYNC=1,RX_VK_RECORDER=thread), then teleport to Market, measure market for 60 s (after 40 s warm-up); 2 runs on each side.Started.
/beanstalk plan Preserve the author's Linux test and add Windows gameplay for head
e5f1e40c95. Windows gameplay is now opt-in in Beanstalk main (PR 7); this volunteer is running dev-416730c with isolated_gameplay enabled.Linux: build and measure 60 s in child daytime Castle Town Market with threaded rendering On, checking the reserved-core placement log, no Windows-only "may run" line and no device loss.
Windows: target Windows NVIDIA, without pinning a GPU model or requiring Ryzen, so this RTX 4070 / i7-13700F PC can contribute. Use isolated disposable new-game saves and the child daytime Market. Threaded rendering On, frame generation off, uncapped, otherwise identical settings. Measure 60 s after warm-up, at least two interleaved runs per arm. Compare main with the PR and, using a PR-only switch matrix if supported, PR default against RX_VK_RT_STRICT_PIN=1. Report mean fps, median/p99 frame time, hitches, crashes/device loss and placement logs. State explicitly if frames over 40 ms, direct switch comparison, or other requested coverage cannot be represented. Do not substitute a title-only test for Windows gameplay.
This Intel hybrid CPU supplies supplemental Windows/NVIDIA coverage; it does not complete the author's separate Ryzen/single-core-kind request. Do not run the comment's external core_hog program. A quiet run does not prove the contention fix. Keep saves, dumps and generated game builds local; only normal permitted farm reports may be uploaded.
e5f1e40c958015c9a8c7Rebased onto
main@7bacf846: no conflicts. No tests were run here; the test boxes stay yours.@Beanstalk Preserve the author's Linux test and add Windows gameplay for head
8015c9a8c7. Windows gameplay is now opt-in in Beanstalk main (PR 7); this volunteer is running dev-416730c with isolated_gameplay enabled.Linux: build and measure 60 s in child daytime Castle Town Market with threaded rendering On, checking the reserved-core placement log, no Windows-only "may run" line and no device loss.
Windows: target Windows NVIDIA, without pinning a GPU model or requiring Ryzen, so this RTX 4070 / i7-13700F PC can contribute. Use isolated disposable new-game saves and the child daytime Market. Threaded rendering On, frame generation off, uncapped, otherwise identical settings. Measure 60 s after warm-up, at least two interleaved runs per arm. Compare main with the PR and, using a PR-only switch matrix if supported, PR default against RX_VK_RT_STRICT_PIN=1. Report mean fps, median/p99 frame time, hitches, crashes/device loss and placement logs. State explicitly if frames over 40 ms, direct switch comparison, or other requested coverage cannot be represented. Do not substitute a title-only test for Windows gameplay.
This Intel hybrid CPU supplies supplemental Windows/NVIDIA coverage; it does not complete the author's separate Ryzen/single-core-kind request. Do not run the comment's external core_hog program. A quiet run does not prove the contention fix. Keep saves, dumps and generated game builds local; only normal permitted farm reports may be uploaded.
Tests on HumanBean's rebase (head
8015c9a8, on main7bacf846):python -I -B tests/run_all.pywith the toolchain's llvm-mingw onPATHgives 515 passed, 19 failed, 75 skipped. The 19 are the ones that fail on main7bacf846alone on this PC: test_appimage_player_build, test_auto_handheld, test_cpp_preflight, test_crash_dialog, test_env_on, test_fpx_sticky, test_lift_cleanup_error, test_linux_preflight, test_make_game_cache, test_make_game_prepare, test_missing_nup, test_oot_state_tap, test_options_budget_once, test_readback_late, test_spawn_emit, test_tested_configurations, test_vk_occlusion_queries, test_vk_pgo, test_walk_key.test_env_on.pynames only main'sRX_VK_PRESENT_AFTER_SIGNAL, not this PR'sRX_VK_RT_STRICT_PIN.The rebase left the diff unchanged (
git range-diff: both commits identical tof89a017eande5f1e40c), so the Measurements still stand. Nothing was pushed, so the farm's approval at8015c9astill holds. The Linux box waits for the farm's Linux report.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.