Performance improvements via new chunky APIs.
This commit is contained in:
parent
4d55000665
commit
2d10c1eb2b
2 changed files with 29 additions and 60 deletions
12
STATUS.md
12
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
|
||||
|
|
|
|||
77
agiGfx.c
77
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue