24 Commits
Author SHA1 Message Date
itaskaev2andClaude Opus 5.5 be5f4db3bf Skip 1.2.1's own scaling draw after DLSS instead of feeding it DLSS's picture (#1106)
On 1.2.1 the light target is scaled to the output by an upscaler of the game's own (twelve texelFetch taps,
fragment shader e6f7ae35b8479ef3), not by the FXAA draw vk_dlaa_skip_draw knows. It addresses its source in the
scene's pixels, so DLSS's output-sized picture swapped in for the light target (tex_info) showed only its top-left
part, two thirds at DLSS quality, stretched over the screen: the "floating camera" of #1106. #660 (169d057) made the
swap reach that draw: before it, DLSS ran on another target and its picture never reached the screen on 1.2.1.

DLSS already writes its picture into the scaling pass's target, so on 1.2.1 a draw in a pass larger than the light
target, without depth, that samples the light target is now skipped (vk_exec_draw_samples). The HUD drawn later into
the same target samples other textures and stays. 1.4.3 keeps the swap, where a tonemap can be the scaling draw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:53:25 +01:00
Valiantsin DzerakhandClaude Opus 5.5 35b3d37ea3 Make a chunk's new texture entries follow replay order, and add a knob that records chunks out of order
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>
2026-11-08 19:53:12 +03:00
ipavlin98 fd4427cb17 Run DLSS on the chunked replay
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)
2026-11-08 19:39:18 +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
Claude 9069c9208f Keep uniform updates inside a pass as scratch overlays and copy them at the pass end
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
2026-11-04 23:54:02 +00:00
Valiantsin DzerakhandClaude Opus 5.5 6c19856136 Merge task/fuseicon: wait for the pipelines of item icon captures instead of skipping their draws
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 15:33:19 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a248cc9f54 Wait for the pipelines of item icon captures instead of skipping their draws
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>
2026-11-04 15:33:07 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a1964a0c31 Free texture pin copies once no recording names them
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>
2026-11-04 15:00:36 +03:00
Michael Anthony Bitoon 5b23d6671b Make DLSS's picture reach the screen and stop the whole image wobbling with DLSS and DLAA
With a DLSS mode the game's own scaling draw covered DLSS's picture with the jittered scene. At a 1280x720 scene and
1920x1080 output the scaling is done by the game's tonemap, not by the FXAA shader vk_dlaa_skip_draw looks for, so no
hash list could catch it. From DLSS's evaluation to the frame's end, passes larger than the light target now sample
DLSS's output in its place (vk_dlaa_swap_img, applied in vk_exec_draw.c tex_info), whichever shader scales.

The motion-vector shader inverted the view-projection in 32-bit floats. With a still camera (identical matrices each
frame) the rounding still gave a few hundredths of a pixel of motion that changed with every jitter step; DLSS
followed it and the whole picture wobbled. mv_pass now makes last frame's view-projection times the inverse of this
frame's in double precision from the uniform snapshot (vk_usnap_host), and the shader only applies it.

Measured on 1.4.3 indoors, still camera, mean per-pixel change between consecutive frames on static areas:
DLSS quality 0.18 -> 0.06, DLAA 0.14 -> 0.05. A camera pan at 10 px per frame shows no ghosting.
2026-11-03 08:40:13 +08:00
Valiantsin DzerakhandClaude Opus 5.5 710b7f5dd0 The device-lost report names the pass or dispatch the GPU was running
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>
2026-11-02 09:50:30 +03:00
Valiantsin DzerakhandClaude Opus 5.5 4ab1dc80f5 Merge task/shadows: pull request #89, shadow options (itaskaev2), with the follow-ups
Keeps pull request #321's anisotropic filtering, which #89 added a second time, and #267's shadow_res beside #89's
shadow_scale. The HDR parameters move from byte 1408 of the support buffer to 1440: translator patches 0010 and 0011
read pcss at 1408 and shadow_spread at 1424.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:39:38 +03:00
ProSepandClaude Opus 5.5 007786d487 Add HDR10 output
With hdr = 1 the game's tonemap shader is replaced by one that keeps highlights up to the display's peak, the present
reads the float picture the game copies into its 8-bit window texture, and a compute pass encodes it as PQ in Rec. 2020
for an HDR10 swapchain. Peak, paper white and HUD brightness are live settings in the menu and totk.ini. The approach
follows the RenoDX TotK mod by souperman9 and Musa; no code was copied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:17:12 +01:00
Valiantsin DzerakhandClaude Opus 5.5 6a04fe9222 Merge pull request #89: Shadow resolution, cascade range, soft and contact shadows, anisotropic filtering (itaskaev2)
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>
2026-10-31 14:44:48 +03:00
Valiantsin DzerakhandClaude Opus 5.5 25b200f94c Draw 3D BC textures through stand-ins and let the recorder end its parts
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>
2026-10-30 23:50:02 +03:00
itaskaev2andClaude Opus 5.5 4af60604de Add screen-space contact shadows
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>
2026-10-30 15:29:53 +01:00
itaskaev2andClaude Opus 5.5 bfb4933531 Add shadow map resolution and anisotropic filtering settings
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>
2026-10-30 14:47:47 +01: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 bac4d6cf91 The shader memo skips its 1 KiB handle compare while no texture or image was bound
memo_match compared the vertex and fragment stages' 64 texture and 64 image handles on every draw. Every OP_BIND_TEX and now every OP_BIND_IMG bumps tex_serial; a key whose serial is current has the same handles, and a key that matched by the compare takes the current serial. memo_match went from 0.95% to 0.27% of the fight replay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:54:00 +03:00
Valiantsin DzerakhandClaude Opus 5.5 c9c06f2501 Render target views of registered textures live as long as their image
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>
2026-10-27 15:53:59 +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 7ceda9475b Size a pass without attachments by the far corner of viewport 0, else of the scissor, and reopen it when a draw needs more area, as MacNativeGames 62da927 does; an offset viewport no longer falls outside the scissor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:40:00 +03:00
Valiantsin DzerakhandClaude Opus 5.5 d161a8aefe Build a waiting draw's stage libraries on idle workers in parallel and skip draws into passes with a per-frame target, with TOTK_PIPE_WAIT and TOTK_PIPE_SKIP knobs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:48:19 +03:00
Valiantsin DzerakhandClaude Opus 5.5 0beafeb066 Write GPU-rendered textures back when the CPU invalidates a pool range or waits for a fence
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>
2026-10-24 10:41:54 +03:00
Valiantsin DzerakhandClaude Opus 5.5 19c9b648d1 Add the recompiler, Windows runtime, tools and docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 22:27:54 +03:00