The sixteen settings the fixed look took away are settings again: the tone mapper choice, gamma correction, mid grey, match tone slope, tone slope, hue clip emulation, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare, hue correction and colour grading. They have their rows in the settings table with the defaults and ranges they had, their rows in the Screen tab under HDR output's three brightness rows (none of them with the game's own HDR), their texts in the four languages, and their lines in the example ini. The tonemap library has the luminance mapper and the grading of the observer-response mapper again, and the support buffer carries five vec4 for it.
A player who changes nothing sees the picture as before. The observer-response mapper leaves each grading step out when its value is 1, and the slider values are divided, not multiplied by a rounded reciprocal, so 50 and 100 % arrive as exactly 1. Evaluated on the CPU in single precision, the shader with every default gave the same bits as the fixed one for 400,000 random scene colours, gains and whites, in HDR and for the SDR picture; the luminance mapper and the game's curve gave the same bits as before they were removed, and the observer-response mapper with its sliders moved stayed within 0.004 % of the pixel of its old result.
Gamma correction now counts with every tone mapper, 2.2 by default, which is what gave the fixed look its true blacks; before the fixed look it ran with the luminance mapper only and the other two showed the dark tones far above black. Its off value brings that lighter picture back for a player who asks for it. The game's own HDR does not take the setting and stays at 2.2.
Lines for these settings that an nx2navi.ini still holds from before the fixed look count again; the example ini and the release notes say so. Nothing of this was seen in the game.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The user asked for it: the mode no longer gains frame rate, and the mesh shader fallback covers the cards it was written for. Its switch in the settings menu's Game tab is gone with the rows under it, and so are their 57 text ids and strings.
low_end_gpu itself does nothing any more. It is still a known key, so an older nx2navi.ini gives no unknown-key warning, and while it is on the log says once that the mode was removed in 0.10.2. The preset that gave nine keys other defaults is gone, and so is the rule that read the mode's keys only while it was on: each of them (mesh_emulation, tex_clobber, foliage, vram_heap_limit, vram_reserve_mb, upload_batch_mb, mirror_stage, mirror_hot, submit_finish_gpu and the others) is an ordinary setting with the default of its row or of its reader, so a line a player wrote by hand keeps counting and a file with only the switch runs the game as it runs for everyone else. guest_opt in nx2navi.ini counts for the next rebuild without the switch too (tools/make_game.py).
The recorder thread read the switch itself: on an AMD card without mesh shaders the mode turned it on. It is off on every AMD card now unless vk_recorder = 1 is set.
nx2navi.ini.example lists the former mode's settings as single settings, the 0.10.2 notes have a Removed entry and name the foliage fix of the commit before this one, and the comments that said what the mode sets say so in the past tense. tests/cfg_parse_check has the new reading of the switch and of the keys without it; the test matrix is at 90 ok with the same two not ok as before (installer_test, worktree_test), and a clean build compiles 169 runtime files and links. Not run in the game.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Each decision moves into a header the runtime and a test both read, as vk_mesh_path.h does: vk_tex_detail.h (tex_detail_auto), vk_dlssg_device.h (dlssg_card_skipped, dlssg_device_create) and mesh_fb_hold.h (the hold's table). The runtime's behaviour is the same as before the move.
tests/tex_detail_check has the budgets from players' logs of 0.10.1 (a handheld's integrated GPU at 10,361 MiB now gets reduced, a 12 GiB card at 11,313 MiB full). tests/dlssg_device_check plays the laptop of the report with stand-ins for the interposer and the loader: the chosen Intel card is skipped, the NVIDIA card is made through the interposer, a card of another vendor is made by the loader without the interposer being asked, and a refusal falls through to the loader. tests/mesh_fb_hold_check holds a list for two presents after its colour draw was skipped, through a wait of 600 frames, and no longer once it is drawn or gone. Built against the rule 0.10.1 had, the first two and mesh_path_check fail (6, 13 and 3 checks).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The low-end GPU mode sets mesh_emulation = 1, and on a card without mesh shaders the emulation then draws the game's task and mesh programs in place of the mesh fallback. On NVIDIA's cards without mesh shaders that ended the game at the first mesh draws of an area with an access violation inside the driver, at nvoglv64.dll+0x10cdffe on a GTX 1050 Ti and on a GTX 1070 (both 0.10.1), and a Titan X (Pascal) report names the same mode. The GTX 1050 Ti's owner set mesh_emulation = 0 and played on through the fallback with the rest of the mode on.
The decision (vk_mesh_path.h) takes the card's vendor: an NVIDIA card is drawn for as if the emulation were not asked for, and the boot line says so. The replay and the pipeline code read the decided path from the device context where each read the setting itself. tests/mesh_path_check.c has the new cases.
Not run in the game: no GTX 10 card here. mesh_path_check passes and every file that includes the changed headers compiles.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The game draws a culled list of grass or leaves twice from one indirect record: into the depth target alone, then into the colour targets. On the mesh shader fallback each draw has compute modules and a pipeline of its own, made at its first draw, and the depth-only one is ready first. Drawn alone it left the list's depth in the scene with nothing drawn over it, so what lies behind failed the depth test there and the sky's colour showed through the ground in the shape of the grass for as long as the colour draw's pipeline built, which is seconds each on AMD's driver for Radeon RX 6000 and older, where the fallback is the default.
A colour draw skipped for its modules or pipeline now stamps its record's address with the present, and clears the stamp when it is drawn. The depth-only draw of a record stamped within the last two presents is skipped as well, counted like a draw whose own pipeline is building. A draw whose stages write memory is never held. mesh_fallback_hold = 0 draws the depth alone as before.
Not run: no card here takes the fallback by default, and the game is the user's to start. The changed file compiles and the settings table's check reports 0 problems.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
pending_draws (vk_exec_draw.c) counts the draws a frame left out because their shader or pipeline was still building;
vk_backend.c frame_hold takes it at the present to hold such a frame back. The comment said both happen on the replay
thread, which is true only with the replay on its own thread. With the replay on the submitting threads, draw_skipped
adds on the thread holding the submission lock (and its parallel chunks) while vk_exec_pending_draws_take reads and
clears it on the presenting thread, so a plain ++ and a read-then-clear could lose a count: a frame missing draws shown
instead of held, or the next frame held for nothing. The add, the take (an exchange) and the read that skips a link
wait are atomics now.
Not reproduced before the change. Checks that passed: pass_cc_check of vk_exec_draw.c with no new -Wall -Wextra
warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the 2026-11-13 session three frames of 87, 91 and 96 ms after an area load each counted as a change of pace, as each was followed by one as long, and loads that only the texture pool saw (no area file read, so no scene line) were charged to play. Consecutive long frames are now one episode, counted once with their summed time, its line giving that time and the first frame's parts; a run of more than 8 long frames none of which stands out is still a change of pace. The texture pool's start and end of an area load mark an area load for the map, as the game's own loads do.
With trace = perf, the slow write-watch queries and freezes past the 200 lines they may log are counted and given every 60 frames, so a late area load still shows them; the session's lines had run out by frame 36,387.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The record's copies put the count the game's culling pass wrote into the first dispatch and, without a task stage, into the draw's instances as it was. A garbage count launched up to millions of workgroups that write nothing and instances that draw nothing, enough to keep the GPU past the driver's timeout; with the fallback now the default on AMD's driver for RDNA 1 and 2, such a count would have moved a hang there instead of removing it. A single-thread compute pass (shaders/meshfb_clamp.comp) cuts the count to the buffers' room, which changes nothing that is drawn; the report word keeps the count asked for, so record_check still logs a mesh-only draw that asked for more than the buffers hold. On for every device that uses the fallback: one dispatch and one barrier per indirect fallback draw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pass mark now counts its mesh draws and those that take their count from the GPU; the gap before a mark notes a group recorded by the workers, whose passes have no marks, where the report said "barriers and state only"; the fatal line names the mesh path (native, the mesh fallback, the emulation). The comment on the RX 6700 XT report in vk_exec.c called the last marked dispatch the replay's own: it is the game's (op_dispatch, cs d7566eb1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vulkan has no quad or line-loop topology. Draws without indices were rewritten already; an indexed quad list went to the GPU with its indices under the triangle list topology, three at a time, joining corners of neighbouring quads, and an indexed loop lacked its closing line. Off by default and read at start; no log of Ocarina has shown such a draw so far, and the new line says when one comes. tests/index_rewrite_check.py checks the index lists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shader memory cache held 777 MiB of SPIR-V for 7115 shaders for the whole session. A background thread now
compresses the SPIR-V of each shader no reader pinned for shader_compress_idle_s seconds (zstd level 1, 26% of the
bytes on the test install's 44,151 modules) and frees the plain copy; readers pin it through gfx_shader_spirv_pin and
get it decoded back. What the drawing thread compares at pipeline misses and draws, the module's FNV-1a and its Binding
decorations, is kept with the shader, so module_binding_missing, the water corrections and the DLSS, crumbs and skip
lists decode nothing. A compute pipeline created at a dispatch and a mesh shader's first pipeline key still decode on
that thread, and the memory report counts those decodes apart, with the SPIR-V as held against uncompressed and the
shaders held compressed. shader_compress = 0 keeps it plain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every report adds a line for video memory outside VMA (the driver's usage and budget per heap against VMA's blocks, and counts of pipelines by kind and by last use, stage libraries, compute pipelines, layouts and descriptor pools, with the swapchain, DLSS and FSR sizes), a line that splits the process's commit by owner (the game's heap and maps, device-local memory, VMA's host blocks, the C runtime heap with the shader caches, pipeline tables, texture cache, log queue and flight recorder in it, the driver's pipeline cache data and the rest), and a line naming what grew by more than 64 MiB since the report before and since play began. Every ten minutes of play and at exit a ranking line orders the categories by growth and marks those that only climbed; tests/mem_growth_check.py checks that bookkeeping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release of what the upscaler leaves now waits for frame generation's own inputs too: in that mode the DLSS sizes
are made for frame generation alone while the upscaler does not run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The line freed the upscaler stand-in's scratch images, FSR 1's and SMAA's images and the DLSS sizes on its own while
this pass did: its versions stay. Of this pass's versions, what they do not cover stays: the stand-in's views of a game
image it read as it is now go with that image (vk_tonemap_forget from vk_exec_image_gone, under a lock), and the
upscaler release frees only what the line's idle release leaves, the reactive mask's snapshots, the FSR upscale
contexts and the DLSS sizes after a failure, at the same 600 presents and with vram_use = high keeping them as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With frame generation running and no DLSS or FSR upscaling, vk_dlaa.c arms and ends the frame in the same places and,
where the upscaler's evaluation would run, makes only frame generation's inputs (fg_only_frame): the scene's depth and
camera found as before, motion vectors from the same pass at the scene's size, no jitter at all, and the picture without
the HUD copied from the game's own scaled picture. DLSS Frame Generation and AMD's frame generation no longer need an
upscaler set; NVIDIA's through NGX still needs DLSS at the start and then keeps running when the upscaler is switched
off. The menu's Frame generation row stays usable with every Upscaling choice and the Upscaling row keeps frame
generation as set, with texts in all four menu languages. The log says at the start and at each change whether frame
generation takes its inputs from the upscaler or runs without one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scratch images of the fallback blit, one per size and format, were kept for the process, the first eight ever
asked for. Their sizes follow the scene's resolution, which DLSS modes, the dynamic resolution and a window or
picture size change move, at up to 32 MiB each at 2160 lines; a ninth size got no scratch and its blit was left out.
A new size now takes the place of the one used longest ago, which goes once the GPU is past the submission being
recorded, and never one the submission being recorded uses. The cache is under a lock, since replay workers record
blits side by side.
The tonemap pass's views were kept by image handle for the process, a game image's view naming a destroyed image that
a new image could get the handle of, and no view at all past sixteen. A view now goes with its image: at a scratch
image's replacement, and with a game image through the replay's image-gone hook.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cache kept the first eight sizes and formats for the whole run and refused every other one, so after a few
window, resolution or DLSS mode changes the stand-in's blit of a new size was left out, while the old sizes' images
stayed. A new size now takes a free entry or the one used longest ago, whose image goes through the deferred
destruction after the tonemap pass drops its views of it (vk_tonemap_forget), before another image can take its
handle. tests/scratch_lru_check.py checks the choice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The larger pool asks for 655,360 sampled images in one set where 524,288 were asked before. The start now logs the
device's limits for one set and one stage next to that count, warns when the count is over them, and a refused
layout ends the start with the limits named instead of a bare failure.
The log line for a slot registered past the pool names both slots a shader reads instead: the pool's last entry, and
the last of the old length for a translation carried over from the translator before. The comments say what they say
without an issue number.
The translator's identity changes with this pool size: after the update every install translates its shaders again
in the background, the earlier translations serve meanwhile, and a shader gets the ground fix once it is translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game makes a texture pool of 33068 descriptors, and the translator clamped every pool handle to 32767, so the textures registered from 32768 up were drawn with whatever slot 32767 held, often an empty image: ground went see-through after an area load until the area was loaded again. VK_POOL_TEX and the translator's TexturePoolLength go to 40960, and a slot registered past it is logged once.
The look-ahead of a submission passed over mesh draws, as their pipelines had no stage libraries. Where they have
them now, a mesh draw the scan reaches is looked up with its mesh and task stages and its state is named to the
pipeline hints, so the library of its task and mesh stages, the one that takes longest to make, is building before
the replay gets to the draw. Never with the mesh fallback, which draws through vertex pipelines of its own.
A mesh draw that waits for a fast link was cut off once its pipeline had been building for 8 ms, since no link
could come. That rule now holds only for a mesh pipeline without stage libraries: with them the draw waits as a
vertex draw does, the few milliseconds of pipe_link_draw_ms from its pipeline's first miss. A one-shot mesh draw
keeps its 8 ms: when the caches lack the task and mesh library, making it is as long a compile as the whole pipeline
was, and the frame's wait pool is not spent on that.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Two play sessions answered the question they were added for: about 0.3 whole-draw skips a frame are lost to the generation moving alone, too little to build the per-slot check for.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A scene draw whose vertex shader reads a matrix palette and whose vertex layout feeds its joint inputs unsigned integers is drawn a second time into the motion vectors with last frame's blocks and camera, as the player's meshes are, with the same depth test and the same guard for bones that turn sharply. Which draw of last frame it continues is decided when the vectors are made, so the replay's threads cannot change it; a draw in doubt keeps the camera's motion. The player's own draws stay with their ownership records.
skin_motion (NX2NAVI_SKIN_MOTION) is 1 by default and 0 turns it off. The motion status line now counts the skinned draws seen, those drawn with their own motion and the others by reason. It has compiled and passed its checks; it has not been played.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The game waits each frame until the GPU has finished the frame before. In Hyrule Field with FSR 4 the last of that
work is one part of 4.7 ms, the temporal upscaler and the interface drawn after it, then 1.4 ms of write-backs, and
what the game reads of the frame is done before the upscaler runs. With NX2NAVI_TAIL_EARLY the replay records that
tail into a command buffer of its own, and when it hands the CPU nothing but timestamps, writes no pool memory but
inline uniform updates of mirrored blocks and reads no guest memory late, the write-backs go to the queue before it
with the executed timeline's signal. The game's waits end there, and the GPU runs the tail while the next frame is
prepared. The GPU's commands are the same; a tail that fails a test goes as before. Off by default.
RX 7900 XT, Ryzen 7 5800X3D, 3440x1440, FSR 4 Native AA, one batch, replay workers off: 50.7 against 42.5 fps
standing and 56.0 against 45.9 along the field route; with FSR 3.1, 51.8 against 47.8 and 54.9 against 52.3. With
three replay workers, FSR 4, standing: 63.5 against 49.4. The screenshots matched those without it standing, along
the route and through warps to five areas, with no black frame outside the loading screens of both.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Registrations no longer move the generation while a submission is replayed, but the replay's own lookups still do when they take a released texture back or make one, and so does a texture another thread of the game finalizes meanwhile. Every such move drops the whole-draw skip, the shader memo and the kept descriptors of a draw, buffer descriptors included, whether or not the slots the draw reads changed.
A new line in the texture pool's periodic log says, per frame, how many graphics binds found the generation moved within their command buffer, how many whole-draw skips and shader memo hits it alone cost, and how many kept buffer and texture descriptors were resolved again from the values they were kept under. One play session then shows whether checking only the slots a draw reads would spare anything. The counts are kept per recording context, since the replay workers record at once.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The stand-in for the game's upscaler enlarged the scene with a linear filter, which is softer than the console's picture. It now enlarges with the edge-adaptive filter the present already scales to the window with: the same shader, built a second time with a constant set so it squares what it filtered, as the linear pass does. The stored square-root values are filtered as before and the decode is the same. standin_filter = bilinear brings the linear pass back, and the setting is live. The linear pass also stays where nothing is enlarged and while DLSS or FSR 3.1 runs, since that upscaler scales the scene itself and its picture goes over the stand-in's. The movies do not go through this pass and are not changed.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Every indexed draw bound its index buffer. The command buffer's kept state now holds the last bound buffer, offset and index type, and a draw with the same three records no bind. They are cleared with the rest of that state wherever the replay continues in another command buffer or something else has bound into this one, as the kept vertex streams are, and the indices the replay makes itself for quads, line loops and byte indices go through the same bind.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A draw into targets the game renders every frame is left out while its shaders or pipeline build, because the next frame draws it again. A storage buffer the shaders write or an image they store into is not redrawn that way: with the draw left out, that memory keeps what it held for everything that reads it. Such a draw now takes the wait a draw into one-shot targets takes, from the frame's wait pool, and is skipped only at the end of it. Only the stages already translated can say that they write; a draw whose writing stage is still being translated is skipped as before. draw_writers_wait = 0 turns the rule off, and the periodic line about waited draws counts the draws that waited by it alone.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A pass mark names the pass or dispatch the GPU had started and the last it finished. When the two are the same mark the GPU stopped in between, and the report named nothing: in a player's log of 0.9.2 (RX 6700 XT) both were one compute dispatch, with 51 later marks made and none started. The report now adds the mark that never started and the kinds of commands the replay recorded between the two (copies, clears, the upscaler, write-backs), which the gap notes already name for the GPU pass timing and now keep for every mark.
draw_crumbs = 1 (off by default, AMD buffer marks only) also writes a mark wherever the shaders or the pipeline state change inside a pass. The device losses reported on RX 6600 and RX 6800 class cards end inside one scene pass of 30 to 100 draws and the draw that hangs is not known; with the setting on, the report lists the shaders of the draws the GPU had not finished.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The settings had a tab of their own; they are rows of the Game tab now, listed under the switch once the mode is on after a restart, and the tab is gone. With the mode off every one of them reads as unset, whatever nx2navi.ini or the environment says, so nothing of the mode acts unless a player turned it on; the build reads guest_opt the same way. The notes describe that, and take the white edges in towns and the dropped textures from the known issues to the fixes.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
In towns, kerb, stair and wall-top edges are drawn untextured for one frame when the game registers a round of streamed textures, and no pool entry of those textures goes to the stand-in in such a frame. A binding written into a draw's own set, and a slot that is unregistered when its marks are read, left no event until now.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Mesh shaders as compute, the texture that gets memory of its own, staged mirror uploads and the shorter wait for the GPU, widened write-watch queries, the video memory limits and reserve, early upload batches, the asynchronous replay, the hot-pool rule and the recording thread on AMD cards without mesh shaders were measured on a Radeon RX 580 and run on no other card. Each keeps what 0.9.1 did unless the key or its own setting says otherwise; the key gives the defaults the way the device profile does. The notes describe them as that option, hold main's two new entries in one Changed section, and list the white edges in towns as a known issue.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The game's scene upscaler samples the post pass's picture through an RGB9E5 texture at the scene target's address. On the console both are one memory; here they are two images, and the RGB9E5 one kept what it held when it was made. With NX2NAVI_NATIVE_TENSOR=1 the network therefore read an old picture or the target's R11G11B10F words as shared-exponent texels, the source of the infinities and noise it wrote. The stand-in already read the target's image for this reason. A network program's output image without storage use is now logged.
Only dispatches of tensor-core programs take the new path, and those run only with NX2NAVI_NATIVE_TENSOR=1, which stays 0 by default. The file compiles without warnings; no game run has checked the picture.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The slope term is a bias per unit of depth slope across one pixel of the target. A shadow map made k times larger on
each side (shadow_scale 2 to 4) has 1/k of the slope across each texel, so the game's factor alone gave k times less
bias on sloped surfaces than at the game's own size, which is what shows as acne. vk_exec_draw.c slope_bias
multiplies the factor by the pass's scale for both the direct draws (record_fixed) and the queued mesh draws; the
constant term counts the depth format's smallest steps and keeps its value. Nothing changes at shadow_scale 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: the settings key lists take both sides; frame generation keeps the camera-cut handling and logs its counters every 600 frames with the cuts among them; the pass-time mark moves into draw_viewport; mo_desc keeps the pose depth and notes the reader.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Ten slots a frame are noted as having no picture when their submission ends: the 128x72 and 64x36 volumes the game registers under new slot numbers in every frame. The events now tell a slot whose entry cannot be viewed in any kind a shader reads from one whose lookup found nothing.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
With the HUD hidden the screen showed the scene at its render size instead of the upscaler's picture. The upscaler ran at the pass after the game's own upscaler, taken for the pass the HUD is drawn into. Without a HUD the game draws no such pass: the next one is the frame's last, which encodes the stand-in's scaled scene into the 8-bit output. The upscaler's picture went into that target and the last draw drew over it. A player's save with the HUD off showed blocks of render pixels, a picture jumping by a render pixel between frames, and the undrawn edge row in white, at every FSR mode and at Native AA; every test save until now had the HUD on. vk_dlaa_upscaler now runs the upscaler right after the stand-in of the game's upscaler wrote its output image, and puts the picture there (NX2NAVI_DLAA_AT_UPSCALER=0: as before).
The edge fill moves to the end of each pass that took the jitter (vk_dlaa_pass_end), for the pass's colour targets and depth, in one copy of eight regions per image. Filled only before the upscaler, the undrawn row had already been read by the water, the bloom and the anti-aliasing pass.
On a copy of the player's save (HUD off), FSR 4 Ultra Performance, world slowed to 1/20, present screenshots 0.25 s apart: the picture moves 0.00 to 0.02 px between shots (old placement: a jump of one render pixel in 5 of 20), the change on edges is 1.9 against 16.9, and no shot has the white row (half had it, with HDR on). Native AA, Performance with HDR, and FSR 3.1 are steady and clean as well; the save with the HUD on is unchanged. A run through a stream: 0 of 106 shots with a bright bottom edge (20 of 102). Frame rate 58.3 fps against 58.6.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
While the player runs through a town the game moves about 32 textures to new storage every four or five frames, back to back in one block, and a frame after their first upload every page of the block shows a write again. A texture up to 64 KiB keeps a hash of its storage and is uploaded again only when its bytes changed: of those, 0 to 4 in a hundred are (9 of 440 in one recording of 600 frames). A larger one has no hash and 75 to 94 in a hundred are uploaded again, whatever the size and format: 3,034 second uploads in those 600 frames beside 4,233 first ones, the ASTC ones decoded and encoded on the GPU a second time. The frames of the second uploads take 0.9 to 2.5 ms longer than frames without uploads, and the frames after them wait 0.3 to 1.3 ms longer for the GPU (six recordings of one player).
tex_same_big = 1 has textures up to 8 MiB keep the hash too. It is off by default: the bytes are read once more at the first upload and once at the comparison, 15 us each for 300 KiB, which together is about what the second upload takes on the submitting thread (27 us), so what it gains is the GPU's work and whatever else a second upload costs its frame, and that is not measured in play. A texture whose comparisons found other bytes twice in a row takes no hash any more, and an area load takes none.
The hash runs eight chains side by side and folds them, 20 GB/s against 7 for the one chain on a Ryzen 7 9850X3D, which the small textures' comparisons get as well. Each step is still a bijection of its chain and of the word it takes.
The pool log's buckets line counts the hashed uploads and the comparisons when there are any. --probe-vkmem has a case that passes with the switch off and on: the same bytes written again, other bytes, and the same bytes after two rewrites in a row.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A draw can be left out before it opens a render pass, and the pass list of a frame capture then showed nothing of it.
passes.txt now ends with the rejected draws of the captured frame by mesh or vertex draw and reason, each with the
first program it met, and says so when there were none. A draw left out by NX2NAVI_SKIP_PROG or NX2NAVI_SKIP_SHADERS
is counted under a reason of its own. The frame to capture and its files are set together under the recorder's lock.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
gpl noted them in ssbo_info, which the bind skip and the kept descriptors leave out once they have warmed up: the
submissions were taken as reading no guest memory again, and the grass and moss flickered again after a minute of play
(every submit wait of a 2100-frame run ended without the GPU). gpa_reader looks once per pipeline for a table binding
and notes the submission at each bind_resources and mo_desc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The table names pools without a mirror, guest memory the GPU reads as it runs, and nothing marked such a submission
live: with NX2NAVI_MIRROR_STAGE the submit wait ended once the replay had submitted, and the game wrote the next frame's
foliage instances while the GPU still drew from them (small grass and moss flickering and sliding as the camera turned).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While an area loads, every refresh left slots, and with any left the next one walked the table: about 67,000 slot reads
and 1.65 ms of the replay per submission in Kokiri Forest on an RX 580, six submissions a frame (profile 2026-11-11:
pool_fill and the slot lookups under the first pool draws, mesh_emu_draw's among them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each draw's indices are now a range of its own and its task records too, reserved by an allocation shader (one
invocation, mesh_emu_alloc_spirv) for the run of draw records queued: before the task stages the task records and the
index ranges of draws without a task stage, after them the index ranges of the others (mesh workgroups with output,
which the task stage now counts in the record, times OutputPrimitives x vertices per primitive). The mesh stage takes
its indices from the draw's range with an atomic in its record; vertex slots stay the arena's, as the indices name
them. So no draw's stages need another's, and the queue (MEQ) records the fills as the draws come, then at the
pre-pass command buffer's end (vk_sync.c pre_end -> vk_exec_mesh_flush): allocation, every task stage, allocation,
every mesh stage, a barrier between each. One by one each draw took three barriers and copied the arena's counters
after the draw before it: an RX 580 spent 5.5 ms of a frame's GPU time on 69 draws' stages in Kokiri Forest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>