Files
NX2navi/runtime/gfx_abi_probe.c
epicmeme546 760a7aaaf9 Hold a memory region thawed after a write thaws it soon after its freeze, so the game's streaming buffers stop going back and forth between frozen and thawed.
Regions the game has not written for a while are write-protected so that the per-submission write-watch queries can skip them, and the first write thaws them. The game's loaders keep writing the buffers they stream an area into, so those regions were frozen again as soon as they had been idle a moment and thawed again moments later: running from the Kakariko Village gate across Hyrule Field froze 31,004 regions and thawed 28,918 by a write in nine minutes. While the loaders wrote and the driver locked new memory, each thaw's VirtualQuery took 15 to 20 ms: a loader thread stalled 78 to 111 ms in thaws in every one of the hitches of 125 to 140 ms of that walk, with the game's main thread waiting for it.

A write that thaws a region within a minute of its freeze now holds it thawed for 4 seconds, doubled at each such thaw up to 10 minutes; a region that stayed frozen longer starts again from nothing. On the same walk: 8,284 freezes and 6,355 thaws, no frame over 100 ms while Link ran where 29 and 52 a minute had been, the slowest 1 % of frames at 19 and 24 fps instead of 9 and 7, and at the walk's end, Link standing at the edge of Lake Hylia, 60 fps instead of 38 with the slowest 1 % at 43 instead of 20. The perf trace's write-watch line counts the holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 14:06:16 -05:00

153 lines
9.7 KiB
C

