Performance improvements via new chunky APIs.

This commit is contained in:
Scott Duensing 2026-10-17 19:45:50 -05:00
parent 4d55000665
commit 2d10c1eb2b
2 changed files with 29 additions and 60 deletions

View file

@ -97,7 +97,7 @@ Sierra's interpreter.
| have.key | Works: a key this cycle that nothing else took; a logic that asks again in the same cycle (KQ3's help screen spins on it) waits a frame per ask and takes any key | `pictures`, `priority`, `textscreen`, `ego` probes; `screenshots/kq3-help-f1.png` |
| Save / restore / restart | Work: restore rebuilds the picture from the script and ends the cycle so logic 0 sees f12 next (KQ3 relies on it); restart keeps menus and set.key mappings (KQ3 builds them only once) | `kq3-saverestore` session; restart + menu matches Sierra |
| Memory | Logics other than 0 are freed at new.room and so are the views no object shows, as Sierra frees a room's resources | ASan run of every session clean |
| Display on planar machines | Picture updates go out as colour runs through jlFillRect (two jlDrawPixel calls per AGI pixel before) | host frames identical either way; ST/Amiga/X68000 snapshots |
| Picture output | Every machine pushes a picture rectangle with one jlSurfacePasteIndexed call, each AGI pixel doubled (colour runs through jlFillRect on the planar machines before, two jlDrawPixel calls per AGI pixel before that) | IIgs frame outside a moving object byte-identical to the old code; JoeyLib's UBER indexed checks on every port |
## Bugs found and fixed in this dive, with causes
@ -109,9 +109,13 @@ Sierra's interpreter.
to 4 KB (byte-sized fill stack, one shared line buffer, the picture script
moved into the jlAlloc'd state).
2. **ST, Amiga, X68000: picture output.** Planar stages have no chunky bytes,
and the fallback drew two jlDrawPixel calls per AGI pixel. Now runs of one
colour per row through jlFillRect. (An 8x8 tile path was tried first and
came out striped: jlTileT's bytes are in each port's own layout.)
and the fallback drew two jlDrawPixel calls per AGI pixel. Then runs of
one colour per row through jlFillRect. (An 8x8 tile path was tried first
and came out striped: jlTileT's bytes are in each port's own layout.)
Since 2026-10-17 every machine pastes the rectangle with
jlSurfacePasteIndexed (doubleWide), widened to whole 2-pixel pairs: IIgs
picture pushes 11.8% -> ~4% of the CPU, X68000 ~4.7x cheaper per frame
drawn.
3. **Text attributes.** set.text.attribute in picture mode stores a background
of white (or black); text.screen clears to that; on the text screen the
colours are used as given; clear.lines and clear.text.rect on the text

View file

@ -51,6 +51,16 @@
// A chunky surface's byte holds two pixels.
#define CHUNKY_PX_PER_BYTE (JL_SURFACE_WIDTH / JL_SURFACE_BYTES_PER_ROW)
// pushRect's paste doubles each AGI pixel (doubleWide): two screen pixels.
typedef char pushRectDoubleWideProof[(AGI_SCREEN_PX_PER_AGI == 2) ? 1 : -1];
// pushRect widens to a multiple of this many AGI pixels (a power of two
// that divides the picture's width): 2 makes whole 16-bit words on the
// IIgs and lands half a planar port's row ends on whole columns. 4 (every
// end on a planar column) profiled 0.9% of the X68000's CPU faster but
// 0.3% of the IIgs's slower: the IIgs pays for the bytes it adds.
#define PUSH_ALIGN_PX 2
typedef char pushAlignProof[(AGI_WIDTH % PUSH_ALIGN_PX == 0 && (PUSH_ALIGN_PX & (PUSH_ALIGN_PX - 1)) == 0) ? 1 : -1];
typedef struct {
int16_t x;
@ -69,7 +79,6 @@ static void drawList(bool updating);
static uint8_t effectivePriority(int16_t x, int16_t y);
static void markDirty(int16_t x, int16_t y, int16_t w, int16_t h);
static void pushRect(const GfxRectT *r);
static void pushRuns(jlSurfaceT *stage, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t screenY);
static void restoreRect(const uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h);
static void saveRect(uint8_t *save, int16_t x, int16_t y, uint8_t w, uint8_t h);
static void shakePhase(const jlSurfaceT *show, uint32_t phaseMs);
@ -298,22 +307,19 @@ static void markDirty(int16_t x, int16_t y, int16_t w, int16_t h) {
}
// Copy a buffer rectangle to the stage, each AGI pixel two screen pixels:
// straight into a chunky stage's bytes, or as runs on a planar one.
// Copy a buffer rectangle to the stage, each AGI pixel two screen pixels
// (jlSurfacePasteIndexed's doubleWide, whatever the stage stores pixels as).
// The buffer holds the whole picture, so the rectangle widens to
// PUSH_ALIGN_PX, which pastes faster than the pixels it adds cost.
static void pushRect(const GfxRectT *r) {
jlSurfaceT *stage;
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
int16_t x;
int16_t y;
int16_t screenY;
uint8_t c;
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
x0 = (r->x < 0) ? 0 : r->x;
x0 = (r->x < 0) ? 0 : (int16_t)(r->x & ~(PUSH_ALIGN_PX - 1));
y0 = (r->y < 0) ? 0 : r->y;
x1 = (int16_t)(r->x + r->w);
x1 = (int16_t)((r->x + r->w + PUSH_ALIGN_PX - 1) & ~(PUSH_ALIGN_PX - 1));
y1 = (int16_t)(r->y + r->h);
if (x1 > AGI_WIDTH) {
x1 = AGI_WIDTH;
@ -324,48 +330,7 @@ static void pushRect(const GfxRectT *r) {
if (x0 >= x1 || y0 >= y1) {
return;
}
stage = jlStageGet();
screenY = (int16_t)((int16_t)gAgi->picRow * AGI_CELL_PX);
if (stage->pixels == NULL) {
pushRuns(stage, x0, y0, x1, y1, screenY);
return;
}
for (y = y0; y < y1; y++) {
const uint8_t *src = &gVis[(uint16_t)y * AGI_WIDTH];
uint8_t *dst = &stage->pixels[(uint16_t)(screenY + y) * JL_SURFACE_BYTES_PER_ROW];
for (x = x0; x < x1; x++) {
c = src[x];
dst[x] = (uint8_t)((c << NIBBLE_SHIFT) | c);
}
}
surfaceMarkDirtyRect(stage, (int16_t)(x0 * AGI_SCREEN_PX_PER_AGI), (int16_t)(screenY + y0),
(int16_t)((x1 - x0) * AGI_SCREEN_PX_PER_AGI), (int16_t)(y1 - y0));
}
// Planar stages (Amiga, ST, X68000) have no bytes to write: each row goes
// out as runs of one colour through jlFillRect, which the ports do fast.
static void pushRuns(jlSurfaceT *stage, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t screenY) {
const uint8_t *src;
int16_t x;
int16_t y;
int16_t run;
uint8_t c;
for (y = y0; y < y1; y++) {
src = &gVis[(uint16_t)y * AGI_WIDTH];
x = x0;
while (x < x1) {
c = src[x];
run = 1;
while (x + run < x1 && src[x + run] == c) {
run++;
}
jlFillRect(stage, (int16_t)(x * AGI_SCREEN_PX_PER_AGI), (int16_t)(screenY + y), (uint16_t)(run * AGI_SCREEN_PX_PER_AGI), 1u, c);
x = (int16_t)(x + run);
}
}
jlSurfacePasteIndexed(jlStageGet(), (int16_t)(x0 * AGI_SCREEN_PX_PER_AGI), (int16_t)((int16_t)gAgi->picRow * AGI_CELL_PX + y0), (uint16_t)(x1 - x0), (uint16_t)(y1 - y0), &gVis[(uint16_t)y0 * AGI_WIDTH + (uint16_t)x0], AGI_WIDTH, true);
}