Files
psxrecomp/runtime/include/duckstation_texture_pack.h
Shokunin 954e28b051 HD preload: bounded contract, safe threads, explicit partial result
Both preload functions now take an aggregate decoded-byte cap and a thread
cap (runtime: 512 MiB, 4 threads). Each image is probed and its RGBA
reserved against the cap before decoding; per-image limits follow the
pack's decode budget, which is no longer grown to usage (pinned images sit
outside it, and outside the Duck 512-entry cap). The pool (texture_pack_
preload.hpp) joins every started thread on unwinding, survives thread
creation failure, and catches worker exceptions. Images not preloaded are
left to the asynchronous budgeted path instead of being marked failed, and
HdTexturePreloadResult reports loaded / over-cap / failed / worker errors
with a complete-or-partial status. The Beetle async worker no longer lets
allocation failures escape its thread entry.

Tests cover both formats: complete preload, over-cap skip with on-demand
fallback, malformed images, injected worker and thread-creation failures,
and budget reservation bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:36:08 -07:00

210 lines
10 KiB
C

#ifndef PSXRECOMP_DUCKSTATION_TEXTURE_PACK_H
#define PSXRECOMP_DUCKSTATION_TEXTURE_PACK_H
#include "hd_texture_pack.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct DuckTexturePack DuckTexturePack;
enum DuckTextureKind { DUCK_TEXTURE_UPLOAD = 0, DUCK_TEXTURE_PAGE = 1 };
/* Independent representation of the modern filename wire format. Source
* width is in VRAM words; offsets and rectangle sizes are expanded texels.
* Hashes are unseeded XXH3-64 over explicitly little-endian native words. */
typedef struct DuckTextureKey {
uint64_t source_hash;
uint64_t palette_hash;
uint16_t source_width_words;
uint16_t source_height;
uint16_t offset_x;
uint16_t offset_y;
uint16_t width;
uint16_t height;
uint8_t kind;
uint8_t depth; /* enum HdTextureDepth */
uint8_t semitransparent; /* STP4 / STP8 / STC16 alpha convention */
uint8_t palette_min;
uint8_t palette_max;
} DuckTextureKey;
typedef struct DuckTexturePackInfo {
const char* root;
const char* diagnostic; /* unsupported config/formats and malformed names */
size_t replacement_count;
size_t ambiguous_count;
size_t ignored_count;
size_t queued_dump_count; /* Cumulative jobs actually submitted to writer. */
size_t pending_dump_sources;
size_t pending_palette_records;
size_t snapshot_bytes;
uint64_t decoded_images, decode_evictions;
size_t decoded_bytes;
} DuckTexturePackInfo;
typedef struct DuckTextureMatch {
DuckTextureKey key;
uint64_t entry_id;
const char* replacement_path;
/* Origin of replacement rectangle relative to current texture page, in
* texels. May be negative for uploads spanning more than one page. */
int32_t origin_u;
int32_t origin_v;
uint16_t source_width;
uint16_t source_height;
/* Intersection with this draw and the surviving native source region.
* These page-space texels clip the full replacement mapping above. */
int32_t clip_u;
int32_t clip_v;
uint16_t clip_width;
uint16_t clip_height;
} DuckTextureMatch;
typedef struct DuckTextureConfig {
uint16_t max_vram_write_splits;
uint16_t max_vram_write_coalesce_width;
uint16_t max_vram_write_coalesce_height;
uint16_t texture_dump_width_threshold;
uint16_t texture_dump_height_threshold;
uint16_t vram_write_dump_width_threshold;
uint16_t vram_write_dump_height_threshold;
uint8_t dump_texture_pages;
uint8_t dump_full_texture_pages;
uint8_t dump_texture_force_alpha_channel;
uint8_t dump_vram_write_force_alpha_channel;
uint8_t dump_c16_textures;
uint8_t reduce_palette_range;
uint8_t convert_copies_to_writes;
uint8_t replacement_scale_linear_filter;
} DuckTextureConfig;
typedef struct DuckTexturePixels {
const uint8_t* rgba;
uint32_t width;
uint32_t height;
uint32_t stride;
void* lease;
} DuckTexturePixels;
/* Accepts a game/package folder containing replacements/ and dumps/, or the
* replacements directory itself. Missing folders are valid empty packs, so
* a new pack can start by dumping. Recursive image scan; no game assets bundled.
* PNG, JPEG and WebP payloads; unsupported options and invalid names are
* diagnosed. Upstream config.yaml keys are root-level scalar options. */
int duck_texture_pack_create(const char* root, DuckTexturePack** out_pack,
char* error, size_t error_capacity);
void duck_texture_pack_destroy(DuckTexturePack* pack);
void duck_texture_pack_get_info(const DuckTexturePack* pack,
DuckTexturePackInfo* out_info);
void duck_texture_pack_get_config(const DuckTexturePack* pack,
DuckTextureConfig* out_config);
int duck_texture_pack_linear_filter(const DuckTexturePack* pack);
uint64_t duck_texture_pack_revision(const DuckTexturePack* pack);
int duck_texture_parse_name(const char* filename, DuckTextureKey* out_key);
int duck_texture_format_name(const DuckTextureKey* key, char* output,
size_t capacity); /* stem only, without .png */
uint64_t duck_texture_hash_words_le(const uint16_t* words, size_t count);
/* Native word rectangle must not wrap. Zero on invalid input (also a valid
* hash value); use valid geometry rather than zero as a success indication. */
uint64_t duck_texture_hash_rect(const uint16_t* vram, size_t count,
uint16_t x, uint16_t y,
uint16_t width_words, uint16_t height);
/* Track post-write native VRAM, after PSX set/check-mask semantics. Wrapped
* uploads are invalidated but deliberately not tracked. Immutable original
* words survive partial overwrites and configurable active-region splits. */
int duck_texture_pack_track_upload(DuckTexturePack* pack,
uint16_t x, uint16_t y,
uint16_t width_words, uint16_t height,
const uint16_t* vram, size_t count);
void duck_texture_pack_invalidate(DuckTexturePack* pack,
uint16_t x, uint16_t y,
uint16_t width_words, uint16_t height);
void duck_texture_pack_reset_tracking(DuckTexturePack* pack);
/* Same-session reload only: clone immutable sources and owned observations
* while the caller keeps native VRAM unchanged. Lookup/image caches remain
* independent; only same-root filename deduplication history is shared.
* This is not a savestate restoration API. Strong replacement on success. */
int duck_texture_pack_copy_tracking(DuckTexturePack* destination,
const DuckTexturePack* source);
/* Begin before changing native VRAM, end after PSX mask semantics. Default
* copies invalidate destination identities. ConvertCopiesToWrites updates an
* existing destination write's original extent when its active region covers
* the copy; it does not clone the source upload into the destination. */
int duck_texture_pack_begin_copy(DuckTexturePack* pack,
uint16_t source_x, uint16_t source_y,
uint16_t destination_x, uint16_t destination_y,
uint16_t width_words, uint16_t height,
const uint16_t* vram, size_t count);
int duck_texture_pack_end_copy(DuckTexturePack* pack,
const uint16_t* vram, size_t count);
/* Only one replacement covering the entire inclusive query is accepted.
* UV wrapping, edge-wrapped pages and multi-image composition fall back.
* Uses HD_TEXTURE_LOOKUP_* statuses. Query geometry is shared with Beetle. */
int duck_texture_pack_match(DuckTexturePack* pack,
const HdTextureDrawQuery* query,
DuckTextureMatch* out_match);
/* Prefer ST names on semitransparent primitives and ordinary names otherwise;
* when only the other convention exists it remains usable. The filename's
* convention always determines how the renderer interprets its alpha. */
int duck_texture_pack_match_draw(DuckTexturePack* pack,
const HdTextureDrawQuery* query,
int semitransparent, DuckTextureMatch* out_match);
/* All intersecting parts, in composition order: pages before uploads, larger
* rectangles before smaller ones. Same-coverage ST variants prefer this draw's
* convention. An insufficient capacity returns ERROR and zero parts, never a
* silently truncated composition. At most 64 parts are supported. */
int duck_texture_pack_match_parts(DuckTexturePack* pack,
const HdTextureDrawQuery* query,
int semitransparent,
DuckTextureMatch* out_matches,
size_t capacity, size_t* out_count);
/* One bounded background worker; no PNG decoding in the render thread.
* Pixels retain raw DuckStation alpha bytes. The renderer interprets the
* key.semitransparent flag; this is not conventional opacity for ST names.
* Release the lease even after pack destruction. */
void duck_texture_pack_set_decode_budget(DuckTexturePack* pack, size_t bytes);
int duck_texture_pack_request_decode(DuckTexturePack* pack, uint64_t entry_id);
/* Elective full preload with the hd_texture_pack_preload contract: bounded
* aggregate decoded bytes and concurrency, pinned images outside the decode
* budget, everything not preloaded left to the asynchronous decode path.
* Returns 1 complete, 0 partial (see out), -1 on error. */
int duck_texture_pack_preload(DuckTexturePack* pack, size_t max_bytes,
unsigned max_threads, HdTexturePreloadResult* out);
int duck_texture_pack_acquire_decoded(DuckTexturePack* pack, uint64_t entry_id,
DuckTexturePixels* out_pixels);
void duck_texture_pixels_release(DuckTexturePixels* pixels);
/* Records a draw's used region and captured palette, unioning nested crops
* until the upload/page retires or a flush occurs. Upload-only by default;
* minimum 16x16, C16 off and reduced palette ranges on. Any semi draw selects
* ST unless ForceAlpha is enabled. FOUND means new usage was recorded, not
* that a file was written. Existing files are never overwritten. */
int duck_texture_pack_dump_draw(DuckTexturePack* pack,
const HdTextureDrawQuery* query,
int semitransparent,
char* error, size_t error_capacity);
/* Finalize current usage without discarding live identities, then drain dump
* writes. Call before disabling dumps/changing folders. Reset/destruction also
* finalize. Reload copies own independent sources and share same-root filename
* history, so destroying a copied pack never steals the original's records.
* Returns FOUND on success (including empty flush), ERROR on failure. */
int duck_texture_pack_flush_dumps(DuckTexturePack* pack,
char* error, size_t error_capacity);
void duck_texture_pack_reset_dump(DuckTexturePack* pack);
/* Queue changed captures without retiring source/palette observations. Draws
* also checkpoint periodically so resident backgrounds appear while playing.
* flush_dumps additionally waits for durable files. */
int duck_texture_pack_checkpoint_dumps(DuckTexturePack* pack,
char* error, size_t error_capacity);
#ifdef __cplusplus
}
#endif
#endif