Files
psxrecomp/runtime/include/hd_texture_pack.h
T
Matthew Stanley c3ff07814c Account existing pinned textures across preload retries
Bound reservations against total retained residency, skip decoding pinned images, and report complete repeated loads truthfully. Cover cap reductions and publication retry in both formats. Tracking: beads-eio.3.386.
2026-10-24 12:36:09 -07:00

227 lines
10 KiB
C

#ifndef PSXRECOMP_HD_TEXTURE_PACK_H
#define PSXRECOMP_HD_TEXTURE_PACK_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* External Beetle-style texture-pack loader and VRAM upload tracker.
* The pack payload is never copied into or owned by the executable. */
typedef struct HdTexturePack HdTexturePack;
enum HdTextureDepth {
HD_TEXTURE_DEPTH_4BPP = 0,
HD_TEXTURE_DEPTH_8BPP = 1,
HD_TEXTURE_DEPTH_16BPP = 2
};
enum HdTextureLookupStatus {
HD_TEXTURE_LOOKUP_ERROR = -1,
HD_TEXTURE_LOOKUP_NONE = 0,
HD_TEXTURE_LOOKUP_FOUND = 1,
HD_TEXTURE_LOOKUP_AMBIGUOUS = 2
};
/* Palette identity of an indexed replacement, named TTTTTTTT-index.png: its
* red channel holds CLUT indices (alpha 0 = transparent) and the renderer
* resolves them through the live CLUT of each draw, so one image serves every
* palette the game draws the texture with (highlights, fades). An exact
* TTTTTTTT-PPPPPPPP image still wins for its own palette. */
#define HD_TEXTURE_PALETTE_INDEXED 0xFFFFFFFFu
typedef struct HdTexturePackInfo {
const char* asset_root; /* valid until hd_texture_pack_destroy */
const char* replacement_root; /* valid until hd_texture_pack_destroy */
size_t replacement_file_count;
size_t unique_key_count;
size_t ambiguous_key_count;
size_t logical_mapping_count;
} HdTexturePackInfo;
typedef struct HdTexturePackEntry {
uint32_t texture_hash;
uint32_t palette_hash;
const char* replacement_path; /* valid until hd_texture_pack_destroy */
const char* logical_path; /* empty when Hashes.ini has no mapping */
} HdTexturePackEntry;
/* A UV interval is inclusive. first > last means that the 8-bit texture
* coordinate wraps through 255 -> 0. page_x/page_y are VRAM word coordinates.
* vram must contain the complete 1024x512 native PSX VRAM image. */
typedef struct HdTextureDrawQuery {
uint16_t page_x;
uint16_t page_y;
uint8_t depth; /* enum HdTextureDepth */
uint8_t u_first;
uint8_t u_last;
uint8_t v_first;
uint8_t v_last;
uint16_t clut_x;
uint16_t clut_y;
const uint16_t* vram;
size_t vram_word_count;
} HdTextureDrawQuery;
typedef struct HdTextureMatch {
HdTexturePackEntry entry;
uint64_t upload_serial;
uint16_t upload_width_words;
uint16_t upload_height;
uint16_t source_word_x; /* first queried word within the original upload */
uint16_t source_y;
} HdTextureMatch;
/* explicit_root wins. When it is NULL/empty, PSXRECOMP_HD_TEXTURE_ROOT is
* consulted. Accepts either:
* root/Hashes.ini + exactly one <name>-texture-replacements directory, or
* the replacement directory directly (Hashes.ini is read from it or parent).
* Returns 1 on success and leaves *out_pack NULL on failure. */
int hd_texture_pack_create(const char* explicit_root,
HdTexturePack** out_pack,
char* error,
size_t error_capacity);
void hd_texture_pack_destroy(HdTexturePack* pack);
void hd_texture_pack_get_info(const HdTexturePack* pack,
HdTexturePackInfo* out_info);
/* Key-only lookup. Ambiguous numeric aliases (for example 1-2.png and
* 00000001-2.png) deliberately return AMBIGUOUS instead of selecting by
* directory enumeration order. */
int hd_texture_pack_lookup(const HdTexturePack* pack,
uint32_t texture_hash,
uint32_t palette_hash,
HdTexturePackEntry* out_entry);
/* The pack's identity: a hash of its replacement keys (nonzero), equal for
* two loads of the same pack. Binds saved residency to the pack. */
uint32_t hd_texture_pack_identity(const HdTexturePack* pack);
/* 1 when any replacement (any palette) is keyed by this upload hash. */
int hd_texture_pack_has_texture(const HdTexturePack* pack, uint32_t texture_hash);
/* Standard reflected IEEE CRC-32 (polynomial 0xEDB88320, initial/final XOR
* 0xFFFFFFFF), feeding each uint16_t explicitly low byte then high byte. */
uint32_t hd_texture_crc32_words_le(const uint16_t* words, size_t word_count);
/* Hashes the complete live CLUT: 16 entries for 4bpp or 256 for 8bpp. CLUT X
* wraps at 1024 and Y wraps at 512. 16bpp has no palette and returns zero. */
uint32_t hd_texture_hash_clut(const uint16_t* vram,
size_t vram_word_count,
uint16_t clut_x,
uint16_t clut_y,
uint8_t depth);
/* Upload bytes are row-major logical upload words, independent of destination
* wrapping. Tracking invalidates/splits every intersected older residency.
* Width <= 1024 and height <= 512; X/Y may wrap. */
int hd_texture_pack_debug_uploads_at(const HdTexturePack* pack, unsigned x, unsigned y,
char* out, size_t cap);
int hd_texture_pack_track_upload(HdTexturePack* pack,
uint16_t x,
uint16_t y,
uint16_t width_words,
uint16_t height,
const uint16_t* words,
size_t word_count,
uint32_t* out_texture_hash);
void hd_texture_pack_invalidate(HdTexturePack* pack,
uint16_t x,
uint16_t y,
uint16_t width_words,
uint16_t height);
void hd_texture_pack_reset_tracking(HdTexturePack* pack);
/* Beetle-style upload residency is host metadata, but it is required for an
* HD replacement to keep matching after a savestate restores the same VRAM.
* The portable little-endian state below records only upload identity and the
* surviving residency fragments; decoded images and GL objects remain caches.
* save allocates with malloc (caller frees). A NULL pack serializes a valid
* empty tracker so states remain renderer-independent. check is side-effect
* free; load has strong replacement semantics and also accepts a NULL pack
* after validating the wire. */
int hd_texture_pack_tracking_state_save(const HdTexturePack* pack,
uint8_t** out_data,
size_t* out_size);
int hd_texture_pack_tracking_state_check(const uint8_t* data,
size_t size);
int hd_texture_pack_tracking_state_load(HdTexturePack* pack,
const uint8_t* data,
size_t size);
size_t hd_texture_pack_tracking_upload_count(const HdTexturePack* pack);
/* Matches only when one uniquely-keyed tracked upload still contains every
* word touched by the texture page/depth/UV query. Any pack-key or residency
* ambiguity falls back explicitly via HD_TEXTURE_LOOKUP_AMBIGUOUS. */
int hd_texture_pack_match(HdTexturePack* pack,
const HdTextureDrawQuery* query,
HdTextureMatch* out_match);
/* Live GP0 draw adapter: derives page_x/page_y/depth from the PS1 texpage word
* and applies the same deterministic residency/CLUT/UV match above. This keeps
* the renderer's texpage interpretation in the focused unit-test surface. */
int hd_texture_pack_match_draw(HdTexturePack* pack,
uint16_t texpage,
uint16_t clut_x,
uint16_t clut_y,
uint8_t u_first,
uint8_t u_last,
uint8_t v_first,
uint8_t v_last,
const uint16_t* vram,
size_t vram_word_count,
HdTextureMatch* out_match);
/* Optional, bounded, single-worker on-demand PNG decode cache. The request is
* asynchronous and low-priority on Windows. request_decode returns FOUND when
* already ready, NONE when queued/in flight, and ERROR for an absent,
* ambiguous, failed, or over-budget image. acquire_decoded returns FOUND only
* when ready. A lease keeps pixels alive across cache eviction and pack
* destruction; always release it with hd_texture_pixels_release. */
typedef struct HdTexturePixels {
const uint8_t* rgba;
uint32_t width;
uint32_t height;
uint32_t stride;
void* lease;
} HdTexturePixels;
void hd_texture_pack_set_decode_budget(HdTexturePack* pack,
size_t budget_bytes);
/* Elective full preload: decode unambiguous replacements up front and pin
* them, bounded by max_bytes of decoded RGBA in aggregate and by max_threads
* concurrent decodes (the caller's thread included). Pinned images are kept
* outside the on-demand decode budget, which stays as configured. Images not
* preloaded (over the cap, unreadable, malformed, a worker failure) are left
* to the normal asynchronous, budgeted decode path; nothing is marked failed
* by the preload. Returns 1 when every image was preloaded, 0 for a partial
* preload (see out), -1 on error. Repeated calls count already pinned images
* without reading/decoding them again; max_bytes caps the pack's total pinned
* residency. A cap smaller than existing residency is rejected with -1 and
* the existing residency reported; it does not discard live pinned images. */
typedef struct HdTexturePreloadResult {
int requested; /* unambiguous replacements considered */
int loaded; /* resident and pinned, including earlier calls */
int over_budget; /* skipped: the aggregate max_bytes was reached */
int failed; /* unreadable, malformed, too large, or worker error */
int worker_errors; /* of failed: a decode that threw (caught) */
size_t bytes; /* total decoded RGBA pinned by this pack */
} HdTexturePreloadResult;
int hd_texture_pack_preload(HdTexturePack* pack, size_t max_bytes,
unsigned max_threads, HdTexturePreloadResult* out);
int hd_texture_pack_request_decode(HdTexturePack* pack,
uint32_t texture_hash,
uint32_t palette_hash);
int hd_texture_pack_acquire_decoded(HdTexturePack* pack,
uint32_t texture_hash,
uint32_t palette_hash,
HdTexturePixels* out_pixels);
void hd_texture_pixels_release(HdTexturePixels* pixels);
#ifdef __cplusplus
}
#endif
#endif /* PSXRECOMP_HD_TEXTURE_PACK_H */