Files
questshock/engine-patches/05-desktop-fix-texture-cache-collision.patch
ml 9d0a0d5798
build / build (push) Successful in 2m52s
Add engine-debug-tools and fix critter animation rendering bugs
- engine-debug-tools/: a debug dump hotkey + dump2png to inspect
  decoded critter frames independent of the renderer (see root
  README.md's "Debugging" section).
- engine-patches/04,05: fix critter sprite scale and an OpenGL
  texture cache collision, found using that tooling.
- mdlp: fix encodeFrames RLE-diffing upscaled frames against a
  previous-frame reference the real RSD8 decoder never implements
  (verified against Night Dive's original Mac source down to the
  68K assembly) - was rendering as scattered static in-game.
- Upscale pipeline: better color quantization (dithering, pure-ramp
  exclusion) and per-resource model selection.
2026-10-18 06:59:47 +02:00

85 lines
4.0 KiB
Diff

--- a/src/MacSrc/OpenGL.cc
+++ b/src/MacSrc/OpenGL.cc
@@ -41,6 +41,7 @@
#include <map>
#include <sstream>
+#include <tuple>
struct CachedTexture {
SDL_Surface *bitmap;
@@ -83,8 +84,23 @@
static SDL_Palette *opaquePalette;
static SDL_Palette *transparentPalette;
-// Texture cache to keep SDL surfaces and GL textures in memory
-static std::map<uint64_t, CachedTexture> texturesByBitsPtr;
+// Texture cache to keep SDL surfaces and GL textures in memory. Keyed by the
+// full (bits, w, h) tuple, not a packed uint64_t - bm->bits is a full
+// pointer, and a 64-bit Linux heap/mmap address routinely has non-zero bits
+// in the 32-47 range, so OR-packing w/h on top of it (as this used to do)
+// can collide: a genuinely different (bits, w, h) triple produces the same
+// packed key as a cached entry for a different bitmap, so a lookup reports a
+// false "hit" and set_texture() reuses that entry's SDL surfaces - sized for
+// the old entry's own dimensions - for an SDL_memmove and glTexImage2D using
+// the *current* bitmap's byte count, corrupting the result with a buffer
+// overflow or leftover stale pixel data from whatever was cached before.
+// bm->bits itself is a recycled slot in the resource system's own bounded
+// LRU cache, not a stable per-resource address, so a collision is a real
+// risk in practice, not just theoretical - most noticeable with content
+// whose per-frame bitmap dimensions vary a lot (critter animations with
+// different poses per frame, especially after an AI upscale mod re-exports
+// each frame at its own independently-upscaled size).
+static std::map<std::tuple<void *, int, int>, CachedTexture> texturesByBitsPtr;
static float view_scale;
static int phys_width;
@@ -577,7 +593,7 @@
bind_texture(toCache.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bm->w, bm->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, toCache.converted->pixels);
- texturesByBitsPtr[(uint64_t)bm->bits | ((uint64_t)bm->w << 32u) | ((uint64_t)bm->h << 48u)] = toCache;
+ texturesByBitsPtr[std::make_tuple(bm->bits, (int)bm->w, (int)bm->h)] = toCache;
return true;
}
@@ -589,7 +605,7 @@
}
static CachedTexture *opengl_get_texture(grs_bitmap *bm) {
- auto iter = texturesByBitsPtr.find((uint64_t)bm->bits | ((uint64_t)bm->w << 32u) | ((uint64_t)bm->h << 48u));
+ auto iter = texturesByBitsPtr.find(std::make_tuple(bm->bits, (int)bm->w, (int)bm->h));
if (iter != texturesByBitsPtr.end()) {
return &iter->second;
}
@@ -685,13 +701,27 @@
isDirty = true;
}
} else {
+ // t->bitmap wraps its source pixels rather than owning a copy (see
+ // convert_texture()), and a cache hit means bm->bits is by
+ // definition the same pointer this entry was first cached with
+ // (opengl_get_texture()'s key includes it) - so memmove-ing
+ // bm->bits into t->bitmap->pixels used to be a no-op self-copy,
+ // never actually refreshing anything. bm->bits is a slot in the
+ // resource system's own bounded LRU cache, reused for different
+ // content across draws while its address stays the same, so a hit
+ // here doesn't mean the pixels are still current - only that this
+ // entry's GL texture object and converted-surface buffer are the
+ // right size to reuse. Re-pointing at the current source instead
+ // keeps every other field (dimensions, palette, clip rect) intact
+ // while making the surface actually see this draw's real pixels.
if (bm->type == BMT_RSD8) {
grs_bitmap decoded;
gr_rsd8_convert(bm, &decoded);
- SDL_memmove(t->bitmap->pixels, decoded.bits, bm->w * bm->h);
+ t->bitmap->pixels = decoded.bits;
} else {
- SDL_memmove(t->bitmap->pixels, bm->bits, bm->w * bm->h);
+ t->bitmap->pixels = bm->bits;
}
+ t->bitmap->pitch = bm->row;
setPaletteForBitmap(bm, t->bitmap);
SDL_BlitSurface(t->bitmap, nullptr, t->converted, nullptr);