Files
NX2navi/runtime/nvn_tex_probe.c
chrg6 79b36044b0 Compute texture layout sizes in 64 bits, so a garbled descriptor cannot wrap to a small size.
nvn_tex.c multiplied a level's width by its samples and its blocks by their bytes in 32 bits, and
nvn_tex_linear_stride did the same: a descriptor with a width past 2^30 (garbled, or not set up yet) wrapped to a small
size, and the texture got a small allocation for a large image. Block counts, row bytes and GOB counts are now 64-bit
(mip, blk, pick_tile and adj_tile take 64-bit sizes), and the linear stride saturates at 2^32 - 1 instead of wrapping.
Real textures lay out as before.

Checked: --probe-nvn-tex gains two cases (an RGBA8 texture 2^30 + 64 texels wide, and its linear stride). Before, the
first failed: 2,048 bytes against 2^35 + 2,048. After, all 135 cases pass, the 40 layout goldens of the original SDK
among them; --probe-nvn shows the same four failures as the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 19:38:18 -05:00

116 lines
5.4 KiB
C

// nx2navi.exe --probe-nvn-tex: texture storage size, block height, alignment and mip offsets of nvn_tex.c.
// Contains code Copyright (c) 2026 jhaizhou (MIT; see THIRD_PARTY_NOTICES.md).
// The 40 size and block-height goldens are original-SDK CPU queries for uncompressed and BC3-sRGB boundary cases; no game bytes.
// The old rounded-up heuristic mismatches 16 of them, including the 160x160 case. Block-linear address checks
// belong to the deswizzler and are not part of this probe.
#include <stdint.h>
#include <stdio.h>
#include "host.h"
#include "nvn_tex.h"
typedef struct { unsigned format, width, height, levels, h; uint64_t size; } LayoutCase;
static const LayoutCase layout_cases[] = {
{0x25, 40, 1, 6, 0, 4608},
{0x25, 40, 8, 6, 0, 4608},
{0x25, 40, 9, 6, 0, 6144},
{0x25, 40, 10, 6, 0, 6144},
{0x25, 40, 11, 6, 1, 6144},
{0x25, 40, 16, 6, 1, 6144},
{0x25, 40, 17, 6, 1, 9216},
{0x25, 40, 21, 6, 1, 10240},
{0x25, 40, 22, 6, 2, 10240},
{0x25, 40, 32, 6, 2, 10240},
{0x25, 40, 33, 6, 2, 16384},
{0x25, 40, 40, 6, 2, 18944},
{0x25, 40, 42, 6, 2, 18944},
{0x25, 40, 43, 6, 3, 18944},
{0x25, 40, 64, 7, 3, 19456},
{0x25, 40, 65, 7, 3, 31744},
{0x25, 40, 85, 7, 3, 37376},
{0x25, 40, 86, 7, 4, 37376},
{0x25, 40, 128, 8, 4, 37888},
{0x25, 40, 129, 8, 4, 62464},
{0x48, 160, 4, 8, 0, 12288},
{0x48, 160, 32, 8, 0, 12288},
{0x48, 160, 36, 8, 0, 17408},
{0x48, 160, 40, 8, 0, 17408},
{0x48, 160, 44, 8, 1, 17408},
{0x48, 160, 64, 8, 1, 17408},
{0x48, 160, 68, 8, 1, 30208},
{0x48, 160, 84, 8, 1, 30208},
{0x48, 160, 88, 8, 2, 30208},
{0x48, 160, 128, 8, 2, 30208},
{0x48, 160, 132, 8, 2, 57344},
{0x48, 160, 160, 8, 2, 57344},
{0x48, 160, 168, 8, 2, 57344},
{0x48, 160, 172, 8, 3, 57344},
{0x48, 160, 256, 9, 3, 57856},
{0x48, 160, 260, 9, 3, 112128},
{0x48, 160, 340, 9, 3, 113152},
{0x48, 160, 344, 9, 4, 113152},
{0x48, 160, 512, 10, 4, 113664},
{0x48, 160, 516, 10, 4, 222208},
};
// Hand-derived from the layout rules: RGBA8 40x128, 8 levels, block height 16 GOBs shrinking per level.
static const uint64_t mip_offsets_rgba8_40x128[9] = {0, 24576, 32768, 34816, 35840, 36352, 36864, 37376, 37888};
static int fail(const char *what, unsigned i, unsigned format, unsigned w, unsigned h, unsigned long long got, unsigned long long want) {
printf("probe-nvn-tex: FAIL %s case %u format=%#x %ux%u: got %llu expected %llu\n", what, i, format, w, h, got, want);
return 1;
}
int probe_nvn_tex(void) {
unsigned n = 0;
for (unsigned i = 0; i < sizeof layout_cases / sizeof *layout_cases; i++) {
const LayoutCase *c = &layout_cases[i];
nvn_texdesc d = {.target = 1, .format = (int)c->format, .samples = 1, .width = c->width, .height = c->height, .depth = 1, .levels = c->levels};
nvn_texlayout l;
nvn_tex_layout(&d, &l);
if (l.tileH != c->h) return fail("block height", i, c->format, c->width, c->height, l.tileH, c->h);
if (l.size != c->size) return fail("size", i, c->format, c->width, c->height, l.size, c->size);
if (l.align != 512) return fail("alignment", i, c->format, c->width, c->height, l.align, 512);
n += 3;
}
nvn_texdesc pad = {.target = 1, .format = 0x48, .samples = 1, .width = 160, .height = 160, .depth = 1, .levels = 8};
unsigned h, dd;
nvn_tex_level_tiles(&pad, 0, &h, &dd);
if (h != 2 || dd != 0) return fail("160x160 BC3 base mip block height", 0, 0x48, 160, 160, h, 2);
pad.pkg_block_h = 4;
nvn_tex_level_tiles(&pad, 0, &h, &dd);
if (h != 3) return fail("explicit packaged H3 over automatic H2", 0, 0x48, 160, 160, h, 3);
n += 2;
nvn_texdesc m = {.target = 1, .format = 0x25, .samples = 1, .width = 40, .height = 128, .depth = 1, .levels = 8};
for (unsigned lv = 0; lv <= 8; lv++) {
uint64_t off = nvn_tex_level_offset(&m, lv);
if (off != mip_offsets_rgba8_40x128[lv]) return fail("mip offset", lv, 0x25, 40, 128, off, mip_offsets_rgba8_40x128[lv]);
n++;
}
// Pitch storage: LINEAR_RENDER_TARGET (0x40) aligns the stride to 128 bytes, LINEAR (0x10) to 32.
nvn_texdesc lin = {.target = 1, .format = 0x25, .flags = 0x40, .samples = 1, .width = 40, .height = 4, .depth = 1, .levels = 1};
nvn_texlayout l;
nvn_tex_layout(&lin, &l);
if (l.size != 256ull * 4 || l.align != 128) return fail("linear render target", 0, 0x25, 40, 4, l.size, 1024);
lin.flags = 0x10;
nvn_tex_layout(&lin, &l);
if (l.size != 160ull * 4 || l.align != 32) return fail("linear", 0, 0x25, 40, 4, l.size, 640);
n += 2;
// A width past 2^30 (a garbled descriptor): its size in 64 bits, never wrapped to a small one. RGBA8 2^30 + 64
// texels by 8 rows is one GOB row of (2^32 + 256)-byte rows: 2^26 + 4 GOBs of 512 bytes, 2^35 + 2048 bytes (in
// 32 bits the row wrapped to 256 bytes and the texture to 2048).
nvn_texdesc wide = {.target = 1, .format = 0x25, .samples = 1, .width = (1u << 30) + 64, .height = 8, .depth = 1, .levels = 1};
nvn_tex_layout(&wide, &l);
if (l.size != (1ull << 35) + 2048) return fail("width past 2^30", 0, 0x25, wide.width, 8, l.size, (1ull << 35) + 2048);
nvn_texdesc wide_lin = wide;
wide_lin.flags = 0x10;
if (nvn_tex_linear_stride(&wide_lin) != UINT32_MAX) return fail("linear stride past 2^32", 0, 0x25, wide.width, 8, nvn_tex_linear_stride(&wide_lin), UINT32_MAX);
n += 2;
printf("probe-nvn-tex: passed (%u cases)\n", n);
return 0;
}