Lots of fixes. Maybe.

This commit is contained in:
Scott Duensing 2026-06-29 01:37:34 -05:00
parent c80610747a
commit 4fdb757671
4 changed files with 424 additions and 247 deletions

17
agi.c
View file

@ -152,7 +152,10 @@ static jlSurfaceT *gBackdrop;
static jlSurfaceT *gStage; static jlSurfaceT *gStage;
static AgiVmT gVm; static AgiVmT gVm;
static LogicCacheSlotT gLogicCache[AGI_MAX_RESOURCES]; static LogicCacheSlotT gLogicCache[AGI_MAX_RESOURCES];
static ViewCacheSlotT gViewCache[AGI_MAX_RESOURCES]; // View cache: 256 slots of ~5 KB each (~1.3 MB). Far too large for the
// IIgs bank-0 BSS, so it is jlAlloc'd at startup (Memory Manager handle
// on the IIgs, malloc elsewhere) rather than declared as a static array.
static ViewCacheSlotT *gViewCache;
static bool gPicReady; static bool gPicReady;
static uint8_t gOverrideRoom; static uint8_t gOverrideRoom;
static bool gOverrideRoomActive; static bool gOverrideRoomActive;
@ -1729,6 +1732,16 @@ int main(int argc, char **argv) {
} }
} }
// The view cache is too large for bank-0 BSS on the IIgs; pull it
// from the platform's large-allocator. jlAlloc does not zero its
// result, so clear it so every slot starts with loaded == false.
gViewCache = (ViewCacheSlotT *)jlAlloc(sizeof(ViewCacheSlotT) * AGI_MAX_RESOURCES);
if (gViewCache == NULL) {
jlShutdown();
return 1;
}
memset(gViewCache, 0, sizeof(ViewCacheSlotT) * AGI_MAX_RESOURCES);
gameDir = resolveGameDir(argc, argv); gameDir = resolveGameDir(argc, argv);
startingRoom = resolveStartingRoom(argc, argv); startingRoom = resolveStartingRoom(argc, argv);
gOverrideRoom = startingRoom; gOverrideRoom = startingRoom;
@ -1858,6 +1871,8 @@ int main(int argc, char **argv) {
jlAudioShutdown(); jlAudioShutdown();
releaseLogicCache(); releaseLogicCache();
releaseViewCache(); releaseViewCache();
jlFree(gViewCache);
gViewCache = NULL;
if (gBackdrop != NULL) { if (gBackdrop != NULL) {
jlSurfaceDestroy(gBackdrop); jlSurfaceDestroy(gBackdrop);
} }

View file

@ -13,6 +13,7 @@
#include "agi.h" #include "agi.h"
#include "joey/core.h"
#include "joey/draw.h" #include "joey/draw.h"
#include "surfaceInternal.h" #include "surfaceInternal.h"
@ -408,8 +409,8 @@ static void plot(AgiPicT *pic, const PicStateT *state, int16_t x, int16_t y) {
// ----- Public API (alphabetical) ----- // ----- Public API (alphabetical) -----
bool agiPicAlloc(AgiPicT *pic) { bool agiPicAlloc(AgiPicT *pic) {
pic->visual = (uint8_t *)malloc(AGI_PIC_PIXELS); pic->visual = (uint8_t *)jlAlloc(AGI_PIC_PIXELS);
pic->priority = (uint8_t *)malloc(AGI_PIC_PIXELS); pic->priority = (uint8_t *)jlAlloc(AGI_PIC_PIXELS);
if (pic->visual == NULL || pic->priority == NULL) { if (pic->visual == NULL || pic->priority == NULL) {
agiPicFree(pic); agiPicFree(pic);
return false; return false;
@ -651,11 +652,11 @@ void agiPicAddView(AgiPicT *pic, const AgiViewT *view,
void agiPicFree(AgiPicT *pic) { void agiPicFree(AgiPicT *pic) {
if (pic->visual != NULL) { if (pic->visual != NULL) {
free(pic->visual); jlFree(pic->visual);
pic->visual = NULL; pic->visual = NULL;
} }
if (pic->priority != NULL) { if (pic->priority != NULL) {
free(pic->priority); jlFree(pic->priority);
pic->priority = NULL; pic->priority = NULL;
} }
} }

246
agiView.c
View file

@ -43,14 +43,157 @@
#endif #endif
// ----- File-local types -----
// Per-draw invariants for one cel blit, grouped so they live in memory
// rather than competing for the w65816's tiny register file. Passing a
// pointer to this keeps the hot blit helpers under the backend's
// register-allocation ceiling.
typedef struct {
const uint8_t *picPriority;
int16_t x;
int16_t width;
uint8_t actorPri;
uint8_t transparent;
bool mirrored;
} AgiCelBlitT;
// ----- Prototypes ----- // ----- Prototypes -----
static void blitPlanar(const AgiCelBlitT *blit, const uint8_t *rle, int16_t height, int16_t topY, jlSurfaceT *stage, int16_t destY);
static const uint8_t *blitRowChunky(const AgiCelBlitT *blit, const uint8_t *rle, const uint8_t *priRow, uint8_t *stageRow);
static void markCelDirty(jlSurfaceT *stage, int16_t x, int16_t width, int16_t destY, int16_t topY, int16_t height);
static bool parseLoop(AgiLoopInfoT *loop, const uint8_t *loopStart, uint16_t maxBytes); static bool parseLoop(AgiLoopInfoT *loop, const uint8_t *loopStart, uint16_t maxBytes);
static const AgiCelInfoT *resolveMirror(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx, bool *outMirrored); static const AgiCelInfoT *resolveMirror(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx, bool *outMirrored);
// ----- Internal helpers (alphabetical) ----- // ----- Internal helpers (alphabetical) -----
// Planar fallback row walker (stage->pixels == NULL). Routes every
// opaque, visible pixel through jlDrawPixel so the port's c2p /
// plane-sync path stays correct. Split out of agiViewDraw so its
// locals don't pile onto the chunky path's register budget.
static void blitPlanar(const AgiCelBlitT *blit, const uint8_t *rle, int16_t height, int16_t topY, jlSurfaceT *stage, int16_t destY) {
int16_t width;
int16_t cy;
int16_t cx;
int16_t drawCx;
int16_t agiX;
int16_t agiY;
int16_t stageX;
int16_t stageY;
uint8_t rleByte;
uint8_t color;
uint8_t runLen;
uint8_t r;
uint8_t picPri;
width = blit->width;
for (cy = 0; cy < height; cy++) {
cx = 0;
for (;;) {
rleByte = *rle++;
if (rleByte == 0u) { break; }
color = (uint8_t)(rleByte >> 4);
runLen = (uint8_t)(rleByte & 0x0Fu);
for (r = 0u; r < runLen; r++) {
if (cx >= width) { break; }
if (color != blit->transparent) {
drawCx = blit->mirrored ? (int16_t)(width - 1 - cx) : cx;
agiX = (int16_t)(blit->x + drawCx);
agiY = (int16_t)(topY + cy);
if (agiX >= 0 && agiX < (int16_t)AGI_PIC_WIDTH && agiY >= 0 && agiY < (int16_t)AGI_PIC_HEIGHT) {
picPri = blit->picPriority[agiY * (int16_t)AGI_PIC_WIDTH + agiX];
if (picPri <= blit->actorPri) {
stageX = (int16_t)(agiX << 1);
stageY = (int16_t)(destY + agiY);
jlDrawPixel(stage, stageX, stageY, color);
jlDrawPixel(stage, (int16_t)(stageX + 1), stageY, color);
}
}
}
cx++;
}
}
}
}
// Chunky fast-path walker for one source row. Consumes the cel's RLE
// stream from `rle`, writing pixel-doubled bytes into `stageRow` where
// the picture priority allows it, and returns the advanced RLE pointer
// so the caller can hand it to the next row. A NULL priRow / stageRow
// means the row is clipped out vertically: the RLE is still walked (so
// the pointer lands correctly for later rows) but nothing is drawn.
static const uint8_t *blitRowChunky(const AgiCelBlitT *blit, const uint8_t *rle, const uint8_t *priRow, uint8_t *stageRow) {
int16_t width;
int16_t cx;
int16_t drawCx;
int16_t agiX;
uint8_t rleByte;
uint8_t color;
uint8_t runLen;
uint8_t packed;
uint8_t r;
width = blit->width;
cx = 0;
for (;;) {
rleByte = *rle++;
if (rleByte == 0u) { break; }
color = (uint8_t)(rleByte >> 4);
runLen = (uint8_t)(rleByte & 0x0Fu);
// Transparent runs and vertically-clipped rows both just skip
// ahead by runLen without touching the stage.
if (color == blit->transparent || priRow == NULL) {
cx = (int16_t)(cx + runLen);
if (cx > width) { cx = width; }
continue;
}
packed = (uint8_t)((color << 4) | color);
for (r = 0u; r < runLen; r++) {
if (cx >= width) { break; }
drawCx = blit->mirrored ? (int16_t)(width - 1 - cx) : cx;
agiX = (int16_t)(blit->x + drawCx);
if (agiX >= 0 && agiX < (int16_t)AGI_PIC_WIDTH) {
if (priRow[agiX] <= blit->actorPri) {
stageRow[agiX] = packed;
}
}
cx++;
}
}
return rle;
}
// Mark the cel's stage bounding box dirty once after a chunky blit.
// Computes the pixel-doubled bbox from the source rect, clips it to the
// stage so the surface marker doesn't reject the whole rect for an
// off-screen edge, and records it if any of it is on-screen.
static void markCelDirty(jlSurfaceT *stage, int16_t x, int16_t width, int16_t destY, int16_t topY, int16_t height) {
int16_t minStageX;
int16_t maxStageX;
int16_t minStageY;
int16_t maxStageY;
minStageX = (int16_t)(x << 1);
maxStageX = (int16_t)((x + width) << 1);
minStageY = (int16_t)(destY + topY);
maxStageY = (int16_t)(destY + topY + height);
if (minStageX < 0) { minStageX = 0; }
if (minStageY < 0) { minStageY = 0; }
if (maxStageX > (int16_t)SURFACE_WIDTH) { maxStageX = (int16_t)SURFACE_WIDTH; }
if (maxStageY > (int16_t)SURFACE_HEIGHT) { maxStageY = (int16_t)SURFACE_HEIGHT; }
if (maxStageX > minStageX && maxStageY > minStageY) {
surfaceMarkDirtyRect(stage, minStageX, minStageY,
(int16_t)(maxStageX - minStageX),
(int16_t)(maxStageY - minStageY));
}
}
static bool parseLoop(AgiLoopInfoT *loop, const uint8_t *loopStart, uint16_t maxBytes) { static bool parseLoop(AgiLoopInfoT *loop, const uint8_t *loopStart, uint16_t maxBytes) {
uint8_t celCount; uint8_t celCount;
uint8_t i; uint8_t i;
@ -149,34 +292,24 @@ void agiViewDraw(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx,
const uint8_t *picPriority, const uint8_t *picPriority,
jlSurfaceT *stage, int16_t destY) { jlSurfaceT *stage, int16_t destY) {
const AgiCelInfoT *cel; const AgiCelInfoT *cel;
AgiCelBlitT blit;
const uint8_t *rle; const uint8_t *rle;
bool mirrored; bool mirrored;
int16_t width;
int16_t height; int16_t height;
uint8_t transparent;
int16_t topY; int16_t topY;
int16_t cy;
int16_t cx;
int16_t drawCx;
int16_t agiX;
int16_t agiY;
int16_t stageX;
int16_t stageY;
uint8_t rleByte;
uint8_t color;
uint8_t packed;
uint8_t runLen;
uint8_t r;
uint8_t picPri;
uint8_t *stagePixels; uint8_t *stagePixels;
cel = resolveMirror(view, loopIdx, celIdx, &mirrored); cel = resolveMirror(view, loopIdx, celIdx, &mirrored);
if (cel == NULL) { if (cel == NULL) {
return; return;
} }
width = (int16_t)cel->width;
height = (int16_t)cel->height; height = (int16_t)cel->height;
transparent = cel->transparentColor; blit.picPriority = picPriority;
blit.x = x;
blit.width = (int16_t)cel->width;
blit.actorPri = actorPri;
blit.transparent = cel->transparentColor;
blit.mirrored = mirrored;
rle = cel->rleData; rle = cel->rleData;
topY = (int16_t)(y - height + 1); topY = (int16_t)(y - height + 1);
stagePixels = (stage != NULL) ? stage->pixels : NULL; stagePixels = (stage != NULL) ? stage->pixels : NULL;
@ -187,10 +320,8 @@ void agiViewDraw(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx,
// direct, marking the cel bbox dirty once at the end. ~80x // direct, marking the cel bbox dirty once at the end. ~80x
// faster on DOSBox @ 386SX-equivalent CPU cycles. // faster on DOSBox @ 386SX-equivalent CPU cycles.
if (stagePixels != NULL) { if (stagePixels != NULL) {
int16_t minStageX = (int16_t)(x << 1); int16_t cy;
int16_t maxStageX = (int16_t)((x + width) << 1); int16_t agiY;
int16_t minStageY = (int16_t)(destY + topY);
int16_t maxStageY = (int16_t)(destY + topY + height);
const uint8_t *priRow; const uint8_t *priRow;
uint8_t *stageRow; uint8_t *stageRow;
@ -208,82 +339,15 @@ void agiViewDraw(const AgiViewT *view, uint8_t loopIdx, uint8_t celIdx,
stageRow = stagePixels + stageRow = stagePixels +
(int32_t)(destY + agiY) * (int32_t)SURFACE_BYTES_PER_ROW; (int32_t)(destY + agiY) * (int32_t)SURFACE_BYTES_PER_ROW;
} }
cx = 0; rle = blitRowChunky(&blit, rle, priRow, stageRow);
for (;;) {
rleByte = *rle++;
if (rleByte == 0u) { break; }
color = (uint8_t)(rleByte >> 4);
runLen = (uint8_t)(rleByte & 0x0Fu);
if (color == transparent) {
cx = (int16_t)(cx + runLen);
if (cx > width) { cx = width; }
continue;
}
if (priRow == NULL) {
cx = (int16_t)(cx + runLen);
if (cx > width) { cx = width; }
continue;
}
packed = (uint8_t)((color << 4) | color);
for (r = 0u; r < runLen; r++) {
if (cx >= width) { break; }
drawCx = mirrored ? (int16_t)(width - 1 - cx) : cx;
agiX = (int16_t)(x + drawCx);
if (agiX >= 0 && agiX < (int16_t)AGI_PIC_WIDTH) {
if (priRow[agiX] <= actorPri) {
stageRow[agiX] = packed;
}
}
cx++;
}
}
}
// Dirty-rect tracking at the cel level only: we know the
// bbox a priori (the cel's stage rect), and per-pixel tight
// bounds aren't worth the inner-loop cost on a 386. Clip to
// the stage so the surface marker doesn't reject the whole
// rect for an off-screen edge.
if (minStageX < 0) { minStageX = 0; }
if (minStageY < 0) { minStageY = 0; }
if (maxStageX > (int16_t)SURFACE_WIDTH) { maxStageX = (int16_t)SURFACE_WIDTH; }
if (maxStageY > (int16_t)SURFACE_HEIGHT) { maxStageY = (int16_t)SURFACE_HEIGHT; }
if (maxStageX > minStageX && maxStageY > minStageY) {
surfaceMarkDirtyRect(stage, minStageX, minStageY,
(int16_t)(maxStageX - minStageX),
(int16_t)(maxStageY - minStageY));
} }
markCelDirty(stage, x, blit.width, destY, topY, height);
return; return;
} }
// Planar fallback (s->pixels NULL: Amiga Phase 9). Goes through // Planar fallback (s->pixels NULL: Amiga Phase 9). Goes through
// jlDrawPixel so the port's c2p / plane-sync path stays correct. // jlDrawPixel so the port's c2p / plane-sync path stays correct.
for (cy = 0; cy < height; cy++) { blitPlanar(&blit, rle, height, topY, stage, destY);
cx = 0;
for (;;) {
rleByte = *rle++;
if (rleByte == 0u) { break; }
color = (uint8_t)(rleByte >> 4);
runLen = (uint8_t)(rleByte & 0x0Fu);
for (r = 0u; r < runLen; r++) {
if (cx >= width) { break; }
if (color != transparent) {
drawCx = mirrored ? (int16_t)(width - 1 - cx) : cx;
agiX = (int16_t)(x + drawCx);
agiY = (int16_t)(topY + cy);
if (agiX >= 0 && agiX < (int16_t)AGI_PIC_WIDTH && agiY >= 0 && agiY < (int16_t)AGI_PIC_HEIGHT) {
picPri = picPriority[agiY * (int16_t)AGI_PIC_WIDTH + agiX];
if (picPri <= actorPri) {
stageX = (int16_t)(agiX << 1);
stageY = (int16_t)(destY + agiY);
jlDrawPixel(stage, stageX, stageY, color);
jlDrawPixel(stage, (int16_t)(stageX + 1), stageY, color);
}
}
}
cx++;
}
}
}
} }

387
agiVm.c

File diff suppressed because it is too large Load diff