65 Commits
Author SHA1 Message Date
Valiantsin DzerakhandClaude Opus 5.5 cf44e9013c Merge task/memoom: wait out a short out-of-memory spell instead of ending the game
Conflicts: the image retry goes through vk_texture_image, sweep_pressure keeps the released-texture cap on the new
drop helpers, and vk_headroom_release also frees the texture pool's anchor.

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 10:11:44 +03:00
Valiantsin DzerakhandClaude Opus 5.5 34b340a208 Cap the released-texture cache by bytes
Released textures stayed until 2048 of them were kept or VRAM passed 90% of the budget. On the RX 9070 a 10-minute
field run kept 818 MiB of them, while almost every take-back came within a second. TOTK_TEX_RELEASED_MIB (default
256) runs the same drops as under VRAM pressure while the released entries hold more own memory than that. On the
same route the run ended with 1.1 GB less process commit and 1 GB less in VMA blocks; 15 dropped textures were
created again and the slow frames did not change.

Also documents TOTK_MEM_RESERVE_MIB.

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

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

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:38:26 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a959eab275 Merge task/vrampress: drop released textures the game has overwritten under VRAM pressure (#754)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 10:08:32 +03:00
Valiantsin DzerakhandClaude Opus 5.5 0544f225f0 Merge pull request #646: a finalized texture slot follows only a live entry of its own texel format (mrtink125792)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:37:18 +03:00
Valiantsin DzerakhandClaude Opus 5.5 beda188776 Drop released textures the game has overwritten when VRAM runs short.
Under pressure only clean mirrored entries could go, so once those ran out the render targets stayed: 1.7-2.0 GiB of released images with the use still over the line. Most of them sit on storage the game has since given to another texture, and they are never taken back. The pressure path now drops those first, 30 s after their release and when no finalized slot has bound them for 600 submits. Clean entries wait 5 s, which ends the re-creation churn in fights, and both kinds stop once the use is back under the line.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:53:17 +03:00
Valiantsin DzerakhandClaude Opus 5.5 1a9d089a3d Write grass maps back at fence waits so fire spreads through grass, and batch the copies
TotK's fire spread reads its 260x260 RG8 grass maps after a fence wait with no invalidate. The fence write-back took targets up to 64 KiB only, so the CPU saw stale maps and fire died where it hit (#132).

A target over wb_fence_limit now goes back unless the GPU drew it in each of the last 16 frames. Raising the limit instead pulled in four 256x256 targets drawn every frame, about 2.7 ms a frame.

Every level and layer of every texture in a write-back now shares one upload batch and one wait. A burst of 9 grass maps took 7.8 ms with one round trip per level and takes 2 to 2.5 ms. The fence write-back on the field replay fell from about 45 to 22 ms per second of play.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:49:46 +03:00
mrtink125792andClaude Opus 5.5 1c5c6fdbe3 Let a finalized texture slot follow only a live entry of its own texel format
slot_resolve sent a slot whose entry was released to any live entry at the same storage, whatever its
format. The quick menu samples its 768x432 RGB10A2 scene capture through such a slot; after a fuse the
Autobuild thumbnail of the fused object, RGBA8 at the same storage, was the live entry there, and the menu
drew it behind the wheel for its opening frames: the fused object's silhouette, or garbled colours when the
thumbnail image held the capture's raw bytes. Maxwell reads the storage through the slot descriptor's
format, so a follower now needs the slot's texel format (sRGB aside); without one the released entry keeps
the slot, and its image is the capture the game last wrote.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:53:46 -05:00
DizzyMan1337andClaude Opus 5.5 f071264d61 DLSS modes switch while the game runs
1.4.3 sets its render size once, at start (main+0x7000e67028 calls
main+0x585bd0(renderer, force)). The function-entry hook catches that call's
renderer, and a TOTK_DLSS change writes the new scene size to the patch page and
has the next nvnQueuePresentTexture call the setup again with force set, on the
game's thread between frames, with the guest registers kept. The heap is sized
for the largest scene a mode asks for. The DLSS texture bias is part of the
sampler cache key and follows the mode and the dlaa switch. The game's scaling
pass is told from the in-place FXAA by its missing depth attachment, not by the
sizes, which change a frame ahead of the drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:31:49 +08:00
itaskaev2andClaude Opus 5.5 438aa41747 Keep the scene at render_scale when DLSS cannot run
A DLSS mode (quality, balanced, performance) shrank the scene to 67, 58 or 50 % of the output and sharpened the
material textures' mip bias to match, even when nvngx_dlss.dll was missing and NGX reported DLSS as not available.
The game's plain scaling then stretched the smaller scene, and the negative bias made textures shimmer in motion.

With dlaa = 1 the scene size now waits on nvngx_dlss.dll next to totk.exe (checked before the device exists, as
the size is decided then) and follows render_scale without it. The mip bias applies only when NGX came up with
DLSS. dlaa = 0 keeps the documented size with the game's scaling, without the bias.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:04:07 +03:00
lowlevel-larryandClaude Opus 5.5 3675286747 Keep a texture's pool flags instead of reading its pool object later
tex_mirrored (vk_texture.c) read t->pool->flags whenever the back end created the host entry of a texture, which can
happen long after nvnTextureInitialize. A game may finalize a memory pool and free the memory the NVNmemoryPool object
lived in while textures from it are still looked up (on a level change, for instance); reading the freed object then
faults. The NVN driver keeps no pointer to the pool either. tex_t now records the pool's flags at Initialize, in the
padding after layout[8], so NVNtexture's size is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:40:51 -03:00
Valiantsin DzerakhandClaude Opus 5.5 cd01186519 Merge pull request #365: Stage large CPU texture uploads in parts of at most 32 MiB (itaskaev2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 17:51:00 +03:00
itaskaev2andClaude Opus 5.5 da9460457e Stage large CPU texture uploads in parts of at most 32 MiB
With astc = cpu a large ASTC array decodes to RGBA8 in one staging buffer: the
1024x1024 array of 121 layers with 11 levels takes 645 MiB. On an Intel UHD with
8 GB of shared memory that allocation failed (-2) and the game closed before the
title screen (#347).

upload now fills one staging buffer per part of at most 32 MiB (a single region
larger than that gets its own) and records one copy per part, all in the same
upload batch, which is submitted as before. The parts fit the staging pool's
64 MiB blocks. The GPU ASTC decoder still reads every region from one buffer.

Fixes #347.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:08:52 +01: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
Valiantsin Dzerakh 683a2fe61b Merge pull request #321: Add an anisotropic filtering setting (ProSep)
# Conflicts:
#	runtime/ui_text.c
#	runtime/ui_text.h
2026-11-01 11:11:17 +03:00
Chris Koos 45430366b1 Return 0 from storage_rewritten when write tracking is unavailable 2026-11-01 05:55:34 +03:00
6f8dc257be Add an anisotropic filtering setting
aniso (game, 2, 4, 8, 16; default 16) raises anisotropic filtering of the game's tiled, filtered, mipmapped colour
samplers, so ground and paths seen at a shallow angle stay sharp into the distance instead of smearing. Depth-compare,
nearest, unmipmapped and clamped samplers keep the game's level: clamped samplers read screen-sized buffers, where
anisotropic taps reach across an object's edge and leave a speckled fringe (the inventory's character preview). The wrap
test reads the game's own modes, so mirror-clamp samplers stay out on devices without mirror-clamp-to-edge, where they
map to mirrored repeat. Read at start, as samplers are cached.

Ported from the aniso half of itaskaev2's shadow and filtering commit and applezra's follow-up that limits it to tiling
samplers. On the Screen tab of the settings menu (English and Russian) and in totk.ini.example.

Co-Authored-By: itaskaev2 <165683571+itaskaev2@users.noreply.github.com>
Co-Authored-By: applezra <Ezra.s.swenson@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 03:15:05 +01:00
Valiantsin DzerakhandClaude Opus 5.5 de3ed42a4c Write back GPU renders into textures that share their page when the texture's own bytes are unchanged (#13, #63, #118, #252)
The page write tracking dropped the render of any texture smaller than a page whenever the CPU wrote another
part of that page. The Purah Pad camera renders the ID, screen rect and depth of what is under the reticle into
4x4 targets in two slots and reads them a frame later. On 1.4.3 one slot's rect target shares its page with data
the CPU writes every frame, so the game read a zero rect every other frame and the target's name label blinked; on
1.2.1 the targets of both slots were dropped and nothing was recognised. A texture that fills no page now keeps a
copy of its storage bytes from the GPU write, and a page write counts only when those bytes changed.
TOTK_WB_EXACT=0 restores the old decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:55:56 +03: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
Valiantsin DzerakhandClaude Opus 5.5 c33af6020b Give a depth image its own memory when it needs more bytes than its NVN storage
AMD's RDNA2 driver places the depth compression metadata after the depth data. Above 1080p that tail of a depth
target bound into the game's texture heap lay over the next texture, and every depth write scrambled it: a garbage
band at the top of the screen, a pink glow, blocks in the grass and a blue checkered scene in the menu (RX 6600 to
6800, issue #185). Reproduced at 2560x1440 on an RDNA2 iGPU with driver 26.10.1; all 63 oversize images there were
D16 or D32 depth targets. Colour images keep their place in the pool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:01:24 +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
applezraandClaude Opus 5.5 5dcac19982 Force anisotropic filtering only on samplers that tile
The aniso setting raised every filtered, mipmapped colour sampler to the chosen level. Some of those read screen-sized
buffers, among them the ones the inventory's character preview is drawn from. Their coordinates stretch steeply at the
model's edges, so the anisotropic taps reach across the edge and leave a speckled fringe around the character. The
level is now raised only for samplers that repeat on U and V, which the surface textures it is meant for do; clamped
samplers keep the game's level. Distant ground stays as sharp as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:56:47 -07:00
Valiantsin DzerakhandClaude Opus 5.5 67b1c03b0d Skip BC3 texel writes that end inside a block, and check the BC3 encoder in --probe-astc
A BC3 image takes whole 4x4 blocks or a part ending on the mip's edge; a WriteTexels region of a 5x5 or 6x6 footprint may end inside one. --probe-astc encodes every footprint to BC3 and holds the blocks to alpha, palette-place, flat-colour and PSNR bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 07:35:00 +03:00
arthurhfcardosoandClaude Sonnet 5.5 dd496bfd5d Encode decoded ASTC textures to BC3 on GPUs without ASTC (astc_bc)
A GPU that cannot sample ASTC gets the textures decoded to RGBA8, four times the video memory of the ASTC blocks. With astc_bc = 1 (default 0, GPU decoder only) a second compute pass, bc3_encode.comp, encodes the decoded texels to BC3 and the image is BC3_UNORM or BC3_SRGB: 8 bits per texel instead of 32. The encoder is the usual bounding-box method with the box diagonal turned to the sign of the colour covariance, and the eight-value alpha mode.

A region of a WriteTexels that does not start on a 4x4 block corner is left as it was and logged. The setting is in totk.ini and the Advanced tab. The SPIR-V header was built with glslang 16.6 (build_shaders.py uses glslc) with the same source hash.

On a GTX 1650 (4 GiB) in the Fire Temple the GPU time per frame went from 10-23 ms to 3-12 ms and the frame rate held at 59-60 fps.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-31 07:27:46 +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
Wellington Dias ba4eae2123 Make BC 3D textures with block views on GPUs whose driver cannot lay them out as render targets, so agl's BC4 volumes exist on Vega.
RADV on GFX9 returns a 0-byte image for a mipmapped block-compressed 3D image with COLOR_ATTACHMENT usage and no
STORAGE (the usage asks for a thin layout the GFX9 address library has no swizzle mode for), and VMA then fails the
allocation: "image creation failed (-3): nvn fmt 0x49 32x32x32" on a Ryzen 5 4600G. create_tex checks the size the
device wants for such an image and, when it is 0, adds STORAGE usage, which keeps RADV on a thick layout; the replay
never sees that usage. If the device still cannot lay it out, the image is made without block views. bind_pool_memory
refuses a 0-byte image. Forcing the storage path on an RX 7800 XT renders the same frames.
2026-10-30 14:56:03 -03:00
Valiantsin DzerakhandClaude Opus 5.5 4d7fa271d4 Make only cube textures cube compatible
Every square 2D array of 6 or more layers was created cube compatible, though the replay makes cube views only of
images whose own target is a cubemap or cubemap array (vk_exec_draw.c get_view). On AMD's Windows driver the flag
changes the image layout, and Savage Wind Recomp traced rainbow blocks on terrain that moved with the mip level to
it (their issue #59, BotW on an RX 580 and a 7800 XT); our AMD and Intel reports show the same symptom (#42, #67,
#81). The flag now goes to cube targets only, on every driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:15:39 +03:00
Valiantsin DzerakhandClaude Opus 5.5 fb13c05870 Merge PR #86: drop released textures that reload intact when video memory runs low
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:56:08 +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 abb00c500c Add a diagnostics key for the fence write-back size limit and trace the targets it skips
wb_fence_limit (TOTK_WB_FENCE_LIMIT, default 65536) replaces the fixed 64 KiB limit of the write-back done when the CPU waits for a fence. With trace = gfx the log names each dirty target left on the GPU because of its size and each target over 64 KiB that a raised limit wrote back. Meant to test whether the fuse weapon switch bug (#18) and the Purah Pad camera bug (#13, #63) come from a target the game reads on the CPU without an invalidate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:27:42 +03:00
arthurhfcardosoandClaude Sonnet 5.5 444abcbebb Drop released textures that reload intact when video memory runs low
On a 4 GiB GPU the released-texture list (up to 2048 entries, each ASTC texture decoded to RGBA8) filled video memory and the heap spilled into system memory: a Depths lava scene went from 12 ms to 60 ms of GPU time per frame and about 16 fps. Above tex_vram_pressure percent of the device-local budget (default 90, 0 = off), each submit now destroys up to 32 of the oldest released entries.

Only entries that come back intact are dropped: mirrored textures the GPU never wrote, which are uploaded again from their storage. A render target exists only in its image; dropping those made textures come back empty (black flashes in the Depths boss fight, about 170 "left uninitialised" aliases a minute).

Adds the setting to totk.ini and the Advanced tab, and a perf log line with the released count and drops.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 09:26:36 -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 166e5a7b7d Drop released textures oldest first, one per release past 2048, instead of all of them once the count passed 4096: that sweep destroyed thousands of images in one frame, 333-422 ms frames every 40-50 s on a GTX 1050 Ti, and the textures still in use came back as new images and uploads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:14:11 +03:00
Valiantsin DzerakhandClaude Opus 5.5 97d3762ba1 Strip milestone and task labels from code comments and drop the dead gfx_stubs.c
Comments named the agent task (T1-T7, M2-M5, Task 2.x) that once owned each file or block, and pointed at local
notes the repository does not carry (backlog items, milestone plans, research notes). They now say what the code
does. gfx_stubs.c held weak stand-ins for functions that every build has defined for a long time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:12:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 6d216840dd Merge the device-local memory census: a mem: vram line every 600 frames with TOTK_TRACE=perf, and backlog item 20 with the released texture entries as the grower and machine B at its budget
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:14:21 +03:00
Valiantsin DzerakhandClaude Opus 5.5 9758b8365e Log what holds device-local memory every 600 frames with TOTK_TRACE=perf: VMA blocks and allocations, texture entries live and released with the bytes of their own memory, VRAM mirrors and pending releases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:39:08 +03:00
Valiantsin DzerakhandClaude Opus 5.5 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 1836d5c5d0 Keep texture image views for the image's lifetime instead of creating them again in every command buffer
Views made per command buffer were created anew every frame: vkCreateImageView under get_view took about 8% of the fight
replay on the 7900X. They now live in one table keyed as before, used under the texture lock, and destroy_tex hands an
image's views to the deferred release with the image (vk_exec_image_gone). The buffer range limits ubo_info and
ssbo_info read on every binding are read once. Fight replay 14.0 to 12.5 ms per frame (15.7 before the branch), field
9.5 to 9.2 ms; a fight run under the validation layer shows only the known cp_t_tcb_20 message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:42:47 +03:00
Valiantsin DzerakhandClaude Opus 5.5 c4108c49a8 Map NVN's 16-bit packed formats with their components counted from the least significant bit, as NVN does for RGB10A2: RGB565 is B5G6R5, RGB5A1 is A1B5G5R5, and the old table, which took LAS Recomp's order, read the Depths terrain's normal and blend maps scrambled and stretched the ground textures along one axis
A1B5G5R5 needs VK_KHR_maintenance5, so RGB5A1 stands in as A1R5G5B5 with R and B swapped by the view swizzle (TexVkFormat.chan); RGBA4 and RGB565 fall back the same way to mandatory formats on devices that lack A4B4G4R4 or B5G6R5. RGB5 and BGR5 now sample alpha as 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 16:48:05 +03:00
Valiantsin DzerakhandClaude Opus 5.5 6029719713 Read depth textures sampled without a compare as one texel, and round the reference of D16 array shadow compares to 16 bits, as MacNativeGames 6283599 does: the game binds linear samplers to depth it reads as data, and the Switch compares D16 against a reference at the texture's precision
The point form of a sampler slot is built once (vk_sampler_point_by_id_locked). Translator patch 0009 rounds the 2DArrayShadow reference in Ryujinx's SPIR-V backend.

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