mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
This pull request fixed lighting that added up frame after frame under shadow storage on Linux: a GPU write noted while a part was recorded kept that part's CPU inputs (the game's resets of its lighting accumulators) out of the shadow. Its fix marked noted ranges as submitted in vk_memory.c; the base copies that came with the black-world fix already move every CPU-changed byte in before the part runs, so that part is not taken and no SHADOW_PENDING_INPUTS setting is added. Taken from it: - tex_probe.c (--probe-vkmem): a range noted as a future GPU write still gets the CPU's bytes before the GPU reads it. The pull request's other check, that a CPU write made after a GPU result loses to it until the write-back, is left out: with base copies the later CPU write wins, as it does on the console. - vk_dump.c: NX2NAVI_DUMP_SHADERS=<hash>,... copies images only for passes that use those shaders, and NX2NAVI_DUMP_RAW=1 also saves each copied image's texel bytes. - tools/fix_linux_ambient_power.py, as it is: patches one translated shader of a player's own cache so a zero exponent no longer gives NaN. It holds two hashes and no shader data. Pull request #264 by intellq. The Windows files compile; the probe was not run, and nothing was run on Linux here. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
659 lines
36 KiB
C
659 lines
36 KiB
C
// nx2navi.exe --probe-vkmem: texture memory checks. The CPU part: block-linear copies against an
|
|
// independent reference, ASTC void-extent blocks, and the NVN -> VkFormat table against GPU0. The GPU part:
|
|
// guest heap imported as a VkBuffer, write watch against CPU, GPU and ReadFile writes, the virtual-pool page table,
|
|
// and textures uploaded from block-linear storage and read back.
|
|
// The reference swizzler walks the GOBs in storage order and places each byte by bit operations, so it shares no
|
|
// addressing code with nvn_bl_offset.
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "host.h"
|
|
#include "nvn_api.h"
|
|
#include "tex.h"
|
|
#include "vk.h"
|
|
#include "platform/platform.h"
|
|
|
|
static uint64_t g_rng = 0x9E3779B97F4A7C15ull;
|
|
static uint8_t rnd8(void) {
|
|
g_rng ^= g_rng << 13; g_rng ^= g_rng >> 7; g_rng ^= g_rng << 17;
|
|
return (uint8_t)(g_rng >> 32);
|
|
}
|
|
|
|
// Byte offset inside a 64 x 8 GOB from the bit layout: x[3:0] -> 0-3, y[0] -> 4, x[4] -> 5, y[2:1] -> 6-7, x[5] -> 8.
|
|
// Bit by bit, independent of tex_deswizzle.c's arithmetic: Tegra X1, or Switch 2 for NVN2 titles (the order
|
|
// tex_deswizzle.c describes).
|
|
static unsigned gob_bits(unsigned x, unsigned y) {
|
|
if (rt_nvn_api_version[0] >= 200)
|
|
return (x & 15) | (y & 3) << 4 | (x >> 4 & 1) << 6 | (y >> 2 & 1) << 7 | (x >> 5 & 1) << 8;
|
|
return (x & 15) | (y & 1) << 4 | (x >> 4 & 1) << 5 | (y >> 1 & 3) << 6 | (x >> 5 & 1) << 8;
|
|
}
|
|
|
|
// lin: slices * rows * row_bytes (tight). bl: block-linear storage, pre-filled with a marker.
|
|
static void ref_swizzle(const uint8_t *lin, uint8_t *bl, uint32_t row_bytes, uint32_t rows, uint32_t slices,
|
|
unsigned th, unsigned td) {
|
|
uint32_t bh = 1u << th, bd = 1u << td;
|
|
uint32_t gx = (row_bytes + 63) / 64, by = (rows + 8 * bh - 1) / (8 * bh), bz = (slices + bd - 1) / bd;
|
|
uint8_t *p = bl;
|
|
for (uint32_t kz = 0; kz < bz; kz++)
|
|
for (uint32_t ky = 0; ky < by; ky++)
|
|
for (uint32_t kx = 0; kx < gx; kx++)
|
|
for (uint32_t z = 0; z < bd; z++) // GOBs of a block: y fastest, then z
|
|
for (uint32_t g = 0; g < bh; g++) {
|
|
for (unsigned y = 0; y < 8; y++)
|
|
for (unsigned x = 0; x < 64; x++) {
|
|
uint32_t ax = kx * 64 + x, ay = (ky * bh + g) * 8 + y, az = kz * bd + z;
|
|
if (ax < row_bytes && ay < rows && az < slices)
|
|
p[gob_bits(x, y)] = lin[((size_t)az * rows + ay) * row_bytes + ax];
|
|
}
|
|
p += 512;
|
|
}
|
|
}
|
|
|
|
static size_t bl_size(uint32_t row_bytes, uint32_t rows, uint32_t slices, unsigned th, unsigned td) {
|
|
uint32_t bh = 1u << th, bd = 1u << td;
|
|
return (size_t)((row_bytes + 63) / 64) * ((rows + 8 * bh - 1) / (8 * bh)) * ((slices + bd - 1) / bd) * 512 * bh * bd;
|
|
}
|
|
|
|
static unsigned g_cases;
|
|
|
|
static int check_swizzle(uint32_t row_bytes, uint32_t rows, uint32_t slices, unsigned th, unsigned td, uint32_t bpb) {
|
|
size_t n = (size_t)row_bytes * rows * slices, bn = bl_size(row_bytes, rows, slices, th, td);
|
|
uint8_t *lin = malloc(n), *back = malloc(n), *ref = malloc(bn), *got = malloc(bn);
|
|
for (size_t i = 0; i < n; i++) lin[i] = rnd8();
|
|
memset(ref, 0xEE, bn);
|
|
memset(got, 0xEE, bn);
|
|
ref_swizzle(lin, ref, row_bytes, rows, slices, th, td);
|
|
tex_bl_copy(got, lin, row_bytes, row_bytes, rows, slices, th, td, 0);
|
|
int bad = 0;
|
|
if (memcmp(ref, got, bn)) { printf("probe-vkmem: FAIL swizzle bpb=%u th=%u td=%u %ux%ux%u differs from the reference\n", bpb, th, td, row_bytes, rows, slices); bad = 1; }
|
|
memset(back, 0, n);
|
|
tex_bl_copy(ref, back, row_bytes, row_bytes, rows, slices, th, td, 1);
|
|
if (!bad && memcmp(lin, back, n)) { printf("probe-vkmem: FAIL deswizzle bpb=%u th=%u td=%u %ux%ux%u differs from the source\n", bpb, th, td, row_bytes, rows, slices); bad = 1; }
|
|
g_cases++;
|
|
free(lin); free(back); free(ref); free(got);
|
|
return bad;
|
|
}
|
|
|
|
static int test_deswizzle(void) {
|
|
static const uint32_t bpbs[] = {1, 2, 4, 8, 16};
|
|
for (unsigned b = 0; b < 5; b++)
|
|
for (unsigned th = 0; th <= 5; th++) {
|
|
uint32_t w = 37 * bpbs[b];
|
|
if (check_swizzle(w, 61, 1, th, 0, bpbs[b])) return 1; // spans several blocks in x and y
|
|
if (check_swizzle(bpbs[b], 1, 1, th, 0, bpbs[b])) return 1; // one block
|
|
if (check_swizzle(70 * bpbs[b], 9, 1, th, 0, bpbs[b])) return 1;
|
|
}
|
|
for (unsigned td = 1; td <= 3; td++) // 3D: depth 5 against block depths 2, 4, 8
|
|
for (unsigned th = 0; th <= 2; th++)
|
|
if (check_swizzle(23 * 4, 19, 5, th, td, 4)) return 1;
|
|
// levels of 256 KiB and more
|
|
if (check_swizzle(1000 * 4, 300, 1, 4, 0, 4)) return 1;
|
|
if (check_swizzle(97 * 16, 70, 9, 2, 2, 16)) return 1;
|
|
|
|
// Through a descriptor: RGBA8 2D with mip levels, level 1 and 2, layer 0, against the reference on the same geometry.
|
|
nvn_texdesc d = {.target = 1, .format = 0x25, .width = 200, .height = 150, .depth = 1, .levels = 4};
|
|
nvn_texlayout L;
|
|
nvn_tex_layout(&d, &L);
|
|
uint8_t *store = malloc(L.size), *lin;
|
|
for (uint64_t i = 0; i < L.size; i++) store[i] = rnd8();
|
|
for (unsigned lv = 0; lv < 4; lv++) {
|
|
uint32_t rb, rows, sl, bpb; uint64_t base; unsigned th, td;
|
|
if (nvn_deswizzle_geom(&d, lv, 0, &rb, &rows, &sl, &base, &th, &td, &bpb)) { printf("probe-vkmem: FAIL geometry of level %u\n", lv); return 1; }
|
|
size_t pitch = rb + 16, n = (size_t)pitch * rows;
|
|
lin = calloc(1, n);
|
|
if (nvn_deswizzle(&d, lv, 0, store, lin, pitch)) { printf("probe-vkmem: FAIL nvn_deswizzle level %u\n", lv); return 1; }
|
|
size_t bn = bl_size(rb, rows, sl, th, td);
|
|
uint8_t *tight = malloc((size_t)rb * rows), *ref0 = malloc(bn), *ref1 = malloc(bn);
|
|
for (uint32_t y = 0; y < rows; y++) memcpy(tight + (size_t)y * rb, lin + (size_t)y * pitch, rb);
|
|
memset(ref0, 0x00, bn);
|
|
memset(ref1, 0xFF, bn);
|
|
ref_swizzle(tight, ref0, rb, rows, sl, th, td);
|
|
ref_swizzle(tight, ref1, rb, rows, sl, th, td);
|
|
// bytes where both fills agree were written by the reference; the untouched storage must hold the same
|
|
for (size_t i = 0; i < bn; i++)
|
|
if (ref0[i] == ref1[i] && store[base + i] != ref0[i]) { printf("probe-vkmem: FAIL level %u differs from the reference at byte %zu\n", lv, i); return 1; }
|
|
// swizzling the linear copy back leaves the storage unchanged
|
|
uint8_t *keep = malloc(bn);
|
|
memcpy(keep, store + base, bn);
|
|
if (nvn_swizzle(&d, lv, 0, lin, pitch, store)) { printf("probe-vkmem: FAIL nvn_swizzle level %u\n", lv); return 1; }
|
|
if (memcmp(keep, store + base, bn)) { printf("probe-vkmem: FAIL swizzle round trip level %u\n", lv); return 1; }
|
|
free(keep); free(ref0); free(ref1);
|
|
g_cases++;
|
|
free(lin); free(tight);
|
|
}
|
|
free(store);
|
|
return 0;
|
|
}
|
|
|
|
// nvn_texels_box against the whole-level path on random boxes: a box read equals the box cut from the deswizzled level,
|
|
// and a box write leaves the storage as deswizzle, overwrite the box, swizzle does (nothing outside the box changes).
|
|
static int check_box(const nvn_texdesc *d, unsigned lv, unsigned layer) {
|
|
uint32_t rb, rows, sl, bpb; uint64_t base; unsigned th, td;
|
|
if (nvn_deswizzle_geom(d, lv, layer, &rb, &rows, &sl, &base, &th, &td, &bpb)) { printf("probe-vkmem: FAIL box geometry\n"); return 1; }
|
|
nvn_texlayout L;
|
|
nvn_tex_layout(d, &L);
|
|
uint8_t *store = malloc(L.size), *want = malloc(L.size), *lin = malloc((size_t)rb * rows * sl);
|
|
for (uint64_t i = 0; i < L.size; i++) store[i] = rnd8();
|
|
uint32_t cols = rb / bpb;
|
|
for (int k = 0; k < 8; k++) {
|
|
uint32_t bx = rnd8() % cols, bn = 1 + rnd8() % (cols - bx), y = rnd8() % rows, h = 1 + rnd8() % (rows - y);
|
|
uint32_t z = sl > 1 ? rnd8() % sl : 0, x = bx * bpb, wb = bn * bpb;
|
|
size_t pitch = wb + 8;
|
|
uint8_t *box = malloc(pitch * h), *ref = malloc(pitch * h);
|
|
// read
|
|
if (nvn_deswizzle(d, lv, layer, store, lin, rb)) { printf("probe-vkmem: FAIL box deswizzle\n"); return 1; }
|
|
memset(box, 0xAB, pitch * h), memset(ref, 0xAB, pitch * h);
|
|
for (uint32_t r = 0; r < h; r++) memcpy(ref + r * pitch, lin + ((size_t)z * rows + y + r) * rb + x, wb);
|
|
if (nvn_texels_box(d, lv, layer, x, y, z, wb, h, store, box, pitch, 1) || memcmp(box, ref, pitch * h)) {
|
|
printf("probe-vkmem: FAIL box read format %#x level %u layer %u box %u,%u,%u %ux%u\n", d->format, lv, layer, x, y, z, wb, h);
|
|
return 1;
|
|
}
|
|
// write
|
|
for (size_t i = 0; i < pitch * h; i++) box[i] = rnd8();
|
|
memcpy(want, store, L.size);
|
|
for (uint32_t r = 0; r < h; r++) memcpy(lin + ((size_t)z * rows + y + r) * rb + x, box + r * pitch, wb);
|
|
if (nvn_swizzle(d, lv, layer, lin, rb, want)) { printf("probe-vkmem: FAIL box swizzle\n"); return 1; }
|
|
if (nvn_texels_box(d, lv, layer, x, y, z, wb, h, store, box, pitch, 0) || memcmp(store, want, L.size)) {
|
|
printf("probe-vkmem: FAIL box write format %#x level %u layer %u box %u,%u,%u %ux%u\n", d->format, lv, layer, x, y, z, wb, h);
|
|
return 1;
|
|
}
|
|
g_cases++;
|
|
free(box), free(ref);
|
|
}
|
|
free(store), free(want), free(lin);
|
|
return 0;
|
|
}
|
|
|
|
static int test_box(void) {
|
|
// one format per block size: 1, 8 and 16 bytes per texel, 8 and 16 bytes per 4x4 block
|
|
int fmt[5] = {0}, n = 0;
|
|
static const int want_bpb[5] = {1, 8, 16, 8, 16}, want_bw[5] = {1, 1, 1, 4, 4};
|
|
for (int f = 1; f < 0x200 && n < 5; f++) {
|
|
int bw, bh, bpb = nvn_fmt_bpb(f, &bw, &bh);
|
|
for (int i = 0; i < 5; i++)
|
|
if (!fmt[i] && bpb == want_bpb[i] && bw == want_bw[i] && bh == bw) fmt[i] = f, n++;
|
|
}
|
|
for (int i = 0; i < 5; i++) {
|
|
if (!fmt[i]) continue;
|
|
nvn_texdesc d2 = {.target = 1, .format = fmt[i], .width = 300, .height = 170, .depth = 1, .levels = 3};
|
|
nvn_texdesc da = {.target = 4, .format = fmt[i], .width = 96, .height = 80, .depth = 3, .levels = 2};
|
|
nvn_texdesc d3 = {.target = 2, .format = fmt[i], .width = 72, .height = 40, .depth = 5, .levels = 2};
|
|
for (unsigned lv = 0; lv < 2; lv++)
|
|
if (check_box(&d2, lv, 0) || check_box(&da, lv, 2) || check_box(&d3, lv, 0)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---- ASTC ----
|
|
static void void_block(uint8_t b[16], uint16_t r, uint16_t g, uint16_t bl, uint16_t a, int hdr) {
|
|
uint64_t lo = 0xFFFFFFFFFFFFFDFCull; // 0x1FC mode, LDR, reserved bits 11, extent all ones
|
|
if (hdr) lo |= 0x200;
|
|
memcpy(b, &lo, 8);
|
|
uint16_t c[4] = {r, g, bl, a};
|
|
memcpy(b + 8, c, 8);
|
|
}
|
|
|
|
static unsigned g_astc_blocks;
|
|
|
|
static int test_astc(void) {
|
|
static const uint8_t dims[14][2] = {{4, 4}, {5, 4}, {5, 5}, {6, 5}, {6, 6}, {8, 5}, {8, 6},
|
|
{8, 8}, {10, 5}, {10, 6}, {10, 8}, {10, 10}, {12, 10}, {12, 12}};
|
|
for (int i = 0; i < 14; i++) {
|
|
unsigned bw = dims[i][0], bh = dims[i][1];
|
|
// a 3x2 grid of void-extent blocks cropped to a size that cuts the last column and row
|
|
unsigned w = bw * 3 - 1, h = bh * 2 - 1;
|
|
uint8_t blocks[6 * 16];
|
|
uint8_t want[6][4];
|
|
for (int k = 0; k < 6; k++) {
|
|
uint8_t c[4];
|
|
for (int j = 0; j < 4; j++) c[j] = rnd8();
|
|
// UNORM16 = c * 257 gives back c in the top 8 bits, plus noise in the low byte that must be dropped
|
|
uint16_t v[4];
|
|
for (int j = 0; j < 4; j++) v[j] = (uint16_t)(c[j] << 8 | rnd8());
|
|
void_block(blocks + 16 * k, v[0], v[1], v[2], v[3], 0);
|
|
memcpy(want[k], c, 4);
|
|
}
|
|
uint8_t *out = calloc(1, (size_t)w * h * 4);
|
|
for (int s = 0; s < 2; s++) {
|
|
astc_decode_ldr(blocks, w, h, bw, bh, s, out);
|
|
for (unsigned y = 0; y < h; y++)
|
|
for (unsigned x = 0; x < w; x++)
|
|
if (memcmp(out + ((size_t)y * w + x) * 4, want[y / bh * 3 + x / bw], 4)) {
|
|
printf("probe-vkmem: FAIL astc %ux%u void extent at (%u,%u)\n", bw, bh, x, y);
|
|
return 1;
|
|
}
|
|
}
|
|
free(out);
|
|
// HDR void extent is an error block in LDR: magenta, and astc_decode_block reports it
|
|
uint8_t hb[16], tmp[12 * 12 * 4];
|
|
void_block(hb, 0x1234, 0x5678, 0x9ABC, 0xDEF0, 1);
|
|
if (astc_decode_block(hb, bw, bh, false, tmp) || memcmp(tmp, "\xff\x00\xff\xff", 4)) {
|
|
printf("probe-vkmem: FAIL astc %ux%u HDR void extent is not an error block\n", bw, bh);
|
|
return 1;
|
|
}
|
|
g_astc_blocks += 7;
|
|
}
|
|
// sRGB void extent: the colour is the stored top byte as well (no transfer curve for constant blocks)
|
|
uint8_t b[16], px[16 * 4];
|
|
void_block(b, 0xFF00, 0x8000, 0x0100, 0xFFFF, 0);
|
|
astc_decode_block(b, 4, 4, true, px);
|
|
if (memcmp(px, "\xff\x80\x01\xff", 4)) { printf("probe-vkmem: FAIL astc sRGB void extent\n"); return 1; }
|
|
g_astc_blocks++;
|
|
// the worker-pool decode gives the same bytes: random blocks (every mode, error blocks included), cropped edges
|
|
for (int i = 0; i < 14; i++) {
|
|
unsigned bw = dims[i][0], bh = dims[i][1], w = 1021, h = 517, n = (w + bw - 1) / bw * ((h + bh - 1) / bh);
|
|
uint8_t *blk = malloc((size_t)n * 16), *a = malloc((size_t)w * h * 4), *m = malloc((size_t)w * h * 4);
|
|
for (size_t k = 0; k < (size_t)n * 16; k++) blk[k] = rnd8();
|
|
for (int s = 0; s < 2; s++) {
|
|
memset(a, 0x11, (size_t)w * h * 4), memset(m, 0x22, (size_t)w * h * 4);
|
|
astc_decode_ldr(blk, w, h, bw, bh, s, a);
|
|
astc_decode_ldr_mt(blk, w, h, bw, bh, s, m);
|
|
if (memcmp(a, m, (size_t)w * h * 4)) {
|
|
printf("probe-vkmem: FAIL astc %ux%u: the worker-pool decode differs from astc_decode_ldr\n", bw, bh);
|
|
return 1;
|
|
}
|
|
}
|
|
free(blk), free(a), free(m);
|
|
g_astc_blocks += n;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---- formats ----
|
|
static unsigned n_mapped, n_supported, n_unmapped, n_unsupported, n_fallback;
|
|
|
|
static int test_formats(void) {
|
|
if (!vk_init()) { printf("probe-vkmem: FAIL vk_init\n"); return 1; }
|
|
printf("probe-vkmem: GPU %s\n", vk_ctx()->props.deviceName);
|
|
for (int f = 0; f <= 0x9C; f++) {
|
|
TexVkFormat t;
|
|
int ok = tex_vk_format(f, &t);
|
|
if (!ok) {
|
|
n_unmapped++;
|
|
printf("probe-vkmem: unmapped %#x: %s\n", f, t.reason ? t.reason : "(no reason)");
|
|
if (!t.reason) return 1;
|
|
continue;
|
|
}
|
|
n_mapped++;
|
|
if (t.vk != t.ideal) n_fallback++;
|
|
int bw, bh, bpb = nvn_fmt_bpb(f, &bw, &bh);
|
|
if (t.astc_bw && (bpb != 16 || bw != t.astc_bw || bh != t.astc_bh)) {
|
|
printf("probe-vkmem: FAIL ASTC format %#x: block %dx%d/%d bytes, table says %ux%u\n", f, bw, bh, bpb, t.astc_bw, t.astc_bh);
|
|
return 1;
|
|
}
|
|
if (tex_vk_format_supported(&t)) n_supported++;
|
|
else {
|
|
n_unsupported++;
|
|
printf("probe-vkmem: unsupported %#x: VkFormat %d lacks features %#x on this GPU\n", f, (int)t.vk, (unsigned)t.need);
|
|
}
|
|
}
|
|
// The GPU-side promises of the fallbacks: RGBA8 and D32S8 must be usable, otherwise the fallback is empty.
|
|
TexVkFormat t;
|
|
tex_vk_format(0x79, &t);
|
|
int ok_astc = tex_vk_format_supported(&t);
|
|
tex_vk_format(0x35, &t);
|
|
int ok_d = tex_vk_format_supported(&t) && t.vk == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
if (!ok_astc || !ok_d) { printf("probe-vkmem: FAIL fallback formats (RGBA8 %d, D32S8 %d)\n", ok_astc, ok_d); return 1; }
|
|
return 0;
|
|
}
|
|
|
|
// ---- GPU part ----
|
|
#define GCHECK(cond, ...) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
printf("probe-vkmem: FAIL "); \
|
|
printf(__VA_ARGS__); \
|
|
printf("\n"); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
static unsigned g_gpu_checks;
|
|
static uint64_t g_gpu_seen, g_rf_seen[2], g_rf_us[2];
|
|
|
|
static void copy_wait(VkBuffer src, VkDeviceSize so, VkBuffer dst, VkDeviceSize doff, VkDeviceSize n) {
|
|
VkCommandBuffer cb = vk_upload_begin();
|
|
VkBufferCopy rg = {so, doff, n};
|
|
vkCmdCopyBuffer(cb, src, dst, 1, &rg);
|
|
VkMemoryBarrier2 mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
|
|
.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT};
|
|
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &mb};
|
|
vkCmdPipelineBarrier2(cb, &di);
|
|
vk_upload_end();
|
|
vk_upload_flush(1);
|
|
}
|
|
|
|
// A CPU-visible pool over the guest heap: GPU writes land in guest memory, CPU writes are read by the GPU, write watch
|
|
// reports the CPU writes and the explicit ones. GPU writes are noted and reported as the replay does
|
|
// (vk_mem_gpu_pending, vk_gpu_written at the fence), and a GPU read follows vk_mem_shadow_sync as a submit does: with
|
|
// shadow storage that is what moves the bytes; with an import they are already in place.
|
|
static int test_import(pool_t *pool) {
|
|
VkCtx *c = vk_ctx();
|
|
uint8_t *m = pool->mem;
|
|
uint64_t a = (uintptr_t)m, pages[64], n;
|
|
GCHECK(vk_pool_init(pool), "vk_pool_init of a CPU-visible pool at %#llx", (unsigned long long)a);
|
|
VkMemSpan sp;
|
|
GCHECK(vk_mem_resolve(a + 0x1000, 0x20000, &sp) && sp.buffer && sp.offset == 0x1000 && sp.avail == pool->size - 0x1000,
|
|
"resolve inside the imported pool (offset %#llx, avail %#llx)", (unsigned long long)sp.offset,
|
|
(unsigned long long)sp.avail);
|
|
GCHECK(!vk_mem_resolve(a + pool->size - 0x10, 0x20, &sp), "a range past the pool end resolved");
|
|
g_gpu_checks += 2;
|
|
|
|
VkBuffer sb;
|
|
VmaAllocation sa;
|
|
uint8_t *s = vk_staging(0x20000, &sb, &sa);
|
|
for (int i = 0; i < 0x20000; i++) s[i] = rnd8();
|
|
vmaFlushAllocation(c->vma, sa, 0, VK_WHOLE_SIZE);
|
|
vk_mem_resolve(a + 0x1000, 0x20000, &sp);
|
|
mem_written_pages(a, pool->size, pages, 64);
|
|
vk_mem_gpu_pending(a + 0x1000, 0x20000);
|
|
vk_mem_shadow_sync(); // as a submit does: the part's CPU inputs go in before its GPU work
|
|
copy_wait(sb, 0, sp.buffer, sp.offset, 0x20000);
|
|
vk_gpu_written(a + 0x1000, 0x20000);
|
|
GCHECK(!memcmp(m + 0x1000, s, 0x20000), "GPU copy into the imported heap range differs from the source");
|
|
n = mem_written_pages(a, pool->size, pages, 64);
|
|
g_gpu_seen = n;
|
|
printf("probe-vkmem: GPU copy of 128 KiB into the imported heap: equal; write watch reported %llu of 32 pages\n",
|
|
(unsigned long long)n);
|
|
|
|
// CPU writes: reported exactly, and the GPU reads them through the same buffer
|
|
for (int i = 0; i < 0x4000; i++) m[0x30000 + i] = rnd8();
|
|
n = mem_written_peek(a, pool->size, pages, 64);
|
|
GCHECK(n == 4 && pages[0] == a + 0x30000 && pages[3] == a + 0x33000, "CPU writes to 4 pages: %llu reported",
|
|
(unsigned long long)n);
|
|
vk_mem_resolve(a + 0x30000, 0x4000, &sp);
|
|
// A GPU write noted for the part being submitted must not keep the part's CPU inputs out: a buffer the shaders
|
|
// add to and the CPU resets (the game's lighting accumulators) depends on the reset reaching the GPU first.
|
|
vk_mem_gpu_pending(a + 0x30000, 0x4000);
|
|
vk_mem_shadow_sync();
|
|
copy_wait(sp.buffer, sp.offset, sb, 0, 0x4000);
|
|
vmaInvalidateAllocation(c->vma, sa, 0, VK_WHOLE_SIZE);
|
|
GCHECK(!memcmp(s, m + 0x30000, 0x4000), "GPU read of CPU-written heap differs");
|
|
vk_gpu_written(a + 0x30000, 0x4000);
|
|
vmaDestroyBuffer(c->vma, sb, sa);
|
|
|
|
// generations: written after `since` -> 1, then 0, explicit host write -> 1
|
|
uint32_t g0, g1, g2, g3;
|
|
vk_mem_changed(a, pool->size, 0, &g0);
|
|
m[0x8000] = 1;
|
|
GCHECK(vk_mem_changed(a + 0x8000, 0x1000, g0, &g1) == 1, "a CPU write after a generation is not seen");
|
|
GCHECK(vk_mem_changed(a, pool->size, g1, &g2) == 0, "no write since the last generation is reported as written");
|
|
vk_guest_written(a + 0x9000, 4);
|
|
GCHECK(vk_mem_changed(a + 0x9000, 0x1000, g2, &g3) == 1, "an explicit guest_written range is not seen");
|
|
GCHECK(vk_mem_changed(RT_KERNEL_BASE, 0x1000, 0, &g3) == -1, "the kernel region is reported as tracked");
|
|
printf("probe-vkmem: write watch: CPU writes reported page-exact; generations see CPU and explicit writes\n");
|
|
g_gpu_checks += 7;
|
|
return 0;
|
|
}
|
|
|
|
// ReadFile into a watched page, buffered and unbuffered: timed, and whether write watch saw the kernel's write.
|
|
static int test_readfile(uint8_t *dst) {
|
|
char dir[PLAT_PATH_MAX], path[PLAT_PATH_MAX + 64];
|
|
GCHECK(plat_temp_dir(dir, sizeof dir), "temp directory");
|
|
snprintf(path, sizeof path, "%s/tvk-%u.tmp", dir, plat_process_id());
|
|
static uint8_t data[0x2000];
|
|
for (size_t i = 0; i < sizeof data; i++) data[i] = rnd8();
|
|
GCHECK(plat_file_write(path, data, sizeof data), "temp file write");
|
|
for (int k = 0; k < 2; k++) {
|
|
uint8_t *p = dst + (size_t)k * 0x2000;
|
|
uint64_t pages[8];
|
|
PlatFile *h = k ? plat_file_open_unbuffered(path) : plat_file_open(path);
|
|
GCHECK(h, "temp file open");
|
|
mem_written_pages((uintptr_t)p, 0x2000, pages, 8);
|
|
uint64_t t0 = plat_now_ns();
|
|
int64_t got = plat_file_pread(h, p, 0x2000, 0);
|
|
uint64_t t1 = plat_now_ns();
|
|
plat_file_close(h);
|
|
GCHECK(got == 0x2000 && !memcmp(p, data, sizeof data), "ReadFile (%s) result", k ? "unbuffered" : "buffered");
|
|
g_rf_seen[k] = mem_written_pages((uintptr_t)p, 0x2000, pages, 8);
|
|
g_rf_us[k] = (t1 - t0) / 1000;
|
|
printf("probe-vkmem: ReadFile %s of 8 KiB into a watched page: %llu us, write watch saw %llu of 2 pages\n",
|
|
k ? "unbuffered" : "buffered", (unsigned long long)g_rf_us[k], (unsigned long long)g_rf_seen[k]);
|
|
}
|
|
plat_file_delete(path);
|
|
g_gpu_checks += 2;
|
|
return 0;
|
|
}
|
|
|
|
// A physical pool, a virtual pool mapping 64 KiB pages of it, resolution and unmapping; the GPU writes through a
|
|
// virtual address and the bytes are read through the physical one.
|
|
static int test_virtual(pool_t *phys, pool_t *virt) {
|
|
VkCtx *c = vk_ctx();
|
|
GCHECK(vk_pool_init(phys) && vk_pool_init(virt), "vk_pool_init of physical / virtual pools");
|
|
uint64_t p = (uintptr_t)phys->mem, v = (uintptr_t)virt->mem;
|
|
nvn_mapping_t rq[2] = {{(const NVNmemoryPool *)(const void *)phys, 0x20000, 0, 0x20000, 0, 0},
|
|
{(const NVNmemoryPool *)(const void *)phys, 0, 0x50000, 0x10000, 0, 0}};
|
|
GCHECK(vk_pool_map_virtual(virt, 2, rq), "MapVirtual of 2 requests");
|
|
VkMemSpan ps, sp;
|
|
GCHECK(vk_mem_resolve(p, 0x40000, &ps) && ps.offset == 0 && ps.alloc, "resolve of the physical pool");
|
|
GCHECK(vk_mem_resolve(v + 8, 4, &sp) && sp.buffer == ps.buffer && sp.offset == 0x20008, "virtual page 0 -> %#llx",
|
|
(unsigned long long)sp.offset);
|
|
GCHECK(vk_mem_resolve(v + 0x10010, 0x10, &sp) && sp.offset == 0x30010, "virtual page 1 -> %#llx", (unsigned long long)sp.offset);
|
|
GCHECK(vk_mem_resolve(v + 0x8000, 0x18000, &sp) && sp.avail == 0x18000, "contiguous pages 0-1 (avail %#llx)",
|
|
(unsigned long long)sp.avail);
|
|
GCHECK(!vk_mem_resolve(v + 0x18000, 0x10000, &sp), "a range into unmapped page 2 resolved");
|
|
GCHECK(!vk_mem_resolve(v + 0x20000, 4, &sp), "unmapped page 2 resolved");
|
|
GCHECK(vk_mem_resolve(v + 0x50000, 0x10000, &sp) && sp.offset == 0 && sp.alloc_offset == 0, "virtual page 5 -> %#llx",
|
|
(unsigned long long)sp.offset);
|
|
VkCommandBuffer cb = vk_upload_begin();
|
|
vkCmdFillBuffer(cb, sp.buffer, sp.offset + 0x100, 0x100, 0xA5C3A5C3u);
|
|
VkMemoryBarrier2 mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT,
|
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
|
.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT};
|
|
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &mb};
|
|
vkCmdPipelineBarrier2(cb, &di);
|
|
vk_upload_end();
|
|
VkBuffer rb;
|
|
VmaAllocation ra;
|
|
uint32_t *r = vk_staging(0x100, &rb, &ra);
|
|
copy_wait(ps.buffer, 0x100, rb, 0, 0x100);
|
|
vmaInvalidateAllocation(c->vma, ra, 0, VK_WHOLE_SIZE);
|
|
int same = 1;
|
|
for (int i = 0; i < 0x40; i++) same &= r[i] == 0xA5C3A5C3u;
|
|
vmaDestroyBuffer(c->vma, rb, ra);
|
|
GCHECK(same, "a GPU fill through virtual page 5 is not in physical page 0");
|
|
nvn_mapping_t un = {NULL, 0, 0, 0x10000, 0, 0};
|
|
GCHECK(vk_pool_map_virtual(virt, 1, &un), "MapVirtual unmap");
|
|
GCHECK(!vk_mem_resolve(v + 8, 4, &sp), "unmapped page 0 still resolves");
|
|
GCHECK(vk_mem_resolve(v + 0x10000, 4, &sp) && sp.offset == 0x30000, "page 1 lost by unmapping page 0");
|
|
printf("probe-vkmem: virtual pool: 3 pages mapped, 1 unmapped, resolution and a GPU write through the alias correct\n");
|
|
g_gpu_checks += 12;
|
|
return 0;
|
|
}
|
|
|
|
static tex_t make_tex(pool_t *pool, uint64_t off, int format, uint32_t w, uint32_t h, uint32_t levels, uint64_t serial) {
|
|
tex_t t;
|
|
memset(&t, 0, sizeof t);
|
|
t.magic = MAGIC('T', 'E', 'X', 0);
|
|
t.d.target = 1, t.d.format = format, t.d.width = w, t.d.height = h, t.d.depth = 1, t.d.levels = levels;
|
|
nvn_tex_layout(&t.d, &t.L);
|
|
// pool_flags as nvnTextureInitialize records them: tex_mirrored (vk_texture.c) reads them, not the pool object
|
|
t.pool = pool, t.pool_flags = (uint16_t)pool->flags, t.off = (int64_t)off, t.mem = pool->mem + off, t.serial = serial;
|
|
return t;
|
|
}
|
|
|
|
// Fills every level of t's storage with random texels through the reference swizzler; lin[lv] keeps them tight.
|
|
static int fill_storage(const tex_t *t, uint8_t **lin) {
|
|
for (unsigned lv = 0; lv < t->d.levels; lv++) {
|
|
uint32_t rb, rows, sl, bpb; uint64_t base; unsigned th, td;
|
|
GCHECK(!nvn_deswizzle_geom(&t->d, lv, 0, &rb, &rows, &sl, &base, &th, &td, &bpb), "geometry of level %u", lv);
|
|
size_t n = (size_t)rb * rows;
|
|
if (!lin[lv]) lin[lv] = malloc(n);
|
|
for (size_t i = 0; i < n; i++) lin[lv][i] = rnd8();
|
|
ref_swizzle(lin[lv], t->mem + base, rb, rows, 1, th, td);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_back(const tex_t *t, unsigned lv, uint8_t *const *lin, int gpu, const char *what) {
|
|
uint32_t rb, rows, sl, bpb; uint64_t base; unsigned th, td;
|
|
nvn_deswizzle_geom(&t->d, lv, 0, &rb, &rows, &sl, &base, &th, &td, &bpb);
|
|
view_t v;
|
|
memset(&v, 0, sizeof v);
|
|
v.set_levels = 1, v.base_level = (uint8_t)lv, v.nlevels = 1;
|
|
NVNcopyRegion r = {0, 0, 0, (int)(t->d.width >> lv ? t->d.width >> lv : 1), (int)(t->d.height >> lv ? t->d.height >> lv : 1), 1};
|
|
size_t n = (size_t)rb * rows;
|
|
uint8_t *got = calloc(1, n);
|
|
int ok = gpu ? vk_texture_read_image(t, &v, &r, got, rb, n) : (vk_read_texels(t, &v, &r, got, rb, n), 1);
|
|
int same = ok && !memcmp(got, lin[lv], n);
|
|
free(got);
|
|
GCHECK(same, "%s level %u (%ux%u, format %#x) read back %s the CPU reference", what, lv, r.width, r.height,
|
|
t->d.format, ok ? "differs from" : "failed against");
|
|
g_gpu_checks++;
|
|
return 0;
|
|
}
|
|
|
|
// Level 0 of image (w x h RGBA8) read through a staging buffer after everything recorded so far has run: whether every
|
|
// texel is `want`, the way a draw sampling the image would see it.
|
|
static int image_is_color(VkImage image, uint32_t w, uint32_t h, const uint8_t want[4]) {
|
|
VkCtx *c = vk_ctx();
|
|
VkBuffer rb;
|
|
VmaAllocation ra;
|
|
size_t n = (size_t)w * h * 4;
|
|
uint8_t *r = vk_staging(n, &rb, &ra);
|
|
VkCommandBuffer cb = vk_upload_begin();
|
|
VkMemoryBarrier2 mb = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
|
.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT};
|
|
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &mb};
|
|
vkCmdPipelineBarrier2(cb, &di);
|
|
VkBufferImageCopy rg = {.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .imageExtent = {w, h, 1}};
|
|
vkCmdCopyImageToBuffer(cb, image, VK_IMAGE_LAYOUT_GENERAL, rb, 1, &rg);
|
|
mb = (VkMemoryBarrier2){.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
|
|
.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT};
|
|
vkCmdPipelineBarrier2(cb, &di);
|
|
vk_upload_end();
|
|
vk_upload_flush(1);
|
|
vmaInvalidateAllocation(c->vma, ra, 0, VK_WHOLE_SIZE);
|
|
int same = 1;
|
|
for (size_t i = 0; i < n; i += 4) same &= !memcmp(r + i, want, 4);
|
|
vmaDestroyBuffer(c->vma, rb, ra);
|
|
return same;
|
|
}
|
|
|
|
static int test_textures(pool_t *tpool, pool_t *phys) {
|
|
uint8_t *lin[4] = {0};
|
|
// block-linear RGBA8 with 3 levels in a CPU-visible pool: uploaded through the GPU, read back from the image
|
|
tex_t t = make_tex(tpool, 0, 0x25, 200, 120, 3, 1);
|
|
if (fill_storage(&t, lin)) return 1;
|
|
const VkTexImage *img = vk_texture_get(&t);
|
|
GCHECK(img && img->mirrored && img->levels == 3 && img->format == VK_FORMAT_R8G8B8A8_UNORM, "RGBA8 texture image");
|
|
for (unsigned lv = 0; lv < 3; lv++)
|
|
if (read_back(&t, lv, lin, 1, "RGBA8")) return 1;
|
|
// CPU writes to the storage: write watch -> re-upload on the next use
|
|
uint64_t up0, re0, re1;
|
|
vk_texture_stats(NULL, &up0, &re0);
|
|
if (fill_storage(&t, lin)) return 1;
|
|
for (unsigned lv = 0; lv < 3; lv++)
|
|
if (read_back(&t, lv, lin, 1, "RGBA8 rewritten")) return 1;
|
|
vk_texture_stats(NULL, NULL, &re1);
|
|
GCHECK(re1 == re0 + 1, "storage rewritten: %llu re-uploads (want 1)", (unsigned long long)(re1 - re0));
|
|
if (read_back(&t, 1, lin, 0, "RGBA8 ReadTexels from storage")) return 1;
|
|
|
|
// BC1, 64x48: blocks uploaded as they are, read back from the image
|
|
uint8_t *bl[4] = {0};
|
|
tex_t b = make_tex(tpool, 0x40000, 0x42, 64, 48, 1, 2);
|
|
if (fill_storage(&b, bl)) return 1;
|
|
img = vk_texture_get(&b);
|
|
GCHECK(img && img->format == VK_FORMAT_BC1_RGBA_UNORM_BLOCK, "BC1 texture image");
|
|
if (read_back(&b, 0, bl, 1, "BC1")) return 1;
|
|
|
|
// RGBA8 in a physical pool: aliases the pool memory; WriteTexels then ReadTexels through the image
|
|
tex_t g = make_tex(phys, 0, 0x25, 64, 64, 1, 3);
|
|
img = vk_texture_get(&g);
|
|
GCHECK(img && !img->mirrored && img->bound, "texture in a physical pool (mirrored %d, bound %d)", img ? img->mirrored : -1,
|
|
img ? img->bound : -1);
|
|
uint8_t *gl[1] = {malloc(64 * 64 * 4)};
|
|
for (int i = 0; i < 64 * 64 * 4; i++) gl[0][i] = rnd8();
|
|
NVNcopyRegion all = {0, 0, 0, 64, 64, 1};
|
|
vk_write_texels(&g, NULL, &all, gl[0], 256, 256 * 64);
|
|
if (read_back(&g, 0, gl, 0, "physical-pool RGBA8 WriteTexels")) return 1;
|
|
|
|
// a slot of a finalized texture follows the live texture at the same storage. The slot's own storage is CPU-written
|
|
// (mirrored), so it follows only a texture holding GPU content its bytes lack (vk_texture.c slot_resolve). The live
|
|
// texture is cleared on the GPU and noted as rendered into, as a draw into it would be; the slot's image must then
|
|
// show the clear, which neither the storage bytes nor a recreated image of the slot's own descriptor hold.
|
|
vk_texture_register(5, &b, NULL);
|
|
vk_texture_release(&b);
|
|
tex_t b2 = make_tex(tpool, 0x40000, 0x25, 16, 12, 1, 4);
|
|
const VkTexImage *live = vk_texture_get(&b2);
|
|
GCHECK(live && live->image && live->width == 16 && live->height == 12, "RGBA8 image of the texture at the slot's storage");
|
|
vk_texture_gpu_written(&b2); // the clear below is its content, not the storage
|
|
const float clear[4] = {0.0f, 1.0f, 1.0f, 1.0f};
|
|
const uint8_t cleared[4] = {0x00, 0xFF, 0xFF, 0xFF};
|
|
VkClearColorValue cv;
|
|
memcpy(cv.float32, clear, sizeof cv.float32);
|
|
VkImageSubresourceRange rng = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
VkCommandBuffer ccb = vk_upload_begin();
|
|
vkCmdClearColorImage(ccb, live->image, VK_IMAGE_LAYOUT_GENERAL, &cv, 1, &rng);
|
|
vk_upload_end();
|
|
VkImage live_img = live ? live->image : VK_NULL_HANDLE;
|
|
const VkTexImage *slot = vk_texture_by_id(5, NULL, NULL);
|
|
GCHECK(live && slot && slot->image == live_img, "slot 5 does not follow the live texture at its storage");
|
|
GCHECK(image_is_color(slot->image, 16, 12, cleared), "the image of slot 5 does not hold the clear written into the live texture");
|
|
samp_par sp;
|
|
memset(&sp, 0, sizeof sp);
|
|
sp.minf = NVN_MIN_FILTER_LINEAR_MIPMAP_LINEAR, sp.magf = NVN_MAG_FILTER_LINEAR, sp.ws = sp.wt = sp.wr = NVN_WRAP_MODE_REPEAT;
|
|
sp.lod_max = 1000, sp.aniso = 16;
|
|
vk_sampler_register(0, &sp);
|
|
VkSampler s0 = vk_sampler_by_id(0), s1 = vk_sampler_for(&sp);
|
|
GCHECK(s0 && s0 == s1 && !vk_sampler_by_id(1), "sampler slots");
|
|
g_gpu_checks += 9;
|
|
vk_texture_release(&t), vk_texture_release(&b2), vk_texture_release(&g);
|
|
for (int i = 0; i < 4; i++) free(lin[i]), free(bl[i]);
|
|
free(gl[0]);
|
|
uint64_t images, ups, reups;
|
|
vk_texture_stats(&images, &ups, &reups);
|
|
printf("probe-vkmem: textures: RGBA8 3 levels and BC1 equal to the CPU reference after upload, re-upload after CPU "
|
|
"writes, physical-pool alias WriteTexels/ReadTexels, slot redirect, samplers (%llu images, %llu uploads, "
|
|
"%llu re-uploads)\n", (unsigned long long)images, (unsigned long long)ups, (unsigned long long)reups);
|
|
return 0;
|
|
}
|
|
|
|
static int test_gpu(void) {
|
|
uint64_t heap = 0;
|
|
GCHECK(mem_set_heap_size(16ull << 20, &heap) == 0 && heap, "SetHeapSize 16 MiB");
|
|
uint8_t *h = (uint8_t *)(uintptr_t)heap;
|
|
pool_t pool = {.magic = NVN_POOL_MAGIC, .flags = 0x24, .mem = h + 0x100000, .size = 0x40000, .game_mem = h + 0x100000};
|
|
pool_t tpool = {.magic = NVN_POOL_MAGIC, .flags = 0x22, .mem = h + 0x400000, .size = 0x100000, .game_mem = h + 0x400000};
|
|
pool_t phys = {.magic = NVN_POOL_MAGIC, .flags = 0x189, .mem = (uint8_t *)(uintptr_t)(RT_GPU_VA_BASE + 0x100000000ull),
|
|
.size = 0x40000, .game_mem = h + 0x800000, .span = 0x40000};
|
|
pool_t virt = {.magic = NVN_POOL_MAGIC, .flags = 0x2a1, .mem = (uint8_t *)(uintptr_t)(RT_GPU_VA_BASE + 0x200000000ull),
|
|
.size = 0x80000, .span = 0x80000};
|
|
if (test_import(&pool) || test_readfile(pool.mem + 0x38000) || test_virtual(&phys, &virt)) return 1;
|
|
GCHECK(vk_pool_init(&tpool), "vk_pool_init of the texture pool");
|
|
if (test_textures(&tpool, &phys)) return 1;
|
|
VkMemStats st;
|
|
vk_mem_stats(&st);
|
|
GCHECK(st.imported_pools == 2 && st.physical_pools == 1 && st.virtual_pools == 1 && !st.import_failures,
|
|
"pool counts %llu/%llu/%llu, %llu import failures", (unsigned long long)st.imported_pools,
|
|
(unsigned long long)st.physical_pools, (unsigned long long)st.virtual_pools, (unsigned long long)st.import_failures);
|
|
vk_pool_fini(&virt), vk_pool_fini(&phys), vk_pool_fini(&tpool), vk_pool_fini(&pool);
|
|
vk_mem_stats(&st);
|
|
GCHECK(!st.imported_pools && !st.physical_pools && !st.virtual_pools, "pools left after fini");
|
|
g_gpu_checks += 2;
|
|
return 0;
|
|
}
|
|
|
|
int probe_vkmem(void) {
|
|
if (test_deswizzle() || test_box() || test_astc() || test_formats()) return 1;
|
|
printf("probe-vkmem: cpu part passed (%u deswizzle cases, %u astc blocks, formats: %u mapped, %u supported by the GPU, "
|
|
"%u unsupported by the GPU, %u unmapped, %u with a fallback)\n",
|
|
g_cases, g_astc_blocks, n_mapped, n_supported, n_unsupported, n_unmapped, n_fallback);
|
|
int bad = test_gpu();
|
|
vk_mem_shutdown();
|
|
vk_shutdown();
|
|
long errors = vk_validation_errors_total();
|
|
if (bad || errors) {
|
|
printf("probe-vkmem: FAILED (%s, %ld validation errors)\n", bad ? "a GPU check failed" : "GPU checks passed", errors);
|
|
return 1;
|
|
}
|
|
printf("probe-vkmem: passed (%u deswizzle cases, %u astc blocks, %u formats mapped, %u GPU checks, GPU writes seen by "
|
|
"write watch %llu/32 pages, ReadFile seen %llu/2 buffered and %llu/2 unbuffered, 0 validation errors)\n",
|
|
g_cases, g_astc_blocks, n_mapped, g_gpu_checks, (unsigned long long)g_gpu_seen,
|
|
(unsigned long long)g_rf_seen[0], (unsigned long long)g_rf_seen[1]);
|
|
return 0;
|
|
}
|