mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
192 lines
10 KiB
C
192 lines
10 KiB
C
// Tegra X1 block-linear <-> linear copies.
|
|
// Contains code Copyright (c) 2026 jhaizhou (MIT; see THIRD_PARTY_NOTICES.md).
|
|
//
|
|
// Layout (from public descriptions):
|
|
// GOB = 64 bytes x 8 rows = 512 bytes; offset of (x byte, y row) within a GOB =
|
|
// (x%64/32)*256 + (y%8/2)*64 + (x%32/16)*32 + (y%2)*16 + x%16
|
|
// block = 1 GOB wide x 2^th GOBs high x 2^td GOBs deep, ordered y first then z inside a block; blocks fill the level
|
|
// in x -> y -> z row-major order.
|
|
// Level and layer start offsets and the block height and depth of a level come from nvn_tex.c; this file only does the
|
|
// addressing inside a level and the copy.
|
|
//
|
|
// Switch 2 (NVN2 titles, NVN API major version 200 and up) stores the 16-byte pieces of a GOB in another order:
|
|
// (x%64/32)*256 + (y%8/4)*128 + (x%32/16)*64 + (y%4)*16 + x%16
|
|
// (four rows of one 16-byte column before the next column). Found on Ocarina of Time's packaged textures: with it the
|
|
// ASTC 4x4 title logo and BC4 masks decode without seams (an edge-continuity search over all 120 orders of the five
|
|
// address bits picks it for both; OCARINA-BRINGUP.md round 18). Block and level arithmetic are unchanged.
|
|
#include "host.h"
|
|
#include "par_jobs.h"
|
|
#include "tex.h"
|
|
#include <string.h>
|
|
|
|
static int gob_nvn2(void) {
|
|
static int v = -1;
|
|
if (v < 0) v = rt_nvn_api_version[0] >= 200;
|
|
return v;
|
|
}
|
|
|
|
static uint32_t gob_off(uint32_t x, uint32_t y) {
|
|
if (gob_nvn2()) return (x % 64 / 32) * 256 + (y % 8 / 4) * 128 + (x % 32 / 16) * 64 + (y % 4) * 16 + x % 16;
|
|
return (x % 64 / 32) * 256 + (y % 8 / 2) * 64 + (x % 32 / 16) * 32 + (y % 2) * 16 + x % 16;
|
|
}
|
|
|
|
uint64_t nvn_bl_offset(uint32_t x, uint32_t y, uint32_t z, uint32_t row_bytes, uint32_t rows, unsigned th, unsigned td) {
|
|
uint32_t bh = 1u << th, bd = 1u << td;
|
|
uint64_t gobs_x = (row_bytes + 63) / 64, blocks_y = (rows + 8 * bh - 1) / (8 * bh);
|
|
uint64_t block = 512ull * bh * bd; // bytes in one block
|
|
return (z / bd) * gobs_x * blocks_y * block // which block layer (along depth)
|
|
+ (y / (8 * bh)) * gobs_x * block // which block row
|
|
+ (x / 64) * block // which block column
|
|
+ ((z % bd) * bh + (y % (8 * bh)) / 8) * 512ull // which GOB within the block
|
|
+ gob_off(x, y);
|
|
}
|
|
|
|
static uint32_t mip(uint32_t s, unsigned l) { s >>= l; return s ? s : 1; }
|
|
|
|
// Geometry of one level: bytes per row of blocks, block rows, slices, where the level and layer start in the storage,
|
|
// and the level's block height and depth. -1 for textures the copy does not handle.
|
|
int nvn_deswizzle_geom(const nvn_texdesc *d, unsigned level, unsigned layer,
|
|
uint32_t *row_bytes, uint32_t *rows, uint32_t *slices, uint64_t *base, unsigned *th, unsigned *td, uint32_t *bpb) {
|
|
if (d->target == 5 || d->target == 6 || d->target == 10 || nvn_tex_is_linear(d) || d->samples > 1) return -1; // MS / BUFFER / LINEAR
|
|
int bw, bh; *bpb = (uint32_t)nvn_fmt_bpb(d->format, &bw, &bh);
|
|
if (!*bpb || level >= (d->levels ? d->levels : 1)) return -1;
|
|
nvn_texlayout L; nvn_tex_layout(d, &L);
|
|
if (layer >= L.layers) return -1;
|
|
int one_d = d->target == 0 || d->target == 3;
|
|
uint32_t w = mip(d->width, level), h = one_d ? 1 : mip(d->height, level);
|
|
*slices = d->target == 2 ? mip(d->depth ? d->depth : 1, level) : 1;
|
|
*row_bytes = (w + bw - 1) / bw * *bpb; *rows = (h + bh - 1) / bh;
|
|
*base = (uint64_t)layer * L.layer_size + nvn_tex_level_offset(d, level);
|
|
nvn_tex_level_tiles(d, level, th, td);
|
|
return 0;
|
|
}
|
|
|
|
// nvn_bl_offset per row instead of per 16 bytes: within a row, GOB column k starts k blocks further, and the four
|
|
// 16-byte segments of a GOB row (x % 64 = 0, 16, 32, 48) sit at 0, 32, 256 and 288 of it (Switch 2: 0, 64, 256, 320).
|
|
// Rows [r0, r1) of the slices one after the other (row z * rows + y): each row finds its own place in the block-linear
|
|
// storage, so any range of rows copies on its own, and tex_bl_copy_mt hands ranges to several threads.
|
|
static void bl_copy_rows(uint8_t *bl, uint8_t *lin, size_t pitch, uint32_t row_bytes, uint32_t rows, uint64_t r0,
|
|
uint64_t r1, unsigned th, unsigned td, int to_linear) {
|
|
static const uint32_t seg_x1[4] = {0, 32, 256, 288}, seg_nvn2[4] = {0, 64, 256, 320};
|
|
const uint32_t *seg = gob_nvn2() ? seg_nvn2 : seg_x1;
|
|
uint64_t block = 512ull << th << td;
|
|
for (uint64_t r = r0; r < r1; r++) {
|
|
uint32_t z = (uint32_t)(r / rows), y = (uint32_t)(r % rows);
|
|
uint8_t *row = lin + (size_t)r * pitch, *g = bl + nvn_bl_offset(0, y, z, row_bytes, rows, th, td);
|
|
uint32_t x = 0;
|
|
if (to_linear)
|
|
for (; x + 64 <= row_bytes; x += 64, g += block)
|
|
for (int s = 0; s < 4; s++) memcpy(row + x + 16 * s, g + seg[s], 16);
|
|
else
|
|
for (; x + 64 <= row_bytes; x += 64, g += block)
|
|
for (int s = 0; s < 4; s++) memcpy(g + seg[s], row + x + 16 * s, 16);
|
|
for (int s = 0; x < row_bytes; s++, x += 16) { // a partial GOB at the row's end
|
|
uint32_t n = row_bytes - x < 16 ? row_bytes - x : 16;
|
|
if (to_linear) memcpy(row + x, g + seg[s], n); else memcpy(g + seg[s], row + x, n);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tex_bl_copy(uint8_t *bl, uint8_t *lin, size_t pitch, uint32_t row_bytes, uint32_t rows, uint32_t slices,
|
|
unsigned th, unsigned td, int to_linear) {
|
|
bl_copy_rows(bl, lin, pitch, row_bytes, rows, 0, (uint64_t)rows * slices, th, td, to_linear);
|
|
}
|
|
|
|
// tex_bl_copy over the par_jobs pool: a level of BL_MT_MIN bytes or more (smaller ones gained nothing: 0.5 MiB took
|
|
// 0.01 ms either way, 2 MiB went 3.3 times faster, 16 MiB 2.5 times) goes as bands of rows, about BL_MT_BAND
|
|
// bytes each, so a large texture's copy out of the game's memory (the upload's storage copy, 75% of its CPU time in an
|
|
// area's streaming) does not run on the drawing thread alone. The bytes written are those of tex_bl_copy.
|
|
#define BL_MT_MIN (1u << 20)
|
|
#define BL_MT_BAND (128u << 10)
|
|
typedef struct {
|
|
uint8_t *bl, *lin;
|
|
size_t pitch;
|
|
uint32_t row_bytes, rows;
|
|
uint64_t all, band;
|
|
unsigned th, td;
|
|
int to_linear;
|
|
} BlJob;
|
|
|
|
static void bl_band(void *arg, int i) {
|
|
const BlJob *j = arg;
|
|
uint64_t r0 = (uint64_t)i * j->band, r1 = r0 + j->band < j->all ? r0 + j->band : j->all;
|
|
bl_copy_rows(j->bl, j->lin, j->pitch, j->row_bytes, j->rows, r0, r1, j->th, j->td, j->to_linear);
|
|
}
|
|
|
|
void tex_bl_copy_mt(uint8_t *bl, uint8_t *lin, size_t pitch, uint32_t row_bytes, uint32_t rows, uint32_t slices,
|
|
unsigned th, unsigned td, int to_linear) {
|
|
uint64_t all = (uint64_t)rows * slices, bytes = all * row_bytes;
|
|
if (bytes < BL_MT_MIN || !row_bytes || par_threads() < 2) {
|
|
tex_bl_copy(bl, lin, pitch, row_bytes, rows, slices, th, td, to_linear);
|
|
return;
|
|
}
|
|
uint64_t band = BL_MT_BAND / row_bytes;
|
|
if (band < 1) band = 1;
|
|
BlJob j = {bl, lin, pitch, row_bytes, rows, all, band, th, td, to_linear};
|
|
par_run((int)((all + band - 1) / band), bl_band, &j);
|
|
}
|
|
|
|
static int move(const nvn_texdesc *d, unsigned level, unsigned layer, uint8_t *bl, uint8_t *lin, size_t pitch, int to_linear,
|
|
int mt) {
|
|
if (nvn_tex_is_linear(d)) {
|
|
if (level || layer || d->samples > 1 || (d->target != 0 && d->target != 1 && d->target != 7)) return -1;
|
|
int bw, bh, bpb = nvn_fmt_bpb(d->format, &bw, &bh);
|
|
if (!bpb) return -1;
|
|
size_t row = (size_t)((d->width + bw - 1) / bw) * bpb;
|
|
uint32_t rows = d->target == 0 ? 1 : (d->height + bh - 1) / bh, stride = nvn_tex_linear_stride(d);
|
|
if (pitch < row || stride < row) return -1;
|
|
for (uint32_t y = 0; y < rows; y++) {
|
|
if (to_linear) memcpy(lin + y * pitch, bl + (size_t)y * stride, row);
|
|
else memcpy(bl + (size_t)y * stride, lin + y * pitch, row);
|
|
}
|
|
return 0;
|
|
}
|
|
uint32_t row_bytes, rows, slices, bpb; uint64_t base; unsigned th, td;
|
|
if (nvn_deswizzle_geom(d, level, layer, &row_bytes, &rows, &slices, &base, &th, &td, &bpb)) return -1;
|
|
if (pitch < row_bytes) return -1;
|
|
(mt ? tex_bl_copy_mt : tex_bl_copy)(bl + base, lin, pitch, row_bytes, rows, slices, th, td, to_linear);
|
|
return 0;
|
|
}
|
|
|
|
int nvn_texels_box(const nvn_texdesc *d, unsigned level, unsigned layer, uint32_t x, uint32_t y, uint32_t z, uint32_t wb,
|
|
uint32_t h, void *storage, void *rows, size_t pitch, int to_linear) {
|
|
uint8_t *st = storage, *ln = rows;
|
|
if (nvn_tex_is_linear(d)) {
|
|
if (level || layer || z || d->samples > 1 || (d->target != 0 && d->target != 1 && d->target != 7)) return -1;
|
|
int bw, bh, bpb = nvn_fmt_bpb(d->format, &bw, &bh);
|
|
if (!bpb) return -1;
|
|
size_t row = (size_t)((d->width + bw - 1) / bw) * bpb;
|
|
uint32_t nrows = d->target == 0 ? 1 : (d->height + bh - 1) / bh, stride = nvn_tex_linear_stride(d);
|
|
if (stride < row || x + (size_t)wb > row || y + (uint64_t)h > nrows) return -1;
|
|
for (uint32_t r = 0; r < h; r++) {
|
|
uint8_t *p = st + (size_t)(y + r) * stride + x, *q = ln + r * pitch;
|
|
if (to_linear) memcpy(q, p, wb); else memcpy(p, q, wb);
|
|
}
|
|
return 0;
|
|
}
|
|
uint32_t row_bytes, nrows, slices, bpb; uint64_t base; unsigned th, td;
|
|
if (nvn_deswizzle_geom(d, level, layer, &row_bytes, &nrows, &slices, &base, &th, &td, &bpb)) return -1;
|
|
if (x + (uint64_t)wb > row_bytes || y + (uint64_t)h > nrows || z >= slices) return -1;
|
|
for (uint32_t r = 0; r < h; r++) {
|
|
uint8_t *q = ln + r * pitch;
|
|
for (uint32_t i = 0; i < wb;) {
|
|
uint32_t xb = x + i, n = 16 - xb % 16; // contiguous up to the end of its 16-byte segment
|
|
if (n > wb - i) n = wb - i;
|
|
uint8_t *p = st + base + nvn_bl_offset(xb, y + r, z, row_bytes, nrows, th, td);
|
|
if (to_linear) memcpy(q + i, p, n); else memcpy(p, q + i, n);
|
|
i += n;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int nvn_deswizzle(const nvn_texdesc *d, unsigned level, unsigned layer, const void *src, void *dst, size_t dst_pitch) {
|
|
return move(d, level, layer, (uint8_t *)src, (uint8_t *)dst, dst_pitch, 1, 0);
|
|
}
|
|
int nvn_deswizzle_mt(const nvn_texdesc *d, unsigned level, unsigned layer, const void *src, void *dst, size_t dst_pitch) {
|
|
return move(d, level, layer, (uint8_t *)src, (uint8_t *)dst, dst_pitch, 1, 1);
|
|
}
|
|
int nvn_swizzle(const nvn_texdesc *d, unsigned level, unsigned layer, const void *src, size_t src_pitch, void *dst) {
|
|
return move(d, level, layer, (uint8_t *)dst, (uint8_t *)src, src_pitch, 0, 0);
|
|
}
|