Files
libretroadmin 1d95b38136 C89: the PowerVR's registers, memory and timing, Holly's interrupts, and the DMA controller
holly_intc, pvr_regs, pvr_sb_regs, pvr_mem, spg and the SH-4's DMAC
become C. Their headers - and the ones they need, _vmem.h, sh4_mem.h,
Renderer_if.h, ta.h, maple_if.h - are made to compile as both, with
the C++-only parts (templates, the Renderer class, the maple devices)
behind __cplusplus. The video memory areas' templates are functions
per size and half; SetREP takes the frame's size from its caller.

A C file's variable is declared once, in its header: serialize.cpp,
ta_ctx.cpp and the others no longer declare their own, which gave
them C++ linkage that MSVC does not link to C.
tools/c89/linkage_check.py finds such declarations after a build and
runs in the Headless workflow. The tests that wrap a function by name
take the C names.
2026-11-12 08:44:36 +01:00

745 lines
24 KiB
C++

/* The texture converters, against upstream's.
*
* Every converter the texture cache has - each pixel format, twiddled,
* compressed, planar, paletted, to 16 and to 32 bits - is given the same
* bytes at five sizes, and a hash and the first four pixels of what comes
* out are printed, one line each. texconv_expected.txt is what upstream
* flycast's converters print for the same bytes; texconv_test.sh compares.
*
* Built with -DUPSTREAM in an upstream tree (and its texconv.cpp object)
* this prints that file's contents, which is how it was made:
* (echo "# random"; ./a.out; echo "# ramp"; ./a.out ramp)
*
* The converters are pure functions of their input, so this is also what a
* rewrite of them has to keep printing. */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#ifdef UPSTREAM
#include "rend/texconv.h"
#define T opengl::pvrTexInfo
#else
#include "rend/TexCache.h"
#endif
static u8 *buf;
static u32 rnd_state = 0x12345678;
static u32 rnd() { rnd_state ^= rnd_state << 13; rnd_state ^= rnd_state >> 17; rnd_state ^= rnd_state << 5; return rnd_state; }
template<typename P, typename F> static void run(const char *name, F fn, u32 w, u32 h)
{
// upstream's VQ converters are handed the indices, this tree's the codebook in front of them
u32 off = 0;
#ifdef UPSTREAM
if (strstr(name, "VQ")) off = 2048;
#endif
if (!fn) { printf("%-12s %4ux%-4u (none)\n", name, w, h); return; }
PixelBuffer<P> pb;
pb.init(w, h);
memset(pb.data(), 0xEE, (size_t)w * h * sizeof(P));
fn(&pb, buf + 2048 + off, w, h);
const u8 *d = (const u8 *)pb.data();
u32 hash = 2166136261u;
for (size_t i = 0; i < (size_t)w * h * sizeof(P); i++) hash = (hash ^ d[i]) * 16777619u;
printf("%-12s %4ux%-4u %08x first", name, w, h, hash);
for (int i = 0; i < 4; i++) { u32 v = 0; memcpy(&v, d + i * sizeof(P), sizeof(P)); printf(" %0*x", (int)sizeof(P) * 2, v); }
printf("\n");
}
#ifndef UPSTREAM
/* "cache": the texture cache's checks for change, against a full upload.
*
* Textures over the same few pages are asked for as a render's parse asks:
* many times a render, with video memory written between renders as the
* SH4 writes it (faulting on a protected page) and, now and then, in the
* middle of one by the renderer (VramLockedWriteRange()). After every
* request the texture's last upload has to be what a new texture made then
* from video memory uploads. Then one texture, written no more, is asked
* for 300 times a render: it may be hashed once a render. */
#include "hw/pvr/pvr_regs.h"
#include "rend/texdecode.h"
#include "rend/rtt_watch.h"
#include "hw/mem/_vmem.h"
extern VArray2 vram;
#include <encodings/xxh32.h>
extern "C" {
uint32_t texconv_xxh32(const void *data, size_t len, uint32_t seed);
void texconv_xxh32_reset(xxh32_state_t *s, uint32_t seed);
void texconv_xxh32_update(xxh32_state_t *s, const void *data, size_t len);
uint32_t texconv_xxh32_digest(const xxh32_state_t *s);
}
settings_t settings;
u32 FrameCount = 1;
unsigned VRAM_SIZE = 8 << 20;
unsigned VRAM_MASK = (8 << 20) - 1;
static u8 cache_regs[pvr_RegSize];
u8 *rend_pvr_regs = cache_regs;
static u8 cache_protected[(16 << 20) / PAGE_SIZE];
static size_t cache_hashed;
extern "C" uint32_t xxh32(const void *data, size_t len, uint32_t seed)
{
cache_hashed += len;
return texconv_xxh32(data, len, seed);
}
extern "C" void xxh32_reset(xxh32_state_t *s, uint32_t seed)
{
texconv_xxh32_reset(s, seed);
}
extern "C" void xxh32_update(xxh32_state_t *s, const void *data, size_t len)
{
texconv_xxh32_update(s, data, len);
}
extern "C" uint32_t xxh32_digest(const xxh32_state_t *s)
{
return texconv_xxh32_digest(s);
}
void _vmem_protect_vram(u32 addr, u32 size)
{
u32 page;
for (page = addr / PAGE_SIZE; page <= (addr + size - 1) / PAGE_SIZE; page++)
cache_protected[page] = 1;
}
void _vmem_unprotect_vram(u32 addr, u32 size)
{
u32 page;
for (page = addr / PAGE_SIZE; page <= (addr + size - 1) / PAGE_SIZE; page++)
cache_protected[page] = 0;
}
bool _vmem_vram_page_protected(u32 page) { return cache_protected[page] != 0; }
void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char *file, int line, const char *fmt, ...) { }
/* (no render to a texture is watched here: rend/rtt_watch.h) */
bool rtt_watch_pending(u32 offset, u32 size, RttWatch *watch) { return false; }
void rtt_watch_overwrite(u32 offset, u32 size) { }
/* How the test's textures are made: on the GPU (the kernel run here,
* rend/texdecode.h, as the renderers run it there), and with parts of them
* made again; and what that took */
static bool cache_gpu;
static bool cache_parts;
static bool cache_force32;
static u32 cache_part_count;
static u32 cache_whole_count;
static size_t cache_part_bytes;
/* Regions that are not what a renderer is to be handed: off their level,
* not on multiples of 8, or overlapping (a copy's must not) */
static u32 cache_bad_regions;
static void part_check(const BaseTextureCacheData *t, bool mips, const TexRegion *r, u32 count)
{
const u32 top = bitscanrev(t->w);
u32 i, j;
if (count == 0 || count > TEXP_REGIONS)
cache_bad_regions++;
for (i = 0; i < count; i++)
{
const u32 lw = mips ? 1u << (top - r[i].level) : t->w, lh = mips ? lw : t->h;
if ((r[i].x | r[i].y | r[i].w | r[i].h) & 7 || r[i].w == 0 || r[i].h == 0
|| r[i].x + r[i].w > lw || r[i].y + r[i].h > lh || (!mips && r[i].level != 0))
cache_bad_regions++;
for (j = 0; j < i; j++)
if (r[j].level == r[i].level && r[j].x < r[i].x + r[i].w && r[i].x < r[j].x + r[j].w
&& r[j].y < r[i].y + r[i].h && r[i].y < r[j].y + r[j].h)
cache_bad_regions++;
}
}
/* A texture whose upload is kept, as the GPU would have it: its levels
* from the smallest, as the cache hands them over */
class CacheTex : public BaseTextureCacheData
{
public:
u8 *gpu;
size_t gpu_bytes;
bool made;
void GetId(char *s, size_t n) override { if (n != 0) s[0] = '\0'; }
static size_t Pixel(TextureType type)
{
return type == TextureType::_8888 ? 4 : type == TextureType::_8 ? 1 : 2;
}
/* Where level @level of the image (0 the largest) is kept, and its width */
size_t LevelAt(u32 level, u32 *width)
{
const size_t pixel = Pixel(tex_type);
size_t at = 0;
u32 l, top;
if (!made_mips)
{
*width = w;
return 0;
}
top = bitscanrev(w);
for (l = 0; l < top - level; l++)
at += ((size_t)1 << (2 * l)) * pixel;
*width = 1u << (top - level);
return at;
}
void UploadToGPU(int width, int height, u8 *data, bool mipmapped, bool mipmapsIncluded) override
{
const size_t pixel = Pixel(tex_type);
gpu_bytes = (size_t)width * height * pixel;
if (mipmapsIncluded)
for (int d = width / 2; d != 0; d /= 2)
gpu_bytes += (size_t)d * d * pixel;
gpu = (u8 *)realloc(gpu, gpu_bytes);
memcpy(gpu, data, gpu_bytes);
cache_whole_count++;
}
bool Force32BitTexture(TextureType type) const override { return cache_force32; }
bool PartMemory(size_t bytes, void **memory) override
{
*memory = NULL;
return cache_parts && gpu != NULL;
}
void UploadPart(const TexRegion *regions, u32 count, u8 *data, const size_t *offset) override
{
const size_t pixel = Pixel(tex_type);
u32 i, y;
part_check(this, made_mips, regions, count);
for (i = 0; i < count; i++)
{
u32 width;
const size_t at = LevelAt(regions[i].level, &width);
for (y = 0; y < regions[i].h; y++)
memcpy(gpu + at + ((size_t)(regions[i].y + y) * width + regions[i].x) * pixel,
data + offset[i] + (size_t)y * regions[i].w * pixel, regions[i].w * pixel);
cache_part_bytes += (size_t)regions[i].w * regions[i].h * pixel;
}
cache_part_count++;
}
bool GpuDecode(const texd_plan& plan) override
{
const size_t pixel = Pixel(tex_type);
u8 *bytes;
u32 i;
if (!cache_gpu)
return false;
bytes = (u8 *)calloc(plan.pal + plan.pal_entries * 4 + 4, 1);
memcpy(bytes, &vram[plan.src], plan.bytes);
if (plan.pal_entries != 0)
memcpy(bytes + plan.pal, (plan.out == TEXD_OUT16 ? palette16_ram : palette32_ram) + plan.pal_index, plan.pal_entries * 4);
gpu_bytes = (size_t)plan.width * plan.height * pixel;
if (plan.mipmapped)
for (u32 d = plan.width / 2; d != 0; d /= 2)
gpu_bytes += (size_t)d * d * pixel;
gpu = (u8 *)realloc(gpu, gpu_bytes);
for (i = 0; i < plan.levels; i++)
{
u32 width;
const size_t at = LevelAt(plan.level[i].mip, &width);
texd_c_level(bytes, &plan, &plan.level[i], gpu + at, width);
}
free(bytes);
cache_whole_count++;
return true;
}
int GpuDecodePart(const texd_plan& plan, const TexRegion *regions, u32 count) override
{
const size_t pixel = Pixel(tex_type);
TexPartPlan part;
u8 *ring;
u32 i, x, y;
if (!cache_gpu)
return TEXP_CPU;
if (!cache_parts || gpu == NULL || !tex_plan_part(&plan, regions, count, &part))
return TEXP_WHOLE;
part_check(this, made_mips, regions, count);
/* the copy as a renderer makes it, and the kernel over each region */
ring = (u8 *)calloc(part.total + 4, 1);
memcpy(ring, &vram[plan.src], part.codebook);
for (i = 0; i < count; i++)
memcpy(ring + part.at[i], &vram[plan.src + part.from[i]], part.bytes[i]);
if (plan.pal_entries != 0)
memcpy(ring + part.pal, (plan.out == TEXD_OUT16 ? palette16_ram : palette32_ram) + plan.pal_index, plan.pal_entries * 4);
texd_c_set(ring);
for (i = 0; i < count; i++)
{
const texd_level *lv = &plan.level[part.level[i]];
u32 width;
const size_t at = LevelAt(regions[i].level, &width);
for (y = regions[i].y; y < regions[i].y + regions[i].h; y++)
for (x = regions[i].x; x < regions[i].x + regions[i].w; x++)
{
const u32 v = texd_texel(part.data[i], part.pal, lv->sr, lv->mode, x, y);
memcpy(gpu + at + ((size_t)y * width + x) * pixel, &v, pixel);
}
cache_part_bytes += (size_t)regions[i].w * regions[i].h * pixel;
}
free(ring);
cache_part_count++;
return TEXP_DONE;
}
};
class CacheOf : public BaseTextureCache<CacheTex>
{
public:
void Free()
{
cache.Each([](u64, CacheTex& t) {
free(t.gpu);
t.Delete();
return true;
});
}
};
/* address, format, width and height (8 << n), planar, compressed */
static const u32 cache_textures[][6] = {
{ 0x10000, Pixel565, 3, 3, 0, 0 },
{ 0x10000, Pixel1555, 3, 3, 0, 0 },
{ 0x12000, Pixel4444, 2, 2, 0, 0 },
{ 0x12800, Pixel565, 2, 1, 1, 0 },
{ 0x13000, Pixel565, 4, 2, 0, 0 },
{ 0x14000, Pixel565, 3, 3, 0, 1 },
};
#define CACHE_TEXTURES (sizeof(cache_textures) / sizeof(cache_textures[0]))
static void cache_key(u32 i, TSP *tsp, TCW *tcw)
{
tsp->full = 0;
tcw->full = 0;
tsp->TexU = cache_textures[i][2];
tsp->TexV = cache_textures[i][3];
tcw->TexAddr = cache_textures[i][0] >> 3;
tcw->PixelFmt = cache_textures[i][1];
tcw->ScanOrder = cache_textures[i][4];
tcw->VQ_Comp = cache_textures[i][5];
}
/* What a texture made now uploads, against what @t has uploaded */
static bool cache_same_as_new(const CacheTex *t, u32 i)
{
CacheTex fresh;
bool same;
cache_key(i, &fresh.tsp, &fresh.tcw);
fresh.area = 0;
fresh.gpu = NULL;
fresh.Create();
/* (unprotected, as a texture updated often is: it leaves no lock) */
fresh.Updates = FREQUENT_UPDATE_COUNT;
fresh.Update();
same = fresh.gpu != NULL && t->gpu_bytes == fresh.gpu_bytes && !memcmp(t->gpu, fresh.gpu, fresh.gpu_bytes);
free(fresh.gpu);
fresh.Delete();
return same;
}
static CacheTex *cache_get(CacheOf *cache, u32 i)
{
TSP tsp;
TCW tcw;
CacheTex *t;
cache_key(i, &tsp, &tcw);
t = cache->getTextureCacheData(tsp, tcw);
if (!t->made)
{
t->gpu = NULL;
t->made = true;
t->Create();
}
if (t->NeedsUpdate())
t->Update();
return t;
}
/* The SH4's write: a protected page faults first */
static void cache_write(u32 addr, u32 bytes, bool same)
{
u32 page;
u32 i;
for (page = addr / PAGE_SIZE; page <= (addr + bytes - 1) / PAGE_SIZE; page++)
if (cache_protected[page])
VramLockedWriteOffset(page * PAGE_SIZE);
if (!same)
for (i = 0; i < bytes; i++)
vram[addr + i] = (u8)rnd();
}
static int cache_test(void)
{
CacheOf cache;
CacheTex *t;
u32 frame;
u32 i;
u32 checks = 0;
size_t still;
vram.data = (u8 *)malloc(8 << 20);
vram.size = 8 << 20;
for (i = 0; i < (8u << 20); i++)
vram[i] = (u8)rnd();
settings.rend.TextureUpscale = 1;
settings.rend.MaxFilteredTextureSize = 256;
for (frame = 0; frame < 400; frame++)
{
u32 n;
u32 lookup;
FrameCount++;
for (n = rnd() % 4; n != 0; n--)
cache_write(0x10000 + (rnd() % 0x8000 & ~1u), 2 + (rnd() % 4096 & ~1u), (rnd() & 1) != 0);
for (lookup = 0; lookup < 24; lookup++)
{
i = rnd() % CACHE_TEXTURES;
if (lookup == 12 && rnd() % 3 == 0)
{
const u32 addr = 0x10000 + (rnd() % 0x8000 & ~1u);
const u32 bytes = 2 + (rnd() % 4096 & ~1u);
VramLockedWriteRange(addr, bytes);
for (n = 0; n < bytes; n++)
vram[addr + n] = (u8)rnd();
}
t = cache_get(&cache, i);
checks++;
if (!cache_same_as_new(t, i))
{
fprintf(stderr, "FAIL: texture %u in render %u, request %u: not what a new one uploads\n", i, frame, lookup);
return 1;
}
}
}
/* changed often enough to be watched by its hash, then written no
* more: hashed once a render, however often it is asked for */
for (frame = 0; frame <= FREQUENT_UPDATE_COUNT; frame++)
{
FrameCount++;
cache_write(cache_textures[0][0], 2, false);
t = cache_get(&cache, 0);
}
if (t->lock_block != nullptr || t->Updates < FREQUENT_UPDATE_COUNT)
{
fprintf(stderr, "FAIL: the texture is not watched by its hash\n");
return 1;
}
for (frame = 0; frame < 12; frame++)
{
u32 lookup;
FrameCount++;
cache_hashed = 0;
for (lookup = 0; lookup < 300; lookup++)
cache_get(&cache, 0);
}
still = cache_hashed;
if (still > t->size)
{
fprintf(stderr, "FAIL: a texture asked for 300 times in a render had %u bytes hashed\n", (unsigned)still);
return 1;
}
printf("texture cache: %u requests, every one what a full upload makes; %u bytes hashed for 300 of one\n",
checks, (unsigned)still);
cache.Free();
return 0;
}
#endif
/* "part": textures watched by their hashes, with a few bytes of them
* written between renders, as an atlas is: only the regions the changed
* pieces are in made again, on the CPU and on the GPU (the kernel run
* here, over the copy a renderer makes for it), every request held to
* what a full upload of a new texture makes. Twiddled, planar (with a
* stride wider and narrower than the texture), compressed (the codebook
* written too), paletted (the palette changed too), mipmapped; to 16, 32
* and 8 bits. And what is made: fewer texels than the texture has. */
/* address, format, width and height (8 << n), planar, compressed,
* mipmapped, stride selected, palette selection, filtered */
static const u32 part_textures[][10] = {
{ 0x100238, Pixel565, 5, 5, 0, 0, 0, 0, 0, 0 },
{ 0x140010, Pixel1555, 3, 5, 0, 0, 0, 0, 0, 0 },
{ 0x150000, Pixel4444, 6, 6, 0, 0, 0, 0, 0, 0 },
{ 0x1d0108, PixelYUV, 4, 4, 0, 0, 0, 0, 0, 0 },
{ 0x1e01c8, Pixel565, 5, 4, 1, 0, 0, 1, 0, 0 },
{ 0x210040, Pixel4444, 6, 3, 1, 0, 0, 1, 0, 0 },
{ 0x260f98, Pixel1555, 5, 4, 1, 0, 0, 0, 0, 0 },
{ 0x290f40, PixelYUV, 5, 5, 1, 0, 0, 0, 0, 0 },
{ 0x2b1828, Pixel565, 5, 5, 0, 1, 0, 0, 0, 0 },
{ 0x2c8000, Pixel1555, 5, 4, 1, 1, 0, 0, 0, 0 },
{ 0x2d0400, PixelPal4, 5, 5, 0, 0, 0, 0, 3, 0 },
{ 0x2e0000, PixelPal8, 4, 4, 0, 0, 0, 0, 16, 1 },
{ 0x2f0008, PixelPal8, 5, 3, 0, 0, 0, 0, 32, 0 },
{ 0x300040, Pixel565, 5, 5, 0, 0, 1, 0, 0, 0 },
{ 0x340000, Pixel4444, 6, 6, 0, 1, 1, 0, 0, 0 },
{ 0x350010, PixelPal4, 4, 4, 0, 0, 1, 0, 5, 1 },
{ 0x360000, PixelBumpMap, 4, 5, 0, 0, 0, 0, 0, 0 },
{ 0x7f8300, Pixel565, 5, 5, 1, 0, 0, 0, 0, 0 }, /* past the end of video memory: its first 62 rows */
};
#define PART_TEXTURES (sizeof(part_textures) / sizeof(part_textures[0]))
static u32 part_made[PART_TEXTURES];
static void part_key(u32 i, TSP *tsp, TCW *tcw)
{
const u32 *d = part_textures[i];
tsp->full = 0;
tcw->full = 0;
tsp->TexU = d[2];
tsp->TexV = d[3];
tsp->FilterMode = d[9];
tcw->TexAddr = d[0] >> 3;
tcw->PixelFmt = d[1];
tcw->VQ_Comp = d[5];
tcw->MipMapped = d[6];
/* (a paletted texture's selection has the scan order's and the stride's bits) */
if (d[1] == PixelPal4 || d[1] == PixelPal8)
tcw->PalSelect = d[8];
else
{
tcw->ScanOrder = d[4];
tcw->StrideSel = d[7];
}
}
static bool part_same_as_new(const CacheTex *t, u32 i)
{
const bool gpu = cache_gpu, parts = cache_parts;
CacheTex fresh;
bool same;
cache_gpu = cache_parts = false;
part_key(i, &fresh.tsp, &fresh.tcw);
fresh.area = 0;
fresh.gpu = NULL;
fresh.made = true;
fresh.Create();
fresh.Updates = FREQUENT_UPDATE_COUNT;
fresh.Update();
same = fresh.gpu != NULL && t->gpu_bytes == fresh.gpu_bytes && !memcmp(t->gpu, fresh.gpu, fresh.gpu_bytes);
free(fresh.gpu);
fresh.Delete();
cache_gpu = gpu;
cache_parts = parts;
return same;
}
static CacheTex *part_get(CacheOf *cache, u32 i)
{
TSP tsp;
TCW tcw;
CacheTex *t;
part_key(i, &tsp, &tcw);
t = cache->getTextureCacheData(tsp, tcw);
if (!t->made)
{
t->gpu = NULL;
t->made = true;
t->Create();
/* watched by its hashes from its first update */
t->Updates = FREQUENT_UPDATE_COUNT;
}
if (t->NeedsUpdate())
{
const u32 parts = cache_part_count;
t->Update();
if (cache_part_count != parts)
part_made[i]++;
}
return t;
}
/* A few bytes of texture @i; its codebook; much of it; the bytes either
* side of where one of the pieces it is hashed by ends; or nothing. (Its
* bytes as the cache locks and hashes them: from its first to the end of
* its largest level.) */
static void part_write(CacheOf *cache, u32 i)
{
const u32 kind = rnd() % 16;
TSP tsp;
TCW tcw;
CacheTex *t;
u32 first, bytes, at, n, k;
part_key(i, &tsp, &tcw);
t = cache->getTextureCacheData(tsp, tcw);
first = t->sa_tex;
bytes = t->sa + t->size - t->sa_tex;
if (kind < 10)
{
n = 1 + rnd() % (kind < 6 ? 64 : 3000);
at = first + rnd() % bytes;
}
else if (kind == 10 && part_textures[i][5])
{
n = 1;
at = first + rnd() % 2048;
}
else if (kind == 11)
{
n = bytes / 2 + rnd() % (bytes / 2);
at = first + rnd() % (bytes - n + 1);
}
else if (kind < 15)
{
/* the last few bytes of a piece, or its first */
n = 1 + rnd() % 8;
at = (first + rnd() % bytes) & ~(TEX_HASH_PIECE - 1);
if (kind != 14)
at -= n;
if (at < first)
at = first;
}
else
return;
if (at + n > first + bytes)
n = first + bytes - at;
for (k = 0; k < n; k++)
vram[at + k] = (u8)rnd();
}
static void part_palette(void)
{
PALETTE_RAM[rnd() % 1024] = rnd();
palette_update();
}
static int part_run(bool gpu, u32 ctrl, bool force32)
{
CacheOf cache;
u32 frame, i;
size_t whole = 0;
cache_gpu = gpu;
cache_parts = true;
cache_force32 = force32;
settings.rend.GpuTextureDecode = gpu;
PAL_RAM_CTRL = ctrl;
TEXT_CONTROL = 10; /* a stride of 320 */
palette_update();
memset(part_made, 0, sizeof(part_made));
cache_part_bytes = 0;
for (frame = 0; frame < 120; frame++)
{
FrameCount++;
if (frame != 0)
for (i = 0; i < PART_TEXTURES; i++)
{
u32 n;
for (n = 1 + rnd() % 3; n != 0; n--)
part_write(&cache, i);
}
if (rnd() % 23 == 0)
part_palette();
if (rnd() % 37 == 0)
TEXT_CONTROL = TEXT_CONTROL == 10 ? 12 : 10;
for (i = 0; i < PART_TEXTURES; i++)
{
CacheTex *t = part_get(&cache, i);
if (!part_same_as_new(t, i))
{
fprintf(stderr, "FAIL: %s, palette %u%s: texture %u in render %u is not what a new one uploads\n",
gpu ? "GPU" : "CPU", ctrl, force32 ? ", 32-bit" : "", i, frame);
return 1;
}
if (frame == 0)
whole += t->gpu_bytes;
}
}
if (cache_bad_regions != 0)
{
fprintf(stderr, "FAIL: %s, palette %u%s: %u regions off their level, not on multiples of 8 or overlapping\n",
gpu ? "GPU" : "CPU", ctrl, force32 ? ", 32-bit" : "", cache_bad_regions);
return 1;
}
for (i = 0; i < PART_TEXTURES; i++)
if (part_made[i] == 0)
{
fprintf(stderr, "FAIL: %s, palette %u%s: texture %u never had a part of it made again\n",
gpu ? "GPU" : "CPU", ctrl, force32 ? ", 32-bit" : "", i);
return 1;
}
printf("texture parts (%s, palette %u%s): every request what a full upload makes; %u KB made in parts, of %u KB a whole round\n",
gpu ? "GPU" : "CPU", ctrl, force32 ? ", 32-bit" : "", (unsigned)(cache_part_bytes >> 10), (unsigned)(whole >> 10));
cache.Free();
return 0;
}
static int part_test(void)
{
u32 i;
vram.data = (u8 *)malloc(8 << 20);
vram.size = 8 << 20;
for (i = 0; i < (8u << 20); i++)
vram[i] = (u8)rnd();
settings.rend.TextureUpscale = 1;
settings.rend.MaxFilteredTextureSize = 256;
for (i = 0; i < 8; i++)
if (part_run((i & 1) != 0, (i & 2) ? 3 : 1, (i & 4) != 0))
return 1;
free(vram.data);
return 0;
}
int main(int argc, char **argv)
{
static const u32 sizes[][2] = { {8, 8}, {32, 32}, {64, 16}, {16, 64}, {256, 256} };
#ifndef UPSTREAM
if (argc > 1 && !strcmp(argv[1], "cache"))
return cache_test();
if (argc > 1 && !strcmp(argv[1], "part"))
return part_test();
#endif
buf = (u8 *)malloc(4 * 1024 * 1024);
if (argc > 1 && !strcmp(argv[1], "ramp"))
for (u32 i = 0; i < 4 * 1024 * 1024; i += 2) { buf[i] = (i / 2) & 0xFF; buf[i + 1] = ((i / 2) >> 8) & 0xFF; }
else
for (u32 i = 0; i < 4 * 1024 * 1024; i++) buf[i] = (u8)rnd();
vq_codebook = buf;
palette_index = 64;
for (u32 i = 0; i < 1024; i++) { palette16_ram[i] = (i * 2654435761u) >> 16; palette32_ram[i] = i * 2246822519u; }
for (auto& s : sizes)
{
u32 w = s[0], h = s[1];
#ifdef UPSTREAM
run<u16>("1555_TW", T[0].TW, w, h); run<u16>("1555_VQ", T[0].VQ, w, h);
run<u16>("565_TW", T[1].TW, w, h); run<u16>("565_VQ", T[1].VQ, w, h);
run<u16>("4444_TW", T[2].TW, w, h); run<u16>("4444_VQ", T[2].VQ, w, h);
run<u16>("BMP_TW", T[4].TW, w, h); run<u16>("BMP_VQ", T[4].VQ, w, h);
run<u16>("PAL4_TW", T[5].TW, w, h); run<u16>("PAL4_VQ", T[5].VQ, w, h);
run<u16>("PAL8_TW", T[6].TW, w, h); run<u16>("PAL8_VQ", T[6].VQ, w, h);
run<u32>("1555_PL32", T[0].PL32, w, h); run<u32>("1555_TW32", T[0].TW32, w, h); run<u32>("1555_VQ32", T[0].VQ32, w, h);
run<u32>("565_PL32", T[1].PL32, w, h); run<u32>("565_TW32", T[1].TW32, w, h); run<u32>("565_VQ32", T[1].VQ32, w, h);
run<u32>("4444_PL32", T[2].PL32, w, h); run<u32>("4444_TW32", T[2].TW32, w, h); run<u32>("4444_VQ32", T[2].VQ32, w, h);
run<u32>("YUV_PL", T[3].PL32, w, h); run<u32>("YUV_TW", T[3].TW32, w, h); run<u32>("YUV_VQ", T[3].VQ32, w, h);
run<u32>("PAL4_TW32", T[5].TW32, w, h); run<u32>("PAL4_VQ32", T[5].VQ32, w, h);
run<u32>("PAL8_TW32", T[6].TW32, w, h); run<u32>("PAL8_VQ32", T[6].VQ32, w, h);
run<u8>("PAL4PT_TW", T[5].TW8, w, h); run<u8>("PAL8PT_TW", T[6].TW8, w, h);
run<u32>("1555_PLVQ32", T[0].PLVQ32, w, h); run<u32>("565_PLVQ32", T[1].PLVQ32, w, h);
run<u32>("4444_PLVQ32", T[2].PLVQ32, w, h); run<u32>("YUV_PLVQ", T[3].PLVQ32, w, h);
#else
run<u16>("1555_TW", &tex1555_TW, w, h); run<u16>("1555_VQ", &tex1555_VQ, w, h);
run<u16>("565_TW", &tex565_TW, w, h); run<u16>("565_VQ", &tex565_VQ, w, h);
run<u16>("4444_TW", &tex4444_TW, w, h); run<u16>("4444_VQ", &tex4444_VQ, w, h);
run<u16>("BMP_TW", &texBMP_TW, w, h); run<u16>("BMP_VQ", &texBMP_VQ, w, h);
run<u16>("PAL4_TW", &texPAL4_TW, w, h); run<u16>("PAL4_VQ", &texPAL4_VQ, w, h);
run<u16>("PAL8_TW", &texPAL8_TW, w, h); run<u16>("PAL8_VQ", &texPAL8_VQ, w, h);
run<u32>("1555_PL32", &tex1555_PL32, w, h); run<u32>("1555_TW32", &tex1555_TW32, w, h); run<u32>("1555_VQ32", &tex1555_VQ32, w, h);
run<u32>("565_PL32", &tex565_PL32, w, h); run<u32>("565_TW32", &tex565_TW32, w, h); run<u32>("565_VQ32", &tex565_VQ32, w, h);
run<u32>("4444_PL32", &tex4444_PL32, w, h); run<u32>("4444_TW32", &tex4444_TW32, w, h); run<u32>("4444_VQ32", &tex4444_VQ32, w, h);
run<u32>("YUV_PL", &texYUV422_PL, w, h); run<u32>("YUV_TW", &texYUV422_TW, w, h); run<u32>("YUV_VQ", &texYUV422_VQ, w, h);
run<u32>("PAL4_TW32", &texPAL4_TW32, w, h); run<u32>("PAL4_VQ32", &texPAL4_VQ32, w, h);
run<u32>("PAL8_TW32", &texPAL8_TW32, w, h); run<u32>("PAL8_VQ32", &texPAL8_VQ32, w, h);
run<u8>("PAL4PT_TW", &texPAL4PT_TW, w, h); run<u8>("PAL8PT_TW", &texPAL8PT_TW, w, h);
run<u32>("1555_PLVQ32", &tex1555_PLVQ32, w, h); run<u32>("565_PLVQ32", &tex565_PLVQ32, w, h);
run<u32>("4444_PLVQ32", &tex4444_PLVQ32, w, h); run<u32>("YUV_PLVQ", &texYUV422_PLVQ, w, h);
#endif
}
return 0;
}