From 79b36044b07aa5beeefaa1bfa7f4df7e31f9764c Mon Sep 17 00:00:00 2001 From: chrg6 Date: Wed, 11 Nov 2026 17:29:46 -0500 Subject: [PATCH] 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 --- runtime/nvn_tex.c | 34 ++++++++++++++++++---------------- runtime/nvn_tex.h | 6 ++++-- runtime/nvn_tex_probe.c | 11 +++++++++++ 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/runtime/nvn_tex.c b/runtime/nvn_tex.c index 4c5c6242..e085cc97 100644 --- a/runtime/nvn_tex.c +++ b/runtime/nvn_tex.c @@ -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); } diff --git a/runtime/nvn_tex.h b/runtime/nvn_tex.h index 844922ff..dd5c2b70 100644 --- a/runtime/nvn_tex.h +++ b/runtime/nvn_tex.h @@ -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; } diff --git a/runtime/nvn_tex_probe.c b/runtime/nvn_tex_probe.c index 841cca40..746f4c6a 100644 --- a/runtime/nvn_tex_probe.c +++ b/runtime/nvn_tex_probe.c @@ -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; }