The table of the frames each target was last rendered into was direct-mapped with 1024 entries. In a 1.4.3 field run about 16 entries rendered the frame before were taken by another subresource each frame, the scene's colour target among them, so every draw there whose pipeline was still building waited for it as one-shot, 50 to 400 ms, instead of skipping a frame. Sets of 8 ways that evict the way rendered longest ago lost none in the same run.
entry_of decides at record time whether a new texture takes the content of a live GPU-written texture at its storage
(alias_copy) or the storage bytes. In a chunked replay the source's GPU ownership is set when a chunk records its
render, so the choice followed the order the threads got to the chunks: a later chunk could take the old bytes before
an earlier chunk recorded the render, and a mirrored texture keeps them. Now a chunk that makes an entry while a live
colour texture of its size sits at the storage waits until the chunks before it are recorded (vk_exec_chunk_settle,
the texture lock released for the wait), skips sources the scan revives at a later op, and ignores GPU ownership a
later chunk set in this submission. slot_resolve and vk_texture_by_id_locked no longer read tslots through a pointer
taken before the wait.
TOTK_REPLAY_SKEW_US (debug) makes the even chunks wait before they record, so the chunk after each one records
first in every submission. With it at 3000 us and 4 threads the field save runs clean (grass drawn, 0 validation errors); the alias path
did not fire there (0 copied, 0 filled from storage), so this does not reproduce the missing grass reported on
RADV, and the fix there stays unconfirmed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DLSS kept the replay on one thread: its decisions follow the passes in replay order, its jitter and camera are noted
draw by draw, and its evaluation reads the per-object motion list the scene's draws build. Now the scan takes DLSS's
decisions on its own state (vk_dlaa_plan, split out of vk_dlaa_pass) and notes the scene camera in replay order
(vk_dlaa_scan_draw), and the copy of the lit scene and the evaluation are records of the chunk that holds the op
(vk_dlaa_exec). A chunk's draws are jittered while they come before the op the scan ended the scene at. The
evaluation waits until the chunks before it are recorded, and the chunks after it wait for it (a hold), since they
sample its output. Chunks note their draws for the per-object motion one at a time (mo_lock in vk_exec_draw.c); the
uniform overrides of a motion draw and shader_for's last-frame variant flag are per thread, as the other chunks' draws
go on meanwhile; the FXAA shader table takes concurrent lookups. fxaa = 0 runs in chunks too.
With dlaa = 1 at 4K (DLSS Quality) on 1.2.1 and an RTX 5070, replay_workers = 4 takes the frame rate from 70 to 78
fps where the CPU is the limit, with no one-frame glitch in 241 consecutive frames.
(cherry picked from commit 04f8bb1b57)
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)
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>
nvnCommandBufferUpdateUniformBuffer between draws was replayed as a buffer copy at that point, which needs a barrier
against the draws that read the old bytes, and a barrier ends the open pass. The terrain and the sky islands' meshes
update before most of their draws: about 200 updates inside passes in a field submission, each a pass end, a full
barrier and a pass begin.
An update inside a pass now goes to the scratch buffer only. Later bindings of a range it covers bind the scratch
bytes (ub_bind, from ubo_info and bind_range); a binding it covers only in part, or at an offset the alignment
refuses, gets a merged copy of the snapshot or guest memory with the submission's updates on top. The copies into the
real buffers are recorded behind one barrier of their own at the next full barrier (vk_exec_ub_flush from
barrier_to). A new update of a range replaces the older overlay and pending copy of it, so the lists stay as short as
the blocks. Copies, fills and storage writes of the same bytes retire the overlays they touch, and a storage binding
over a pending copy brings the copies forward with a pass restart. bind_sig hashes the overlay each of the draw's
uniform and storage bindings reads, so a draw of an updated block binds the newest bytes and the others still skip
their descriptor build. TOTK_UB_OVERLAY=0 copies at once as before; ab alternates; TOTK_TRACE=perf prints the
counts.
1.4.3, RTX 4070 Ti, 2560x1440 DLAA, standing still on Great Sky Island, TOTK_AB=10 (tools/ab_summary.py):
17.03 ms a frame with the copies at once, 14.39 ms with the overlays (-15.5%, 58.7 to 69.5 fps); replay time on the
CPU 8.81 to 8.30 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
The icon capture draws into a colour target rendered for the first time, with the per-frame depth buffer. Under
pipe_skip any and fullscreen the recent depth made the pass look per-frame, so draws with a pipeline still building
were dropped and the icon kept missing parts (a fused weapon without its blade) until the item was captured again.
vk_exec_targets_recent now ignores a recent depth target while a colour target is rendered for the first time.
On fight2 with a warm driver cache no draw waited. With a cold cache the first world frame after loading waits for
the scene's new HDR target (2.9 s without prepared shaders), and one 64x64 capture waited 29 ms in the fight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every texture initialization got a 128-byte host copy for recordings to point at, kept for the whole run. The game
initializes its transient targets again every frame (about 29 new copies a frame on 1.4.3), so host memory grew by
about 44 MiB per minute of play, standing still too, until the commit limit ended long sessions (#670).
Each pin now counts the recording copies whose ops name it: counted in flatten_recording, uncounted when a copy or a
per-submission clone is freed. A pin with no copy left is freed NVN_PIN_AGE (1200) submits after its last use, so a
recording still in progress keeps its pins, and never while the replay holds it as a current render target, which
carries over to the next submission and is compared by pointer. TOTK_PIN_SWEEP=0 keeps every copy.
A 300 s standing-still run on the field save: host memory without VRAM grew 5 MiB, against about 220 MiB before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The replay marks the start and the end of every render pass and dispatch
(vk_exec.c vk_exec_pass_begin and vk_exec_pass_end, vk_exec_draw.c
op_dispatch and op_dispatch_ind), outside the pass so no pass is broken.
With VK_AMD_buffer_marker the GPU writes the last started and finished mark
into words 2 and 3 of the breadcrumb buffer; with
VK_NV_device_diagnostic_checkpoints the same points are checkpoints, read
with vkGetQueueCheckpointData2NV after the loss. A ring of 4096 records maps
a mark to its submission, index, target formats and size, draw count and the
input ids of the first and last draw's shaders. vk_device_fault_report logs
both marks with their records. The calls go through the recorder thread.
TOTK_PASS_CRUMBS (menu: GPU pass marks): unset, AMD buffer marks only; 1,
also NVIDIA checkpoints, whose extension is enabled only then; 0, none.
The design follows Savage Wind Recomp 3fae40c; the code is ours.
--probe-vkexec gets a pass marks part: pass, dispatch, pass in one
submission; the marks read back after the fence name the last pass and the
report text says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sampler keeps both the DLSS mip bias and pull request #321's anisotropic level; DLAA's pass hook runs after the
contact shadows'; the extension arrays grow for NGX's extensions beside HDR's and the calibrated timestamps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: the known-key and section lists in cfg.c keep both sides; vk_backend.c, vk_device.c and vk_exec_draw.c include platform.h and vk_dlaa.h; create_instance takes the window's instance extensions from gfx_window_vk_extensions as on main, in a 12-entry array for NGX's.
Merged with applezra's follow-ups on the same branch: the sun shadow filter narrowed by the shadow map scale
(translator patch 0011), anisotropic filtering forced only on tiling samplers, and TOTK_DUMP_UBO with a vec4 count.
Conflicts with dev: cfg.c (names, display section, defaults next to aspect and device_profile), menu.cpp (the new rows
after the aspect row), vk_texture.c (the pool alias check takes scale == 1 and dev's bind_pool_memory size argument),
build_xlate.py (patches 0005-0011 are ours; the Linux note kept), totk.ini.example (both sections). The contact pass
release went into gfx_present.c, the shared half of the old gfx_window.c.
aniso defaults to game: the picture stays as before until a player raises it in the menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
AMD driver 26.10.1 on RDNA2 cards wrote past the end of a 3D BC4 volume made
BLOCK_TEXEL_VIEW_COMPATIBLE and 2D_ARRAY_COMPATIBLE, which lost the device at the
logo (issue #5). 3D BC textures no longer get block views: draws into a level go
to an uncompressed 2D array stand-in that is copied back at the pass end
(TOTK_BLOCK_VIEWS_3D=1 restores the old path).
Parts the recorder thread recorded and the submitting thread ended turned the
scene green, then white, on the same driver once a submission was split. The
recorder now writes the closing breadcrumb and ends the part itself
(TOTK_REC_END=0 restores the old split). The recorder stays off on AMD by default.
Based on the patch Pelipooper posted in issue #5; stand-ins extended to every
mip level.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DLAA runs DLSS at the scene's own size on the R11G11B10F light target, right before the bloom chain's first pass,
so bloom, post and HUD read the anti-aliased picture. NGX is loaded from the driver's NGX core at run time; no NVIDIA
SDK code is compiled in, and nvngx_dlss.dll goes next to totk.exe. The scene's draws get a Halton(2,3) viewport
jitter that steps after each DLAA run; camera motion vectors come from the scene depth reprojected through this and
last frame's view-projection of the scene camera block (c4), read from the uniform snapshot. One DLSS feature per
scene size keeps dynamic resolution from stalling. dlaa and fxaa switch live from the settings menu; fxaa skips the
draw of the game's FXAA pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TotK fills a full-resolution G-buffer with a B10G11R11 scene colour, an R32F linear depth and a D32F depth, lights it
in half resolution, and composites the lighting into the scene colour in one pass whose only target is that colour,
with a D16 class depth. vk_contact.c recognises both passes, and when the pass after the composite begins (or a
dispatch or copy comes first) runtime/shaders/contact_shadow.comp marches each pixel 16 steps toward the sun through
the linear depth and darkens the scene colour where nearby geometry blocks the ray: the small shadows at feet, under
stones and in grass that the shadow maps miss. Transparent surfaces, the sky and post-processing come after.
The projection, far plane and sun direction come from the fragment stage's 0x940-byte camera block (c4): c4[7].x,
c4[8].y, c4[14].y, and the first cascade's depth row c4[44].xyz, whose negation points toward the sun. Scene colour
images get STORAGE usage while the setting is on.
contact_shadows (percent strength, default 0) is in totk.ini.example and on the Screen tab of the menu (English and
Russian). TOTK_CONTACT_DEBUG=1 shows the shadow factor in grey instead of the frame. It costs about 0.45 ms per frame
at a 3840x2160 scene on an RTX 4080 SUPER.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TotK draws its sun cascades into a 960x960 DEPTH16 array of 4 layers, and its static shadows into 1440x1440 and
720x720 arrays of 2, whatever the output resolution, and shrinks the cascades under load. shadow_scale (1 to 4,
default 1) creates those images that many times larger on each side: passes into them scale the game's viewports,
scissors and support-buffer viewport size, and copies scale their regions, so the game is unaware of it. Images the
CPU writes or that alias pool memory keep their size. When the maps are enlarged, depth-compare samplers filter
linearly, because the game's few nearest taps are spaced for its own texels and leave hard, stepped edges otherwise.
aniso (game, 2, 4, 8, 16; default 16) raises anisotropic filtering of linear mipmapped colour samplers; depth-compare,
nearest and unmipmapped samplers keep the game's setting.
Both are in totk.ini.example and on the Screen tab of the settings menu (English and Russian).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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>
TotK rewrites per-object uniform blocks on other threads soon after it submits the frame that reads them, and the
replay records for milliseconds after the submit call, so late draws could read another object's matrix (phantom
rocks, a flat far field). The guard in nvn_api_core.c obs_release_read only knows 1.2.1's producer. runtime/vk_usnap.c
copies every uniform range of imported pools that the submission binds into an arena, after MacNativeGames'
TOTK_UBO_SNAPSHOT, and ubo_info binds the copy; ranges a submission still in flight writes are left out, and GPU writes
within the submission drop the copies they touch. TOTK_UBO_SNAPSHOT=0 binds live memory; =ab alternates.
Town on an RX 9070 (in-launch A/B, 70 s): 2235 ranges, 1.9 MiB per submission in 0.49 ms; interval +0.02 ms, replay
+0.19 ms. The sort of the ranges was half of the cost with qsort (0.53 ms for about 10,000 bindings) and is a radix
sort now. fight2 interval within noise. The four probes passed on 1.4.3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fight frame of about 2,000 draws made 15 parts on the 7900X at 64 draws a part, each split costing the render thread a submit, a new command buffer and a mirror prologue (vk_sync_split, 7.6% of the replay). maybe_split now also waits until TOTK_SPLIT_US (1000, 0 off) has passed since the part began, so a frame makes 7-9 parts there. A millisecond of recording is well under a millisecond of GPU work, so the GPU still starts early in the frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vk_exec_pass_begin created every attachment view again in each submission and freed them at its fence: 2% of the fight replay on the 7900X, more on drivers with a slower vkCreateImageView. view_of now takes the view from vk_exec_target_view, which keeps it in the sampled-view tables under the texture lock, and vk_exec_image_gone releases it with the image. Images that are not registered textures keep the per-submission views.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imported pools put vertices, indices, constants and storage buffers in system memory, so discrete GPUs read them over
PCIe on every draw. A pool now gets a device-local mirror on its first use as a vertex, index, uniform, storage or
indirect buffer; bindings, buffer copies, fills and uniform updates use it at the same offsets. The first access of a
submission syncs the pages written since (write-watch snapshots shared with the texture checks, plus
nvnMemoryPoolFlushMappedRange ranges) in a prologue command buffer before the part; what the GPU writes into a mirror
goes back to guest memory at the end of the submission. TOTK_MEMORY=import|mirror|verify overrides the device class;
verify compares sampled mirror pages with guest memory at the fence. The vkexec probe gains a mirror part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nvn_pool_invalidate was a weak no-op, so nvnMemoryPoolInvalidateMappedRange never wrote anything back. TotK
invalidates its 260x260 R16 grass maps and 1x1 targets before reading them; they now go through guest_read.
The grass system also renders a 128x128 RGBA8 cut map into a CPU-cached pool every frame and reads it with no
invalidate. Without its texels the CPU saw no cut: swings left the grass standing, with no blades and no insects.
After nvnSyncWait or nvnQueueFinish, dirty mirrored targets up to 64 KiB rendered in a submission up to the waited
fence go back to storage (cpu_synced, vk_texture_write_back_small), unless the CPU rewrote it since.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>