mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -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>
121 lines
7.4 KiB
C
121 lines
7.4 KiB
C
// runtime/vk_target_frames.h: which passes count as rendered frame after frame, so that a draw into them is skipped
|
|
// while its pipeline builds instead of holding the replay thread. Built and run by tests/target_frames_check.py;
|
|
// prints each failed check and exits 1 on any.
|
|
// The frame numbers of the first block are those of a player's log: an area change after 238 frames of loading
|
|
// screen, where every pass read as one-shot and six mesh draws waited 52 to 660 ms each.
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "vk_target_frames.h"
|
|
|
|
static int fails;
|
|
#define CHECK(c, ...) do { if (!(c)) { fails++; printf("FAIL line %d: ", __LINE__); printf(__VA_ARGS__); printf("\n"); } } while (0)
|
|
|
|
static TfrTarget T(uint64_t image, uint32_t level, uint32_t layer, uint64_t owner) {
|
|
return (TfrTarget){image, level, layer, owner};
|
|
}
|
|
|
|
// The verdict with the steady rule and without it; *by: only the steady rule made it per-frame.
|
|
static int pass(const TfrTarget *c, int n, const TfrTarget *d, uint64_t stamp, int any, int steady, int *by) {
|
|
int b = 0, r = tfr_pass_recent(c, n, d, stamp, any, steady, &b);
|
|
if (by) *by = b;
|
|
return r;
|
|
}
|
|
|
|
int main(void) {
|
|
int by;
|
|
// ---- the area change of the log
|
|
TfrTarget scene = T(0x1000, 0, 0, 11), scene_d = T(0x1001, 0, 0, 12); // drawn every frame
|
|
TfrTarget probe = T(0x2000, 0, 0, 21), probe_d = T(0x2001, 0, 0, 22); // every second frame
|
|
TfrTarget probe_l3 = T(0x2000, 0, 3, 21); // a layer drawn every 15th frame
|
|
TfrTarget half = T(0x3000, 0, 0, 31), half_d = T(0x3001, 0, 0, 32); // the half-resolution buffers
|
|
for (uint64_t f = 16000; f <= 17925; f++) {
|
|
tfr_note(&scene, f), tfr_note(&scene_d, f), tfr_note(&half, f), tfr_note(&half_d, f);
|
|
if (f % 2) tfr_note(&probe, f), tfr_note(&probe_d, f);
|
|
if (f % 15 == 0) tfr_note(&probe_l3, f);
|
|
}
|
|
// the frame after: per-frame by being recent, with or without the rule
|
|
CHECK(pass(&scene, 1, &scene_d, 17926, 1, 0, NULL) == 1, "a pass drawn the frame before is per-frame");
|
|
CHECK(pass(&scene, 1, &scene_d, 17926, 0, 0, NULL) == 1, "strict: every target recent");
|
|
CHECK(pass(&scene, 1, &scene_d, 17926, 1, 1, &by) == 1 && !by, "recent passes are not counted as steady ones");
|
|
// 238 frames of loading screen later (its own targets are other images)
|
|
const uint64_t now = 18163;
|
|
TfrTarget load = T(0x9000, 0, 0, 91);
|
|
for (uint64_t f = 17926; f < now; f++) tfr_note(&load, f);
|
|
CHECK(pass(&scene, 1, &scene_d, now, 1, 0, NULL) == 0, "without the rule the scene pass reads as one-shot after the pause");
|
|
CHECK(pass(&scene, 1, &scene_d, now, 1, 1, &by) == 1 && by, "the scene pass is per-frame after the pause");
|
|
CHECK(pass(&scene, 1, &scene_d, now, 0, 1, &by) == 1 && by, "strict: the scene pass too");
|
|
CHECK(pass(&probe, 1, &probe_d, now, 1, 1, NULL) == 1, "the probe pass (128x128, seen 17925/17923) is per-frame");
|
|
CHECK(pass(&probe_l3, 1, &probe_d, now, 1, 0, NULL) == 0, "a layer drawn every 15th frame is never recent");
|
|
CHECK(pass(&probe_l3, 1, &probe_d, now, 1, 1, NULL) == 1, "a layer drawn every 15th frame, in 128 frames, is steady");
|
|
// the 960x540 pass of the log: one colour target made new, one steady, the depth target drawn earlier this frame
|
|
TfrTarget fresh = T(0x4000, 0, 0, 41), cc[2] = {fresh, half};
|
|
tfr_note(&fresh, now), tfr_note(&half, now), tfr_note(&half_d, now); // the pass begins: its targets are noted
|
|
CHECK(pass(cc, 2, &half_d, now, 1, 0, NULL) == 0, "without the rule: no recent colour target, a new one beside");
|
|
CHECK(pass(cc, 2, &half_d, now, 1, 1, &by) == 1 && by, "a steady colour target beside a new one: per-frame");
|
|
CHECK(pass(cc, 2, &half_d, now, 0, 1, NULL) == 0, "strict: the new colour target keeps the pass one-shot");
|
|
// the next frame all of it is recent again
|
|
tfr_note(&scene, now), tfr_note(&scene_d, now);
|
|
CHECK(pass(&scene, 1, &scene_d, now + 1, 1, 0, NULL) == 1, "recent again in the next frame");
|
|
CHECK(pass(&fresh, 1, &half_d, now + 1, 1, 0, NULL) == 1, "the new target is recent in its second frame");
|
|
|
|
// ---- what stays one-shot
|
|
TfrTarget lut = T(0x5000, 0, 0, 51);
|
|
CHECK(pass(&lut, 1, NULL, now, 1, 1, NULL) == 0, "a target never rendered is one-shot");
|
|
tfr_note(&lut, now);
|
|
CHECK(pass(&lut, 1, NULL, now, 1, 1, NULL) == 0, "its first frame, already noted by its pass, is still one-shot");
|
|
tfr_note(&lut, now); // a second pass into it in the same frame
|
|
for (int load_no = 1; load_no < TFR_STEADY; load_no++) { // generated again at each area load, far apart
|
|
uint64_t f = now + (uint64_t)load_no * 5000;
|
|
CHECK(pass(&lut, 1, NULL, f, 1, 1, NULL) == 0, "a table generated at %d area loads is one-shot at the next", load_no);
|
|
tfr_note(&lut, f);
|
|
}
|
|
CHECK(pass(&lut, 1, NULL, now + (uint64_t)TFR_STEADY * 5000, 1, 1, NULL) == 1,
|
|
"after %d loads the table counts as steady: its pipelines are recorded by then", TFR_STEADY);
|
|
// a volume generated a few slices per frame: each slice is its own target and is rendered once
|
|
for (uint32_t z = 0; z < 64; z++) {
|
|
TfrTarget slice = T(0x6000, 0, z, 61);
|
|
CHECK(pass(&slice, 1, NULL, now + 10 + z / 4, 1, 1, NULL) == 0, "volume slice %u is one-shot", z);
|
|
tfr_note(&slice, now + 10 + z / 4);
|
|
}
|
|
// the item icon capture: a new colour target with the per-frame depth buffer, once
|
|
TfrTarget icon = T(0x7000, 0, 0, 71);
|
|
uint64_t f2 = now + 1;
|
|
tfr_note(&scene_d, f2), tfr_note(&icon, f2);
|
|
CHECK(pass(&icon, 1, &scene_d, f2, 1, 1, NULL) == 0, "an icon's first capture is one-shot though its depth buffer is recent");
|
|
CHECK(pass(&icon, 1, &scene_d, f2, 1, 0, NULL) == 0, "and was before the rule");
|
|
// an image handed on to a texture at other storage starts again: the per-frame history of its handle is not the
|
|
// new texture's
|
|
TfrTarget reused = T(scene.image, 0, 0, 500);
|
|
CHECK(tfr_steady(&scene, f2 + 300) == 1, "the scene target is steady");
|
|
CHECK(pass(&reused, 1, NULL, f2 + 300, 1, 1, NULL) == 0, "the same image as another texture is not steady");
|
|
tfr_note(&reused, f2 + 300);
|
|
CHECK(tfr_steady(&scene, f2 + 301) == 0, "and the texture that had it lost its count with the image");
|
|
CHECK(tfr_find(&reused, 0)->n == 1, "the new texture's count begins at one");
|
|
// a pass with no target at all
|
|
CHECK(pass(NULL, 0, NULL, now, 1, 1, NULL) == 0 && pass(NULL, 0, NULL, now, 0, 1, NULL) == 0, "no targets: not per-frame");
|
|
|
|
// ---- the table keeps the steady targets through an area's first frame
|
|
// 400 per-frame targets, a pause, then 600 targets made new are rendered before the old ones come round again
|
|
memset(tfr, 0, sizeof tfr);
|
|
for (uint64_t f = 1; f <= 100; f++)
|
|
for (uint32_t i = 0; i < 400; i++) {
|
|
TfrTarget t = T(0x7f3a00000000ull + (uint64_t)(i / 4) * 0x2c0, 0, i % 4, 1000 + i / 4);
|
|
tfr_note(&t, f);
|
|
}
|
|
for (uint32_t i = 0; i < 600; i++) {
|
|
TfrTarget t = T(0x7f3b00000000ull + (uint64_t)i * 0x2c0, 0, 0, 5000 + i);
|
|
tfr_note(&t, 400);
|
|
}
|
|
int kept = 0;
|
|
for (uint32_t i = 0; i < 400; i++) {
|
|
TfrTarget t = T(0x7f3a00000000ull + (uint64_t)(i / 4) * 0x2c0, 0, i % 4, 1000 + i / 4);
|
|
kept += tfr_steady(&t, 400);
|
|
}
|
|
CHECK(kept == 400, "%d of 400 steady targets still known after 600 new ones in one frame", kept);
|
|
|
|
if (fails) return printf("%d checks failed\n", fails), 1;
|
|
printf("target_frames_check: ok\n");
|
|
return 0;
|
|
}
|