fix(vk): on weak Intel laptops the shaders are prepared and the scene's passes draw within seconds, the pipeline cache is saved, and a Debug button sets the lightest rows #404
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!404
Loading…
Reference in a new issue
No description provided.
Delete branch "Yeetbot90/Longshot:fix/igpu-weak-laptop"
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?
Why
On a weak Intel laptop (i3-1115G4, UHD Graphics, 2 cores / 4 threads, 16 GB, Windows) the game is close to unplayable. The build's shader preparation is stopped by its watchdog, so every start builds every shader in play. The scene's light and exposure passes are left out for minutes (the Deku Tree's dialogue drawn blown out). The GPU device was lost in the Deku Tree's last cutscene, and on
nightly-132the game crashed inside Intel's driver entering Hyrule Castle, both times with video memory over what the driver allows. This PR lets the preparation finish, draws those passes within seconds, keeps integrated GPUs under their memory allowance with room to spare (and gives textures up as soon as the driver refuses memory), and adds a Debug button that sets the lightest rows at once. He also reports a blown-out Castle Town square and Hyrule Castle, and black streaks on Link's hair: what his logs say about those, and the switches that tell their causes apart on his laptop, are under What changed and Testing.What changed
The causes, from Unity's logs: two sets of five on
nightly-130-0caf0618(main @0caf0618) and five onnightly-132-5e072fc8(main @5e072fc8).serial_builds_on). With the optimizer on it took 0.3 s to 389.5 s for each of 37 dispatches onnightly-130, about 22 minutes on that one thread. Onnightly-132it took 351 s in Kokiri Forest alone, 169 s for the longest. The preparation's 300 s watchdog stops it and leaveslongshot_prepare_skipped, so the preparation was skipped in 15 of 15 logs.kept_only), the rule that frees textures by the driver's count waited even past the allowance. The device was lost at 6.6 of 6.5 GB.nightly-132logs). At 6.7 GB of 7.0 GB allowed, entering Hyrule Castle added 560 MB of imports (1648 to 2048 MB live, plus 160 MB retired but still imported). The count reached 7.5 of 7.0 GB and six graphics pipelines were not made (-2,VK_ERROR_OUT_OF_DEVICE_MEMORY). The nextvkCmdDispatchIndirectthen faulted insideigvk64.dll, reading0x107e, a null pointer plus an offset, as Intel's driver does when its own allocation failed. That dispatch is the mesh fallback's:longshot_vk.dll+0x21a1f0matches the return address offallback_record'svkCmdDispatchIndirect(cmd, f->records, 20)in a build of main @5e072fc8with the nightly's own toolchain. Its pipelines and buffers are made and published correctly, so the fault is the driver's, out of memory.The changes:
jobs_push_serial(fn, on_failure, first)ingpu/vk/vk_jobs.cpp/.hadds a front lane. The game's dispatches go there, ahead of the mesh fallback's model builds, in order among themselves. After each build the thread callspipeline_cache_between_builds().pipeline_cache_saveingpu/vk/vk_draw.cpp. A save put off on Intel's serial builds (thePcacheFetchlock against igvk64's crash invkGetPipelineCacheData) is now owed (pcache_save_owed).pipeline_cache_between_buildsruns it on the serial thread between two builds, the one moment no compute build is under way. The function's signature is unchanged (checks cut it out by its text).gpu/vk/vk_draw.cpp,gpu/vk/vk_shader.h. On Intel's serial path a dispatch's first build is without the optimizer (VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT, as the mesh fallback's builds already are).compute_optimizethen builds the optimized one on the back lane, and the dispatch binds it once made (Program::compute_optimized). The first pipeline stays for the session, because a recording may hold it.precompile_runandprecompile_warmingpu/vk/vk_draw.cpp, throughprecompile_compute_quick. On Intel's serial builds they take the optimized pipeline when the caches have it, and otherwise build the unoptimized one, so the preparation finishes within its 300 s. A program made this way owes its optimized build (Program::optimize_owed), which its first dispatch queues on the back lane. Programs that are never dispatched are never optimized.blocks_at_startingpu/vk/vram_compact.h, called fromgpu/vk/vk_vma.cpp. An integrated GPU now makes no texture blocks at the start, because its heap is the PC's RAM. Blocks are made as needed and given back, and the driver's-count rule is not held back bykept_only. Dedicated cards keep the 15% of the heap made at the start (the ~170 msvkAllocateMemorymeasured on AMD's Windows driver).RX_VK_IMAGE_PREALLOCstill sets a number.count_line_pctingpu/vk/vram_compact.h, used bytexture_frameingpu/vk/vk_texture.cpp. The textures give up the excess once the driver's count passes 90% of the allowance on an integrated GPU, not 97%. A dedicated card's driver pages what is past the allowance out to system memory; an integrated GPU's heap is already system memory, so its driver's own allocations fail instead (both crashes above). On Unity's laptop 97% left 215 MB of room, less than the 560 MB Hyrule Castle added at once; 90% leaves 717 MB. Dedicated cards keep 97%. The VRAM log line's "room in the blocks: not squeezed" note goes by the same line.room_in_blocksingpu/vk/vram_compact.h, used bytexture_frame. The line above alone would not have squeezed in his Hyrule Castle run: the last count before the load was 6709 of 7172 MB (93.5%), but 1121 MB of it was free inside VMA's blocks, more than an eighth of the allowance, and that room made the squeeze wait. On a dedicated card new images take that space. On an integrated GPU what grows near the line is the driver's own memory (pipelines, command memory) and the 64 MB staging arenas the load made, which no block's free space takes. The room no longer spares the squeeze on an integrated GPU; the compaction (on by default) gives the freed space back, so the ratchet the rule was written against (#2156, #2711) does not come back. Dedicated cards unchanged.note_out_of_memoryingpu/vk/vk_draw.cppcounts every pipeline the driver refuses withVK_ERROR_OUT_OF_DEVICE_MEMORYor_HOST_MEMORY(bothguarded_graphics_heldandguarded_compute_held, which every build goes through). At its next look (every 10 frames, about 3 s at his 3.5 fps)texture_framegives up the count's excess over its line, 256 MB at least (vram_compact::short_excess), on any GPU, and logsvk: the driver refused N pipeline(s) for want of memory. His six refusals came before the fault inigvk64.dll; the textures did not react to them.vk: integrated GPU texture budgetprinted 45% of the allowance (the dedicated cards' share for textures) as "driver allows 3331 MB" on a laptop allowed 7402 MB. It now prints the allowance and the share apart. The comment that called the share 70% now says 45% up to 8.5 GB, else 70%.low_vram_texture_mipsingpu/vk/vk_texture.cpp. It also counts an integrated GPU on a PC with 16 GB of RAM or less (system_ram_bytes()) as small, and it goes by Minimum's budgets (opts.budget == 1), not only the preset, because the button below leaves the preset on Custom with those budgets. On Minimum, decoded ASTC textures then keep at most 1024 texels: Unity's 1365x1365x90 arrays go from 53 MB to 13 MB. Render targets and depth images are untouched.gpu/vk/auto_settings.h. Auto on an integrated GPU with 4 threads or fewer sets Render resolution to 33% (RES_ULTRA_PERFORMANCE) instead of Minimum's 50%.gpu/vk/vk_menu.cpp,gpu/vk/vk_menu.h, Debug tab:intel_quick_computeasksintel_compiler_row, since Mesa's ANV on Linux ignores the flag). Off: one optimized build, as before.tests/test_blocks_at_start.pyand.cpp(the integrated GPU's blocks and line; the pool fixture that #50 added on main compiles the realblocks_at_start, which now asks the device type, so its stub device has one and an integrated case was added),tests/test_texture_squeeze.py(the squeeze goes bycount_line_pctandroom_in_blocks, and the refusals are counted and acted on),tests/test_vram_compact.cpp(room_in_blocksandshort_excesswith his numbers), and the checks named under Measurements.README.md(the smaller mips on integrated GPUs;longshot_prepare_skippedunder If something goes wrong),docs/development.md(RX_VK_COMPUTE_QUICK_FIRST).serial_builds_onexcept the memory blocks, the 90% line, the room rule, the mips rule, the Auto rule and the button, which all need an integrated GPU, and the refusals rule, which runs only once a driver has refused memory. Rows a player set on Custom stay as they are; only Auto and the button change rows.What the newest logs show that this PR does not change, for whoever picks it up next:
GPU-bound waitwas 0.0 to 0.1 ms in every sample: 97 in Kokiri Forest, 29 in Hyrule Field, 42 in the Market and Hyrule Castle. Those runs had medians of 5.4, 5.4 and 8.5 fps (110 to 185 ms a frame). The replay thread's own time was 4 ms a frame in the Market and 25 to 31 ms in Kokiri Forest and Hyrule Field. The rest goes to the game's threads, which share 4 hardware threads with the replay and the shader builds (vcores: waits of 0.2 to 2 ms on average, up to 1.9 s). A lower resolution or less GPU work will not raise this laptop's frame rate much; fewer builds in play (the preparation finishing) is what frees the CPU.slow uploadline), 5.3 s in all across four logs, and a 24 KB texture took 17 ms. No local fix.RX_VK_SYNC_PIPELINES=1builds each pipeline at its draw instead of leaving it out, which tells the two apart on his laptop (Testing).OpIsNanas they are, and both went wrong on other drivers: hair drew white on RX 580s until f16 was lowered to f32 there, and AMD's older Windows compiler failedOpIsNanwith black and white blocks (RX_VK_NAN_BITS; the NaN guard on colour outputs is such a test too). One switch each, in Testing; whichever clears the streaks becomes a default for Intel in a follow-up.tests/test_cpm_discovery.pyfails on main @7ff5256e(#524) with'types.SimpleNamespace' object has no attribute 'find', and on this branch since it merged main.Flags and settings
intel_quick_compute_builds)e072164e; every other GPURX_VK_COMPUTE_QUICK_FIRSTRX_VK_LOW_VRAM_MIPS=0turns it off, as before)RX_VK_IMAGE_PREALLOC=<n>still sets a number)RX_VK_SQUEEZE_ALWAYS=1still squeezes whatever room the blocks hold)RX_VK_SYNC_PIPELINES=1(existing, a test)RX_VK_NAN_BITS=1,RX_VK_NO_F16=1,RX_VK_ASTC_BC_ALL=0(existing, tests)isnanas a test of bits; f16 math lowered to f32; the characters' ASTC kept as RGBA8, not BC3Measurements
main:
0caf0618(2026-11-14)this PR: fix/igpu-weak-laptop @
688441b1Since measured: the in-game column for main is Unity's logs; the Hyrule Castle rows are from his
nightly-132-5e072fc8logs (main @5e072fc8), which hold every change main made since0caf0618that touches these paths. This PR's column is the game-free checks, run at688441b1(main @7ff5256emerged in).Hardware: main's in-game column is Unity's laptop (i3-1115G4, Intel UHD Graphics, driver 0x195bb0, 16 GB, Windows; 7402 MB allowed at the start); the checks ran on this PC (Windows 11, llvm-mingw clang 20261104, Python 3)
Scene: his play to the Deku Tree's last cutscene (two sets of five
nightly-130logs), and from Kokiri Forest through Hyrule Field and the Market into Hyrule Castle (fivenightly-132logs); 640x480 output, Render resolution 33%, FSR 3.1, frame generation Auto, Custom preset; the checks need no gameMethod: lines counted by script from his 15 logs; the crash address matched against a build of main @
5e072fc8with the nightly's own toolchain (llvm-symbolizer,llvm-objdump); the PR's changed checks run against main's source or the branch before each fix, and against this PR's;tests/run_all.pyon both sidesHow to reproduce:
python -I -B tests/run_all.pyon each side; for main's source, copy this PR'stests/changes into a worktree at0caf0618and run the checks named below; for the crash, build main @5e072fc8withRX_VK_ONLY=1and runllvm-symbolizer --obj=longshot_vk.dll 0x18021a1f00caf0618688441b1nightly-130, 5nightly-132)nightly-130); 169 s, 351 s in all in Kokiri Forest (nightly-132)nightly-132)-2(hisnightly-132log)igvk64.dllnexttest_texture_squeezeon the branch before288e98c5and again before688441b1;test_vram_compactbefore688441b1)tests/run_all.py1be60eae)688441b1: the same 5 andtest_cpm_discovery, which fails on main @7ff5256etoo (#524)The pipeline cache: in Unity's first set of logs, five starts each loaded 182 KB and one save was logged in all. In his second set it grew from 0.6 MB to 77 MB over five starts, so saves did happen there. The owed save makes them happen between builds, not only when no build is under way.
tests/run_all.py, both sides on this PC: main @1be60eaeand this branch at288e98c5gave 532 passed, 5 failed, 34 skipped each (test_release,test_oot_state_tap,test_tested_configurations,test_dev_packs,test_walk_key: this PC's setup, none reads these files).test_dlss_sdk_cachefailed once on the branch while both suites ran at once and passed twice on each side alone. At159a12d9(main @7ff5256emerged in) every check was run by name excepttest_longshot_ctlandtest_oot_state_tap, which start a detached process that opens a console window on this PC: the same 4 setup failures as main,test_cpm_discovery(fails on main @7ff5256etoo, #524), and 12 checks that declare a Windows skip and only ran because they were named.Testing
Tested: On this PC (Windows 11, llvm-mingw clang 20261104, no Intel GPU), as in Measurements: the changed checks fail without their fix and pass with it;
tests/run_all.pyfails the same checks on both sides; the renderer (longshot_vk.dll) builds with llvm-mingw at688441b1. The causes were read from Unity's 15 logs, counted by script, and the Hyrule Castle crash's address matched against a build of main @5e072fc8made with the same toolchain as his nightly.Not tested: This PR in the game, on any GPU: this PC has no Intel GPU, so nobody has yet seen the preparation finish, the Deku Tree cutscene or Hyrule Castle stay within the allowance, or the menu rows. Not measured: whether Intel's Windows driver honours
DISABLE_OPTIMIZATIONfor these shaders and how much faster that build is; the GPU cost of the unoptimized passes until their optimized builds arrive; how often the 90% line makes textures come back (a hitch each) in big areas. Not built: Linux (host code is portable C++; only the Windows toolchain was run).Needs other testers:
cache/longshot_prepare_skipped, rebuild, press Set up for a weak laptop, play to the Deku Tree's last cutscene and into Hyrule Castle; expecting the preparation to finish, noSLOW compilein play, no device loss, no crash inigvk64.dll, the start lineintegrated GPU texture budgetnaming about 7400 MB allowed, and theVRAM:lines under 90% of the allowance; then Death Mountain Trail and Goron City, the heaviest areas after the castle, for the same lines (T-AUXG)RX_VK_SYNC_PIPELINES=1set (pauses expected while it builds). Square not blown out: the left-out draws are the cause. Still blown out: something else, and a screenshot and log of that run (T-AUXG)RX_VK_NAN_BITS=1,RX_VK_NO_F16=1andRX_VK_ASTC_BC_ALL=0, one at a time, a close-up of Link's hair in each; whichever clears them names the cause (T-AUXG)Screenshots
Nothing captured: the two new rows show only on Intel or integrated graphics, and this PC has neither. The memory line and the log line change nothing on screen. Unity's screenshots of main are described under Why.
Risk and rollback
RX_VK_COMPUTE_QUICK_FIRST=0) goes back to one optimized build.vkAllocateMemoryof 128 MB when textures grow, not measured on Intel's or AMD's integrated drivers.RX_VK_IMAGE_PREALLOC=9restores the old count.RX_VK_SQUEEZE_ALWAYSdoes not restore 97%; reverting288e98c5does.RX_VK_SQUEEZE_ALWAYS=1already squeezed this way on any GPU; reverting688441b1restores the wait.RX_VK_LOW_VRAM_MIPS=0turns it off.longshot_prepare_skippedkeeps skipping until it is rebuilt or the file deleted (README).Reviewer notes
Start with
precompile_compute_quickandcompute_optimizeingpu/vk/vk_draw.cpp, thenblocks_at_start,count_line_pct,room_in_blocksandshort_excessingpu/vk/vram_compact.hand their use intexture_frame(gpu/vk/vk_texture.cpp), andnote_out_of_memoryingpu/vk/vk_draw.cpp. Overlaps: #288 changes the line that callslow_vram_texture_mips(not its body); #50 (merged) added the pool fixture intests/test_blocks_at_start.py, updated here for the device type; #284 reworks how that test finds its source, so whichever lands second merges it; #27 movestest_low_vram_texture_mips.pyandtest_precompile_compute_guard.pyto a shared source cutter. Left for a follow-up on purpose: the CPU contention on 4-thread laptops (What changed, the last list), which is where this laptop's frame rate is held; the compaction's timers counted in presents; the hair and bright-square causes until his switch runs name them; andtest_cpm_discoveryon main (#524). Open questions: should the quick compute builds go to every GPU on serial builds (RX_VK_COMPUTE_SERIAL=1)? Should Auto keep 50% and leave 33% to the button? Is 90% right for every integrated GPU, or should AMD's APUs, whose driver may page, keep 97%?Issues
Reported by Unity on Discord, in the issues channel (his report): the shader stutter and the blown-out Deku Tree dialogue, the device loss in the Deku Tree's last cutscene (
nightly-130), the crash entering Hyrule Castle (nightly-132), the blown-out Castle Town square and Hyrule Castle, and the black streaks on Link's hair, each with his logs and options file (the lines quoted under Measurements). There is no issue on the tracker for them yet.Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.WIP: fix(vk): on weak Intel laptops the scene's passes draw within seconds, the pipeline cache is saved, and a Debug button sets the lightest rowsto fix(vk): on weak Intel laptops the shaders are prepared and the scene's passes draw within seconds, the pipeline cache is saved, and a Debug button sets the lightest rowsI checked this PR against Mesa's Intel driver on Linux (Iris Xe, i9-13900H, Mesa 26.2.4). serial_builds_on() goes by vendorID == 0x8086, and intel_quick defaults to 1 even where the row is hidden, so the quick compute builds also run on Linux Intel, not only on Windows as the description says. On Mesa they don't do anything: ANV ignores VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT. Building compute shader 0a9b6e3892907675 alone (held to 32 lanes, shader cache off) gave the same instruction count, cycles, spills and compile time with and without the flag (18.4–18.9 s, two runs each), and the optimized rebuild through the same pipeline cache took 0.0 s. So on Linux it just adds a second pipeline handle per dispatch, which is harmless but doesn't help. It might be worth limiting it to Intel's Windows driver (dev.driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, or #ifdef _WIN32 as the row does), so the row and the code agree. On Linux the slow part is ANV's register allocation spilling at SIMD32, not its optimizer. I have fixes for that on a branch: compute-pipeline pinning on Mesa Intel, a compile gate weighted by memory, a smaller preparation plan, a lower spilling rate and a lighter mesh fallback epilogue. I'll open a PR for them once I've finished testing on this laptop. Your branch merges cleanly with mine, and the checks we both touch pass on the merge.
ok
Thanks, that's right: the row was Windows-only but the code wasn't.
e072164emakesintel_quick_compute()also askintel_compiler_row()(Intel graphics on Windows, the row's own rule), so on Linux with ANV the dispatches get the one optimized build, as before.test_intel_quick_compute.pychecks it, anddocs/development.mdnow says Windows only. Good to hear the branches merge cleanly; I'll look out for your ANV spilling PR.