57 Commits
Author SHA1 Message Date
Valiantsin DzerakhandClaude Opus 5.5 3e037f0044 Keep the shadow log of players without sync_ahead as it was, sort the new setting names, and fix the sync_ahead frame-time sentence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 17:55:57 +03:00
itaskaev2 2e5d59df83 Merge remote-tracking branch 'origin/dev' into carry/sync-ahead
# Conflicts:
#	docs/architecture.md
#	runtime/cfg.c
#	runtime/vk_sync.c
2026-11-09 09:54:45 +01:00
Valiantsin DzerakhandClaude Opus 5.5 cf44e9013c Merge task/memoom: wait out a short out-of-memory spell instead of ending the game
Conflicts: the image retry goes through vk_texture_image, sweep_pressure keeps the released-texture cap on the new
drop helpers, and vk_headroom_release also frees the texture pool's anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 10:12:55 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8e05d2b409 Give textures a pool of their own with 64 MiB blocks
Textures shared the default 256 MiB blocks with the mirrors and other buffers, and a block went back to the driver
only once everything in it was gone. On the RX 9070 after a few minutes of roaming the field, 3 of 9 device-local
blocks were 13-27% full, held by a hundred small textures and buffers each, and blocks held 0.8-1.4 GB more than their
allocations; where device memory takes process commit, that commit backs nothing.

Own-memory textures now go to a VMA pool with 64 MiB blocks (TOTK_TEX_BLOCK_MIB, 0 = the default pools). Images of a
quarter block or more, images the driver wants a dedicated allocation for, and images the pool's memory type does not
take get memory of their own; the requirements are read before the image is made, since VMA checks neither for a
custom pool. The headroom thread keeps the pool's next block ahead like the default pools'.

Same 10-minute route: 807 MiB of blocks beyond their allocations instead of 1037, most pool blocks 75-100% full, slow
frames not up. Probes pass on v143.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 10:11:44 +03:00
Valiantsin DzerakhandClaude Opus 5.5 0005c01cf5 Remove the stray brace in vk_mem_census that stopped vk_memory.c from compiling
79c6951 left the closing brace of a debug block in the per-heap loop, so the function ended early and the build failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:57:32 +03:00
Valiantsin DzerakhandClaude Opus 5.5 213bc6af6a Wait out a short out-of-memory spell instead of ending the game
Crash logs ended in fatal errors when the system's commit ran out for a moment: a staging buffer (-2), a guest commit
with the system at 24442 of 24447 MB, image creation (-2). Windows grows a system-managed page file and some drivers
charge device memory to the process commit, so the same request often succeeds a moment later.

oom.c holds the wait nvn_api_cmd.c realloc_wait already used: it requests a release, retries every 10 ms for up to
30 s, logs the first 8 with the commit numbers. rt_oom_request sets a flag that vk_texture_new_submit takes (one atomic
load per submit): it drops up to 256 released textures of the two kinds sweep_pressure drops (overwritten, clean
mirrored), never GPU-written content, and frees the headroom blocks. vk_oom_release does at once what needs no submit:
headroom blocks, finished upload batches and deferred objects.

- memory.c commit: waits and retries without frz_lock between tries; every caller waits, boot ones too.
- vk_staging and the draw-data chunks (vk_exec_draw.c scratch): release, then wait; the open upload batch is not
  submitted, its caller still records into it.
- create_tex: release and one retry; no wait under the texture lock, the texture is asked for again at its next use.
- the texture write-back buffer: release, then wait.
- the headroom thread keeps no spare block for 30 s after a failure.

TOTK_TEST_OOM=<site>:<n> (commit, staging, image, chunk) fails the first n attempts; probe-kernel forces 3 refused
commits, probe-vkexec 2 staging buffers, 1 image and 1 chunk, and both pass through them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:53:30 +03:00
Valiantsin DzerakhandClaude Opus 5.5 5e479f593b Remove the stray brace in vk_mem_census that stopped vk_memory.c from compiling
79c6951 left the closing brace of a debug block in the per-heap loop, so the function ended early and the build failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:51:04 +03:00
Valiantsin DzerakhandClaude Opus 5.5 79c6951688 Measure whether device-local memory takes process commit instead of assuming it per vendor
c838d96 counted system memory for every discrete GPU but NVIDIA's, an assumption. An RTX 5080 log (#398) showed 10.4 GB
of process commit at frame 1200, about what the RX 9070 holds with its VRAM counted, and the RTX 3060 Ti in #670 ran
out of system commit (17.8 GB) at 4K with its VRAM full. vk_mem_host_probe now allocates 128 MiB of device-local memory
once after device creation and compares the process commit before and after; half the size or more turns the system
memory cap on. The log line carries the measured amount, so user logs show what each driver does.

