Add engine-debug-tools and fix critter animation rendering bugs
build / build (push) Successful in 2m52s
build / build (push) Successful in 2m52s
- 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.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
--- a/src/GameSrc/objsim.c
|
||||
+++ b/src/GameSrc/objsim.c
|
||||
@@ -26,8 +26,10 @@
|
||||
// Object simulator code for Citadel
|
||||
#define __OBJSIM_SRC
|
||||
|
||||
-#include <string.h>
|
||||
+#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <sys/stat.h>
|
||||
|
||||
#include "Shock.h"
|
||||
|
||||
@@ -1924,6 +1926,93 @@
|
||||
*h = s_critterNativeH[id][frame];
|
||||
}
|
||||
|
||||
+// Resource id of the last-drawn critter animation, updated every frame from
|
||||
+// gameobj.c's FAUBJ_CRIT case - read by debug_dump_critter_animation() on
|
||||
+// the debug dump hotkey (see engine-debug-tools/, root README.md's
|
||||
+// "Debugging" section). Debug-build only: see
|
||||
+// engine-debug-tools/engine-patches/.
|
||||
+static Id s_lastCritterId = 0;
|
||||
+
|
||||
+void debug_track_critter_draw(Id id) { s_lastCritterId = id; }
|
||||
+
|
||||
+// Dumps every frame of the last-drawn critter's current animation (the
|
||||
+// whole resource id, not just the currently-visible frame) as raw
|
||||
+// palette-index bytes, one file per frame, independent of the renderer's
|
||||
+// own texture/GL state - lets a corrupted frame be told apart from
|
||||
+// corruption introduced later, in rendering. Format: 2-byte LE width,
|
||||
+// 2-byte LE height, then width*height index bytes - converted to a viewable
|
||||
+// image by engine-debug-tools/dump2png, not here, to avoid this debug path
|
||||
+// depending on the renderer's own palette/SDL state.
|
||||
+void debug_dump_critter_animation(void) {
|
||||
+ RefTable *prt;
|
||||
+ int numFrames, frame;
|
||||
+ char path[256];
|
||||
+ FILE *f;
|
||||
+
|
||||
+ if (s_lastCritterId == 0) {
|
||||
+ fprintf(stderr, "[debug dump] no critter drawn yet\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ mkdir("dumps", 0755); // ignore EEXIST - fopen below reports any real failure
|
||||
+
|
||||
+ prt = (RefTable *)ResLock(s_lastCritterId);
|
||||
+ if (prt == NULL) {
|
||||
+ fprintf(stderr, "[debug dump] ResLock(%d) failed\n", (int)s_lastCritterId);
|
||||
+ return;
|
||||
+ }
|
||||
+ numFrames = prt->numRefs;
|
||||
+ ResUnlock(s_lastCritterId);
|
||||
+
|
||||
+ fprintf(stderr, "[debug dump] id=%d numFrames=%d\n", (int)s_lastCritterId, numFrames);
|
||||
+
|
||||
+ for (frame = 0; frame < numFrames; frame++) {
|
||||
+ FrameDesc *fd;
|
||||
+ grs_bitmap decoded;
|
||||
+ uchar *bits;
|
||||
+ short w, h;
|
||||
+
|
||||
+ fd = (FrameDesc *)RefLock(MKREF(s_lastCritterId, frame));
|
||||
+ if (fd == NULL) {
|
||||
+ fprintf(stderr, "[debug dump] RefLock frame %d failed\n", frame);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (fd->bm.type == BMT_RSD8) {
|
||||
+ if (gr_rsd8_convert(&fd->bm, &decoded) != 0 /* GR_UNPACK_RSD8_OK */) {
|
||||
+ fprintf(stderr, "[debug dump] gr_rsd8_convert frame %d failed\n", frame);
|
||||
+ RefUnlock(MKREF(s_lastCritterId, frame));
|
||||
+ continue;
|
||||
+ }
|
||||
+ bits = decoded.bits;
|
||||
+ w = decoded.w;
|
||||
+ h = decoded.h;
|
||||
+ } else {
|
||||
+ bits = fd->bm.bits;
|
||||
+ w = fd->bm.w;
|
||||
+ h = fd->bm.h;
|
||||
+ }
|
||||
+
|
||||
+ snprintf(path, sizeof(path), "dumps/id%d_frame%d.raw", (int)s_lastCritterId, frame);
|
||||
+ f = fopen(path, "wb");
|
||||
+ if (f != NULL) {
|
||||
+ uchar hdr[4];
|
||||
+ hdr[0] = (uchar)(w & 0xFF);
|
||||
+ hdr[1] = (uchar)((w >> 8) & 0xFF);
|
||||
+ hdr[2] = (uchar)(h & 0xFF);
|
||||
+ hdr[3] = (uchar)((h >> 8) & 0xFF);
|
||||
+ fwrite(hdr, 1, 4, f);
|
||||
+ fwrite(bits, 1, (size_t)w * (size_t)h, f);
|
||||
+ fclose(f);
|
||||
+ fprintf(stderr, "[debug dump] wrote %s (%dx%d)\n", path, (int)w, (int)h);
|
||||
+ } else {
|
||||
+ fprintf(stderr, "[debug dump] failed to open %s for writing\n", path);
|
||||
+ }
|
||||
+
|
||||
+ RefUnlock(MKREF(s_lastCritterId, frame));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
errtype obj_set_secondary_properties() {
|
||||
char i, j;
|
||||
RefTable *prt;
|
||||
--- a/src/GameSrc/Headers/objsim.h
|
||||
+++ b/src/GameSrc/Headers/objsim.h
|
||||
@@ -109,6 +109,10 @@
|
||||
errtype obj_create_player(ObjLoc *plr_loc);
|
||||
Ref ref_from_critter_data(ObjID oid, int triple, byte posture, short frame, short view);
|
||||
void get_critter_native_size(Id id, short frame, short *w, short *h);
|
||||
+// Debug-build only - see engine-debug-tools/, root README.md's "Debugging"
|
||||
+// section.
|
||||
+void debug_track_critter_draw(Id id);
|
||||
+void debug_dump_critter_animation(void);
|
||||
errtype obj_zero_unused();
|
||||
grs_bitmap *obj_get_model_data(ObjID id, fix *x, fix *y, fix *z, grs_bitmap *bm2, Ref *ref1, Ref *ref2);
|
||||
errtype obj_model_hack(ObjID id, uchar *hack_x, uchar *hack_y, uchar *hack_z, uchar *hack_type);
|
||||
--- a/src/GameSrc/gameobj.c
|
||||
+++ b/src/GameSrc/gameobj.c
|
||||
@@ -1019,6 +1019,7 @@
|
||||
{
|
||||
short native_w = 0, native_h = 0;
|
||||
get_critter_native_size(REFID(ref), REFINDEX(ref), &native_w, &native_h);
|
||||
+ debug_track_critter_draw(REFID(ref));
|
||||
_fr_draw_bitmap(tpdata, _fdt_dist, 0, anch.ul.x, anch.ul.y, native_w, native_h);
|
||||
}
|
||||
release_critter_bitmap_fast(ref);
|
||||
--- a/src/Libraries/INPUT/Source/sdl_events.c
|
||||
+++ b/src/Libraries/INPUT/Source/sdl_events.c
|
||||
@@ -481,6 +481,19 @@
|
||||
SDL_Event ev;
|
||||
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
+ // Debug-build only (engine-debug-tools/, root README.md's
|
||||
+ // "Debugging" section): K dumps every frame of the last-drawn
|
||||
+ // critter's current animation to dumps/. Checked here, before the
|
||||
+ // switch below dispatches on event type at all, so it can never
|
||||
+ // collide with an existing game keybinding - a plain letter rather
|
||||
+ // than Insert, which some keyboards don't send at all (compact
|
||||
+ // layouts, or routed through Fn).
|
||||
+ if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_k) {
|
||||
+ extern void debug_dump_critter_animation(void);
|
||||
+ debug_dump_critter_animation();
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
switch (ev.type) {
|
||||
case SDL_QUIT:
|
||||
// a bit hacky at this place, but this would allow exiting the game via the window's [x] button
|
||||
@@ -0,0 +1,166 @@
|
||||
--- a/src/Libraries/2D/Source/RSD/rsdcvt.c
|
||||
+++ b/src/Libraries/2D/Source/RSD/rsdcvt.c
|
||||
@@ -37,6 +37,7 @@
|
||||
*
|
||||
*/
|
||||
#include <assert.h>
|
||||
+#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "grs.h"
|
||||
#include "bitmap.h"
|
||||
@@ -47,6 +48,14 @@
|
||||
|
||||
uchar *grd_unpack_buf=NULL;
|
||||
|
||||
+// Set by debug_dump_critter_animation() (engine-debug-tools/, root
|
||||
+// README.md's "Debugging" section) around its own gr_rsd8_convert() calls,
|
||||
+// so a trace of this decode's internals - which path ran, whether it wrote
|
||||
+// fewer bytes than the bitmap's own area, and (gr_rsd8_unpack(),
|
||||
+// RSDUnpack.c) each opcode consumed - prints only for those calls, not
|
||||
+// every RSD8 decode in the game. Debug-build only.
|
||||
+int qs_debug_rsd8_trace = 0;
|
||||
+
|
||||
/*************************************************/
|
||||
/* Puts 0's in place of skips and pads with 0's. */
|
||||
/* i.e., grd_unpack_buf is entirely overwritten. */
|
||||
@@ -138,6 +147,15 @@
|
||||
}
|
||||
}
|
||||
rsd_done:
|
||||
+ if (qs_debug_rsd8_trace) {
|
||||
+ long written = (long)(p_dst - dbm->bits);
|
||||
+ long expected = (long)dbm->row * (long)dbm->h;
|
||||
+ fprintf(stderr,
|
||||
+ "[debug rsd8] path=%s sbm(w=%d h=%d row=%d) dbm(row=%d h=%d) "
|
||||
+ "written=%ld expected=%ld short_by=%ld\n",
|
||||
+ (dbm->w == dbm->row) ? "fast" : "slow", sbm->w, sbm->h, sbm->row,
|
||||
+ dbm->row, dbm->h, written, expected, expected - written);
|
||||
+ }
|
||||
if ((over_run = (dbm->bits+dbm->row*dbm->h)-p_dst)) {
|
||||
assert(over_run > 0);
|
||||
LG_memset (p_dst, kSkipColor, over_run);
|
||||
--- a/src/Libraries/2D/Source/RSD/RSDUnpack.c
|
||||
+++ b/src/Libraries/2D/Source/RSD/RSDUnpack.c
|
||||
@@ -22,6 +22,7 @@
|
||||
//
|
||||
|
||||
#include "grs.h"
|
||||
+#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -34,6 +35,10 @@
|
||||
short count,count2;
|
||||
ushort longcode;
|
||||
uint32_t longval, *longdest, *longsrc;
|
||||
+
|
||||
+ extern int qs_debug_rsd8_trace; // rsdcvt.c - engine-debug-tools/, root README.md's "Debugging" section
|
||||
+ uchar *src_start = src, *dest_start = dest;
|
||||
+ long op_index = 0;
|
||||
|
||||
do
|
||||
{
|
||||
@@ -42,6 +47,7 @@
|
||||
{
|
||||
count = *(src++); // get count
|
||||
val = *(src++); // get val
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=run count=%d val=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count, (int)val);
|
||||
|
||||
if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
@@ -63,6 +69,7 @@
|
||||
else if (code<0x80) // dump (copy) bytes
|
||||
{
|
||||
count = code;
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=dump count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count);
|
||||
if (code>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
count2 = count>>2;
|
||||
@@ -84,6 +91,7 @@
|
||||
{
|
||||
count = code & 0x007f; // clear high byte
|
||||
val = longval = 0L;
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=skip count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count);
|
||||
|
||||
if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
@@ -105,11 +113,15 @@
|
||||
longcode = * (ushort *) src;
|
||||
src += 2L;
|
||||
|
||||
- if (!longcode) break; // done?
|
||||
+ if (!longcode) {
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=terminator\n", op_index++, (long)(src-src_start), (long)(dest-dest_start));
|
||||
+ break; // done?
|
||||
+ }
|
||||
else if (longcode<0x8000) // skip (zero)
|
||||
{
|
||||
count = longcode;
|
||||
val = longval = 0L;
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longskip count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count);
|
||||
|
||||
if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
@@ -129,6 +141,7 @@
|
||||
else if (longcode<0xC000) // dump (copy)
|
||||
{
|
||||
count = longcode & 0x7fff; // clear high bit
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longdump count=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count);
|
||||
|
||||
if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
@@ -151,6 +164,7 @@
|
||||
{
|
||||
count = longcode & 0x3fff;
|
||||
val = *(src++); // get val
|
||||
+ if (qs_debug_rsd8_trace) fprintf(stderr, "[debug rsd8 op] #%ld src_off=%ld dest_off=%ld type=longrun count=%d val=%d\n", op_index++, (long)(src-src_start), (long)(dest-dest_start), (int)count, (int)val);
|
||||
|
||||
if (count>=kMinLongLoop) // if at least kMinLongLoop bytes, do long word stuff
|
||||
{
|
||||
--- a/src/GameSrc/objsim.c
|
||||
+++ b/src/GameSrc/objsim.c
|
||||
@@ -1979,11 +1979,43 @@
|
||||
}
|
||||
|
||||
if (fd->bm.type == BMT_RSD8) {
|
||||
- if (gr_rsd8_convert(&fd->bm, &decoded) != 0 /* GR_UNPACK_RSD8_OK */) {
|
||||
+ extern int qs_debug_rsd8_trace; // rsdcvt.c - engine-debug-tools/, root README.md's "Debugging" section
|
||||
+ uchar *first_copy;
|
||||
+ size_t nbytes;
|
||||
+ int rsd8_ok;
|
||||
+
|
||||
+ qs_debug_rsd8_trace = 1;
|
||||
+ rsd8_ok = gr_rsd8_convert(&fd->bm, &decoded) == 0 /* GR_UNPACK_RSD8_OK */;
|
||||
+ qs_debug_rsd8_trace = 0;
|
||||
+ if (!rsd8_ok) {
|
||||
fprintf(stderr, "[debug dump] gr_rsd8_convert frame %d failed\n", frame);
|
||||
RefUnlock(MKREF(s_lastCritterId, frame));
|
||||
continue;
|
||||
}
|
||||
+
|
||||
+ // Re-decode the same compressed source (fd->bm.bits, unchanged
|
||||
+ // by the first call - only grd_unpack_buf, the shared
|
||||
+ // decode-output buffer, gets overwritten) a second time and
|
||||
+ // compare: decode runs back to back on this single thread, with
|
||||
+ // nothing else able to touch fd->bm.bits or grd_unpack_buf in
|
||||
+ // between, so a byte-for-byte match here means any garbled
|
||||
+ // pixels are a deterministic property of this exact compressed
|
||||
+ // stream (bad source data, or a decoder bug it triggers), not a
|
||||
+ // one-off from a resource-cache slot being reused mid-decode.
|
||||
+ nbytes = (size_t)decoded.w * (size_t)decoded.h;
|
||||
+ first_copy = malloc(nbytes);
|
||||
+ if (first_copy != NULL) {
|
||||
+ memcpy(first_copy, decoded.bits, nbytes);
|
||||
+
|
||||
+ qs_debug_rsd8_trace = 1;
|
||||
+ gr_rsd8_convert(&fd->bm, &decoded);
|
||||
+ qs_debug_rsd8_trace = 0;
|
||||
+
|
||||
+ fprintf(stderr, "[debug dump] frame %d repeat-decode of bm.bits=%p %s\n", frame,
|
||||
+ (void *)fd->bm.bits, memcmp(first_copy, decoded.bits, nbytes) == 0 ? "identical" : "DIFFERS");
|
||||
+ free(first_copy);
|
||||
+ }
|
||||
+
|
||||
bits = decoded.bits;
|
||||
w = decoded.w;
|
||||
h = decoded.h;
|
||||
Reference in New Issue
Block a user