diff --git a/STATUS.md b/STATUS.md index 066515c..b685197 100644 --- a/STATUS.md +++ b/STATUS.md @@ -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 diff --git a/agiGfx.c b/agiGfx.c index 919cdb1..1c5432d 100644 --- a/agiGfx.c +++ b/agiGfx.c @@ -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); }