mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
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>
This commit is contained in:
@@ -55,12 +55,14 @@ static fmt_t fmt_of(int f) {
|
||||
|
||||
int nvn_fmt_bpb(int f, int *bw, int *bh) { fmt_t t = fmt_of(f); if (bw) *bw = t.bw; if (bh) *bh = t.bh; return t.bpb; }
|
||||
|
||||
static uint32_t mip(uint32_t s, unsigned l) { s >>= l; return s ? s : 1; }
|
||||
static uint32_t blk(uint32_t s, uint32_t b) { return (s + b - 1) / b; }
|
||||
static unsigned pick_tile(uint32_t gobs) { return gobs >= 16 ? 4 : gobs >= 8 ? 3 : gobs >= 4 ? 2 : gobs >= 2 ? 1 : 0; }
|
||||
static unsigned adj_tile(unsigned shift, uint32_t unit, uint32_t dim) {
|
||||
// Sizes in 64 bits: a descriptor's width or height times its samples or bytes per block can pass 2^32 (a texture
|
||||
// garbled or not yet set up), which in 32 bits wrapped to a small size and a small allocation.
|
||||
static uint64_t mip(uint64_t s, unsigned l) { s >>= l; return s ? s : 1; }
|
||||
static uint64_t blk(uint64_t s, uint64_t b) { return (s + b - 1) / b; }
|
||||
static unsigned pick_tile(uint64_t gobs) { return gobs >= 16 ? 4 : gobs >= 8 ? 3 : gobs >= 4 ? 2 : gobs >= 2 ? 1 : 0; }
|
||||
static unsigned adj_tile(unsigned shift, uint64_t unit, uint64_t dim) {
|
||||
if (!shift) return 0;
|
||||
uint32_t x = unit << (shift - 1);
|
||||
uint64_t x = unit << (shift - 1);
|
||||
if (x >= dim) while (--shift) { x >>= 1; if (x < dim) break; }
|
||||
return shift;
|
||||
}
|
||||
@@ -90,14 +92,14 @@ static uint64_t level_offset(const nvn_texdesc *d, unsigned level, unsigned dpl,
|
||||
uint32_t sx, sy; samples_xy(d->samples, &sx, &sy);
|
||||
uint64_t off = 0;
|
||||
for (unsigned i = 0; i < level; i++) {
|
||||
uint32_t lw = blk(mip(w * sx, i), t.bw);
|
||||
uint32_t lh = dpl >= 2 ? blk(mip(h * sy, i), t.bh) : 1;
|
||||
uint32_t ld = dpl >= 3 ? mip(z, i) : 1;
|
||||
uint32_t lwb = lw * t.bpb;
|
||||
uint64_t lw = blk(mip((uint64_t)w * sx, i), t.bw);
|
||||
uint64_t lh = dpl >= 2 ? blk(mip((uint64_t)h * sy, i), t.bh) : 1;
|
||||
uint64_t ld = dpl >= 3 ? mip(z, i) : 1;
|
||||
uint64_t lwb = lw * t.bpb;
|
||||
unsigned th = adj_tile(tileH, 8, lh), td = adj_tile(tileD, 1, ld);
|
||||
uint32_t wg = (lwb + 63) / 64, hg = (lh + 7) / 8;
|
||||
uint32_t wt = wg, ht = (hg + (1u << th) - 1) >> th, dt = (ld + (1u << td) - 1) >> td;
|
||||
off += (uint64_t)wt * ht * dt << (9 + th + td);
|
||||
uint64_t wg = (lwb + 63) / 64, hg = (lh + 7) / 8;
|
||||
uint64_t wt = wg, ht = (hg + (1u << th) - 1) >> th, dt = (ld + (1u << td) - 1) >> td;
|
||||
off += wt * ht * dt << (9 + th + td);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
@@ -116,7 +118,7 @@ static uint64_t layer_bytes(const nvn_texdesc *d, unsigned dpl, int layered,
|
||||
uint32_t w, uint32_t h, uint32_t z, unsigned tileH, unsigned tileD, fmt_t t) {
|
||||
uint64_t ls = level_offset(d, d->levels ? d->levels : 1, dpl, w, h, z, tileH, tileD, t);
|
||||
if (layered) {
|
||||
uint32_t hh = blk(h, t.bh); unsigned th = tileH, td = tileD;
|
||||
uint64_t hh = blk(h, t.bh); unsigned th = tileH, td = tileD;
|
||||
while (th && hh <= (8u << (th - 1))) th--;
|
||||
while (td && z <= (1u << (td - 1))) td--;
|
||||
uint64_t align = 1ull << (th + td + 9);
|
||||
@@ -150,7 +152,7 @@ void nvn_tex_layout(const nvn_texdesc *d, nvn_texlayout *o) {
|
||||
unsigned pl = (unsigned)(d->pkg_block_h - 1) & 0xFF;
|
||||
if (dpl >= 2) tileH = pl & 0xF;
|
||||
if (dpl == 3) tileD = pl >> 4;
|
||||
} else if (dpl == 2) { uint32_t ah = blk(h * sy, t.bh); tileH = pick_tile((ah + ah / 2) / 8); }
|
||||
} else if (dpl == 2) { uint64_t ah = blk((uint64_t)h * sy, t.bh); tileH = pick_tile((ah + ah / 2) / 8); }
|
||||
else if (dpl == 3) tileD = pick_tile(z + z / 2);
|
||||
uint64_t ls = layer_bytes(d, dpl, layered, w, h, z, tileH, tileD, t);
|
||||
if ((d->flags & 0x200) && !d->pkg_block_h && minimal_layout_on()) {
|
||||
@@ -178,8 +180,8 @@ void nvn_tex_level_tiles(const nvn_texdesc *d, unsigned level, unsigned *tileH,
|
||||
shape(d, &dpl, &layered, &w, &h, &z);
|
||||
uint32_t sx, sy; samples_xy(d->samples, &sx, &sy);
|
||||
nvn_texlayout L; nvn_tex_layout(d, &L);
|
||||
uint32_t lh = dpl >= 2 ? blk(mip(h * sy, level), t.bh) : 1;
|
||||
uint32_t ld = dpl >= 3 ? mip(z, level) : 1;
|
||||
uint64_t lh = dpl >= 2 ? blk(mip((uint64_t)h * sy, level), t.bh) : 1;
|
||||
uint64_t ld = dpl >= 3 ? mip(z, level) : 1;
|
||||
*tileH = adj_tile(L.tileH, 8, lh);
|
||||
*tileD = adj_tile(L.tileD, 1, ld);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ void nvn_tex_level_tiles(const nvn_texdesc *d, unsigned level, unsigned *tileH,
|
||||
/* LINEAR_RENDER_TARGET (0x40) uses pitch storage even without LINEAR (0x10).
|
||||
NX2NAVI's CPU-loaded water height/mask textures use that flag alone. */
|
||||
static inline int nvn_tex_is_linear(const nvn_texdesc *d) { return (d->flags & 0x50) != 0; }
|
||||
// computed in 64 bits; a stride past 32 bits (a garbled descriptor) saturates instead of wrapping to a small one
|
||||
static inline uint32_t nvn_tex_linear_stride(const nvn_texdesc *d) {
|
||||
if (d->stride) return d->stride;
|
||||
int bw, bh; int bpb = nvn_fmt_bpb(d->format, &bw, &bh);
|
||||
uint32_t align = (d->flags & 0x40) ? 128 : 32;
|
||||
return (((d->width + bw - 1) / bw) * bpb + align - 1) & ~(align - 1);
|
||||
uint64_t align = (d->flags & 0x40) ? 128 : 32;
|
||||
uint64_t s = ((((uint64_t)d->width + bw - 1) / bw) * bpb + align - 1) & ~(align - 1);
|
||||
return s > UINT32_MAX ? UINT32_MAX : (uint32_t)s;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,17 @@ int probe_nvn_tex(void) {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user