RX 9070: "a 128 MiB device-local allocation took 128 MiB of process commit".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:39:54 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a689922180 Decode ASTC to BC3 by default on integrated GPUs
The size rule for TOTK_ASTC_BC looked at the largest device-local heap only. On an integrated GPU that heap is system
RAM reported as VRAM (10.6 GiB on the Ally X, 14.9 GiB on the 880M), so RGBA8 stayed on exactly where memory is
shortest. vk_mem_uma() now turns BC3 on there as well.

7900X iGPU, field save, same exe, frame 1800: RGBA8 commit 7047 MB, GPU 49-53 ms, 16.8-18.4 fps; BC3 commit 6155 MB,
GPU 42.6 ms, 21.6-21.8 fps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:20:09 +03:00
Valiantsin DzerakhandClaude Opus 5.5 c838d9692f Count system memory in the device-local budget where device memory takes host memory
The paths that give memory back under pressure (headroom's spare block, released textures, pool mirrors) watched the
Vulkan heap budget only. On a UMA GPU that budget is gigabytes of the same RAM the game runs in, and on Windows the
AMD driver charges device-local allocations to the process commit one to one (RX 9070: VRAM use and private bytes
grew by the same 1164, 256 and 256 MiB). Crash logs show the result: the process at 4.4-10.5 GB of commit, 16 GB
machines at 80-94% RAM, std::bad_alloc inside amdvlk64.dll (#285), allocation failures on Intel UHD.

vk_mem_host_room (vk_memory.c) is what the system can still give beyond a reserve (TOTK_MEM_RESERVE_MIB, 1536):
commit, and on a UMA GPU physical memory too; Linux overcommits, so there only a UMA GPU's available memory counts.
NVIDIA is left out, not measured. vk_mem_vram caps the budget at use plus that room, and headroom keeps its spare
block only while four blocks fit in it.

RX 9070, field save, 60 s with the room near zero from the start (TOTK_MEM_RESERVE_MIB=21000): commit 7124 MB instead
of 8965, VRAM 2631 MiB instead of 3781, released textures 153 instead of 1866; mirrors refused for 423 MiB of pools,
GPU 4.45 ms a frame instead of 3.34 ms, 60 fps either way. Without pressure nothing changes. The census logs the
room, the device class line the mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:02:36 +03:00
Valiantsin DzerakhandClaude Opus 5.5 6e9ca1ebfd Log the process commit in crash reports and in the memory census
A driver that runs out of memory faults or throws std::bad_alloc instead of returning an error: #285's log shows
bad_alloc inside amdvlk64.dll under gpl_link, and nothing in the report told that apart from a driver bug. The crash
report now prints the process and system commit, as rt_fatal does for our own failed allocations; --probe-kernel
checks the line.

The census at frames 600 and 1800 logs the commit as a baseline and, per heap, VMA's blocks and allocations beside
the driver's usage and budget, so a report shows whose memory grew.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 08:53:57 +03:00
Valiantsin DzerakhandClaude Opus 5.5 0704543249 Merge pull request #947: sync shadow pools from the per-submission write-watch snapshots (corne-ac)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:41:23 +03:00
ipavlin98 6b4d84ec50 Record large submissions in chunks on several threads
replay_workers = N (0, the default, keeps the sequential replay) records a submission of enough draws in chunks that start between passes, on N threads and in twice as many chunks, and submits them in order. In the town at 4K on an RTX 5070, 4 threads take the replay from 8.2 to 4.5 ms a frame and the frame rate from 71 to 79.

A sequential scan on the submitting thread applies only what is queue state (fixed-function and bind ops, render targets, render enable and conditions, samples-passed counting) and keeps a copy of it where each chunk starts, with how far its lists got: inline uniform updates, RENDER_COND results, contact shadow marches and the ops that drop uniform copies. A chunk takes those from the lists instead of from the chunks before it, starts with every image in GENERAL and ends with a full barrier, so its recording depends only on the copy and the lists.

Each thread records with a context of its own: the replay's memos, descriptor state, occlusion query segments and per-command-buffer state moved from file statics into VkExec and VkExecDraw, and the tables the contexts share (stand-ins, depth twins, views, mirrors, timestamps, completion callbacks, crumbs) take locks or atomics. The scan makes render targets live in replay order and notes the op (vk_texture_revive), since a chunk can sample a storage before the chunk that renders it got there or after the scan went past a later target there: a chunk's texture slots resolve as of its own op, and the chunks after a texture with no entry yet wait for the chunk that makes it. Uniform copies are dropped at the op that writes them (vk_usnap_drop_at).

Contact shadows plan their march in replay order (vk_contact_plan) and the chunk that holds its op records it; the HDR source keeps the copy that comes last in replay order. DLSS and fxaa = 0 act on the frame draw by draw and keep the replay sequential. TOTK_REPLAY_STATE_CHECK=1 compares the state after the last chunk with the scan's.

A draw or dispatch whose shaders write storage buffers drops the uniform copies they cover from its own op on, as the other GPU writes do (scan_writes). The scan still translates no shader: it reads the shaders the recording contexts translated from a table shared with them (vk_exec_draw_writes), and an op whose shader no context translated yet holds the chunks after it until it is recorded. Before, the chunk that recorded such a write dropped the copies for every position at that moment, so a later chunk recorded first bound the copy taken at the submit call: on an RX 7800 XT on RADV the grass near the camera came out dark or unshaded in some runs with 4 threads.

TOTK_TRACE=perf counts the chunks held per submission by reason and logs when each chunk of the last submission began. The settings menu has the thread count under Advanced, Scheduling, with the count this run uses under it.

The recorder thread begins, ends and submits the submission's open part (vk_rec.c rec_end_cb), so a chunk's mirror prologue stays off it: vk_sync_part waits until the recorder is idle, as the part pool allows one thread at a time, then records, ends and submits the prologue itself. Through the recorder the prologue could reach the queue before it was ended, and the GPU lost the device at the title screen.

(cherry picked from commit b94e2e9642)
2026-11-08 19:39:17 +03:00
corne-acandClaude Opus 5.5 a733b94645 Keep shadow_snap off by default under Wine until it has been measured there
Shadow storage is on by default under Wine as well, and the region snapshots were only measured on a native Linux
host. TOTK_SHADOW_SNAP=1 still turns them on under Wine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:20:03 +02:00
corne-acandClaude Opus 5.5 02752376f7 Sync shadow pools from the per-submission write-watch snapshots
Each 2 MiB region is now queried at most once per guest submission, shared with the texture checks, instead of once
per pool at every part of a submission. On a Steam Deck at Lookout Landing the replay thread's write tracking fell
from about 4.4 to 1.3 ms a frame and the frame rate rose about 7.5%. TOTK_SHADOW_SNAP=0 restores the old queries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 22:01:05 +02:00
itaskaev2andClaude Opus 5.5 ce9ed75cee Merge dev into carry/sync-ahead
vk_memory.c: dev now keeps every finalized pool until retire_value (at
least the submission being recorded), which also covers a shadow with a
deferred copy, so shadow_had_deferred goes. The deferred shadow prologue
and dev's upload scratch pool both stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:22:50 +01:00
Valiantsin DzerakhandClaude Opus 5.5 36a83bd78f Keep pool memory and texture records until the submission being recorded is done
vk_pool_fini freed import, physical and virtual pools right after vkQueueWaitIdle, which covers submitted work only;
with the synchronous replay another game thread may be recording a submission that names the pool. Every pool kind
now retires through the completion queue at the later of the recording's value and the next one.

destroy_tex freed the texture record while other threads still held &e->pub (render targets for a pass, storage
bindings writing wr_fence through it): they read a stale VkImage and cached views by that handle. The record is now
freed with its image's stamp (vk_defer_free), and views of a dropped entry are deferred at once instead of cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:05:49 +03:00
Valiantsin DzerakhandClaude Opus 5.5 dc45de0e9a Stamp deferred frees with the submission being recorded, whichever thread destroys the object
With the synchronous replay (the default since 7be68ec) a game thread finalizing a texture destroys released texture
cache entries while another game thread records a submission that slot_resolve may already have handed one of them.
defer() saw the recording's value only on the recording thread and stamped the others with the last submitted value,
so the completion thread freed the image before the submission naming it ran: "write invalid" device faults on NVIDIA
(#788, #802). vk_sync_submit now notes the value at the start of the recording, and every deferral made from then on
waits for it. A fight run counted 10 such deferrals during the world load.

Device faults now log the deferred-free state, and with VK_EXT_device_address_binding_report the object that held the
faulting address and whether it was unbound before the fault (TOTK_ADDR_REPORT=0 turns the map off). The census line
counts deferrals made off the recording thread during a recording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:16:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 57037ccc29 Deswizzle block-linear textures on the GPU when their storage is an imported pool
The replay thread deswizzled every block-linear texture on the CPU, and entering a new area stalled it for 50-236 ms. Now a compute pass reads the texture straight from the pool's imported buffer and writes a linear copy into a device-local scratch pool; the upload batch copies it into the image, or the ASTC decoder takes it. Large texture loads submit the batch early so the staging and scratch pools do not grow on the replay thread, and both pools are made at device creation.

On fight2 the render thread waits about 270 ms on textures per run instead of 900, and the load frame drops from 145 to about 80 ms. Mean fps is unchanged. TOTK_DESWIZZLE=cpu turns it off and check compares every upload with the CPU result (0 of 196k regions differ). Shadow mode (Linux/Wine) and textures outside the device's limits stay on the CPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:38:26 +03:00
ProSepandClaude Opus 5.5 4624bf8076 Let the game run one frame further ahead of the GPU (TOTK_SYNC_AHEAD, off by default)
Each frame the game waits in one nvnSyncWait (1.2.1 lr 0x7000b23dfc) for the frame it presented last. On the Switch
that work is long done by then; here it is still in the replay and on the GPU, and on Great Sky Island the wait is
about 4.6 ms of a 13 ms frame. With TOTK_SYNC_AHEAD=1 that one wait waits for the frame before it instead, once the
replay thread submitted the asked value, so the game builds the next frame while the GPU runs the last one.

The game then writes guest memory that GPU work still in flight reads. Shadow storage makes that safe: a shadow update
of a pool with a reader in flight is queued and copied by the GPU in the next part's prologue, in queue order, instead
of memcpy'd under that reader (TOTK_SHADOW_DEFER=1, the default with the switch; 0 makes the replay thread wait for
the reader instead). The guard is read under the shadow lock and the wait takes that lock once after raising it, so no
sync that read the old guard is still copying when the game writes again. The switch needs shadow storage (Linux and
Wine by default, TOTK_SHADOW=1 on native Windows) and the 1.2.1 call site; otherwise it stays off with a log line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:55:55 +01:00
Valiantsin DzerakhandClaude Opus 5.5 a959eab275 Merge task/vrampress: drop released textures the game has overwritten under VRAM pressure (#754)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:08:32 +03:00
Valiantsin DzerakhandClaude Opus 5.5 9a81e3e2f1 Keep regions the GPU writes from being frozen read-only, and thaw a frozen one before a GPU write.
Storage-buffer stores, copies and fills land in imported guest memory without a fault or a write-watch bit, so a
region only the GPU wrote looked idle and was frozen while the GPU kept writing it. LAS Recomp never changes the
protection of imported memory because of NVIDIA. A possible cause of the NVIDIA "write invalid" device faults
(#788, #802), not proven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:07:38 +03:00
Valiantsin DzerakhandClaude Opus 5.5 beda188776 Drop released textures the game has overwritten when VRAM runs short.
Under pressure only clean mirrored entries could go, so once those ran out the render targets stayed: 1.7-2.0 GiB of released images with the use still over the line. Most of them sit on storage the game has since given to another texture, and they are never taken back. The pressure path now drops those first, 30 s after their release and when no finalized slot has bound them for 600 submits. Clean entries wait 5 s, which ends the re-creation churn in fights, and both kinds stop once the use is back under the line.

TOTK_TRACE=perf gains mem: released lines that split released memory by kind, age, overlap and dedicated memory, and count take-backs and re-creations. TOTK_VRAM_LIMIT_MIB caps the budget the port sees, so the path can be driven on a large card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:53:17 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8d349fa50d Merge branch 'dev' into task/wbmerge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:55:37 +03:00
Valiantsin DzerakhandClaude Opus 5.5 33d3645d90 Reuse the write-back merge's snapshot buffers instead of creating them per submission
A VMA allocation that needs a new memory block costs 8-11 ms on the replay thread, so finished submissions now return their chunks to a pool that chunk_new draws from. Chunk sizes are powers of two so that one fits the next request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:55:36 +03:00
Valiantsin DzerakhandClaude Opus 5.5 835310412c Copy a mirrored pool's flushes as exact ranges, so far-apart small flushes stop copying the pool between them
nvn_pool_flushed kept one span from the lowest to the highest flush of a pool and mirror_sync copied all of it. In
Kakariko one 252 MiB pool copied 22,121 MiB for 847 MiB flushed, in spans of up to the whole pool. Over PCIe 3.0 x8
(RX 6600, Ryzen 5 5500, issue #698) a typical span takes about 30 ms of GPU time, which fits the frame spikes there.

A pool now keeps up to 32 sorted ranges and merges the two nearest above that. Writes between ranges still reach the
mirror through the write watch. Over a walk in Kakariko the upload fell from 472.8 to 125.5 MiB and no flush range
reached 16 MiB. TOTK_FLUSH_SPAN=1 restores the span. The mirror summary gains a line with the upload by source, and
--probe-vkexec checks the range list and that two far-apart flushes copy only their pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:41:54 +03:00
Valiantsin Dzerakh 1cd3e3a584 Merge branch 'pr/528' into task/wbmerge 2026-11-03 15:56:15 +03:00
Michael Anthony Bitoon e2fdb5b226 Merge a shader's storage-buffer writes back into guest memory word by word
In mirror mode the replay copied every range a shader wrote from the VRAM mirror back to guest memory at the end of the
submission. TotK's particle emitters write a new particle's whole record into a free slot of a buffer the simulation
shaders update, so the copy put the mirror's stale words over fresh spawns: the waterfall splashes and mist showed about
once a second and the falling rain drifted. Now each written range gets a snapshot in the prologue of the part that
first writes it, after the CPU-to-mirror copies, and at the end of the submission shaders/wb_merge.comp writes only the
32-bit words that differ from the snapshot. Words the GPU left alone keep what the CPU wrote. About 0.1 MiB and three or
four dispatches a frame in rain. TOTK_WB_MERGE=0 restores the copies. The merge's push descriptors go through rec_push
so they keep their place in the recorder's call order.
2026-11-03 01:19:10 +08:00
ExpertKT f75405ee51 Log a warning when memory = import is used on a discrete GPU 2026-11-03 00:31:53 +08:00
Valiantsin DzerakhandClaude Opus 5.5 328589abbe Add TOTK_REC_CHECK and TOTK_REC_DELAY_US to find objects the recorder still names
TOTK_REC_CHECK=1 searches the packets of the part being recorded for each image, view and buffer vk_memory.c defers or
destroys. A deferral stamped below the part's value, or a destruction of an object a packet names, is logged with the
packet op, both stamps and the GPU's value, and counted by kind and op in a summary at exit. put() zeroes each packet's
room while the check is on: packers reserve room for optional arrays they may leave unwritten, and those words held
handles from the ring's previous lap. TOTK_REC_DELAY_US=N makes the recorder wait N us before each batch, as on a slow
machine. rec_begin takes the part's value for the check. Both are off by default; off, the recorder pays one test per
batch and per packet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:02:38 +03:00
Valiantsin DzerakhandClaude Opus 5.5 90a3b07762 Stamp an object the replay defers with the submission it records
vk_memory.c defer stamped every deferred image, view and buffer with the last submitted timeline value. An object the
replay lets go of while it records a submission can be named by that submission too: vk_texture.c slot_resolve hands a
draw a released entry, and entry_of destroys released entries at that address later in the same submission. The
completion thread then destroyed the object once the GPU reached the earlier value, before the submission naming it
ran, and with the recorder thread behind, before the recorder made the call. The stamp is now the later of the last
submitted value and vk_exec_recording_fence, as LAS Recomp and Savage Wind Recomp stamp their deferrals
(runtime/nvn/vulkan/textures.c defer). TOTK_DEFER_STAMP=0 brings back the old stamp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:59:49 +03:00
Valiantsin DzerakhandClaude Opus 5.5 b6ab660b00 A zero-byte memory pool initialises as an empty pool in the Vulkan back end
vk_pool_init gave a size-0 pool with storage a record: the host import of 0 bytes failed (the driver refuses the
memory, VK_ERROR_INVALID_EXTERNAL_HANDLE) and the record went into the pool index as the empty range [mem, mem]. A
lookup at that address then found the empty pool before the pool that starts there and reported it unbacked. TotK
makes such pools now and then (MacNativeGames d3407e29, 1.2.1). Such a pool now gets no record, as a GPU_NO_ACCESS
pool; vk_pool_fini finds none and does nothing. Removal by key already kept a neighbour's range safe at Finalize.

--probe-vkexec gains an empty pool part: a zero-byte pool at the start of another, lookups there before and after
its Finalize. It failed before ("a lookup at the start of a pool finds the empty pool there").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:14:10 +03:00
Chris Koos d9d6264bb2 Default shadow storage to on when running under Wine 2026-10-31 15:02:30 -07:00
Valiantsin DzerakhandClaude Opus 5.5 43a18a9bb1 Merge pull request #60: Linux port (AppImage) and Steam Deck support (wdiasjunior)
The branch forked 79 commits before main and 98 before dev. Resolutions:

1. Aspect ratio: dev's implementation stays (camera patch at page +56 on 1.4.3, tools/ultrawide_layout.py layouts per
   size in romfs-patches-<w>x<h>, the aspect = fit|stretch setting). The PR's second implementation is dropped: the
   aspect sets of patches-v121.txt and patches-v143.txt (the 1.4.3 one patched the same viewport sites as dev's),
   tools/aspect_layouts.py, the resolution.c shapes that limited game resolutions to 16:9, 16:10 and 21:9, and the
   picture_fit setting. Its letterbox code (gfx_fit_rect) is kept and reads TOTK_ASPECT.
2. Code dev changed where the PR moved it: the stick dead zone, the test gamepad and the paddles go to input.c; the
   DPI awareness, keep-awake and IME detach of gfx_window.c go to gfx_window_win.c; device fault reports and GPU
   breadcrumbs to gfx_present.c. gfx_window_dpi gets an SDL version.
3. dev's Win32 calls in shared code go through platform.h: the shader cache pack reader (PlatFile), the romfs
   overlay checks, the audio pacing waits, the motion, ASTC and device fault locks. plat_cpu_usable and
   plat_commit_info get Linux versions; plat_cpu_count counts the machine's processors on both. dpi_probe.c becomes
   dpi_probe_win.c.
4. assume_focus defaults to 1 as the PR has it (a load interrupted by a focus loss can stop for good) and stays live,
   with dev's Game tab row.
5. The frame limits 40 and 45 join dev's list. The BC 3D storage fallback for Vega only runs with
   TOTK_BLOCK_VIEWS_3D=1, since dev makes 3D BC images without block views by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 13:26:42 +03:00
Valiantsin DzerakhandClaude Opus 5.5 43704c0d9f Turn off the recorder thread on AMD GPUs and GPL on AMD's RDNA 1/2 driver, and log GPU breadcrumbs on a lost device
AMD drivers lost the device at boot or at menu opens with the recorder thread; vk_recorder = 0 ended it for three
RX 9070 XT users (issue #5), and RADV saw an illegal opcode at the end of a command buffer (issue #40). It now
defaults to off on vendor 0x1002; TOTK_VK_RECORDER=1 turns it back on.

On AMD's separate driver branch for RDNA 1/2 ("AMD proprietary shader compiler") every device loss came with a cold
pipeline cache and draws waiting on fast-linked pipelines, and the recorder setting did not help there. TOTK_GPL now
has no fixed default: off on that branch, on elsewhere.

VK_AMD_buffer_marker breadcrumbs (vk_crumbs.c) mark the start and end of each submission part, upload batch, ASTC
decode chunk and present; on device lost the log names the last point the GPU reached and the last one it finished.

vk_fsr.c ensure_images held no queue lock around vkDeviceWaitIdle, which needs every queue externally synchronized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:48:38 +03:00
Valiantsin DzerakhandClaude Opus 5.5 112fb2dd74 Log the VK_EXT_device_fault report when the device is lost
vk_device_fault_report queries vkGetDeviceFaultInfoEXT once per process after VK_ERROR_DEVICE_LOST and logs the
description, the faulting addresses with type and precision, and the vendor codes. The submit, upload, occlusion
query and wait fatals call it before rt_fatal, and the present path calls it on a failed acquire, blit submit or
present. Threads that lose the device together wait until the report is written. The extension counts as usable only
when the driver reports the deviceFault feature. No cost unless the device is lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:21:14 +03:00
Wellington Dias 5a84b19476 Give CPU-visible pools shadow storage on Linux, so the game renders and plays.
On Linux, write-protecting memory the GPU imported costs milliseconds per submit and the import's pinning clears the
write watch, so each CPU-visible pool gets a host-visible copy instead: the pages the CPU wrote since the last sync go in
right before each submit (skipping bytes the GPU still has to write back), and the replay's GPU writes are copied back
into guest memory at their fence (vk_gpu_written). In 1.2.1 play it costs about 0.2 ms a frame. TOTK_SHADOW=1 turns it
on under Windows too.

Also: build_game.py fetches the VMA header the Windows release pins when the system has none; plat_localtime and
plat_gmtime reject the times Windows rejects; the SDL text entry honours TOTK_SWKBD_TEST=cancel; probe-vkmem follows
the replay's protocol for GPU writes and syncs.
2026-10-29 17:29:31 -03:00
Wellington Dias 1d51343ac1 Build and run the runtime natively on Linux.
Shared runtime code now goes through runtime/platform/platform.h, implemented by platform/win32.c and a new
platform/linux.c: futex locks, a userfaultfd and PAGEMAP_SCAN write watch, signal-based async preemption, our own
fiber switch, and a SIGSEGV handler for the thaw and crash hooks. The window, input, menu and software keyboard get
SDL3 back ends beside the Win32 ones, with the swapchain (gfx_present.c) and input merging (input.c) shared. SHA-256
replaces BCrypt, ELF builds get their own override sections, and the build tools gain Linux branches.

On Linux the selftest and the gfx-abi, astc, nvn and window probes pass. probe-vkmem still fails: imported guest
memory cannot be write-watched on Linux, which the shadow buffers in vk_memory.c will address.
2026-10-29 16:56:56 -03:00
Valiantsin DzerakhandClaude Opus 5.5 ce150130df Make code comments say what they mean instead of pointing at things a reader cannot follow
Comments cited codes from local notes (port-lessons P1, backlog), task labels (T6c1-T6c3, M4), bare upstream commit
hashes and issue numbers, a file that no longer exists (nvn_api.c, now nvn_api_*.c), a generated file (main_0103.c)
and line numbers into upstream files that refs/ has moved past, so they pointed at unrelated code. Those now name the
file, function or project and say what the cited code does. Attribution of copied code keeps its pinned commits.

Corrected facts: TOTK_REPLAY_ASYNC defaults to 0, and the shader cache, pipeline cache and pipeline keys live next to
totk.exe, not in build/v121.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:58:01 +03:00
Valiantsin DzerakhandClaude Opus 5.5 1f7d80306e Freeze idle regions that also hold memory outside NVN pools, and freeze after 60 idle submissions instead of 300
Write-watch queries skip frozen 2 MiB regions, but a region was frozen only when it lay wholly in imported pools and
wholly committed. NVN pools are small and not 2 MiB aligned, so most regions also hold other guest memory or reserved
holes: in town 340 of the 436 MiB queried every frame lay in regions nothing had written for 300 submissions. On the
GTX 1050 Ti laptop the queries took up to 2.2 ms a frame, 1.4 ms of it in queries that found no write.

mem_freeze now makes the committed pages of a region read-only when all of them are read-write and leaves reserved
holes alone; thaw_locked already restored such regions run by run. The pool rule is gone: the only kernel writes into
guest memory are file reads, which thaw their buffer first, and every user-mode write faults and thaws as before.
TOTK_FREEZE_IDLE defaults to 60 (in a fight 100 of 257 queried MiB lay in regions idle for 30-299 submissions; a thaw
by a write costs about 60 us on the RX 9070 machine), and import mode freezes too, for the texture checks of APUs.

RX 9070, per frame after load: town 436 -> 96 MiB queried, 0.68 -> 0.38 ms; fight 168 MiB, 0.45 ms (257 MiB, 0.57 ms
with the new rule at 300 submissions), about 20 thaws a second. TOTK_MEMORY=verify in the fight: 3.65 million pages compared, 0 mismatches. The gfx-abi probe
covers a region with reserved holes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:22:30 +03:00
Valiantsin DzerakhandClaude Opus 5.5 01dfe5d5a6 Take staging buffers from a VMA pool of their own that keeps two 64 MiB blocks from boot: in the default pool a block made by the headroom thread or by a burst of uploads held the render thread's staging allocations under the memory type's lock, 47-50 ms for one in the first frame after a save load (that frame 138 -> 70 ms)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:06:40 +03:00
Valiantsin DzerakhandClaude Opus 5.5 97d3762ba1 Strip milestone and task labels from code comments and drop the dead gfx_stubs.c
Comments named the agent task (T1-T7, M2-M5, Task 2.x) that once owned each file or block, and pointed at local
notes the repository does not carry (backlog items, milestone plans, research notes). They now say what the code
does. gfx_stubs.c held weak stand-ins for functions that every build has defined for a long time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:12:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 6d216840dd Merge the device-local memory census: a mem: vram line every 600 frames with TOTK_TRACE=perf, and backlog item 20 with the released texture entries as the grower and machine B at its budget
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:14:21 +03:00
Valiantsin DzerakhandClaude Opus 5.5 9758b8365e Log what holds device-local memory every 600 frames with TOTK_TRACE=perf: VMA blocks and allocations, texture entries live and released with the bytes of their own memory, VRAM mirrors and pending releases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:39:08 +03:00
Valiantsin DzerakhandClaude Opus 5.5 b92f113073 Check a texture's storage pages only in the 2 MiB regions stamped since its last check: an unchanged 4 MiB texture read 1,024 page generations, at about 1,100 checks a frame
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:01:54 +03:00
Valiantsin DzerakhandClaude Opus 5.5 353c237208 Count the replay's buffer traffic with plain adds: the replay is the census's only writer, and the locked add was 2% of fight replay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:55:17 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a7e362fc0a Run 1.4.3 into its opening cutscene: CPU-visible physical pools, no add-on content list, probes on SDK 20.5
1.4.3's GPU heap allocator creates a physical pool with flags 0x18c (CPU_CACHED, GPU_NO_ACCESS, COMPRESSIBLE): it
keeps the game's storage and gets device memory like the 0x189 pools of 1.2.1, which virtual pools alias. The CPU
never writes that storage (its hash was unchanged over 3600 frames of the intro), so nothing is synced.
nn::aoc::ListAddOnContent, which 1.4.3's main calls at start, returns no add-on content instead of running the SDK
on an uninitialized aoc client.

probe-ipc runs the 1.4.3 SDK's renderer semaphore constructor (sdk+0x221fb0) instead of the 1.2.1 sf heaps, and
expects SDK 20.5 to keep its audren:u manager session open. probe-services checks ListAddOnContent.
package_build.py takes --version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 18:09:47 +03:00
Valiantsin DzerakhandClaude Opus 5.5 b0805112ed Merge main into task/menu: the docs and comment cleanup, with the Dear ImGui set-up of menu.cpp kept in the LAS Recomp notices row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:16:23 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8b514f6ead Keep every default in the settings table: cfg_value, cfg_int, cfg_double and cfg_bool read it, and the consumers of table keys drop their own defaults
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:32:30 +03:00
Valiantsin DzerakhandClaude Opus 5.5 111f748c51 Remove citations of local build/ notes from code comments and correct the TOTK_REPLAY_ASYNC default in vk_backend.c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:23:21 +03:00