// --probe-gfx-abi: every host object of nvn_obj.h fits the storage nvn.h reserves
// for its NVN type, the layouts the game reads directly (NVNtextureView, the command buffer's inline fast-path
// fields) sit where NX2NAVI's NVN 55 code expects them, and the GPU address window lies above every guest region.
// The same facts are static assertions in nvn_obj.h; this mode prints the sizes so a change shows its margin.
// Needs no game files.
#include <stddef.h>
#include <stdio.h>
#include "gfx.h"
#include "platform/platform.h"
static int failures, checks;
#define CHECK(cond, ...) do { checks++; if (!(cond)) { failures++; printf(" FAIL: " __VA_ARGS__); putchar('\n'); } } while (0)
typedef struct { const char *host, *nvn; size_t size, reserved; } Fit;
#define FIT(T, H) {#T, #H, sizeof(T), sizeof(H)}
static const Fit fits[] = {
FIT(dev_builder_t, NVNdeviceBuilder), FIT(dev_t_, NVNdevice),
FIT(queue_builder_t, NVNqueueBuilder), FIT(queue_t, NVNqueue),
FIT(pool_builder_t, NVNmemoryPoolBuilder), FIT(pool_t, NVNmemoryPool),
FIT(dpool_t, NVNtexturePool), FIT(dpool_t, NVNsamplerPool),
FIT(tex_builder_t, NVNtextureBuilder), FIT(tex_t, NVNtexture),
FIT(view_t, NVNtextureView),
FIT(samp_builder_t, NVNsamplerBuilder), FIT(samp_t, NVNsampler),
FIT(prog_t, NVNprogram),
FIT(sync_t, NVNsync),
FIT(ev_builder_t, NVNeventBuilder), FIT(ev_t, NVNevent),
FIT(win_builder_t, NVNwindowBuilder), FIT(win_t, NVNwindow),
FIT(cmd_t, NVNcommandBuffer),
FIT(color_st, NVNcolorState), FIT(chmask_st, NVNchannelMaskState),
FIT(blend_st, NVNblendState), FIT(ms_st, NVNmultisampleState),
FIT(poly_st, NVNpolygonState), FIT(ds_st, NVNdepthStencilState),
FIT(va_st, NVNvertexAttribState), FIT(vs_st, NVNvertexStreamState),
};
_Static_assert(sizeof(NVNtextureView) == 16, "NVNtextureView is 16 bytes in NX2NAVI's NVN 55");
_Static_assert(offsetof(cmd_t, inline_cur) == 0x20 && offsetof(cmd_t, inline_end) == 0x28, "cmd_t inline fields");
_Static_assert(RT_GPU_VA_BASE >= RT_ADDRESS_LIMIT, "GPU address window overlaps the guest address space");
// Write watch: a committed heap page and an alias page, written once, are reported once.
static void check_write_watch(void) {
const RtGuestLayout *g = mem_layout();
uint64_t heap = 0, pages[8], n;
CHECK(mem_set_heap_size(0x200000, &heap) == 0 && heap == g->heap.base, "SetHeapSize 2 MiB");
mem_written_pages(heap, 0x200000, pages, 8); // drop whatever the commit itself marked
volatile uint8_t *p = (volatile uint8_t *)(uintptr_t)heap;
p[0x5000] = 1;
n = mem_written_pages(heap, 0x200000, pages, 8);
CHECK(n == 1 && pages[0] == heap + 0x5000, "heap write: %llu pages reported (want 1 at %#llx)",
(unsigned long long)n, (unsigned long long)(heap + 0x5000));
printf("write-watch: heap page written once: %llu reported at +%#llx\n", (unsigned long long)n,
n ? (unsigned long long)(pages[0] - heap) : 0ull);
n = mem_written_pages(heap, 0x200000, pages, 8);
CHECK(n == 0, "second call reported %llu pages (want 0)", (unsigned long long)n);
printf("write-watch: second call: %llu reported\n", (unsigned long long)n);
p[0x7000] = 2;
p[0x9000] = 3;
p[0xA000] = 4;
n = mem_written_pages(heap, 0x200000, pages, 2);
uint64_t m = mem_written_pages(heap, 0x200000, pages + 2, 6);
printf("write-watch: three pages written, max 2: %llu then %llu\n", (unsigned long long)n, (unsigned long long)m);
CHECK(n == 2 && m == 1, "max 2 reported %llu, then %llu (want 2, 1)", (unsigned long long)n, (unsigned long long)m);
// Frozen region: read-only until the first write thaws it (the write lands and is reported); mem_thaw (a file
// read) thaws it and holds it thawed; SetHeapSize below decommits it.
mem_written_pages(heap, 0x200000, pages, 8);
CHECK(mem_freeze(heap) == 1 && mem_frozen(heap), "freeze of an idle committed 2 MiB region");
uint64_t end;
CHECK(plat_mem_state(heap, heap + 1, &end) == PLAT_PROT_READ, "frozen region is not read-only");
p[0x3000] = 7;
CHECK(!mem_frozen(heap) && p[0x3000] == 7, "a write into a frozen region did not thaw it");
n = mem_written_pages(heap, 0x200000, pages, 8);
CHECK(n == 1 && pages[0] == heap + 0x3000, "write after a thaw: %llu pages reported (want 1)", (unsigned long long)n);
// written moments after its freeze: a region the game keeps writing, held thawed for a while (memory.c "Hot regions")
CHECK(mem_freeze(heap) == 0 && !mem_frozen(heap), "a region a write thawed right after its freeze was frozen again");
mem_thaw(heap + 0x6000, 0x1000);
CHECK(!mem_frozen(heap) && mem_freeze(heap) == 0, "mem_thaw did not thaw and hold the region");
p[0x6000] = 8;
printf("write-watch: frozen region thawed by a write (reported once) and by mem_thaw (held)\n");
uint64_t a = g->alias.base + 0x10000;
CHECK(mem_map_physical(a, 0x4000) == 0, "MapPhysicalMemory");
mem_written_pages(a, 0x4000, pages, 8);
*(volatile uint8_t *)(uintptr_t)(a + 0x1000) = 1;
n = mem_written_pages(a, 0x4000, pages, 8);
CHECK(n == 1 && pages[0] == a + 0x1000, "alias write: %llu pages reported (want 1)", (unsigned long long)n);
CHECK(mem_written_pages(a, 0x4000, pages, 8) == 0, "alias: second call reported pages");
// A region with reserved holes freezes its committed pages only, and a write thaws them without committing a hole.
uint64_t ra = a & ~0x1fffffull;
CHECK(mem_freeze(ra) == 1 && mem_frozen(ra), "freeze of a 2 MiB region with reserved holes");
CHECK(plat_mem_state(a, a + 1, &end) == PLAT_PROT_READ, "its committed pages are not read-only");
*(volatile uint8_t *)(uintptr_t)(a + 0x2000) = 9;
CHECK(!mem_frozen(ra) && *(volatile uint8_t *)(uintptr_t)(a + 0x2000) == 9, "a write did not thaw the region with holes");
CHECK(plat_mem_state(a, a + 0x10000, &end) == PLAT_PROT_RW && end == a + 0x4000, "the thawed pages are not read-write");
CHECK(plat_mem_state(a + 0x4000, a + 0x4001, &end) == PLAT_MEM_RESERVED, "a hole got committed");
printf("write-watch: region with reserved holes frozen and thawed by a write\n");
// NX2NAVI_WB_PROBE=1 (memory.c, "Probed regions"): a probed region is inaccessible until its first access, which is
// noted and runs again; a frozen one goes back to read-only first and then thaws on a write; mem_thaw ends a probe
// and holds the region. With the setting off mem_probe arms nothing.
if (mem_probing()) {
volatile uint8_t *q = (volatile uint8_t *)(uintptr_t)a;
CHECK(mem_probe(ra, 0x200000) == 1, "probe of a 2 MiB region");
CHECK(plat_mem_state(a, a + 1, &end) == PLAT_PROT_NONE, "a probed region is not inaccessible");
CHECK(q[0x2000] == 9, "a read of a probed region did not return its byte");
CHECK(plat_mem_state(a, a + 0x10000, &end) == PLAT_PROT_RW && end == a + 0x4000, "the read did not end the probe");
CHECK(mem_freeze(ra) == 1 && mem_probe(ra, 0x200000) == 1, "probe of a frozen region");
q[0x3000] = 5; // ends the probe (read-only again), faults once more and thaws
CHECK(!mem_frozen(ra) && q[0x3000] == 5, "a write into a probed frozen region did not land");
CHECK(plat_mem_state(a, a + 0x10000, &end) == PLAT_PROT_RW && end == a + 0x4000, "its pages are not read-write after it");
CHECK(mem_probe(ra, 0x200000) == 1, "probe again");
mem_thaw(a, 0x1000);
CHECK(plat_mem_state(a, a + 1, &end) == PLAT_PROT_RW && mem_probe(ra, 0x200000) == 0,
"mem_thaw did not end the probe and hold the region");
mem_probe_report(0);
printf("write-back probe: a read and a write end a probe and run again, a frozen region thaws after it, "
"mem_thaw ends and holds\n");
} else {
CHECK(mem_probe(ra, 0x200000) == 0, "mem_probe armed a region with the probe off");
}
CHECK(mem_unmap_physical(a, 0x4000) == 0, "UnmapPhysicalMemory");
CHECK(mem_written_pages(RT_KERNEL_BASE, 0x1000, pages, 8) == UINT64_MAX, "kernel region is not write-watched");
CHECK(mem_set_heap_size(0, &heap) == 0, "SetHeapSize 0");
printf("write-watch: alias page written once: reported once, then not\n");
}
int probe_gfx_abi(void) {
size_t tightest = (size_t)-1;
const char *tight = "";
for (size_t i = 0; i < sizeof fits / sizeof *fits; i++) {
const Fit *f = &fits[i];
CHECK(f->size <= f->reserved, "%s is %zu bytes, %s reserves %zu", f->host, f->size, f->nvn, f->reserved);
if (f->reserved - f->size < tightest) tightest = f->reserved - f->size, tight = f->host;
}
CHECK(sizeof(NVNtextureView) == 16 && sizeof(view_t) == 16, "NVNtextureView %zu bytes, view_t %zu (want 16)",
sizeof(NVNtextureView), sizeof(view_t));
CHECK(offsetof(cmd_t, guest_flags) == 0x10, "cmd_t.guest_flags at %#zx (want 0x10)", offsetof(cmd_t, guest_flags));
CHECK(offsetof(cmd_t, inline_cur) == 0x20, "cmd_t.inline_cur at %#zx (want 0x20)", offsetof(cmd_t, inline_cur));
CHECK(offsetof(cmd_t, inline_end) == 0x28, "cmd_t.inline_end at %#zx (want 0x28)", offsetof(cmd_t, inline_end));
CHECK(sizeof(samp_par) == 44, "samp_par is %zu bytes (want 44)", sizeof(samp_par));
CHECK(RT_GPU_VA_BASE >= RT_ADDRESS_LIMIT && RT_GPU_VA_SIZE > 0, "GPU address window %#llx overlaps the guest space",
RT_GPU_VA_BASE);
check_write_watch();
printf("gfx-abi: %d layouts fit (tightest %s, %zu bytes spare), cmd_t inline fields at +0x20/+0x28, "
"NVNtextureView %zu bytes\n", (int)(sizeof fits / sizeof *fits), tight, tightest, sizeof(NVNtextureView));
if (failures) {
printf("probe-gfx-abi: FAILED (%d of %d checks)\n", failures, checks);
return 1;
}
printf("probe-gfx-abi: passed\n");
return 0;
}