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.
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>
Replacement images were decoded on demand by a single low-priority
worker, so a texture first seen mid-race appeared late and could be
evicted again. When replacements are on, configuring a pack now decodes
every unambiguous image (DuckStation and Beetle formats) across all
cores and pins it in RAM before the game draws; pinned images are never
evicted by the decode budget. Logs the count, size and time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Filtered replacement sampling: with any texture filter but nearest, the
replacement is sampled through its mip chain (textureGrad), the footprint
clamped to the primitive's own UV bounds so atlas neighbours cannot bleed
in. Texture windows (repeat) and UVs outside the page keep hd_texel.
- Edge clamp: an out-of-bounds sample clamped to the primitive's UV bounds
takes the edge's sub-texel position too (keeping the outside fraction read
a 1 px sliver of the next glyph at sprite borders).
- GL cache: 4096 slots and a PSX_HD_GL_CACHE_MIB budget (default 2048 MiB)
with a direct-mapped hint table; a 4x pack of one race scene is several
hundred textures, and the old 256 slot / 128 MiB cache re-uploaded and
re-mipped them every frame.
- Invalidation: duck_texture_pack_invalidate returns early when a native
write touches no residency or cache entry (almost every draw under HD
authority lands on the frame buffer), and retire_unused walks the
residency lists once instead of once per source.
- Indexed replacements: a TTTTTTTT-index.png key resolves through the palette
in use at draw time, for packs whose textures are shared by several CLUTs.
- Debug: TCP {"cmd":"hd_uploads","x":..,"y":..} lists the texture uploads
covering a VRAM point (pack authoring).
Measured with a full HD HUD and course pack for R4: steady 60 fps in races
where the old cache dropped to the high 20s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkyma8rbyPiccs9n2bkyG6
- The .hdres sidecar (version 2) carries the pack's identity, a hash of its
sorted replacement keys (hd_texture_pack_identity); a load with another
pack is refused, as the docs say.
- The learned upload rectangles are bounded by area as well as count: a
restage (load, rewind) re-hashes at most one VRAM's worth of words.
- gl_hd_texture_pack_test: residency is refused with a different pack;
before the load a textured draw shows native artwork, after it the same
draw shows the HD replacement and counts as an applied draw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewind and in-session savestate loads have no sidecar. Each Beetle-keyed
upload rectangle seen in the session is remembered (at most 256); after a
restage it becomes resident again only if the restored words still hash to
a replacement key, so the HUD keeps its replacement across loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>