mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
Found in the first run of the previous two commits (a first visit of Kokiri Forest from a state that had only seen Hyrule Field): - The count of frames a target was rendered in started again whenever the guest's texture object changed, and the game initializes its render target objects anew every frame at the same storage: the log showed `seen 2129/2128 in 1 frames`, so no target ever became steady and the rule did nothing. The owner of an image is now the storage address the texture cache keys it by. An image handed on to a texture elsewhere still starts at none. - A one-shot mesh draw waited its 8 ms in every frame that met the same pipeline while it compiled for 150 to 1100 ms: 17 waits, 170 ms, every draw skipped at the end, up to four in one frame. A mesh pipeline that has been building for longer than that wait is a compile, and the draws after the first do not wait for it (vk_pipeline_building_ms). - The last wait of a full-screen draw with no picture to hold was 40 ms a draw, and two such draws made 86 ms in one frame. It is 40 ms a frame now. - A one-shot vertex draw skipped at the end of its wait gets its log line however short the wait was (seven were skipped with no line, so their targets could not be named). That run against the same cell of the earlier comparison (same exe path, cache state, route and settings; one run each): the load's worst wait of the draws in one frame 99 ms against 735 ms, no mesh wait over 13 ms against 524 and 206 ms. Two long frames remain in both runs (628 and 862 ms here, 660 and 854 ms there): two compute pipelines built at their first dispatch, about 395 and 397 ms. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
109 lines
6.8 KiB
C
109 lines
6.8 KiB
C
// Target history: the presented frame (gfx_counters.frames + 1; 0: never) each render target subresource was last
|
|
// rendered into, the one before, and in how many frames. vk_exec_draw.c prepare_draw_ex lets a draw into a target the
|
|
// game does not render frame after frame (a one-shot draw: a volume or lookup table generated once) wait for its
|
|
// shaders and pipeline instead of skipping it. Own header so tests/target_frames_check.c checks the rules without
|
|
// Vulkan; vk_exec.c is the one runtime file that includes it (the table is static).
|
|
// Per subresource, not per image: a volume may be generated a few slices per frame.
|
|
//
|
|
// Recent means within TFR_RECENT frames, not the frame before alone: the game's output images are a ring of three, its
|
|
// probe and shadow layers are refreshed in turn every second to fourth frame, and the mips of its small effect buffers
|
|
// alternate between images. Sets of TFR_WAYS, the oldest entry replaced: hundreds of subresources are rendered a
|
|
// frame, and in a direct-mapped table of 1024 a fifth of the scene passes and most of the probe layers read as never
|
|
// rendered because another subresource had taken their slot. Each misread made a draw whose pipeline was still
|
|
// building wait for it, so a new area was entered one pipeline at a time.
|
|
//
|
|
// Steady targets (NX2NAVI_PIPE_SKIP_STEADY, default 1; 0 = as before). Recent alone called every pass one-shot in the
|
|
// first frame after the game had drawn something else for a while (a loading screen between two areas, a full-screen
|
|
// menu): the scene's own targets, the probe layers and the half-resolution buffers had last been rendered hundreds of
|
|
// frames before, so each draw with a new pipeline waited on the replay thread, and a mesh draw, whose pipeline has no
|
|
// fast link, for its whole compile. One player's session shows it three times at area changes: frames of 954, 1015
|
|
// and 684 ms, six mesh draws waiting 52 to 660 ms each in passes whose targets had been rendered in thousands of
|
|
// frames. An entry therefore also counts the frames its subresource was rendered in, for as long as the image is the
|
|
// texture at the same storage (its owner, the storage GPU address the texture cache keys the image by: an image
|
|
// handed on to a texture elsewhere starts again at none). The guest's texture object does not serve as the owner:
|
|
// the game initializes its render target objects anew every frame at the same storage, so a count kept per object
|
|
// never passed one. From TFR_STEADY frames on the target is one the game draws again and again, and its draws are
|
|
// skipped while a pipeline builds, as in any other frame, however long ago the last one was. A target generated once
|
|
// never gets there; one generated again at every area load would after TFR_STEADY loads, by when its pipelines are
|
|
// recorded and prebuilt at the start.
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#define TFR_SETS 1024
|
|
#define TFR_WAYS 8
|
|
#define TFR_RECENT 4
|
|
#define TFR_STEADY 8
|
|
|
|
// One render target of a pass: the image handle as a number, the subresource, the image's owner (0 unknown).
|
|
typedef struct { uint64_t image; uint32_t level, layer; uint64_t owner; } TfrTarget;
|
|
typedef struct { uint64_t image; uint32_t level, layer, n; uint64_t last, prev, owner; } TargetFrames;
|
|
static TargetFrames tfr[TFR_SETS * TFR_WAYS];
|
|
|
|
// The target's entry, or with make the entry it takes (the set's oldest, an empty one first); NULL without make
|
|
// when it has none.
|
|
static inline TargetFrames *tfr_find(const TfrTarget *t, int make) {
|
|
uint64_t h = (t->image ^ (uint64_t)t->level << 56 ^ (uint64_t)t->layer << 40) * 0x9E3779B97F4A7C15ull;
|
|
TargetFrames *set = &tfr[(h >> 54) * TFR_WAYS], *oldest = set;
|
|
for (int i = 0; i < TFR_WAYS; i++) {
|
|
if (set[i].image == t->image && set[i].level == t->level && set[i].layer == t->layer) return &set[i];
|
|
if (set[i].last < oldest->last) oldest = &set[i];
|
|
}
|
|
if (!make) return NULL;
|
|
*oldest = (TargetFrames){.image = t->image, .level = t->level, .layer = t->layer};
|
|
return oldest;
|
|
}
|
|
|
|
// The target is rendered in frame stamp.
|
|
static inline void tfr_note(const TfrTarget *t, uint64_t stamp) {
|
|
TargetFrames *e = tfr_find(t, 1);
|
|
if (e->owner != t->owner) e->owner = t->owner, e->n = 0; // another texture has the image now: its own count
|
|
if (e->last != stamp) e->prev = e->last, e->last = stamp, e->n += e->n < UINT32_MAX;
|
|
}
|
|
|
|
// The frame before stamp that last rendered the target, 0 for none.
|
|
static inline uint64_t tfr_before(const TfrTarget *t, uint64_t stamp) {
|
|
const TargetFrames *e = tfr_find(t, 0);
|
|
return !e ? 0 : e->last == stamp ? e->prev : e->last;
|
|
}
|
|
|
|
static inline int tfr_recent(const TfrTarget *t, uint64_t stamp) {
|
|
uint64_t before = tfr_before(t, stamp);
|
|
return before && stamp - before <= TFR_RECENT;
|
|
}
|
|
|
|
// No frame before stamp rendered the target.
|
|
static inline int tfr_first(const TfrTarget *t, uint64_t stamp) { return !tfr_before(t, stamp); }
|
|
|
|
// TFR_STEADY frames or more before stamp rendered the target under the owner it has now.
|
|
static inline int tfr_steady(const TfrTarget *t, uint64_t stamp) {
|
|
const TargetFrames *e = tfr_find(t, 0);
|
|
return e && e->owner == t->owner && e->n - (e->last == stamp) >= TFR_STEADY;
|
|
}
|
|
|
|
// Whether the pass with the n_color colour targets c and the depth target d (NULL: none) is one the game renders
|
|
// frame after frame, so that a draw into it may be skipped in frame stamp and comes back in the next.
|
|
// any 0: every target is recent (and the pass has one). any 1: a colour target is, or the depth target is while no
|
|
// colour target is rendered for the first time: the game's item icon capture draws into a new colour target with the
|
|
// per-frame depth buffer, once, and its draws skipped there stay missing from the icon until the item is captured
|
|
// again. steady 1: a steady target counts as a recent one; *by_steady is set when only that made the answer 1.
|
|
static inline int tfr_pass_recent(const TfrTarget *c, int n_color, const TfrTarget *d, uint64_t stamp, int any,
|
|
int steady, int *by_steady) {
|
|
int n = 0, color[2] = {0, 0}, depth[2] = {0, 0}, color_first = 0, all[2] = {1, 1};
|
|
for (int i = 0; i <= n_color; i++) {
|
|
const TfrTarget *t = i < n_color ? &c[i] : d;
|
|
if (!t || !t->image) continue;
|
|
n++;
|
|
int recent = tfr_recent(t, stamp), kept = recent || (steady && tfr_steady(t, stamp));
|
|
if (recent) *(i < n_color ? &color[0] : &depth[0]) = 1;
|
|
else all[0] = 0;
|
|
if (kept) *(i < n_color ? &color[1] : &depth[1]) = 1;
|
|
else all[1] = 0;
|
|
if (i < n_color && tfr_first(t, stamp)) color_first = 1; // one never rendered is neither recent nor steady
|
|
}
|
|
int r[2]; // without the steady rule, and with it
|
|
for (int k = 0; k < 2; k++) r[k] = any ? color[k] || (depth[k] && !color_first) : n > 0 && all[k];
|
|
if (by_steady) *by_steady = r[1] && !r[0];
|
|
return r[1];
|
|
}
|