69 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
itaskaev2andClaude Opus 5.5 0adcc96bae Bind the motion draws' set 1 before pushing set 0 under the validation layer, as the replay's draws do
vk_mvobj_execute's draws (mo_emit) push set 0 and then bind set 1. Under the validation layer (SDK 1.4.363) a push for
a new INDEPENDENT_SETS layout keeps the push set the layer made for an earlier layout (the replay's last draw) and drops
the writes to bindings that layout lacks: every run with DLSS and validation reported the last-frame banks as missing
(VUID-vkCmdDrawIndexed-None-08114, variable vp_c20) and then faulted inside VkLayer_khronos_validation.dll. The
replay's own draws avoid this by binding set 1 first (set1_first in bind_descriptors); the motion draws now do the
same, only while the layer is active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:11:08 +01: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
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
Valiantsin DzerakhandClaude Opus 5.5 7c5f88d33c Rebind only the vertex streams a draw reads that changed, and leave the streams it does not read bound
With vb_used the streams a draw does not read got the zero buffer, so draws in a row that read different streams
bound all 17 slots again almost every draw. On an RTX 5060 Laptop that cost 16 % of the frame rate (issue #974). Now
only the read streams are compared, one bind covers the changed ones, and the others keep the buffer bound earlier
in the command buffer, which the deferred frees keep alive until the submission is done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 13:50:32 +03: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 2eb5adebd6 Merge #898: record only the dynamic states that changed since the last pipeline state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:07:22 +03:00
Claude 752e15bcc7 Record only the dynamic states that changed since the last pipeline state in the command buffer
Each change of pipeline state in prepare_draw re-recorded the whole dynamic state through vk_pipeline_set_dynamic:
about twenty commands, the vertex input packet rebuilt, and the blend enable, equation and write mask of every
attachment, however few fields differed. Every replay pipeline declares the same dynamic states, which a pipeline bind
leaves as they were, so vk_pipeline_set_dynamic_delta compares the values with what the command buffer already holds
and records only the commands that differ; the vertex input is rebuilt only when the vertex shader, attributes or
streams change. The record is per command buffer (B.dyn_valid, cleared with the rest of B on a new one or a split).
TOTK_DYN_DELTA=0 records the full set as before; ab alternates; TOTK_TRACE=perf counts calls, commands and calls that
changed nothing.

1.4.3, RTX 4070 Ti, 2560x1440 DLAA, standing still on Great Sky Island, TOTK_AB=10 (tools/ab_summary.py): 13.75 ms
a frame with the full sets, 12.59 ms with the deltas (-8.5%, 72.7 to 79.4 fps); replay 7.77 to 6.67 ms. An A/A run
with the same method moved the frame by 0.08 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
2026-11-07 03:00:57 +00:00
Claude 324f85602f Clear the uniform overlays once their copies are recorded
After vk_exec_ub_flush the full barrier barrier_to records makes the copied bytes visible, so later bindings can read
the real buffer. The overlays stayed until the submission's end instead: every later binding inside their span
scanned them, and a block an overlay covered in part got a merged scratch copy that the descriptor build skip then
left alone. With the updates between passes overlaid too (Great Sky Island: 274 of 594 per submission) that was 290
merged copies and 1.1 ms more replay a frame, more than the barriers saved on the GPU (0.3 ms).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
2026-11-07 00:18:52 +00:00
Claude 49b2314dd8 Overlay uniform updates between passes too, so a run of them before a pass costs one barrier
An update between passes was copied at once behind a full barrier of its own, and the copy it recorded made the next
update or pass begin need another: a run of k updates before a pass cost k + 1 full barriers, about 250 updates a
frame on Great Sky Island. They now become overlays like the updates inside a pass; every GPU reader of the real
buffer after them comes behind barrier_to, which records the copies first. TOTK_UB_OVERLAY_OUT: 1, 0, ab; the perf
line counts the overlays made between passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
2026-11-06 22:35:01 +00:00
Claude b317f1c37e Order overlapping uniform overlay copies with a transfer barrier
vk_exec_ub_flush recorded the copies of a pass's overlaid uniform updates back to back behind one barrier. Copies
with no barrier between them may run in any order, and ov_add only drops an older pending copy of exactly the same
range, so two updates that overlap in part could leave the older bytes in the buffer where they overlap. A copy that
overlaps one recorded since the last barrier now waits for it behind a transfer-to-transfer barrier; the perf line
counts those barriers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BspNcezsiV8zT8wcnXrrME
2026-11-06 22:33:24 +00:00
Valiantsin DzerakhandClaude Opus 5.5 da24dd4cba Bind only the vertex buffers a draw's vertex input reads
The game keeps about ten vertex streams bound while a draw reads a few of them, and bind_vertex_buffers resolved and
compared all 17 slots on every draw. vk_pipeline_set_dynamic now returns the bindings of the state's vertex input,
from the same vertex_input that builds the dynamic input or the pipeline's, and the other slots get the zero buffer.
A stream is taken from the previous bind only when it was resolved there. In the fort, bind_vertex_buffers drops from
9.0% to 7.2% of the replay thread's work (two profiles, recorder on). TOTK_VB_USED=0 binds every stream as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 20:43:33 +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 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 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 Dzerakh d5e5a454ce Merge branch 'dev' into task/wbmerge
# Conflicts:
#	runtime/cfg.c
2026-11-04 14:09:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 80349df1a6 Merge pull request #594: per-object motion vectors for DLSS, Ultra Performance, live DLSS modes and per-mode presets (DizzyMan1337)
Merged on top of #566. The camera motion is #594's: its blocks in camb also feed the per-object pass, and
dlaa_mv.comp stays as on main, fed the identity and the reprojection made on the CPU in double. #566's
vk_usnap_host goes, vk_usnap_bytes serves the same. #566's scaling-draw swap, camera rune guard and output-size
scaling pass stay in vk_dlaa.c. Translator patch 0012 becomes 0015 after dev's 0012-0014.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 19:27:04 +03:00
DizzyMan1337andClaude Opus 5.5 ad3ecd8f3c Per-object motion vectors for DLSS, camera motion in double precision, Ultra Performance and DLSS presets.
Solid scene draws whose position reads an object's own uniform blocks (bones c5, place c7, c6) are drawn a second time
with a vertex shader variant (translator patch 0012) that computes last frame's position from last frame's blocks; the
game writes those into two copies that take turns, and the uniform snapshot keeps both (vk_mvprev.c). The draws are
listed during the replay and recorded through the recorder thread with repeated state skipped. Blocks whose last-frame
copies measured wrong (c9 and c11 of walls and cliffs) keep this frame's; positions without an object block (terrain,
grass) keep the camera's motion, which measured as good.

The camera motion is reprojected in double precision on the CPU: the float inverse made noise on far surfaces and a
still camera that DLSS followed into a wobble. DLSS gets Ultra Performance (a third of the output, also in the F1 menu)
and NVIDIA's recommended preset per mode (K, M for Performance, L for Ultra Performance; TOTK_DLSS_PRESET overrides).

Test switches: TOTK_MV_CHECK measures the motion vectors and DLSS's output against the picture, TOTK_DLAA_TIMES times
DLSS and the motion passes on the GPU. A reactive mask pass for transparents is included but off (TOTK_DLSS_REACTIVE):
its mask covers sky and fog too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:31:58 +08: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
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
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
MierzwaMatheus f4f9d6dd53 Keep the replay's transient buffers and the zero buffer in host-coherent memory
The CPU fills these chunks and the zero buffer and never flushes them. On Intel integrated GPUs (ANV on Meteor
Lake) the host-cached memory type that VMA picks for random host access is not coherent, so the GPU read what
an earlier submission had left there. A lighting fragment shader got another draw's UpdateUniformBuffer payload
as its step count (0x3f800000 instead of 96 or 128), looped about a billion times and lost the device; other
stale payloads drew the black frames. Requiring HOST_COHERENT keeps the same memory types on GPUs where host
memory is already coherent.
2026-11-02 01:22:13 -03:00
Valiantsin DzerakhandClaude Opus 5.5 f700c28cc4 Merge pull request #113: Add NVIDIA DLSS/DLAA and a toggle for the game's FXAA (DizzyMan1337)
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>
2026-11-01 11:40:27 +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
SchulzCode 9e079622dc Merge main (0.2.1) into the DLSS branch
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.
2026-10-31 16:19:27 +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 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
DizzyMan1337andClaude Opus 5.5 2e8b4ff1b1 DLSS upscaling: quality, balanced and performance modes
TOTK_DLSS (dlaa|quality|balanced|performance, read at start) sets the scene to
67, 58 or 50 % of the output. DLSS then runs at the begin of the game's scaling
pass (the FXAA shader drawing the light target into the output-sized target,
before the HUD) and its result replaces that draw; the HUD is drawn afterwards
and never jittered or passed through DLSS. The jitter now ends where the scene
ends (the bloom), separately from where DLSS runs. Scaled modes add a mip bias
of log2(scale) to repeating, mipmapped material samplers.

With a scene smaller than the output, fxaa = 0 no longer skips the game's
scaling draw (it left the 3D picture out).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:33:03 +08:00
DizzyMan1337andClaude Opus 5.5 6d181cf327 Merge upstream v0.1.9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:32:04 +08:00
applezraandClaude Opus 5.5 bc5eb0cb15 Narrow the sun shadow filter by the shadow map scale
The deferred lighting shaders soften the sun shadow with four compares around the receiver, offset by element 56 .xy of
their lighting block. The offset is in UV and the game sized it for its own 960x960 cascades, so with shadow_scale above
1 it spans that many more texels and the near cascade shows four separate copies of each edge. Translator patch 0011
multiplies those reads by the support buffer's new shadow_spread field in fragment shaders that sample a 2D array
shadow texture, leaving fp_c4 (the cascade matrices) alone. support_fill writes 1 / shadow_scale there, so the spacing
stays the game's in texels; at shadow_scale 1 the shaders behave as before. The support buffer grows to 1440 bytes and
build_xlate.py expects eleven patches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 21:10:27 -07: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
TotK localandClaude Opus 5.5 1512d9ed65 Add NVIDIA DLAA and a switch for the game's FXAA
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>
2026-10-31 03:34:55 +08:00
Valiantsin DzerakhandClaude Opus 5.5 3b5469a83f Sample compare-sampled R32F and R16 textures of compute passes through a depth twin
TotK's compute passes sample a 4x4 R32_SFLOAT texture through a shadow sampler. Vulkan allows depth comparison only
on formats with SAMPLED_IMAGE_DEPTH_COMPARISON, which AMD's drivers do not give R32_SFLOAT, so the result there was
undefined (20 validation errors in 40 s of field play). Such an image now gets a D32_SFLOAT (or D16_UNORM) twin
copied through a buffer right before each dispatch that samples it, and the dispatch samples the twin. Formats the
driver compares as they are keep the colour image. TOTK_DEPTH_TWIN=0 turns it off. Design after Savage Wind Recomp's
depth twins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:09:36 +03:00
itaskaev2andClaude Opus 5.5 24571ec0b0 Add soft sun shadows (PCSS) through a translator patch
The deferred lighting shaders take the sun shadow as four explicit-LOD compares on the cascade array. Translator patch
0010 turns every explicit-LOD compare on a 2D array texture into a PCSS lookup when the support buffer's new pcss
field has x above 0: eight raw depth fetches within pcss.w find the blockers, (receiver - blocker) * pcss.x gives the
penumbra width clamped to pcss.y..pcss.z, and eight compares on a disc of that radius average to the result. With x at
0 the original single compare runs behind a branch. Radii are in UV, so they hold at any shadow_scale.

The support buffer grows to 1424 bytes; support_fill writes the field from pcss (percent of the default width, 0 to
200, default 0; TOTK_PCSS_K overrides the factor at 100 percent, default 0.5). build_xlate.py expects ten patches. The
setting is in totk.ini.example and on the Screen tab of the menu (English and Russian).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:00:23 +01: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
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 3af201b43b Bind uniform blocks from a copy taken when the submission starts recording
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>
2026-10-28 20:25:40 +03:00
Valiantsin DzerakhandClaude Opus 5.5 3fb4b8d4b9 Make the replay's Vulkan calls on a recorder thread: each vkCmd* into the replay command buffer becomes a packet in a ring that the recorder makes in order, every other command buffer is called at once, and the submit waits for the ring to empty. Town on an RX 9070: replay 9.98 -> 8.29 ms a frame (in-launch A/B), no new validation messages, same picture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:41:46 +03:00
Valiantsin DzerakhandClaude Opus 5.5 41f201ad39 Skip full-screen draws while their pipeline builds by default (TOTK_PIPE_SKIP=fullscreen): in a fight a full-screen pass whose stage libraries were cold in the driver cache held three frames in a row at 64-72 ms, each spending the 50 ms wait budget, and some draws were skipped after all
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:02:18 +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 2bcba6bc3f Keep which shader of a program serves each stage per program serial instead of reading every stage's control block several times a draw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:01:54 +03:00
Valiantsin DzerakhandClaude Opus 5.5 bcbbcb0b1a Keep texture lookups per slot instead of per global texture generation, and vertex buffer spans while their ranges repeat
TotK finalizes and initializes transient textures every frame: the global tex_gen and slots_gen moved about every 16
draws in the monsters fight, so every cache keyed on them (the slot cache in vk_texture_by_id_locked, the texture types
of answers_for, the texture descriptor memo) lived for a few draws. A slot resolves through the entries at its own
storage address only, so each address hash now has its own generation and each slot a token
(vk_texture_slot_token: registration serial and address generation, lock-free). The memos check the global generation
first and the slot's token only when it moved.

The descriptor memo no longer drops an entry at each submission (the first binding still makes the storage check; a
re-upload writes the same image) or pass (the feedback layout is set on each hit from the kept image), and is twice
the size. feedback_scan keeps each handle's sampled subresource across passes instead of looking up every fragment
texture after every texture bind. bind_vertex_buffers keeps each stream's resolved span while its range and
vk_mem_gen stay the same.

Monsters fight on the RX 9070: replay 12.0 -> 10.8 ms per frame; field 8.1 -> 7.4 ms. TOTK_TEX_MEMO=check and
TOTK_SHADER_MEMO=check runs found no difference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:55:17 +03:00
Valiantsin DzerakhandClaude Opus 5.5 6b65fb6c0e Keep sampled-texture descriptors between draws while the slot, sampler, submission and feedback pass stay the same
tex_info resolved the slot, built a view key, probed the view table and looked up the sampler for every texture
binding of every draw. A direct-mapped memo keyed by handle, resource type and separate returns the descriptor while
vk_texture_slots_gen, vk_sampler_gen, the submission epoch and (with feedback-loop images) the pass are unchanged;
storage images are not kept. TOTK_TEX_MEMO=0 turns it off, =check recomputes every hit (a fight run with it and
TOTK_SHADER_MEMO=check stopped on nothing). Against main, profiler on, no bind stats: fight replay 13.9 to 11.9 ms per
frame, render thread busy 16.3 to 14.6 ms, CPU-bound windows 15 to 9; field replay 9.5 to 8.2 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:50:22 +03:00