LOTS of optimizations for the IIgs.
This commit is contained in:
parent
ea1e853d5d
commit
af366e7e81
40 changed files with 6864 additions and 361 deletions
|
|
@ -103,7 +103,7 @@ static void initialPaint(SurfaceT *screen, bool audioOk) {
|
|||
surfaceClear(screen, COLOR_BG);
|
||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
|
||||
audioOk ? COLOR_HINT : COLOR_BG);
|
||||
surfacePresent(screen);
|
||||
stagePresent();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -128,9 +128,9 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -171,11 +171,11 @@ int main(void) {
|
|||
|
||||
if (flashFrames > 0) {
|
||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_BAR);
|
||||
surfacePresentRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H);
|
||||
stagePresentRect(BAR_X, BAR_Y, BAR_W, BAR_H);
|
||||
flashFrames--;
|
||||
if (flashFrames == 0) {
|
||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
|
||||
surfacePresentRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H);
|
||||
stagePresentRect(BAR_X, BAR_Y, BAR_W, BAR_H);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
277
examples/draw/draw.c
Normal file
277
examples/draw/draw.c
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
// Drawing primitive smoke test. Lays out a 2x2 grid of cells, each
|
||||
// exercising one family of primitives. On screen each cell should show
|
||||
// a clear visual signal that the underlying inner loops (C or ASM)
|
||||
// produced the expected pixel pattern.
|
||||
//
|
||||
// TL: drawPixel + drawLine (8-octant fan from cell center; pixel
|
||||
// row of all 16 colors along the cell's bottom edge).
|
||||
// TR: drawRect + fillRect (concentric outlines + filled blocks at
|
||||
// deliberately odd x / odd width to catch nibble-edge bugs).
|
||||
// BL: drawCircle + fillCircle (concentric outlines + a small filled
|
||||
// disk at center).
|
||||
// BR: tileCopy / tileCopyMasked / tileSnap+tilePaste / floodFill.
|
||||
//
|
||||
// Runs in HOST_MODE_TAKEOVER and holds the frame until the user
|
||||
// presses ESC / RETURN / SPACE.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <joey/joey.h>
|
||||
|
||||
#define CELL_W 160
|
||||
#define CELL_H 100
|
||||
|
||||
// Color slots (palette 0). Color 0 is the library-forced black.
|
||||
#define C_BG 0
|
||||
#define C_BORDER 1 // white
|
||||
#define C_RED 2
|
||||
#define C_GREEN 3
|
||||
#define C_BLUE 4
|
||||
#define C_YELLOW 5
|
||||
#define C_CYAN 6
|
||||
#define C_MAGENTA 7
|
||||
#define C_ORANGE 8
|
||||
#define C_GRAY 9
|
||||
|
||||
static void buildPalette(SurfaceT *screen);
|
||||
static void drawCellBorder(SurfaceT *screen, int16_t cx, int16_t cy);
|
||||
static void drawAllCellBorders(SurfaceT *screen);
|
||||
static void drawPrimitivesPixelLine(SurfaceT *screen);
|
||||
static void drawPrimitivesRect(SurfaceT *screen);
|
||||
static void drawPrimitivesCircle(SurfaceT *screen);
|
||||
static void drawPrimitivesTileFlood(SurfaceT *screen);
|
||||
static void waitForKey(void);
|
||||
|
||||
|
||||
static void buildPalette(SurfaceT *screen) {
|
||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
||||
|
||||
// 16 distinct $0RGB entries. Index 0 is forced to black anyway.
|
||||
colors[0] = 0x000;
|
||||
colors[1] = 0xFFF; // white
|
||||
colors[2] = 0xF00; // red
|
||||
colors[3] = 0x0F0; // green
|
||||
colors[4] = 0x00F; // blue
|
||||
colors[5] = 0xFF0; // yellow
|
||||
colors[6] = 0x0FF; // cyan
|
||||
colors[7] = 0xF0F; // magenta
|
||||
colors[8] = 0xF80; // orange
|
||||
colors[9] = 0x888; // mid gray
|
||||
colors[10] = 0x800;
|
||||
colors[11] = 0x080;
|
||||
colors[12] = 0x008;
|
||||
colors[13] = 0x880;
|
||||
colors[14] = 0x088;
|
||||
colors[15] = 0x808;
|
||||
paletteSet(screen, 0, colors);
|
||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
||||
}
|
||||
|
||||
|
||||
static void drawCellBorder(SurfaceT *screen, int16_t cx, int16_t cy) {
|
||||
drawRect(screen, cx, cy, CELL_W, CELL_H, C_BORDER);
|
||||
}
|
||||
|
||||
|
||||
static void drawAllCellBorders(SurfaceT *screen) {
|
||||
drawCellBorder(screen, 0, 0);
|
||||
drawCellBorder(screen, CELL_W, 0);
|
||||
drawCellBorder(screen, 0, CELL_H);
|
||||
drawCellBorder(screen, CELL_W, CELL_H);
|
||||
}
|
||||
|
||||
|
||||
// Top-left cell: drawPixel + drawLine.
|
||||
//
|
||||
// 8 lines fan out from the cell center (80, 50). Four are diagonal
|
||||
// (the new ASM Bresenham path) and four are axis-aligned (drawLine
|
||||
// routes those to fillRect). A horizontal row of 14 pixels along the
|
||||
// cell's bottom verifies drawPixel: each pixel uses a different color
|
||||
// index so the leftmost ones at color 0 are invisible (they are bg)
|
||||
// and 1..13 progress through the palette.
|
||||
static void drawPrimitivesPixelLine(SurfaceT *screen) {
|
||||
int16_t cx;
|
||||
int16_t cy;
|
||||
int16_t i;
|
||||
|
||||
cx = CELL_W / 2; // 80
|
||||
cy = CELL_H / 2; // 50
|
||||
|
||||
drawLine(screen, cx, cy, cx + 70, cy, C_RED); // E (horizontal)
|
||||
drawLine(screen, cx, cy, cx + 60, cy - 40, C_GREEN); // NE (diagonal)
|
||||
drawLine(screen, cx, cy, cx, cy - 45, C_BLUE); // N (vertical)
|
||||
drawLine(screen, cx, cy, cx - 60, cy - 40, C_YELLOW); // NW
|
||||
drawLine(screen, cx, cy, cx - 70, cy, C_CYAN); // W
|
||||
drawLine(screen, cx, cy, cx - 60, cy + 40, C_MAGENTA); // SW
|
||||
drawLine(screen, cx, cy, cx, cy + 45, C_ORANGE); // S
|
||||
drawLine(screen, cx, cy, cx + 60, cy + 40, C_GRAY); // SE
|
||||
|
||||
// Pixel row: 14 single-pixel writes at consecutive x to exercise
|
||||
// both odd and even nibble paths.
|
||||
for (i = 0; i < 14; i++) {
|
||||
drawPixel(screen, (int16_t)(10 + i * 10), (int16_t)(CELL_H - 6),
|
||||
(uint8_t)((i + 1) & 0x0F));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Top-right cell: drawRect + fillRect.
|
||||
//
|
||||
// Four nested rectangles with deliberately odd x/y/w/h to exercise
|
||||
// the partial-byte (nibble) edge handling in halFastFillRect. The
|
||||
// outermost is filled, the next outline-only, then filled with odd
|
||||
// width, then a 1-pixel-wide vertical bar (drawRect collapses to a
|
||||
// line via fillRect's 1-wide path).
|
||||
static void drawPrimitivesRect(SurfaceT *screen) {
|
||||
int16_t ox;
|
||||
|
||||
ox = CELL_W; // cell origin x
|
||||
|
||||
// Outer fill, even-aligned.
|
||||
fillRect(screen, ox + 8, 8, 144, 84, C_RED);
|
||||
// Inner outline, odd x to test partial-nibble edges.
|
||||
drawRect(screen, ox + 17, 17, 124, 64, C_YELLOW);
|
||||
// Odd width fill, odd x.
|
||||
fillRect(screen, ox + 25, 25, 35, 48, C_GREEN);
|
||||
// 1-pixel vertical bar (degenerate rect through fillRect 1-wide path).
|
||||
fillRect(screen, ox + 100, 25, 1, 48, C_BORDER);
|
||||
// Odd-x odd-w narrow bar to specifically hit hasLeading + hasTrailing
|
||||
// in halFastFillRect.
|
||||
fillRect(screen, ox + 75, 25, 7, 48, C_CYAN);
|
||||
}
|
||||
|
||||
|
||||
// Bottom-left cell: drawCircle + fillCircle.
|
||||
//
|
||||
// Concentric outlines at decreasing radii, alternating colors, plus a
|
||||
// small filled disk at the center. Center is at the cell midpoint.
|
||||
static void drawPrimitivesCircle(SurfaceT *screen) {
|
||||
int16_t cx;
|
||||
int16_t cy;
|
||||
|
||||
cx = CELL_W / 2;
|
||||
cy = CELL_H + CELL_H / 2;
|
||||
|
||||
drawCircle(screen, cx, cy, 45, C_BORDER);
|
||||
drawCircle(screen, cx, cy, 35, C_GREEN);
|
||||
drawCircle(screen, cx, cy, 25, C_YELLOW);
|
||||
drawCircle(screen, cx, cy, 15, C_CYAN);
|
||||
fillCircle(screen, cx, cy, 8, C_MAGENTA);
|
||||
}
|
||||
|
||||
|
||||
// Bottom-right cell: tile + flood fill.
|
||||
//
|
||||
// Top portion: a 16x16 colored block, then tileSnap one of its 8x8
|
||||
// quadrants and tilePaste the captured tile to a neighbor block;
|
||||
// also tileCopy the same source quadrant to a third location to
|
||||
// exercise the full surface-to-surface path. Then a tileCopyMasked
|
||||
// case: paint a 2x1 (16x8) "stripe" containing a transparent color 0
|
||||
// pattern interleaved with color, paste it over a solid backdrop with
|
||||
// transparent=0; the backdrop should show through the transparent
|
||||
// nibbles.
|
||||
//
|
||||
// Bottom portion: drawRect outlines a closed region, floodFillBounded
|
||||
// fills its interior with a different color, stopping at the outline.
|
||||
static void drawPrimitivesTileFlood(SurfaceT *screen) {
|
||||
int16_t ox;
|
||||
int16_t oy;
|
||||
int16_t bx;
|
||||
int16_t by;
|
||||
int16_t i;
|
||||
int16_t px;
|
||||
TileT snapBuf;
|
||||
|
||||
ox = CELL_W; // 160
|
||||
oy = CELL_H; // 100
|
||||
|
||||
// Source 16x16 block at (168, 108): a 4-quadrant pattern.
|
||||
fillRect(screen, ox + 8, oy + 8, 8, 8, C_RED);
|
||||
fillRect(screen, ox + 16, oy + 8, 8, 8, C_GREEN);
|
||||
fillRect(screen, ox + 8, oy + 16, 8, 8, C_BLUE);
|
||||
fillRect(screen, ox + 16, oy + 16, 8, 8, C_YELLOW);
|
||||
|
||||
// tileSnap the top-left red quadrant (block bx=21, by=13) and
|
||||
// tilePaste it next to the 16x16 source as the 5th quadrant.
|
||||
bx = (int16_t)((ox + 8) / 8);
|
||||
by = (int16_t)((oy + 8) / 8);
|
||||
tileSnap(screen, (uint8_t)bx, (uint8_t)by, &snapBuf);
|
||||
tilePaste(screen, (uint8_t)(bx + 4), (uint8_t)by, &snapBuf);
|
||||
|
||||
// tileCopy from the green quadrant onto a fresh location below.
|
||||
tileCopy(screen, (uint8_t)(bx + 4), (uint8_t)(by + 1),
|
||||
screen, (uint8_t)(bx + 1), (uint8_t)by);
|
||||
|
||||
// tileCopyMasked test: build a "transparent" striped pattern at
|
||||
// (208, 132). The tile's source has color 0 in alternating
|
||||
// nibbles. Paste it onto a solid orange backdrop so transparent
|
||||
// nibbles let the orange show through.
|
||||
fillRect(screen, ox + 80, oy + 32, 16, 8, C_ORANGE); // backdrop
|
||||
// Build a vertical-stripe source at (240, 132): col-pixel = (px % 2 ? color : 0)
|
||||
for (i = 0; i < 8; i++) {
|
||||
for (px = 0; px < 16; px++) {
|
||||
drawPixel(screen, (int16_t)(ox + 112 + px), (int16_t)(oy + 32 + i),
|
||||
(uint8_t)((px & 1) ? C_MAGENTA : 0));
|
||||
}
|
||||
}
|
||||
// tileCopyMasked: source at block (ox+112)/8 = 34..35, by 16
|
||||
// -> dst at backdrop block (ox+80)/8 = 30..31, by 16
|
||||
tileCopyMasked(screen, (uint8_t)((ox + 80) / 8), (uint8_t)((oy + 32) / 8),
|
||||
screen, (uint8_t)((ox + 112) / 8), (uint8_t)((oy + 32) / 8),
|
||||
0);
|
||||
|
||||
// Flood-fill region: a small bordered rectangle in the cell's
|
||||
// lower portion. Outline drawn in C_BORDER; floodFillBounded
|
||||
// from a point inside should fill with C_CYAN, stopping at the
|
||||
// border.
|
||||
drawRect(screen, ox + 16, oy + 60, 64, 32, C_BORDER);
|
||||
floodFillBounded(screen, (int16_t)(ox + 48), (int16_t)(oy + 76),
|
||||
C_CYAN, C_BORDER);
|
||||
|
||||
// Plain floodFill: solid block then re-fill to a new color.
|
||||
fillRect(screen, ox + 96, oy + 60, 48, 32, C_GREEN);
|
||||
floodFill(screen, (int16_t)(ox + 120), (int16_t)(oy + 76), C_GRAY);
|
||||
}
|
||||
|
||||
|
||||
static void waitForKey(void) {
|
||||
joeyWaitForAnyKey();
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
JoeyConfigT config;
|
||||
SurfaceT *screen;
|
||||
|
||||
config.hostMode = HOST_MODE_TAKEOVER;
|
||||
config.codegenBytes = 8 * 1024;
|
||||
config.maxSurfaces = 4;
|
||||
config.audioBytes = 64 * 1024;
|
||||
config.assetBytes = 128 * 1024;
|
||||
|
||||
if (!joeyInit(&config)) {
|
||||
fprintf(stderr, "joeyInit failed: %s\n", joeyLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
buildPalette(screen);
|
||||
surfaceClear(screen, C_BG);
|
||||
drawAllCellBorders(screen);
|
||||
drawPrimitivesPixelLine(screen);
|
||||
drawPrimitivesRect(screen);
|
||||
drawPrimitivesCircle(screen);
|
||||
drawPrimitivesTileFlood(screen);
|
||||
stagePresent();
|
||||
|
||||
waitForKey();
|
||||
|
||||
joeyShutdown();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ static void buildPalette(SurfaceT *screen) {
|
|||
|
||||
static void drawAndPresent(SurfaceT *screen, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color) {
|
||||
fillRect(screen, x, y, (uint16_t)w, (uint16_t)h, color);
|
||||
surfacePresentRect(screen, x, y, (uint16_t)w, (uint16_t)h);
|
||||
stagePresentRect(x, y, (uint16_t)w, (uint16_t)h);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ static void initialPaint(SurfaceT *screen) {
|
|||
gView[i].valid = false;
|
||||
gView[i].connected = false;
|
||||
}
|
||||
surfacePresent(screen);
|
||||
stagePresent();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -226,9 +226,9 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ static void initialPaint(SurfaceT *screen) {
|
|||
gCellLit[row][col] = false;
|
||||
}
|
||||
}
|
||||
surfacePresent(screen);
|
||||
stagePresent();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ static void presentChangedCells(SurfaceT *screen, int16_t cursorCol, int16_t cur
|
|||
drawCell(screen, col, row, lit);
|
||||
x = (int16_t)(MARGIN_X + col * (CELL_W + GAP));
|
||||
y = (int16_t)(MARGIN_Y + row * (CELL_H + GAP));
|
||||
surfacePresentRect(screen, x, y, CELL_W, CELL_H);
|
||||
stagePresentRect(x, y, CELL_W, CELL_H);
|
||||
gCellLit[row][col] = lit;
|
||||
}
|
||||
}
|
||||
|
|
@ -195,19 +195,19 @@ static void updateCursor(SurfaceT *screen, int16_t cursorCol, int16_t cursorRow)
|
|||
if (gLastCursorX != mouseX || gLastCursorY != mouseY) {
|
||||
if (gLastCursorCol != CELL_NONE) {
|
||||
drawCell(screen, gLastCursorCol, gLastCursorRow, gCellLit[gLastCursorRow][gLastCursorCol]);
|
||||
surfacePresentRect(screen,
|
||||
stagePresentRect(
|
||||
(int16_t)(MARGIN_X + gLastCursorCol * (CELL_W + GAP)),
|
||||
(int16_t)(MARGIN_Y + gLastCursorRow * (CELL_H + GAP)),
|
||||
CELL_W, CELL_H);
|
||||
} else if (gLastCursorX >= 0 && gLastCursorY >= 0) {
|
||||
// Old cursor was in a gap region. Stamp background over it.
|
||||
fillRect(screen, gLastCursorX, gLastCursorY, CURSOR_W, CURSOR_H, COLOR_BACKGROUND);
|
||||
surfacePresentRect(screen, gLastCursorX, gLastCursorY, CURSOR_W, CURSOR_H);
|
||||
stagePresentRect(gLastCursorX, gLastCursorY, CURSOR_W, CURSOR_H);
|
||||
}
|
||||
}
|
||||
|
||||
drawCursor(screen, mouseX, mouseY);
|
||||
surfacePresentRect(screen, mouseX, mouseY, CURSOR_W, CURSOR_H);
|
||||
stagePresentRect(mouseX, mouseY, CURSOR_W, CURSOR_H);
|
||||
|
||||
gLastCursorX = mouseX;
|
||||
gLastCursorY = mouseY;
|
||||
|
|
@ -233,9 +233,9 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
// library contract, so the leftmost stripe is black in every band.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <joey/joey.h>
|
||||
|
||||
|
|
@ -16,13 +15,11 @@
|
|||
#define BAND_HEIGHT (SURFACE_HEIGHT / BAND_COUNT)
|
||||
#define STRIPE_COUNT 16
|
||||
#define STRIPE_WIDTH (SURFACE_WIDTH / STRIPE_COUNT)
|
||||
#define DISPLAY_SECONDS 5
|
||||
|
||||
static void buildPalettes(SurfaceT *screen);
|
||||
static void buildScbs(SurfaceT *screen);
|
||||
static void drawStripes(SurfaceT *screen);
|
||||
static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn);
|
||||
static void waitSeconds(int seconds);
|
||||
|
||||
|
||||
static void buildPalettes(SurfaceT *screen) {
|
||||
|
|
@ -102,15 +99,6 @@ static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn) {
|
|||
}
|
||||
|
||||
|
||||
static void waitSeconds(int seconds) {
|
||||
time_t start;
|
||||
time_t now;
|
||||
|
||||
start = time(NULL);
|
||||
do {
|
||||
now = time(NULL);
|
||||
} while ((long)(now - start) < (long)seconds);
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
|
|
@ -128,9 +116,9 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -138,9 +126,9 @@ int main(void) {
|
|||
buildPalettes(screen);
|
||||
buildScbs(screen);
|
||||
drawStripes(screen);
|
||||
surfacePresent(screen);
|
||||
stagePresent();
|
||||
|
||||
waitSeconds(DISPLAY_SECONDS);
|
||||
joeyWaitForAnyKey();
|
||||
|
||||
joeyShutdown();
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
#define BALL_TILES_X (BALL_W / 8)
|
||||
#define BALL_TILES_Y (BALL_H / 8)
|
||||
|
||||
#define TILE_BYTES 32
|
||||
#define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES)
|
||||
// SaveUnder must store rounded-up byte boundaries: x rounded down to
|
||||
// even, width rounded up to even. Worst case for BALL_W=16 (already
|
||||
|
|
@ -122,9 +121,9 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
screen = stageGet();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
fprintf(stderr, "stageGet returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -145,7 +144,7 @@ int main(void) {
|
|||
buildPalette(screen);
|
||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
||||
surfaceClear(screen, COLOR_BG);
|
||||
surfacePresent(screen);
|
||||
stagePresent();
|
||||
|
||||
backup.bytes = gBallBackup;
|
||||
|
||||
|
|
@ -157,7 +156,7 @@ int main(void) {
|
|||
|
||||
spriteSaveUnder(screen, ball, x, y, &backup);
|
||||
spriteDraw(screen, ball, x, y);
|
||||
surfacePresentRect(screen, backup.x, backup.y, backup.width, backup.height);
|
||||
stagePresentRect(backup.x, backup.y, backup.width, backup.height);
|
||||
haveBackup = true;
|
||||
|
||||
for (;;) {
|
||||
|
|
@ -168,7 +167,7 @@ int main(void) {
|
|||
|
||||
// Stash the prior ball's region before restoring the bytes
|
||||
// under it. Do all off-screen work (restore + move + draw)
|
||||
// first, then waitVBL + ONE surfacePresentRect covering both
|
||||
// first, then waitVBL + ONE stagePresentRect covering both
|
||||
// old and new regions. Putting waitVBL immediately before the
|
||||
// present lets the present land inside the VBL window so the
|
||||
// CRT never sees a half-updated framebuffer (matters most on
|
||||
|
|
@ -206,7 +205,7 @@ int main(void) {
|
|||
: (backup.y + backup.height));
|
||||
|
||||
joeyWaitVBL();
|
||||
surfacePresentRect(screen, unionX, unionY,
|
||||
stagePresentRect(unionX, unionY,
|
||||
(uint16_t)(unionRight - unionX),
|
||||
(uint16_t)(unionBottom - unionY));
|
||||
haveBackup = true;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,40 @@ void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex);
|
|||
// Read a pixel value. Off-surface coordinates return 0.
|
||||
uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y);
|
||||
|
||||
// Plot a line from (x0, y0) to (x1, y1) using Bresenham. Endpoints
|
||||
// are inclusive. Off-surface pixels are skipped per-pixel; lines that
|
||||
// pass entirely off-surface draw nothing. Horizontal and vertical
|
||||
// runs hit fast paths.
|
||||
void drawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex);
|
||||
|
||||
// Outline a rectangle (1-pixel-wide border). 1xN / Nx1 degenerate
|
||||
// to vertical / horizontal lines; 1x1 to a single pixel. Negative or
|
||||
// zero dimensions are no-ops.
|
||||
void drawRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex);
|
||||
|
||||
// Fill a solid rectangle. Negative or zero dimensions are no-ops.
|
||||
void fillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex);
|
||||
|
||||
// Outline a circle of radius r centered at (cx, cy) using Bresenham
|
||||
// midpoint. r == 0 plots a single pixel.
|
||||
void drawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
|
||||
// Fill a disk of radius r centered at (cx, cy). r == 0 plots a single
|
||||
// pixel. Spans are emitted per scanline using midpoint symmetry.
|
||||
void fillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
|
||||
// Flood fill a 4-connected region starting at (x, y). Replaces every
|
||||
// pixel of the original color reached via N/S/E/W steps. No-op if
|
||||
// (x, y) is off-surface or already matches newColor.
|
||||
void floodFill(SurfaceT *s, int16_t x, int16_t y, uint8_t newColor);
|
||||
|
||||
// Flood fill a 4-connected region starting at (x, y), stopping at
|
||||
// boundary pixels of color boundaryColor. Replaces every reachable
|
||||
// pixel that is not boundaryColor with newColor. Used for vector-art
|
||||
// rendering (e.g. Sierra-style picture playback): outline a closed
|
||||
// region with drawLine in boundaryColor, then fill with this.
|
||||
void floodFillBounded(SurfaceT *s, int16_t x, int16_t y, uint8_t newColor, uint8_t boundaryColor);
|
||||
|
||||
// Blit an asset onto the surface at (x, y). Source nibbles overwrite
|
||||
// destination nibbles verbatim -- the caller is responsible for
|
||||
// matching the asset's palette to the destination palette (typically
|
||||
|
|
|
|||
|
|
@ -89,6 +89,12 @@ typedef enum {
|
|||
|
||||
void joeyInputPoll(void);
|
||||
|
||||
// Block until the user presses any key. Internally polls via
|
||||
// joeyInputPoll, so per-port halInputPoll machinery (including
|
||||
// audio-friendly IRQ-driven samplers) keeps working while the
|
||||
// wait loop runs.
|
||||
void joeyWaitForAnyKey(void);
|
||||
|
||||
bool joeyKeyDown(JoeyKeyE key);
|
||||
bool joeyKeyPressed(JoeyKeyE key);
|
||||
bool joeyKeyReleased(JoeyKeyE key);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "palette.h"
|
||||
#include "asset.h"
|
||||
#include "draw.h"
|
||||
#include "tile.h"
|
||||
#include "present.h"
|
||||
#include "input.h"
|
||||
#include "audio.h"
|
||||
|
|
|
|||
|
|
@ -63,6 +63,20 @@
|
|||
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
||||
#endif
|
||||
|
||||
// ----- ORCA-C named load segments -----
|
||||
//
|
||||
// On the IIgs the ORCA Linker fits each load segment in its own bank,
|
||||
// so spilling cross-platform .c files into named segments is the way
|
||||
// to keep monolithic IIgs binaries under the 64 KB-per-bank _ROOT
|
||||
// limit. The `segment "name";` statement is ORCA-C-specific syntax
|
||||
// (see ORCA/C ch. 30); other ports' compilers don't recognize it, so
|
||||
// the macro evaluates to nothing on Amiga/ST/DOS.
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
#define JOEYLIB_SEGMENT(name) segment name;
|
||||
#else
|
||||
#define JOEYLIB_SEGMENT(name)
|
||||
#endif
|
||||
|
||||
// ----- Library version -----
|
||||
|
||||
#define JOEYLIB_VERSION_MAJOR 1
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
// Present / slam.
|
||||
// Stage present.
|
||||
//
|
||||
// surfacePresent copies pixels, SCBs, and palettes from a source
|
||||
// surface to the visible display. On chunky platforms (IIgs, DOS) this
|
||||
// is a direct copy; on planar platforms (Amiga, Atari ST) this is a
|
||||
// chunky-to-planar conversion. See docs/DESIGN.md section 7.
|
||||
// stagePresent flips the library-owned stage (back-buffer) to the
|
||||
// display. On chunky platforms (IIgs, DOS) this is a direct copy; on
|
||||
// planar platforms (Amiga, Atari ST) this is a chunky-to-planar
|
||||
// conversion. Drawing primitives mark per-row dirty ranges on the
|
||||
// stage as a side effect, so stagePresent only touches rows that
|
||||
// actually changed since the last present. See docs/DESIGN.md.
|
||||
|
||||
#ifndef JOEYLIB_PRESENT_H
|
||||
#define JOEYLIB_PRESENT_H
|
||||
|
|
@ -12,12 +14,15 @@
|
|||
#include "surface.h"
|
||||
#include "types.h"
|
||||
|
||||
// Present the entire source surface to the display.
|
||||
void surfacePresent(const SurfaceT *src);
|
||||
// Flip the dirty regions of the stage to the display, then clear the
|
||||
// dirty state. Cheap when nothing has changed since the last call.
|
||||
void stagePresent(void);
|
||||
|
||||
// Present a rectangular region of the source surface to the display.
|
||||
// The rect is clipped to the surface. Negative or zero dimensions are
|
||||
// no-ops.
|
||||
void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||
// Flip a specific rectangular region of the stage to the display,
|
||||
// regardless of dirty state. Coordinates are clipped to the surface;
|
||||
// negative or zero dimensions are no-ops. Does not consult or modify
|
||||
// the dirty arrays -- callers mixing stagePresentRect with stagePresent
|
||||
// in the same frame may see redundant work on the next stagePresent.
|
||||
void stagePresentRect(int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ typedef struct SurfaceT SurfaceT;
|
|||
SurfaceT *surfaceCreate(void);
|
||||
|
||||
// Release an offscreen surface previously returned by surfaceCreate.
|
||||
// Passing NULL is a no-op. Passing the screen surface is a no-op.
|
||||
// Passing NULL is a no-op. Passing the stage is a no-op.
|
||||
void surfaceDestroy(SurfaceT *s);
|
||||
|
||||
// The library's pre-allocated screen surface. This is the surface the
|
||||
// library presents to the display. Always valid between joeyInit and
|
||||
// joeyShutdown.
|
||||
SurfaceT *surfaceGetScreen(void);
|
||||
// The library-owned stage: the back-buffer surface that stagePresent
|
||||
// flips to the display. Always valid between joeyInit and joeyShutdown.
|
||||
// On IIgs the stage's pixel buffer is pinned to bank $01 SHR space with
|
||||
// shadow inhibited so writes are full-speed (2.8 MHz) and isolated from
|
||||
// the displayed framebuffer until the next stagePresent.
|
||||
SurfaceT *stageGet(void);
|
||||
|
||||
// Copy pixels, SCBs, and palettes from src into dst. Both must be valid
|
||||
// surfaces.
|
||||
|
|
|
|||
96
include/joey/tile.h
Normal file
96
include/joey/tile.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Tiles: 8x8 pixel blocks aligned on the 8-pixel grid of any surface.
|
||||
//
|
||||
// A "tile" in JoeyLib isn't a separate object -- it's just the 8x8
|
||||
// region of a SurfaceT at block coordinates (bx, by), where bx is in
|
||||
// [0, 39] and by is in [0, 24] (40x25 blocks per 320x200 surface).
|
||||
// The tile API is a small set of operations that move 32-byte chunks
|
||||
// between surfaces or fill them with a solid color.
|
||||
//
|
||||
// Why this shape: anything you can do with a regular surface --
|
||||
// drawPixel, drawLine, fillRect, blits -- also works on tile-aligned
|
||||
// regions, so authors can paint, edit, and procedurally generate tile
|
||||
// content using the same primitives they use for everything else.
|
||||
// "Fonts," "terrain tilesets," and "spritesheets" are just surfaces
|
||||
// you treat as tile sources at draw time.
|
||||
//
|
||||
// Snap / paste use a small TileT value type so callers don't need a
|
||||
// 32 KB scratch surface for save-under-style work.
|
||||
//
|
||||
// Block coords map to pixels by multiplying by 8 -- byte-aligned in
|
||||
// 4bpp packed (8 px = 4 bytes per row), so all the operations are
|
||||
// byte-shoveling memcpys with no shifting.
|
||||
|
||||
#ifndef JOEYLIB_TILE_H
|
||||
#define JOEYLIB_TILE_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "surface.h"
|
||||
#include "types.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
#define TILE_PIXELS_PER_SIDE 8
|
||||
#define TILE_BYTES_PER_ROW 4
|
||||
#define TILE_BYTES (TILE_BYTES_PER_ROW * TILE_PIXELS_PER_SIDE)
|
||||
#define TILE_BLOCKS_PER_ROW (SURFACE_WIDTH / TILE_PIXELS_PER_SIDE)
|
||||
#define TILE_BLOCKS_PER_COL (SURFACE_HEIGHT / TILE_PIXELS_PER_SIDE)
|
||||
|
||||
// Sentinel for asciiMap entries that should not draw. drawText
|
||||
// advances the cursor past TILE_NO_GLYPH chars without writing.
|
||||
#define TILE_NO_GLYPH ((uint16_t)0xFFFFu)
|
||||
|
||||
// ----- Types -----
|
||||
|
||||
// Stack-allocated 32-byte snapshot buffer for tileSnap / tilePaste.
|
||||
typedef struct TileT {
|
||||
uint8_t pixels[TILE_BYTES];
|
||||
} TileT;
|
||||
|
||||
// ----- API -----
|
||||
|
||||
// Copy the 8x8 block at (srcBx, srcBy) on src to (dstBx, dstBy) on
|
||||
// dst, opaque. Out-of-range block coordinates on either side are
|
||||
// silent no-ops; src and dst can be the same surface.
|
||||
void tileCopy(SurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
||||
const SurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||
|
||||
// Like tileCopy but pixels equal to transparentIndex are skipped --
|
||||
// the destination pixel keeps its original value. Use this for fonts
|
||||
// (transparentIndex = 0 leaves the page background showing through
|
||||
// glyph backgrounds) and for any tileset where some pixels are meant
|
||||
// to be see-through.
|
||||
void tileCopyMasked(SurfaceT *dst, uint8_t dstBx, uint8_t dstBy,
|
||||
const SurfaceT *src, uint8_t srcBx, uint8_t srcBy,
|
||||
uint8_t transparentIndex);
|
||||
|
||||
// Fill the 8x8 block at (bx, by) with a solid color. Equivalent to
|
||||
// fillRect(s, bx*8, by*8, 8, 8, colorIndex) but skips the rect
|
||||
// clipping math since tile coords are already known to be in range.
|
||||
void tileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
||||
|
||||
// Capture the 8x8 block at (bx, by) into the caller's TileT. Used
|
||||
// for save-under style work where allocating a scratch surface would
|
||||
// be overkill.
|
||||
void tileSnap(const SurfaceT *src, uint8_t bx, uint8_t by, TileT *out);
|
||||
|
||||
// Paste a TileT back onto a surface at block (bx, by). Always
|
||||
// opaque; use tileCopyMasked if you need transparency.
|
||||
void tilePaste(SurfaceT *dst, uint8_t bx, uint8_t by, const TileT *in);
|
||||
|
||||
// Draw a NUL-terminated ASCII string at block (bx, by) using glyphs
|
||||
// pulled from fontSurface.
|
||||
//
|
||||
// asciiMap is a 256-entry table mapping ASCII code to glyph location
|
||||
// on fontSurface, encoded as a packed uint16_t: low byte = source
|
||||
// blockX, high byte = source blockY. asciiMap[c] == TILE_NO_GLYPH
|
||||
// causes that character to be skipped (cursor advances, nothing
|
||||
// drawn). Glyph color 0 is treated as transparent so the underlying
|
||||
// surface shows through glyph backgrounds.
|
||||
//
|
||||
// The cursor wraps to the next row at the right edge and truncates
|
||||
// at the bottom edge.
|
||||
void drawText(SurfaceT *dst, uint8_t bx, uint8_t by,
|
||||
const SurfaceT *fontSurface, const uint16_t *asciiMap,
|
||||
const char *str);
|
||||
|
||||
#endif
|
||||
|
|
@ -60,6 +60,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/Hello
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/Pattern
|
||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||
DRAW_BIN := $(BINDIR)/Draw
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/Keys
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
|
|
@ -76,7 +78,7 @@ DATA_DIR := $(BINDIR)/DATA
|
|||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||
|
||||
.PHONY: all amiga clean-amiga
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -118,6 +120,10 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(DRAW_BIN): $(DRAW_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||
DRAW_BIN := $(BINDIR)/DRAW.PRG
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
|
|
@ -61,7 +63,7 @@ DATA_DIR := $(BINDIR)/DATA
|
|||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||
|
||||
.PHONY: all atarist clean-atarist
|
||||
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
all atarist: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -110,6 +112,10 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||
|
||||
$(DRAW_BIN): $(DRAW_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||
DRAW_BIN := $(BINDIR)/DRAW.EXE
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
|
|
@ -54,7 +56,7 @@ DATA_DIR := $(BINDIR)/DATA
|
|||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||
|
||||
.PHONY: all dos clean-dos
|
||||
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(DATA_FILES)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -94,6 +96,11 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
$(DRAW_BIN): $(DRAW_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||
|
|
|
|||
101
make/iigs.mk
101
make/iigs.mk
|
|
@ -13,16 +13,20 @@ BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
|||
BINDIR := $(BUILD)/bin
|
||||
|
||||
PORT_C_SRCS_ALL := $(wildcard $(SRC_PORT)/iigs/*.c)
|
||||
# Hand-rolled .asm sources go through ORCA's macro assembler via
|
||||
# iix-build.sh's `assemble` dispatch. Each .asm declares its target
|
||||
# load segment in the START operand (e.g. peislam.asm -> PEISLAMS)
|
||||
# so the linker places its bytes in a separate bank from _ROOT.
|
||||
# See ORCA/M for IIgs ch. 6 "Load Segments" for the mechanism.
|
||||
PORT_ASM_SRCS_ALL := $(wildcard $(SRC_PORT)/iigs/*.asm)
|
||||
|
||||
# audio.c is the no-op stub linked into every demo. audio_full.c is the
|
||||
# real implementation (NewHandle / fopen / JSL trampoline) and links
|
||||
# only into AUDIO -- the IIgs build is monolithic, so pulling Memory
|
||||
# Manager + ORCA stdio into every binary blows the linker's
|
||||
# "Expression too complex" budget. The two files define the same
|
||||
# halAudio* symbols; iigs/audio.c is filtered out of the AUDIO source
|
||||
# set, audio_full.c is filtered out of the everyone-else set.
|
||||
PORT_C_SRCS := $(filter-out %/audio_full.c, $(PORT_C_SRCS_ALL))
|
||||
PORT_C_SRCS_AUDIO := $(filter-out %/audio.c, $(PORT_C_SRCS_ALL))
|
||||
# audio_full.c declares its functions in the AUDIOIMPL load segment
|
||||
# (`segment "AUDIOIMPL"` at file scope, see ORCA/C ch. 30) so the
|
||||
# implementation code lives in its own bank, not _ROOT. That lets
|
||||
# the same source link into every binary, replacing the earlier
|
||||
# audio.c-stub vs audio_full.c-real split. The 34 KB NTP replayer
|
||||
# bytes still ride along via the xxd-baked header.
|
||||
PORT_C_SRCS := $(PORT_C_SRCS_ALL)
|
||||
|
||||
# IIgs uses NTPstreamsound for SFX, not the libxmp+overlay combo that
|
||||
# DOS and ST share, so src/core/audioSfxMix.c is unused here. Filter
|
||||
|
|
@ -34,9 +38,6 @@ CORE_C_SRCS_IIGS := $(filter-out %/audioSfxMix.c, $(CORE_C_SRCS))
|
|||
CODEGEN_SRCS := $(REPO_DIR)/src/codegen/spriteEmitIigs.c \
|
||||
$(REPO_DIR)/src/codegen/spriteCompile.c
|
||||
|
||||
LIB_SRCS := $(CORE_C_SRCS_IIGS) $(PORT_C_SRCS) $(CODEGEN_SRCS)
|
||||
LIB_SRCS_AUDIO := $(CORE_C_SRCS_IIGS) $(PORT_C_SRCS_AUDIO) $(CODEGEN_SRCS)
|
||||
|
||||
# NinjaTrackerPlus replayer. Assembled with Merlin32 from the staged
|
||||
# source at toolchains/iigs/ntp/ninjatrackerplus.s. Output is a 34 KB
|
||||
# raw 65816 binary that the IIgs audio HAL loads at runtime via
|
||||
|
|
@ -45,13 +46,17 @@ LIB_SRCS_AUDIO := $(CORE_C_SRCS_IIGS) $(PORT_C_SRCS_AUDIO) $(CODEGEN_SRCS)
|
|||
# load address even though it was assembled with `org $0F0000`.
|
||||
NTP_SRC := $(REPO_DIR)/toolchains/iigs/ntp/ninjatrackerplus.s
|
||||
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
||||
NTP_HEADER := $(BUILD)/audio/ntpplayer_data.h
|
||||
NTP_ASM := $(BUILD)/audio/ntpdata.asm
|
||||
IIGS_MERLIN := $(REPO_DIR)/toolchains/iigs/merlin32/bin/merlin32
|
||||
|
||||
LIB_SRCS := $(CORE_C_SRCS_IIGS) $(PORT_C_SRCS) $(PORT_ASM_SRCS_ALL) $(NTP_ASM) $(CODEGEN_SRCS)
|
||||
|
||||
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||
HELLO_BIN := $(BINDIR)/HELLO
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN
|
||||
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||
DRAW_BIN := $(BINDIR)/DRAW
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
|
|
@ -77,7 +82,11 @@ IIX_INCLUDES := \
|
|||
-I $(REPO_DIR)/src/codegen
|
||||
|
||||
.PHONY: all iigs iigs-disk clean-iigs
|
||||
all iigs: $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN)
|
||||
# Building the disk implicitly builds every binary it depends on, so
|
||||
# `make iigs` ends with a fresh joey.2mg on every change. Without this,
|
||||
# stale disk images would silently mask binary updates -- a surprise
|
||||
# when the run script always boots from joey.2mg.
|
||||
all iigs: $(DISK_IMG)
|
||||
|
||||
$(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -85,51 +94,55 @@ $(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
|||
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
||||
mv $(BUILD)/audio/ntpplayer $@
|
||||
|
||||
# Bake the NTP replayer bytes into a C header so audio_full.c can link
|
||||
# the player into the AUDIO binary instead of fopen'ing a separate
|
||||
# NTPPLAYER.BIN at runtime. NTP is bank-internal / PIC, so the linked
|
||||
# bytes still BlockMove cleanly into the Memory Manager handle the HAL
|
||||
# allocates. Same xxd-i pattern as test_assets.h.
|
||||
$(NTP_HEADER): $(NTP_BIN)
|
||||
# Bake the NTP replayer bytes into an ORCA-M asm file. The asm declares
|
||||
# the bytes in a `data NTPDATA` segment; ORCA's linker groups same-
|
||||
# name object segments into one load segment, and the GS/OS loader
|
||||
# places it in its own bank. Net effect: the 34 KB of NTP bytes don't
|
||||
# crowd _ROOT in any binary, so audio_full.c can link into every demo
|
||||
# (vs the old audio.c-stub split). audio_full.c references
|
||||
# gNtpPlayerBytes / gNtpPlayerBytes_len as externs (case-sensitive
|
||||
# symbol match against the asm labels).
|
||||
$(NTP_ASM): $(NTP_BIN) $(REPO_DIR)/toolchains/iigs/bin-to-asm.sh
|
||||
@mkdir -p $(dir $@)
|
||||
@echo "// Generated by make/iigs.mk -- NinjaTrackerPlus replayer bytes." > $@
|
||||
@echo "#ifndef JOEYLIB_NTPPLAYER_DATA_H" >> $@
|
||||
@echo "#define JOEYLIB_NTPPLAYER_DATA_H" >> $@
|
||||
@printf "static const unsigned char gNtpPlayerBytes[] = {\n" >> $@
|
||||
@xxd -i < $(NTP_BIN) >> $@
|
||||
@printf "};\nstatic const unsigned int gNtpPlayerBytes_len = %d;\n" $$(wc -c < $(NTP_BIN)) >> $@
|
||||
@echo "#endif" >> $@
|
||||
$(REPO_DIR)/toolchains/iigs/bin-to-asm.sh $(NTP_BIN) $@ NTPDATA gNtpPlayerBytes gNtpPlayerBytes_len
|
||||
|
||||
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
||||
# #pragma noroot). The example source supplies main(); libjoey sources
|
||||
# are the extras. The chtyp post-step tags the output as GS/OS S16
|
||||
# ($B3) so GS/OS recognizes it as launchable; the file-type lives in
|
||||
# a user.com.apple.FinderInfo xattr that iix and profuse preserve.
|
||||
$(HELLO_BIN): $(HELLO_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
#
|
||||
# All binaries use ORCA-C large memory model (-b). Cost: slightly
|
||||
# larger / slower compiled C per the ORCA docs. Win: 32-bit pointers
|
||||
# everywhere, so library asm can take SurfaceT* args via one
|
||||
# consistent ABI (small-mm 16-bit pointers truncated bank bytes,
|
||||
# which broke any asm that wanted to address bank-1 stage memory).
|
||||
$(HELLO_BIN): $(HELLO_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(HELLO_SRC) $(LIB_SRCS)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(HELLO_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(PATTERN_SRC) $(LIB_SRCS)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(PATTERN_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
$(DRAW_BIN): $(DRAW_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(KEYS_SRC) $(LIB_SRCS)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(DRAW_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(JOY_SRC) $(LIB_SRCS)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(KEYS_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
# Sprite demo uses ORCA-C large memory model (-b) so pointers are
|
||||
# 32-bit and the codegen-arena JSL stub can call cross-bank into the
|
||||
# arena. Without -b, ORCA-C's 16-bit pointers would lose the bank
|
||||
# byte and the stub would JSL into bank 0 (system memory) -> crash.
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(JOY_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(SPRITE_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
|
@ -152,18 +165,18 @@ $(info iigs: php-cli not installed -- AUDIO demo will ship without TEST.NTP; ins
|
|||
AUDIO_DATA_FILES := $(AUDIO_SFX)
|
||||
endif
|
||||
|
||||
$(AUDIO_BIN): $(AUDIO_SRC) $(LIB_SRCS_AUDIO) $(NTP_HEADER) $(IIGS_BUILD)
|
||||
$(AUDIO_BIN): $(AUDIO_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -I $(dir $(NTP_HEADER)) -I $(EXAMPLES)/audio -o $@ $(AUDIO_SRC) $(LIB_SRCS_AUDIO)
|
||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -I $(EXAMPLES)/audio -o $@ $(AUDIO_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
# Assemble an 800KB ProDOS 2img containing the examples, ready to
|
||||
# mount in GSplus alongside a GS/OS boot volume.
|
||||
iigs-disk: $(DISK_IMG)
|
||||
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(AUDIO_DATA_FILES) $(IIGS_PACKAGE)
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(AUDIO_DATA_FILES) $(IIGS_PACKAGE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) -- $(AUDIO_DATA_FILES)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) -- $(AUDIO_DATA_FILES)
|
||||
|
||||
clean-iigs:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
|
|
@ -17,27 +17,31 @@
|
|||
#
|
||||
# scripts/run-amiga.sh # runs Pattern
|
||||
# scripts/run-amiga.sh hello # runs Hello
|
||||
# scripts/run-amiga.sh keys # runs Keys
|
||||
# scripts/run-amiga.sh draw # runs Draw
|
||||
#
|
||||
# Argument is any built example name (case-insensitive); the script
|
||||
# normalizes it to PascalCase (first letter upper, rest lower) which
|
||||
# is the JoeyLib convention for Amiga binary filenames.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "usage: $0 [example-name]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
prog=${1:-pattern}
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/amiga/bin
|
||||
support=$repo/toolchains/emulators/support
|
||||
|
||||
case $prog in
|
||||
hello) file=Hello ;;
|
||||
pattern) file=Pattern ;;
|
||||
keys) file=Keys ;;
|
||||
joy) file=Joy ;;
|
||||
sprite) file=Sprite ;;
|
||||
audio) file=Audio ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
||||
esac
|
||||
prog_lower=${prog,,}
|
||||
file=${prog_lower^}
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
echo "$bin_dir/$file not built. Run 'make amiga' first." >&2
|
||||
echo "available examples in $bin_dir:" >&2
|
||||
find "$bin_dir" -maxdepth 1 -type f -executable -printf '%f\n' >&2 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -56,12 +60,9 @@ dump_keep=/tmp/joeylib-amiga-dump
|
|||
trap 'mkdir -p "$dump_keep"; cp "$work"/*.txt "$dump_keep"/ 2>/dev/null; rm -rf "$work"' EXIT
|
||||
|
||||
mkdir -p "$work/s"
|
||||
cp "$bin_dir/Hello" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Pattern" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Keys" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Joy" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Sprite" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Audio" "$work/" 2>/dev/null || true
|
||||
# Stage every built binary (executable file at top of bin_dir, no
|
||||
# extension on Amiga). DATA/ is copied separately below.
|
||||
find "$bin_dir" -maxdepth 1 -type f -executable -exec cp -t "$work/" {} +
|
||||
# Stage the DATA folder (test.mod, test.sfx) the audio demo loads from
|
||||
# the boot volume at runtime.
|
||||
if [[ -d "$bin_dir/DATA" ]]; then
|
||||
|
|
|
|||
|
|
@ -6,28 +6,31 @@
|
|||
#
|
||||
# scripts/run-atarist.sh # runs PATTERN.PRG
|
||||
# scripts/run-atarist.sh hello # runs HELLO.PRG
|
||||
# scripts/run-atarist.sh keys # runs KEYS.PRG
|
||||
# scripts/run-atarist.sh draw # runs DRAW.PRG
|
||||
#
|
||||
# Argument is any built example name (case-insensitive); the script
|
||||
# upper-cases it and appends .PRG, then checks the file exists.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "usage: $0 [example-name]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
prog=${1:-pattern}
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/atarist/bin
|
||||
|
||||
case $prog in
|
||||
hello) file=HELLO.PRG ;;
|
||||
pattern) file=PATTERN.PRG ;;
|
||||
keys) file=KEYS.PRG ;;
|
||||
joy) file=JOY.PRG ;;
|
||||
sprite) file=SPRITE.PRG ;;
|
||||
audio) file=AUDIO.PRG ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
||||
esac
|
||||
file=${prog^^}.PRG
|
||||
|
||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
echo "$bin_dir/$file not built. Run 'make atarist' first." >&2
|
||||
if compgen -G "$bin_dir/*.PRG" > /dev/null; then
|
||||
echo "available examples in $bin_dir:" >&2
|
||||
ls "$bin_dir"/*.PRG | xargs -n1 basename >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $tos ]]; then
|
||||
|
|
|
|||
|
|
@ -3,26 +3,29 @@
|
|||
#
|
||||
# scripts/run-dos.sh # runs PATTERN
|
||||
# scripts/run-dos.sh hello # runs HELLO
|
||||
# scripts/run-dos.sh keys # runs KEYS
|
||||
# scripts/run-dos.sh draw # runs DRAW
|
||||
#
|
||||
# Argument is any built example name (case-insensitive); the script
|
||||
# upper-cases it and appends .EXE, then checks the file exists.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "usage: $0 [example-name]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
prog=${1:-pattern}
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/dos/bin
|
||||
|
||||
case $prog in
|
||||
hello) file=HELLO.EXE ;;
|
||||
pattern) file=PATTERN.EXE ;;
|
||||
keys) file=KEYS.EXE ;;
|
||||
joy) file=JOY.EXE ;;
|
||||
sprite) file=SPRITE.EXE ;;
|
||||
audio) file=AUDIO.EXE ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
||||
esac
|
||||
file=${prog^^}.EXE
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
echo "$bin_dir/$file not built. Run 'make dos' first." >&2
|
||||
if compgen -G "$bin_dir/*.EXE" > /dev/null; then
|
||||
echo "available examples in $bin_dir:" >&2
|
||||
ls "$bin_dir"/*.EXE | xargs -n1 basename >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -17,16 +17,28 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "usage: $0 [example-name]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
prog=${1:-pattern}
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
case $prog in
|
||||
hello|pattern|keys|joy|sprite|audio) ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
||||
esac
|
||||
bin_dir=$repo/build/iigs/bin
|
||||
target=${prog^^}
|
||||
if [[ ! -f "$bin_dir/$target" ]]; then
|
||||
echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
|
||||
if compgen -G "$bin_dir/*" > /dev/null; then
|
||||
echo "available examples in $bin_dir:" >&2
|
||||
find "$bin_dir" -maxdepth 1 -type f -printf '%f\n' \
|
||||
| grep -vE '\.2mg$|\.txt$' >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
data_disk=$bin_dir/joey.2mg
|
||||
|
||||
for f in "$sys_disk" "$data_disk"; do
|
||||
if [[ ! -f $f ]]; then
|
||||
|
|
@ -42,24 +54,30 @@ mkdir -p "$out"
|
|||
cp "$sys_disk" "$work/boot.po"
|
||||
cp "$data_disk" "$work/joey.2mg"
|
||||
|
||||
# Lua script: on every CPU stop (BRK, breakpoint, watchpoint, manual
|
||||
# halt), append a state snapshot to crash.txt. This way we don't need
|
||||
# the user to type anything at the debugger window -- whatever halts
|
||||
# the CPU lands a record in crash.txt.
|
||||
cat > "$work/crash-hook.lua" <<'LUA'
|
||||
-- Crash diagnostics for IIgs demos. Auto-resumes the initial debug
|
||||
-- pause so the user doesn't need to type "go". On any subsequent halt
|
||||
-- (BRK, watchpoint, breakpoint) outside ROM, dumps registers + bytes
|
||||
-- around PC to crash.txt. ROM halts (PB == 0xFE/0xFF) are skipped so
|
||||
-- we don't fill the file with normal IIgs ROM stack walking.
|
||||
# Lua script: drives Finder via natural keyboard + macadb key fields
|
||||
# to launch the requested example, dumps register state on any halt
|
||||
# (BRK, breakpoint, watchpoint, manual stop). Field names for keys
|
||||
# come from MAME's apple2gs macadb input definitions:
|
||||
# :macadb:KEY0 -> "d D"
|
||||
# :macadb:KEY1 -> "o O"
|
||||
# :macadb:KEY2 -> "j J", "p P"
|
||||
# :macadb:KEY3 -> "Command / Open Apple"
|
||||
# Letters not on KEY0..2 fall back to natkeyboard:post() (which
|
||||
# handles modifier-less character entry only).
|
||||
# Type the FULL program name to disambiguate (DRAW vs DATA which both
|
||||
# start with D and live in the same JOEYLIB volume).
|
||||
prog_select_str=${target}
|
||||
cat > "$work/crash-hook.lua" <<LUA
|
||||
-- Crash diagnostics + Finder driver for IIgs demos. Auto-resumes
|
||||
-- the initial debug pause; at calibrated frame counts taps "J" to
|
||||
-- select JOEYLIB, Cmd-O to open it, the program first letter to
|
||||
-- select the binary, Cmd-O to launch it. On any halt (BRK trap,
|
||||
-- watchpoint, breakpoint) dumps registers + bytes around PC to
|
||||
-- crash.txt.
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local prog = cpu.spaces["program"]
|
||||
local outpath = "/tmp/mame-iigs/crash.txt"
|
||||
|
||||
local function in_rom(pb)
|
||||
return pb == 0xFE or pb == 0xFF
|
||||
end
|
||||
|
||||
local function dump(label)
|
||||
local f = io.open(outpath, "a")
|
||||
if f == nil then return end
|
||||
|
|
@ -75,16 +93,19 @@ local function dump(label)
|
|||
f:write(string.format(" %02X", b))
|
||||
end
|
||||
f:write("\n")
|
||||
-- Probe addresses from joeyDraw.asm
|
||||
f:write(string.format(" probe 012050=%02X 011F80=%02X 023000=%02X\n",
|
||||
prog:read_u8(0x012050),
|
||||
prog:read_u8(0x011F80),
|
||||
prog:read_u8(0x023000)))
|
||||
f:close()
|
||||
end
|
||||
|
||||
-- Lua can't reliably terminate MAME from this version's API; instead
|
||||
-- write a marker file and let the bash launcher poll for it and kill
|
||||
-- the process. "done" file = launcher should shut down.
|
||||
local done_marker = "/tmp/mame-iigs/.done"
|
||||
local started = false
|
||||
local crashed = false
|
||||
local boot_frames = 0
|
||||
|
||||
local function signal_done(reason)
|
||||
local f = io.open(done_marker, "w")
|
||||
if f ~= nil then
|
||||
|
|
@ -93,13 +114,86 @@ local function signal_done(reason)
|
|||
end
|
||||
end
|
||||
|
||||
local nat = manager.machine.natkeyboard
|
||||
|
||||
local function get_field(port, field_name)
|
||||
local p = manager.machine.ioport.ports[port]
|
||||
if p == nil then return nil end
|
||||
return p.fields[field_name]
|
||||
end
|
||||
|
||||
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
||||
|
||||
local function press(f) if f then f:set_value(1) end end
|
||||
local function release(f) if f then f:set_value(0) end end
|
||||
|
||||
local function log_step(label)
|
||||
local f = io.open("/tmp/mame-iigs/steps.txt", "a")
|
||||
if f ~= nil then
|
||||
f:write(string.format("frame=%d %s\n", boot_frames, label))
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
local function snap(label)
|
||||
log_step("snap " .. label)
|
||||
manager.machine.video:snapshot()
|
||||
end
|
||||
|
||||
-- Finder navigation: natkeyboard handles per-letter scancode/timing
|
||||
-- correctly; the Cmd modifier is forced via the macadb field for the
|
||||
-- duration of the post() call. Mirrors the proven approach in the
|
||||
-- pre-existing /tmp/auto_run.lua.
|
||||
local steps = {
|
||||
{ 2700, function() snap("preboot") end },
|
||||
{ 3000, function() snap("at_3000_finder") log_step("post J") nat:post("J") end },
|
||||
{ 3120, function() snap("after_J") log_step("Cmd hold") press(key_cmd) end },
|
||||
{ 3126, function() log_step("post o under Cmd") nat:post("o") end },
|
||||
{ 3180, function() log_step("Cmd release") release(key_cmd) end },
|
||||
{ 3300, function() snap("after_Cmd_O") end },
|
||||
{ 3540, function() log_step("post name") nat:post("${prog_select_str}") end },
|
||||
{ 3600, function() snap("after_first") end },
|
||||
{ 3660, function() log_step("Cmd hold #2") press(key_cmd) end },
|
||||
{ 3666, function() log_step("post o #2") nat:post("o") end },
|
||||
{ 3720, function() log_step("Cmd release #2") release(key_cmd) end },
|
||||
{ 3900, function() snap("after_launch") end },
|
||||
{ 6000, function() snap("running") end },
|
||||
{ 9000, function() snap("running_2") end },
|
||||
{ 12000, function() snap("running_3") end },
|
||||
{ 13500, function() snap("running_4") end },
|
||||
{ 14500, function() snap("running_5") end },
|
||||
{ 15500, function() snap("running_6") end },
|
||||
{ 16500, function() snap("running_7") end },
|
||||
}
|
||||
local step_idx = 1
|
||||
|
||||
-- Probe-address poll. Watch 3 addresses and log every change, so we
|
||||
-- can verify which (if any) is reachable by our long-mode STAs.
|
||||
local last1, last2, last3, last4, last5, last6, last7 = -1, -1, -1, -1, -1, -1, -1
|
||||
local function check_ckpt()
|
||||
local v1 = prog:read_u8(0x012050)
|
||||
local v2 = prog:read_u8(0x011F80)
|
||||
local v3 = prog:read_u8(0x023000)
|
||||
local v4 = prog:read_u8(0xE12050) -- SHR pixel byte (mirror of $012050 once blitted)
|
||||
local v5 = prog:read_u8(0xE19D00) -- SCB row 0 (0 = 320 mode)
|
||||
local v6 = prog:read_u8(0xE15000) -- SHR pixel byte mid-screen (row ~38, col ~64)
|
||||
local v7 = prog:read_u8(0xE19E00) -- palette[0][0] low byte (color 0 lo)
|
||||
if v1 ~= last1 or v2 ~= last2 or v3 ~= last3 or v4 ~= last4 or v5 ~= last5 or v6 ~= last6 or v7 ~= last7 then
|
||||
local f = io.open("/tmp/mame-iigs/ckpt-trace.txt", "a")
|
||||
if f ~= nil then
|
||||
f:write(string.format("frame=%d 012050=%02X 011F80=%02X 023000=%02X E12050=%02X E19D00=%02X E15000=%02X E19E00=%02X\n",
|
||||
boot_frames, v1, v2, v3, v4, v5, v6, v7))
|
||||
f:close()
|
||||
end
|
||||
last1, last2, last3, last4, last5, last6, last7 = v1, v2, v3, v4, v5, v6, v7
|
||||
end
|
||||
end
|
||||
|
||||
emu.register_periodic(function()
|
||||
local dbg = manager.machine.debugger
|
||||
if dbg == nil then return end
|
||||
if dbg.execution_state == "stop" then
|
||||
if not started then
|
||||
-- First halt is the -debug startup pause; auto-resume so
|
||||
-- emulation begins without manual input.
|
||||
started = true
|
||||
dbg.execution_state = "run"
|
||||
return
|
||||
|
|
@ -111,10 +205,12 @@ emu.register_periodic(function()
|
|||
end
|
||||
else
|
||||
boot_frames = boot_frames + 1
|
||||
-- Watchdog: ~30 wall-sec at 60 Hz. If nothing crashes by
|
||||
-- then, dump current state (likely the demo running fine)
|
||||
-- and tell the launcher to shut down so we can grab joeylog.
|
||||
if boot_frames > 1800 and not crashed then
|
||||
check_ckpt()
|
||||
while step_idx <= #steps and boot_frames >= steps[step_idx][1] do
|
||||
steps[step_idx][2]()
|
||||
step_idx = step_idx + 1
|
||||
end
|
||||
if boot_frames > 18000 and not crashed then
|
||||
crashed = true
|
||||
dump("watchdog")
|
||||
signal_done("watchdog")
|
||||
|
|
@ -130,7 +226,8 @@ cat <<EOF
|
|||
MAME apple2gs (auto-launch ${prog^^}):
|
||||
Boot disk: $work/boot.po (flop3)
|
||||
|
||||
GS/OS will boot directly into ${prog^^}; no Finder navigation needed.
|
||||
Boots GS/OS, waits ~50s for Finder, then drives the keyboard via Lua
|
||||
to select the JOEYLIB volume and double-launch ${prog^^}.
|
||||
On crash the MAME debugger halts and Lua dumps state to:
|
||||
$out/crash.txt
|
||||
|
||||
|
|
@ -146,6 +243,11 @@ cleanup() {
|
|||
if [[ -f $work/debug.log ]]; then
|
||||
mv -f "$work/debug.log" "$out/debug.log"
|
||||
fi
|
||||
# Snapshots that the Lua hook captured for visual debugging.
|
||||
if [[ -d $work/snap ]]; then
|
||||
rm -rf "$out/snap"
|
||||
mv "$work/snap" "$out/snap"
|
||||
fi
|
||||
local profuse=$repo/toolchains/iigs/gg-tools/bin/profuse
|
||||
local mnt=$work/_mnt
|
||||
if [[ -x $profuse && -f $work/joey.2mg ]]; then
|
||||
|
|
@ -167,11 +269,11 @@ cleanup() {
|
|||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Headless by default (-video none). Set MAME_WINDOW=1 to get a real
|
||||
# emulator window for interactive use.
|
||||
video_arg="-video none"
|
||||
if [[ "${MAME_WINDOW:-0}" = "1" ]]; then
|
||||
# Visible by default. Set MAME_HEADLESS=1 to suppress the video window
|
||||
# (CI / batch runs that only care about crash.txt).
|
||||
video_arg="-window"
|
||||
if [[ "${MAME_HEADLESS:-0}" = "1" ]]; then
|
||||
video_arg="-video none"
|
||||
fi
|
||||
|
||||
# Clear the done-marker the Lua hook uses to signal shutdown.
|
||||
|
|
@ -182,13 +284,14 @@ mame apple2gs \
|
|||
-flop3 "$work/boot.po" \
|
||||
-flop4 "$work/joey.2mg" \
|
||||
$video_arg -sound none \
|
||||
-nothrottle \
|
||||
-debug -debuglog \
|
||||
-autoboot_script "$work/crash-hook.lua" &
|
||||
mame_pid=$!
|
||||
|
||||
# Poll for the done-marker. Kill MAME once Lua signals it. Cap total
|
||||
# wall-clock at 60 s in case MAME never writes the marker.
|
||||
deadline=$((SECONDS + 60))
|
||||
# wall-clock at 6 min so we don't outlive the Lua-side watchdog (5 min).
|
||||
deadline=$((SECONDS + 360))
|
||||
while kill -0 "$mame_pid" 2>/dev/null; do
|
||||
if [[ -f $out/.done ]]; then
|
||||
kill "$mame_pid" 2>/dev/null
|
||||
|
|
|
|||
|
|
@ -11,13 +11,18 @@
|
|||
#
|
||||
# scripts/run-iigs.sh # boots (Pattern hint)
|
||||
# scripts/run-iigs.sh hello # boots, hints HELLO
|
||||
# scripts/run-iigs.sh keys # boots, hints KEYS
|
||||
# scripts/run-iigs.sh joy # boots, hints JOY
|
||||
# scripts/run-iigs.sh sprite # boots, hints SPRITE
|
||||
# scripts/run-iigs.sh audio # boots, hints AUDIO
|
||||
# scripts/run-iigs.sh draw # boots, hints DRAW
|
||||
#
|
||||
# Argument is any built example name (case-insensitive); upper-case
|
||||
# it for the Finder hint and existence-check.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "usage: $0 [example-name]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
prog=${1:-pattern}
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
|
|
@ -33,10 +38,17 @@ sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
|||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
||||
|
||||
case $prog in
|
||||
hello|pattern|keys|joy|sprite|audio) ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
||||
esac
|
||||
target=${prog^^}
|
||||
bin_dir=$repo/build/iigs/bin
|
||||
if [[ ! -f "$bin_dir/$target" ]]; then
|
||||
echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
|
||||
if compgen -G "$bin_dir/*" > /dev/null; then
|
||||
echo "available examples in $bin_dir:" >&2
|
||||
find "$bin_dir" -maxdepth 1 -type f -printf '%f\n' \
|
||||
| grep -vE '\.2mg$|\.txt$' >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
|
||||
if [[ ! -f $f ]]; then
|
||||
|
|
@ -107,7 +119,6 @@ cp "$data_disk" "$work/joey.2mg"
|
|||
# install_support_iigs_null_c600.
|
||||
cp "$null_c600" "$work/c600.rom"
|
||||
|
||||
target=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
||||
cat <<EOF
|
||||
GSplus launching GS/OS 6.0.4.
|
||||
Once Finder is up:
|
||||
|
|
|
|||
483
src/core/draw.c
483
src/core/draw.c
|
|
@ -8,13 +8,31 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "joey/draw.h"
|
||||
#include "joey/debug.h"
|
||||
#include "hal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// On IIgs, hoist all primitive functions out of _ROOT into a named
|
||||
// DRAWPRIMS load segment. drawLine/drawCircle/fillCircle/floodFill/
|
||||
// floodFillBounded together push past the 64 KB-per-bank budget for
|
||||
// the simpler binaries (PATTERN was the first to fail). On other
|
||||
// ports this macro vanishes.
|
||||
JOEYLIB_SEGMENT("DRAWPRIMS")
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
// Flood-fill seed stack: each entry is (x, y) = 4 bytes, so 512 slots
|
||||
// = 2 KB. For 320x200 surfaces with reasonable region sizes this is
|
||||
// well above the worst-case scanline-fill seed depth (typically <50).
|
||||
// On overflow the fill silently truncates rather than crashing.
|
||||
#define FLOOD_STACK_SIZE 512
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static bool blitClip(int16_t *dstX, int16_t *dstY, int16_t *srcX, int16_t *srcY, int16_t *w, int16_t *h, int16_t srcW, int16_t srcH);
|
||||
static void clipRect(int16_t *x, int16_t *y, int16_t *w, int16_t *h, bool *outVisible);
|
||||
static void fillRectClipped(SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
|
||||
static void floodFillInternal(SurfaceT *s, int16_t startX, int16_t startY, uint8_t newColor, uint8_t matchColor, bool matchEqual);
|
||||
static uint8_t srcPixel(const uint8_t *row, int16_t x);
|
||||
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble);
|
||||
|
||||
|
|
@ -117,6 +135,207 @@ static void fillRectClipped(SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_
|
|||
}
|
||||
|
||||
|
||||
// Smith's scanline flood fill. Implements both the unbounded and the
|
||||
// boundary-stopped variants in one pass: the matching predicate is
|
||||
// (pixel == matchColor) when matchEqual is true (unbounded floodFill,
|
||||
// matchColor is the original seed color) or (pixel != matchColor)
|
||||
// when matchEqual is false (floodFillBounded, matchColor is the
|
||||
// boundary that stops the fill).
|
||||
//
|
||||
// Algorithm:
|
||||
// 1. Push seed (x, y) on stack.
|
||||
// 2. Pop a seed; skip if its pixel no longer matches (already
|
||||
// filled by an earlier span overlap).
|
||||
// 3. Scan left and right from the seed to find the longest run of
|
||||
// matching pixels containing it -- this is the current span.
|
||||
// 4. Fill the span with newColor.
|
||||
// 5. Walk the row above and the row below, scanning the columns
|
||||
// that overlap the just-filled span; for each contiguous run of
|
||||
// matching pixels, push the rightmost x of that run as a new
|
||||
// seed (so popping that seed next will scan the same run).
|
||||
// 6. Repeat until the stack drains.
|
||||
//
|
||||
// Stack overflow truncates the fill rather than crashing; for vector
|
||||
// art (Sierra-style picture playback) the input is well-behaved and
|
||||
// 512 entries is plenty.
|
||||
static void floodFillInternal(SurfaceT *s, int16_t startX, int16_t startY, uint8_t newColor, uint8_t matchColor, bool matchEqual) {
|
||||
static int16_t stackX[FLOOD_STACK_SIZE];
|
||||
static int16_t stackY[FLOOD_STACK_SIZE];
|
||||
static uint8_t floodMarkBuf[SURFACE_WIDTH];
|
||||
int16_t sp;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t leftX;
|
||||
int16_t rightX;
|
||||
uint8_t *row;
|
||||
uint8_t pix;
|
||||
bool pixMatch;
|
||||
uint8_t newNibble;
|
||||
|
||||
newNibble = (uint8_t)(newColor & 0x0F);
|
||||
matchColor = (uint8_t)(matchColor & 0x0F);
|
||||
|
||||
sp = 0;
|
||||
stackX[sp] = startX;
|
||||
stackY[sp] = startY;
|
||||
sp++;
|
||||
|
||||
while (sp > 0) {
|
||||
sp--;
|
||||
x = stackX[sp];
|
||||
y = stackY[sp];
|
||||
|
||||
if (y < 0 || y >= SURFACE_HEIGHT || x < 0 || x >= SURFACE_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
row = &s->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||
|
||||
// Highest-tier asm fast path: seed-test + walk-left + walk-right
|
||||
// + 1-row fill + scan-above + scan-below + push, all in one
|
||||
// cross-segment call. The asm caches row addr / match decoder
|
||||
// across every sub-operation. C just pops and dispatches; this
|
||||
// path completes the entire per-seed work.
|
||||
{
|
||||
bool seedMatched;
|
||||
if (halFastFloodWalkAndScans(s->pixels, x, y,
|
||||
matchColor, newNibble, matchEqual,
|
||||
stackX, stackY,
|
||||
&sp, FLOOD_STACK_SIZE,
|
||||
&seedMatched, &leftX, &rightX)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Tier-2 asm fast path: combined seed test + walk-left +
|
||||
// walk-right in one cross-segment call. Falls back to the
|
||||
// pure-C walks below on ports without an asm implementation.
|
||||
{
|
||||
bool seedMatched;
|
||||
if (halFastFloodWalk(row, x, matchColor, newNibble, matchEqual,
|
||||
&seedMatched, &leftX, &rightX)) {
|
||||
if (!seedMatched) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
pix = srcPixel(row, x);
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual) {
|
||||
if (!pixMatch) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (pixMatch || pix == newNibble) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk left to find the start of the matching run.
|
||||
leftX = x;
|
||||
while (leftX > 0) {
|
||||
pix = srcPixel(row, (int16_t)(leftX - 1));
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual ? !pixMatch : (pixMatch || pix == newNibble)) {
|
||||
break;
|
||||
}
|
||||
leftX--;
|
||||
}
|
||||
|
||||
// Walk right to find the end.
|
||||
rightX = x;
|
||||
while (rightX < SURFACE_WIDTH - 1) {
|
||||
pix = srcPixel(row, (int16_t)(rightX + 1));
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual ? !pixMatch : (pixMatch || pix == newNibble)) {
|
||||
break;
|
||||
}
|
||||
rightX++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the span. Bypass fillRect's clipping wrapper: walk-out
|
||||
// already guaranteed leftX/rightX are in [0..SURFACE_WIDTH-1]
|
||||
// and the seed-pop bounds check did the same for y.
|
||||
{
|
||||
int16_t spanW = (int16_t)(rightX - leftX + 1);
|
||||
if (!halFastFillRect(s, leftX, y, (uint16_t)spanW, 1, newNibble)) {
|
||||
fillRectClipped(s, leftX, y, spanW, 1, newNibble);
|
||||
}
|
||||
}
|
||||
|
||||
// Scan rows above and below for run boundaries. The hot
|
||||
// per-pixel match check goes through halFastFloodScanRow on
|
||||
// ports that have it (IIgs); fills markBuf[] with 1/0 per
|
||||
// pixel so the run-edge walk below is array-only -- no
|
||||
// function call, no nibble extract.
|
||||
{
|
||||
int16_t i;
|
||||
int16_t spanLen;
|
||||
uint8_t *scanRow;
|
||||
int16_t scanY;
|
||||
int16_t side;
|
||||
bool curHit;
|
||||
bool prevHit;
|
||||
|
||||
spanLen = (int16_t)(rightX - leftX + 1);
|
||||
for (side = 0; side < 2; side++) {
|
||||
if (side == 0) {
|
||||
if (y <= 0) {
|
||||
continue;
|
||||
}
|
||||
scanY = (int16_t)(y - 1);
|
||||
} else {
|
||||
if (y >= SURFACE_HEIGHT - 1) {
|
||||
continue;
|
||||
}
|
||||
scanY = (int16_t)(y + 1);
|
||||
}
|
||||
scanRow = &s->pixels[scanY * SURFACE_BYTES_PER_ROW];
|
||||
// Prefer the combined scan+push asm path (one call per
|
||||
// scan, no markBuf and no per-pixel C edge walk).
|
||||
if (!halFastFloodScanAndPush(scanRow, leftX, rightX,
|
||||
matchColor, newNibble, matchEqual,
|
||||
scanY, stackX, stackY,
|
||||
&sp, FLOOD_STACK_SIZE)) {
|
||||
if (!halFastFloodScanRow(scanRow, leftX, rightX,
|
||||
matchColor, newNibble, matchEqual,
|
||||
floodMarkBuf)) {
|
||||
// C fallback: fill markBuf the slow way.
|
||||
for (i = 0; i < spanLen; i++) {
|
||||
pix = srcPixel(scanRow, (int16_t)(leftX + i));
|
||||
pixMatch = (pix == matchColor);
|
||||
floodMarkBuf[i] = (uint8_t)(matchEqual
|
||||
? (pixMatch ? 1 : 0)
|
||||
: ((!pixMatch && pix != newNibble) ? 1 : 0));
|
||||
}
|
||||
}
|
||||
// Walk markBuf for run-edge transitions.
|
||||
prevHit = false;
|
||||
for (i = 0; i < spanLen; i++) {
|
||||
curHit = floodMarkBuf[i] != 0;
|
||||
if (!curHit && prevHit) {
|
||||
if (sp < FLOOD_STACK_SIZE) {
|
||||
stackX[sp] = (int16_t)(leftX + i - 1);
|
||||
stackY[sp] = scanY;
|
||||
sp++;
|
||||
}
|
||||
}
|
||||
prevHit = curHit;
|
||||
}
|
||||
if (prevHit) {
|
||||
if (sp < FLOOD_STACK_SIZE) {
|
||||
stackX[sp] = rightX;
|
||||
stackY[sp] = scanY;
|
||||
sp++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble) {
|
||||
uint8_t *byte;
|
||||
|
||||
|
|
@ -142,6 +361,131 @@ static uint8_t srcPixel(const uint8_t *row, int16_t x) {
|
|||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void drawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t err;
|
||||
int16_t ir;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (r == 0) {
|
||||
drawPixel(s, cx, cy, colorIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast path: when the bounding circle is fully on-surface we can
|
||||
// hand off to the port asm (no per-pixel bounds check needed in
|
||||
// the inner loop) and mark the bounding box dirty once.
|
||||
ir = (int16_t)r;
|
||||
if (cx - ir >= 0 && cx + ir < SURFACE_WIDTH &&
|
||||
cy - ir >= 0 && cy + ir < SURFACE_HEIGHT &&
|
||||
halFastDrawCircle(s, cx, cy, r, colorIndex)) {
|
||||
surfaceMarkDirtyRect(s, (int16_t)(cx - ir), (int16_t)(cy - ir),
|
||||
(uint16_t)(2 * ir + 1), (uint16_t)(2 * ir + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// Bresenham midpoint: maintain (x, y) on the perimeter, eight-
|
||||
// octant symmetry plots all 8 reflections each iteration. Routes
|
||||
// through drawPixel so off-surface pixels clip individually.
|
||||
x = (int16_t)r;
|
||||
y = 0;
|
||||
err = (int16_t)(1 - x);
|
||||
while (x >= y) {
|
||||
drawPixel(s, (int16_t)(cx + x), (int16_t)(cy + y), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx - x), (int16_t)(cy + y), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx + x), (int16_t)(cy - y), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx - x), (int16_t)(cy - y), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx + y), (int16_t)(cy + x), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx - y), (int16_t)(cy + x), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx + y), (int16_t)(cy - x), colorIndex);
|
||||
drawPixel(s, (int16_t)(cx - y), (int16_t)(cy - x), colorIndex);
|
||||
y++;
|
||||
if (err <= 0) {
|
||||
err = (int16_t)(err + 2 * y + 1);
|
||||
} else {
|
||||
x--;
|
||||
err = (int16_t)(err + 2 * (y - x) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void drawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
int16_t dx;
|
||||
int16_t dy;
|
||||
int16_t sx;
|
||||
int16_t sy;
|
||||
int16_t err;
|
||||
int16_t e2;
|
||||
int16_t tmp;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Horizontal and vertical fast paths use fillRect; the general
|
||||
// case Bresenham routes per-pixel through drawPixel so per-pixel
|
||||
// off-surface clipping just works.
|
||||
if (y0 == y1) {
|
||||
if (x0 > x1) {
|
||||
tmp = x0;
|
||||
x0 = x1;
|
||||
x1 = tmp;
|
||||
}
|
||||
fillRect(s, x0, y0, (uint16_t)(x1 - x0 + 1), 1, colorIndex);
|
||||
return;
|
||||
}
|
||||
if (x0 == x1) {
|
||||
if (y0 > y1) {
|
||||
tmp = y0;
|
||||
y0 = y1;
|
||||
y1 = tmp;
|
||||
}
|
||||
fillRect(s, x0, y0, 1, (uint16_t)(y1 - y0 + 1), colorIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Diagonal: if both endpoints are on-surface, the inner Bresenham
|
||||
// can run without per-pixel bound checks. Hand off to the port
|
||||
// fast path; bounding-box dirty marking happens here in C either
|
||||
// way.
|
||||
if (x0 >= 0 && x0 < SURFACE_WIDTH && x1 >= 0 && x1 < SURFACE_WIDTH &&
|
||||
y0 >= 0 && y0 < SURFACE_HEIGHT && y1 >= 0 && y1 < SURFACE_HEIGHT &&
|
||||
halFastDrawLine(s, x0, y0, x1, y1, colorIndex)) {
|
||||
int16_t bbx = (x0 < x1) ? x0 : x1;
|
||||
int16_t bby = (y0 < y1) ? y0 : y1;
|
||||
int16_t bbw = (int16_t)(((x0 > x1) ? x0 : x1) - bbx + 1);
|
||||
int16_t bbh = (int16_t)(((y0 > y1) ? y0 : y1) - bby + 1);
|
||||
surfaceMarkDirtyRect(s, bbx, bby, bbw, bbh);
|
||||
return;
|
||||
}
|
||||
|
||||
dx = (int16_t)((x1 > x0) ? (x1 - x0) : (x0 - x1));
|
||||
dy = (int16_t)(-((y1 > y0) ? (y1 - y0) : (y0 - y1)));
|
||||
sx = (int16_t)((x0 < x1) ? 1 : -1);
|
||||
sy = (int16_t)((y0 < y1) ? 1 : -1);
|
||||
err = (int16_t)(dx + dy);
|
||||
while (1) {
|
||||
drawPixel(s, x0, y0, colorIndex);
|
||||
if (x0 == x1 && y0 == y1) {
|
||||
break;
|
||||
}
|
||||
e2 = (int16_t)(2 * err);
|
||||
if (e2 >= dy) {
|
||||
err = (int16_t)(err + dy);
|
||||
x0 = (int16_t)(x0 + sx);
|
||||
}
|
||||
if (e2 <= dx) {
|
||||
err = (int16_t)(err + dx);
|
||||
y0 = (int16_t)(y0 + sy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||
uint8_t *byte;
|
||||
uint8_t nibble;
|
||||
|
|
@ -153,6 +497,7 @@ void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!halFastDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex)) {
|
||||
byte = &s->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
||||
nibble = colorIndex & 0x0F;
|
||||
if (x & 1) {
|
||||
|
|
@ -161,6 +506,83 @@ void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
|||
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(s, x, y, 1, 1);
|
||||
}
|
||||
|
||||
|
||||
void drawRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (w == 0 || h == 0) {
|
||||
return;
|
||||
}
|
||||
// Degenerate dimensions: a 1xN or Nx1 rect IS a line, and a 1x1
|
||||
// rect is a single pixel. fillRect handles both correctly so we
|
||||
// don't need to fork the inner logic.
|
||||
if (h == 1 || w == 1) {
|
||||
fillRect(s, x, y, w, h, colorIndex);
|
||||
return;
|
||||
}
|
||||
// Top edge.
|
||||
fillRect(s, x, y, w, 1, colorIndex);
|
||||
// Bottom edge.
|
||||
fillRect(s, x, (int16_t)(y + (int16_t)h - 1), w, 1, colorIndex);
|
||||
// Left edge (interior only -- top and bottom corners already drawn).
|
||||
fillRect(s, x, (int16_t)(y + 1), 1, (uint16_t)(h - 2), colorIndex);
|
||||
// Right edge (interior only).
|
||||
fillRect(s, (int16_t)(x + (int16_t)w - 1), (int16_t)(y + 1), 1, (uint16_t)(h - 2), colorIndex);
|
||||
}
|
||||
|
||||
|
||||
void fillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
int16_t y;
|
||||
int16_t x;
|
||||
int16_t ir;
|
||||
uint16_t xx;
|
||||
uint16_t yy;
|
||||
uint16_t r2;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (r == 0) {
|
||||
drawPixel(s, cx, cy, colorIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
ir = (int16_t)r;
|
||||
if (cx - ir >= 0 && cx + ir < SURFACE_WIDTH &&
|
||||
cy - ir >= 0 && cy + ir < SURFACE_HEIGHT &&
|
||||
halFastFillCircle(s, cx, cy, r, colorIndex)) {
|
||||
surfaceMarkDirtyRect(s, (int16_t)(cx - ir), (int16_t)(cy - ir),
|
||||
(uint16_t)(2 * ir + 1), (uint16_t)(2 * ir + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// For each y from 0 to r, find the largest x such that x*x + y*y
|
||||
// <= r*r and emit a horizontal span. Maintain xx=x*x, yy=y*y
|
||||
// incrementally so the hot loop never does a 32-bit multiply --
|
||||
// critical on 65816 / 68000 / 286 where mul is slow or absent.
|
||||
// (y+1)^2 = y^2 + 2y + 1; (x-1)^2 = x^2 - 2x + 1. r is uint16_t
|
||||
// so xx, yy, r2 fit in uint16_t for any r where x*x+y*y can equal
|
||||
// r2 (i.e. r <= 255 -> r2 <= 65025).
|
||||
xx = (uint16_t)(r * r);
|
||||
r2 = xx;
|
||||
yy = 0;
|
||||
x = (int16_t)r;
|
||||
for (y = 0; y <= (int16_t)r; y++) {
|
||||
while (xx + yy > r2) {
|
||||
xx = (uint16_t)(xx - (uint16_t)(2 * x - 1));
|
||||
x--;
|
||||
}
|
||||
fillRect(s, (int16_t)(cx - x), (int16_t)(cy + y), (uint16_t)(2 * x + 1), 1, colorIndex);
|
||||
if (y > 0) {
|
||||
fillRect(s, (int16_t)(cx - x), (int16_t)(cy - y), (uint16_t)(2 * x + 1), 1, colorIndex);
|
||||
}
|
||||
yy = (uint16_t)(yy + (uint16_t)(2 * y + 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void fillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
|
|
@ -182,8 +604,54 @@ void fillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t
|
|||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
if (!halFastFillRect(s, sx, sy, (uint16_t)sw, (uint16_t)sh, colorIndex)) {
|
||||
fillRectClipped(s, sx, sy, sw, sh, colorIndex);
|
||||
}
|
||||
surfaceMarkDirtyRect(s, sx, sy, sw, sh);
|
||||
}
|
||||
|
||||
|
||||
void floodFill(SurfaceT *s, int16_t x, int16_t y, uint8_t newColor) {
|
||||
uint8_t *row;
|
||||
uint8_t seedColor;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
row = &s->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||
seedColor = srcPixel(row, x);
|
||||
if ((seedColor & 0x0F) == (newColor & 0x0F)) {
|
||||
return;
|
||||
}
|
||||
floodFillInternal(s, x, y, newColor, seedColor, true);
|
||||
}
|
||||
|
||||
|
||||
void floodFillBounded(SurfaceT *s, int16_t x, int16_t y, uint8_t newColor, uint8_t boundaryColor) {
|
||||
uint8_t *row;
|
||||
uint8_t pix;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
row = &s->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||
pix = srcPixel(row, x);
|
||||
// Starting on a boundary pixel or already-filled pixel: nothing
|
||||
// to do.
|
||||
if ((pix & 0x0F) == (boundaryColor & 0x0F)) {
|
||||
return;
|
||||
}
|
||||
if ((pix & 0x0F) == (newColor & 0x0F)) {
|
||||
return;
|
||||
}
|
||||
floodFillInternal(s, x, y, newColor, boundaryColor, false);
|
||||
}
|
||||
|
||||
|
||||
uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y) {
|
||||
|
|
@ -225,6 +693,10 @@ void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y) {
|
|||
}
|
||||
|
||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
||||
srcRow = &src->pixels[srcY0 * srcRowBytes];
|
||||
dstRow = &dst->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||
if (!halFastBlitRect(dstRow, x, srcRow, srcX0,
|
||||
copyW, copyH, srcRowBytes, 0xFFFFu)) {
|
||||
for (row = 0; row < copyH; row++) {
|
||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||
|
|
@ -234,6 +706,8 @@ void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y) {
|
|||
}
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(dst, x, y, copyW, copyH);
|
||||
}
|
||||
|
||||
|
||||
void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y, uint8_t transparentIndex) {
|
||||
|
|
@ -259,6 +733,10 @@ void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t
|
|||
|
||||
transparent = (uint8_t)(transparentIndex & 0x0F);
|
||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
||||
srcRow = &src->pixels[srcY0 * srcRowBytes];
|
||||
dstRow = &dst->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||
if (!halFastBlitRect(dstRow, x, srcRow, srcX0,
|
||||
copyW, copyH, srcRowBytes, (uint16_t)transparent)) {
|
||||
for (row = 0; row < copyH; row++) {
|
||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||
|
|
@ -271,6 +749,8 @@ void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t
|
|||
}
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(dst, x, y, copyW, copyH);
|
||||
}
|
||||
|
||||
|
||||
void surfaceClear(SurfaceT *s, uint8_t colorIndex) {
|
||||
|
|
@ -282,5 +762,8 @@ void surfaceClear(SurfaceT *s, uint8_t colorIndex) {
|
|||
}
|
||||
nibble = colorIndex & 0x0F;
|
||||
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||
if (!halFastSurfaceClear(s, doubled)) {
|
||||
memset(s->pixels, doubled, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
surfaceMarkDirtyAll(s);
|
||||
}
|
||||
|
|
|
|||
120
src/core/hal.h
120
src/core/hal.h
|
|
@ -23,6 +23,14 @@ bool halInit(const JoeyConfigT *config);
|
|||
// Per-port teardown. Restores display mode, frees HW-adjacent buffers.
|
||||
void halShutdown(void);
|
||||
|
||||
// Allocate / release the SURFACE_PIXELS_SIZE-byte pixel buffer that
|
||||
// backs the library-owned stage surface. Ports that have a
|
||||
// hardware-friendly pin location for the back buffer (IIgs $01/2000
|
||||
// with SHR shadow inhibited) return that address here; ports with no
|
||||
// such constraint just malloc/free.
|
||||
uint8_t *halStageAllocPixels(void);
|
||||
void halStageFreePixels(uint8_t *pixels);
|
||||
|
||||
// Present the entire source surface to the display.
|
||||
void halPresent(const SurfaceT *src);
|
||||
|
||||
|
|
@ -64,4 +72,116 @@ void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1
|
|||
void halAudioStopSfx(uint8_t slot);
|
||||
void halAudioFrameTick(void);
|
||||
|
||||
// Optional fast-path hooks. Each returns true if the port handled the
|
||||
// operation in a port-specific accelerated path; false means the
|
||||
// caller should fall back to the platform-agnostic C implementation.
|
||||
//
|
||||
// Funneling all asm dispatches through hal.c (one TU per port) avoids
|
||||
// the cumulative ORCA Linker "Expression too complex" failure that
|
||||
// hits when multiple cross-platform TUs each call into a named load
|
||||
// segment full of asm primitives. Cross-platform code in src/core/
|
||||
// only ever calls into HAL, so the link-time expression cost is paid
|
||||
// once per binary -- not once per TU that wants speed.
|
||||
//
|
||||
// Each port must provide all of these; ports without an accelerated
|
||||
// path simply return false from every hook.
|
||||
bool halFastSurfaceClear(SurfaceT *s, uint8_t doubled);
|
||||
bool halFastFillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex);
|
||||
bool halFastTileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord);
|
||||
// Tile primitives operate on already-computed row-0 pointers from
|
||||
// the C wrapper. dstRow0 / srcRow0 point at the first byte of the
|
||||
// 8x8 region within their respective surfaces (stride 160). For
|
||||
// tilePaste / tileSnap the TileT side is a packed 32-byte buffer
|
||||
// (stride 4); the corresponding pointer points at byte 0 of that
|
||||
// buffer.
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0);
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent);
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
|
||||
// drawPixel inner: caller has already done NULL + bounds checks.
|
||||
// (x, y) are guaranteed in [0..SURFACE_WIDTH-1] x [0..SURFACE_HEIGHT-1].
|
||||
// colorIndex is the 0..15 nibble. Surface dirty marking happens in
|
||||
// the C wrapper after this returns.
|
||||
bool halFastDrawPixel(SurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex);
|
||||
|
||||
// drawLine inner for the diagonal case. Caller ensures both endpoints
|
||||
// are inside the surface bounds, so the inner loop runs without
|
||||
// per-pixel clip checks. The C wrapper still routes pure horizontal
|
||||
// and vertical lines through fillRect (which has its own fast path).
|
||||
bool halFastDrawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex);
|
||||
|
||||
// drawCircle / fillCircle inner. Caller has already validated that
|
||||
// the entire bounding circle (cx-r .. cx+r, cy-r .. cy+r) fits inside
|
||||
// the surface bounds, so the inner loop plots every octant pixel
|
||||
// unconditionally. r is guaranteed > 0; the cx == 0 / r == 0 cases
|
||||
// stay in the C wrapper.
|
||||
bool halFastDrawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
bool halFastFillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
|
||||
// floodFill helper: combined seed test + walk-left + walk-right for
|
||||
// one row. Returns true if the port handled it (asm path taken). The
|
||||
// out-param seedMatched tells the caller whether the seed pixel
|
||||
// satisfied the match criterion -- if false, caller skips this pop;
|
||||
// if true, leftXOut/rightXOut hold the run boundaries.
|
||||
// Returns false if no asm path; caller falls back to C walks.
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
bool *seedMatched,
|
||||
int16_t *leftXOut, int16_t *rightXOut);
|
||||
|
||||
// floodFill helper for the row-above / row-below run-detection scans.
|
||||
// Walks pixels [leftX..rightX] inclusive of `row`, writing 1 byte per
|
||||
// pixel into markBuf (1 = qualifies for flood, 0 = does not). The C
|
||||
// side then walks markBuf for run-edge transitions, replacing the
|
||||
// per-pixel srcPixel + match check inside the inner loop.
|
||||
// Returns true if the port handled it; false to fall back to C.
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
uint8_t *markBuf);
|
||||
|
||||
// Combined per-pixel scan + run-edge walk + seed push. Higher-level
|
||||
// than halFastFloodScanRow: replaces both the markBuf fill AND the C
|
||||
// loop that walks markBuf for falling edges. *spInOut is read on entry
|
||||
// and updated with the new top-of-stack on return. Returns true if
|
||||
// the port handled it (caller skips the C run-edge walk entirely);
|
||||
// false to fall back to halFastFloodScanRow + C walk.
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
int16_t scanY,
|
||||
int16_t *stackX, int16_t *stackY,
|
||||
int16_t *spInOut, int16_t maxSp);
|
||||
|
||||
// Highest-level flood helper: combined seed-test + walk-left + walk-right
|
||||
// + scan-above + scan-below + push for ONE popped seed. Replaces three
|
||||
// cross-segment HAL calls (halFastFloodWalk + 2x halFastFloodScanAndPush)
|
||||
// per dispatch loop iteration with one. The asm internally caches row
|
||||
// addr / matchByte / nibble decoder across all three sub-operations.
|
||||
//
|
||||
// pixels is the surface base (s->pixels). On return, leftXOut / rightXOut
|
||||
// hold the matching-run boundaries (only valid if seedMatched != 0); the
|
||||
// caller does the 1-row halFastFillRect using those bounds. *spInOut is
|
||||
// updated with any new seeds the asm pushed for the row above/below.
|
||||
//
|
||||
// Returns true if the port handled it; false to fall back to
|
||||
// halFastFloodWalk + the per-side halFastFloodScanAndPush calls.
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
int16_t *stackX, int16_t *stackY,
|
||||
int16_t *spInOut, int16_t maxSp,
|
||||
bool *seedMatched,
|
||||
int16_t *leftXOut, int16_t *rightXOut);
|
||||
|
||||
// surfaceBlit / surfaceBlitMasked rect-copy helper. Caller has done
|
||||
// the clip math: dstRow0 / srcRow0 point at row 0 of the source/dest
|
||||
// regions, dstX / srcX are intra-row pixel offsets, copyW/copyH are
|
||||
// the clipped extents. dst stride is hardcoded SURFACE_BYTES_PER_ROW.
|
||||
// transparent == $FFFF means opaque (always copy); any 0..15 value
|
||||
// means src nibbles equal to that index are skipped.
|
||||
// Returns true if the port handled it; false to fall back to C.
|
||||
bool halFastBlitRect(uint8_t *dstRow0, int16_t dstX,
|
||||
const uint8_t *srcRow0, int16_t srcX,
|
||||
int16_t copyW, int16_t copyH, int16_t srcRowBytes,
|
||||
uint16_t transparent);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -57,23 +57,26 @@ bool joeyInit(const JoeyConfigT *config) {
|
|||
|
||||
memcpy(&gConfig, config, sizeof(gConfig));
|
||||
|
||||
if (!surfaceAllocScreen()) {
|
||||
setError("failed to allocate screen surface");
|
||||
// halInit must run before stageAlloc: on IIgs the stage's pixel
|
||||
// buffer comes from halStageAllocPixels, which depends on shadow /
|
||||
// SHR setup that halInit performs.
|
||||
if (!halInit(&gConfig)) {
|
||||
const char *halMsg = halLastError();
|
||||
setError(halMsg != NULL ? halMsg : "halInit failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stageAlloc()) {
|
||||
setError("failed to allocate stage surface");
|
||||
halShutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!codegenArenaInit(gConfig.codegenBytes != 0 ? gConfig.codegenBytes
|
||||
: DEFAULT_CODEGEN_BYTES)) {
|
||||
setError("failed to allocate codegen arena");
|
||||
surfaceFreeScreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!halInit(&gConfig)) {
|
||||
const char *halMsg = halLastError();
|
||||
setError(halMsg != NULL ? halMsg : "halInit failed");
|
||||
codegenArenaShutdown();
|
||||
surfaceFreeScreen();
|
||||
stageFree();
|
||||
halShutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -99,9 +102,9 @@ void joeyShutdown(void) {
|
|||
return;
|
||||
}
|
||||
halInputShutdown();
|
||||
halShutdown();
|
||||
codegenArenaShutdown();
|
||||
surfaceFreeScreen();
|
||||
stageFree();
|
||||
halShutdown();
|
||||
gInitialized = false;
|
||||
clearError();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,25 @@ void joeyInputPoll(void) {
|
|||
}
|
||||
|
||||
|
||||
void joeyWaitForAnyKey(void) {
|
||||
int16_t i;
|
||||
|
||||
// Prime the previous-state snapshot so a key already held when the
|
||||
// wait starts has to be released and re-pressed (rising edge) to
|
||||
// satisfy the wait. Otherwise auto-repeat or a key still down from
|
||||
// the previous frame would exit instantly.
|
||||
joeyInputPoll();
|
||||
while (1) {
|
||||
joeyInputPoll();
|
||||
for (i = (int16_t)(KEY_NONE + 1); i < (int16_t)KEY_COUNT; i++) {
|
||||
if (joeyKeyPressed((JoeyKeyE)i)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool joeyKeyDown(JoeyKeyE key) {
|
||||
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,40 @@
|
|||
// Present / slam dispatcher.
|
||||
// Stage present dispatcher.
|
||||
//
|
||||
// Validates and clips the source rectangle, then routes to the port's
|
||||
// HAL implementation for the actual pixel format conversion and
|
||||
// display-memory write.
|
||||
// stagePresent walks the per-row dirty bands set by drawing primitives
|
||||
// and asks the port HAL to flip just those rows to the display, then
|
||||
// resets the dirty state. stagePresentRect bypasses dirty tracking
|
||||
// entirely and slams a caller-specified rectangle (after clipping).
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "joey/debug.h"
|
||||
#include "joey/present.h"
|
||||
#include "hal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void surfacePresent(const SurfaceT *src) {
|
||||
if (src == NULL) {
|
||||
void stagePresent(void) {
|
||||
SurfaceT *stage;
|
||||
|
||||
stage = stageGet();
|
||||
if (stage == NULL) {
|
||||
return;
|
||||
}
|
||||
halPresent(src);
|
||||
halPresent(stage);
|
||||
stageDirtyClearAll();
|
||||
}
|
||||
|
||||
|
||||
void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||
void stagePresentRect(int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||
SurfaceT *stage;
|
||||
int16_t sx;
|
||||
int16_t sy;
|
||||
int16_t sw;
|
||||
int16_t sh;
|
||||
|
||||
if (src == NULL) {
|
||||
stage = stageGet();
|
||||
if (stage == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -59,5 +67,5 @@ void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, u
|
|||
return;
|
||||
}
|
||||
|
||||
halPresentRect(src, sx, sy, (uint16_t)sw, (uint16_t)sh);
|
||||
halPresentRect(stage, sx, sy, (uint16_t)sw, (uint16_t)sh);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ static void spriteDrawInterpreted(SurfaceT *s, SpriteT *sp, int16_t x, int16_t y
|
|||
writeDstNibble(dstRow, (int16_t)(dx + col), nibble);
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(s, dx, dy, w, h);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -276,6 +277,7 @@ void spriteDraw(SurfaceT *s, SpriteT *sp, int16_t x, int16_t y) {
|
|||
// need clip math (they walk fixed offsets).
|
||||
if (sp->slot != NULL && isFullyOnSurface(x, y, widthPx, heightPx)) {
|
||||
spriteCompiledDraw(s, sp, x, y);
|
||||
surfaceMarkDirtyRect(s, x, y, (int16_t)widthPx, (int16_t)heightPx);
|
||||
return;
|
||||
}
|
||||
spriteDrawInterpreted(s, sp, x, y);
|
||||
|
|
@ -556,6 +558,8 @@ void spriteRestoreUnder(SurfaceT *s, const SpriteBackupT *backup) {
|
|||
shift = (copyBytes == (int16_t)spriteBytesPerRow) ? 0 : 1;
|
||||
if (sp->routineOffsets[shift][SPRITE_OP_RESTORE] != SPRITE_NOT_COMPILED) {
|
||||
spriteCompiledRestoreUnder(s, backup);
|
||||
surfaceMarkDirtyRect(s, backup->x, backup->y,
|
||||
(int16_t)backup->width, (int16_t)backup->height);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -568,6 +572,8 @@ void spriteRestoreUnder(SurfaceT *s, const SpriteBackupT *backup) {
|
|||
&backup->bytes[(uint16_t)row * (uint16_t)copyBytes],
|
||||
(size_t)copyBytes);
|
||||
}
|
||||
surfaceMarkDirtyRect(s, backup->x, backup->y,
|
||||
(int16_t)backup->width, (int16_t)backup->height);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Surface allocation, destruction, persistence, and the library-owned
|
||||
// screen surface.
|
||||
// stage (the back-buffer surface).
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "joey/surface.h"
|
||||
#include "hal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
#define SURFACE_PALETTE_BYTES (SURFACE_PALETTE_ENTRIES * (uint32_t)sizeof(uint16_t))
|
||||
|
|
@ -19,20 +20,52 @@
|
|||
|
||||
// ----- Module state -----
|
||||
|
||||
static SurfaceT *gScreen = NULL;
|
||||
static SurfaceT *gStage = NULL;
|
||||
|
||||
uint8_t gStageMinWord[SURFACE_HEIGHT];
|
||||
uint8_t gStageMaxWord[SURFACE_HEIGHT];
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
static void widenRow(int16_t y, uint8_t minWord, uint8_t maxWord) {
|
||||
if (minWord < gStageMinWord[y]) {
|
||||
gStageMinWord[y] = minWord;
|
||||
}
|
||||
if (maxWord > gStageMaxWord[y]) {
|
||||
gStageMaxWord[y] = maxWord;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
SurfaceT *stageGet(void) {
|
||||
return gStage;
|
||||
}
|
||||
|
||||
|
||||
void surfaceCopy(SurfaceT *dst, const SurfaceT *src) {
|
||||
if (dst == NULL || src == NULL || dst == src) {
|
||||
return;
|
||||
}
|
||||
memcpy(dst, src, sizeof(SurfaceT));
|
||||
memcpy(dst->pixels, src->pixels, SURFACE_PIXELS_SIZE);
|
||||
memcpy(dst->scb, src->scb, sizeof(src->scb));
|
||||
memcpy(dst->palette, src->palette, sizeof(src->palette));
|
||||
surfaceMarkDirtyAll(dst);
|
||||
}
|
||||
|
||||
|
||||
SurfaceT *surfaceCreate(void) {
|
||||
SurfaceT *s = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||
SurfaceT *s;
|
||||
|
||||
s = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
s->pixels = (uint8_t *)calloc(1, SURFACE_PIXELS_SIZE);
|
||||
if (s->pixels == NULL) {
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
@ -41,18 +74,14 @@ void surfaceDestroy(SurfaceT *s) {
|
|||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (s == gScreen) {
|
||||
if (s == gStage) {
|
||||
return;
|
||||
}
|
||||
free(s->pixels);
|
||||
free(s);
|
||||
}
|
||||
|
||||
|
||||
SurfaceT *surfaceGetScreen(void) {
|
||||
return gScreen;
|
||||
}
|
||||
|
||||
|
||||
bool surfaceLoadFile(SurfaceT *dst, const char *path) {
|
||||
FILE *fp;
|
||||
long fileSize;
|
||||
|
|
@ -90,6 +119,7 @@ bool surfaceLoadFile(SurfaceT *dst, const char *path) {
|
|||
return false;
|
||||
}
|
||||
fclose(fp);
|
||||
surfaceMarkDirtyAll(dst);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -121,21 +151,81 @@ bool surfaceSaveFile(const SurfaceT *src, const char *path) {
|
|||
}
|
||||
|
||||
|
||||
// ----- Internal (alphabetical) -----
|
||||
void surfaceMarkDirtyAll(const SurfaceT *s) {
|
||||
int16_t row;
|
||||
|
||||
bool surfaceAllocScreen(void) {
|
||||
if (gScreen != NULL) {
|
||||
return true;
|
||||
}
|
||||
gScreen = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||
return gScreen != NULL;
|
||||
}
|
||||
|
||||
|
||||
void surfaceFreeScreen(void) {
|
||||
if (gScreen == NULL) {
|
||||
if (s != gStage) {
|
||||
return;
|
||||
}
|
||||
free(gScreen);
|
||||
gScreen = NULL;
|
||||
for (row = 0; row < SURFACE_HEIGHT; row++) {
|
||||
gStageMinWord[row] = 0;
|
||||
gStageMaxWord[row] = (uint8_t)(SURFACE_WORDS_PER_ROW - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Drawing primitives pass the rect they actually wrote (already
|
||||
// clipped to surface bounds, w and h positive). For non-stage surfaces
|
||||
// the call is a no-op so primitives can call unconditionally without
|
||||
// branching themselves.
|
||||
void surfaceMarkDirtyRect(const SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h) {
|
||||
int16_t row;
|
||||
int16_t yEnd;
|
||||
uint8_t minWord;
|
||||
uint8_t maxWord;
|
||||
|
||||
if (s != gStage) {
|
||||
return;
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
minWord = (uint8_t)(x >> 2);
|
||||
maxWord = (uint8_t)((x + w - 1) >> 2);
|
||||
yEnd = y + h;
|
||||
for (row = y; row < yEnd; row++) {
|
||||
widenRow(row, minWord, maxWord);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- Internal (alphabetical) -----
|
||||
|
||||
bool stageAlloc(void) {
|
||||
if (gStage != NULL) {
|
||||
return true;
|
||||
}
|
||||
gStage = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||
if (gStage == NULL) {
|
||||
return false;
|
||||
}
|
||||
gStage->pixels = halStageAllocPixels();
|
||||
if (gStage->pixels == NULL) {
|
||||
free(gStage);
|
||||
gStage = NULL;
|
||||
return false;
|
||||
}
|
||||
memset(gStage->pixels, 0, SURFACE_PIXELS_SIZE);
|
||||
stageDirtyClearAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void stageDirtyClearAll(void) {
|
||||
int16_t row;
|
||||
|
||||
for (row = 0; row < SURFACE_HEIGHT; row++) {
|
||||
gStageMinWord[row] = STAGE_DIRTY_CLEAN_MIN;
|
||||
gStageMaxWord[row] = STAGE_DIRTY_CLEAN_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void stageFree(void) {
|
||||
if (gStage == NULL) {
|
||||
return;
|
||||
}
|
||||
halStageFreePixels(gStage->pixels);
|
||||
free(gStage);
|
||||
gStage = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,57 @@
|
|||
|
||||
#include "joey/surface.h"
|
||||
|
||||
// Pixels are reached through a pointer rather than an inline array so
|
||||
// that the per-port HAL can pin the stage's pixel buffer to a specific
|
||||
// hardware-friendly address (e.g. IIgs $01/2000 with SHR shadow
|
||||
// inhibited at $C035 so writes stay in fast bank $01 instead of
|
||||
// auto-mirroring to $E1). Caller-side `s->pixels[i]` syntax is
|
||||
// unchanged; only allocation/copy paths in surface.c shift to a
|
||||
// two-buffer model.
|
||||
struct SurfaceT {
|
||||
uint8_t pixels[SURFACE_PIXELS_SIZE];
|
||||
uint8_t *pixels;
|
||||
uint8_t scb[SURFACE_HEIGHT];
|
||||
uint16_t palette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
};
|
||||
|
||||
// Allocate and free the library's pre-allocated screen surface. Called
|
||||
// from init.c during joeyInit / joeyShutdown.
|
||||
bool surfaceAllocScreen(void);
|
||||
void surfaceFreeScreen(void);
|
||||
// 16-bit words per scanline. SHR / chunky 4bpp packed = 2 px per byte,
|
||||
// 4 px per 16-bit word. SURFACE_BYTES_PER_ROW (160) / 2 = 80 words.
|
||||
// Dirty tracking grain is 16-bit words because that matches the IIgs
|
||||
// PEI / PHA slam unit and the Amiga / ST c2p group is 16 px = 4 words.
|
||||
#define SURFACE_WORDS_PER_ROW (SURFACE_BYTES_PER_ROW / 2)
|
||||
|
||||
// Sentinels for "row is clean": min > max can never happen for a real
|
||||
// dirty range, so the present loop tests `min > max` to skip a row.
|
||||
#define STAGE_DIRTY_CLEAN_MIN 0xFFu
|
||||
#define STAGE_DIRTY_CLEAN_MAX 0x00u
|
||||
|
||||
// Per-row dirty word bands for the stage. gStageMinWord[y] is the
|
||||
// leftmost dirty 16-bit column on row y (inclusive); gStageMaxWord[y]
|
||||
// is the rightmost (inclusive). Both default to the CLEAN sentinels
|
||||
// after stageAlloc and after each stagePresent.
|
||||
extern uint8_t gStageMinWord[SURFACE_HEIGHT];
|
||||
extern uint8_t gStageMaxWord[SURFACE_HEIGHT];
|
||||
|
||||
// Drawing primitives call this with their already-clipped destination
|
||||
// rect. If `s` is the stage, the affected rows' [minWord, maxWord]
|
||||
// bands are widened to cover the rect. If `s` is any other surface,
|
||||
// the call is a no-op -- non-stage surfaces never get presented, so
|
||||
// they don't carry dirty state.
|
||||
void surfaceMarkDirtyRect(const SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h);
|
||||
|
||||
// Shorthand for "every row, full width" -- used by surfaceClear and
|
||||
// the bulk-replace paths (surfaceCopy, surfaceLoadFile). No-op if `s`
|
||||
// is not the stage.
|
||||
void surfaceMarkDirtyAll(const SurfaceT *s);
|
||||
|
||||
// Reset every row to CLEAN. Called by stagePresent after the slam.
|
||||
void stageDirtyClearAll(void);
|
||||
|
||||
// Allocate and free the library-owned stage (the back-buffer surface
|
||||
// that stagePresent flips to the display). Called from init.c during
|
||||
// joeyInit / joeyShutdown. The stage's pixel storage is supplied by
|
||||
// the port HAL via halStageAllocPixels.
|
||||
bool stageAlloc(void);
|
||||
void stageFree(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
280
src/core/tile.c
Normal file
280
src/core/tile.c
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
// Tiles as 8x8 surface regions. The whole API is byte-shoveling
|
||||
// between SurfaceT regions (or between a SurfaceT region and a
|
||||
// stack TileT buffer); no separate tileset container, no allocator.
|
||||
//
|
||||
// Block coords (bx, by) map to pixel (bx*8, by*8). At 4bpp packed
|
||||
// each tile row is 4 bytes wide, so byte-aligned memcpy is the inner
|
||||
// loop for everything except the masked / transparent variant, which
|
||||
// has to read-modify-write each byte to preserve destination pixels
|
||||
// under transparent (color-0 by convention) source nibbles.
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "joey/tile.h"
|
||||
#include "hal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// (No <string.h> -- the 4-byte-per-row inner copies are spelled out
|
||||
// inline below. Avoiding memcpy / memset from the DRAWPRIMS load
|
||||
// segment keeps cross-bank relocation references out of 13/SysLib;
|
||||
// without that the ORCA Linker hits "Expression too complex" on
|
||||
// the small-binary builds.)
|
||||
|
||||
// Hoist tile primitives into the DRAWPRIMS load segment. Asm
|
||||
// dispatches go through halFast* hooks in src/port/iigs/hal.c so
|
||||
// only one TU references the asm symbols (avoids the cumulative
|
||||
// "Expression too complex" link failure).
|
||||
JOEYLIB_SEGMENT("DRAWPRIMS")
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void copyTileOpaque(uint8_t *dst, const uint8_t *src);
|
||||
static void copyTileMasked(uint8_t *dst, const uint8_t *src, uint8_t transparent);
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
// 32-byte block copy with the surface row stride between rows. dst
|
||||
// and src already point at the row-0 first byte of their respective
|
||||
// 8x8 regions.
|
||||
static void copyTileOpaque(uint8_t *dst, const uint8_t *src) {
|
||||
uint8_t row;
|
||||
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
dst[3] = src[3];
|
||||
dst += SURFACE_BYTES_PER_ROW;
|
||||
src += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Same as copyTileOpaque but treats source nibbles equal to
|
||||
// `transparent` as skip-through. The src bytes are inspected nibble-
|
||||
// by-nibble; only non-transparent nibbles overwrite the destination.
|
||||
static void copyTileMasked(uint8_t *dst, const uint8_t *src, uint8_t transparent) {
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
uint8_t srcByte;
|
||||
uint8_t dstByte;
|
||||
uint8_t transHi;
|
||||
uint8_t transLo;
|
||||
uint8_t srcHi;
|
||||
uint8_t srcLo;
|
||||
|
||||
transHi = (uint8_t)((transparent & 0x0F) << 4);
|
||||
transLo = (uint8_t)(transparent & 0x0F);
|
||||
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
||||
srcByte = src[col];
|
||||
srcHi = (uint8_t)(srcByte & 0xF0);
|
||||
srcLo = (uint8_t)(srcByte & 0x0F);
|
||||
// Both nibbles transparent: skip the byte entirely.
|
||||
if (srcHi == transHi && srcLo == transLo) {
|
||||
continue;
|
||||
}
|
||||
dstByte = dst[col];
|
||||
if (srcHi != transHi) {
|
||||
dstByte = (uint8_t)((dstByte & 0x0F) | srcHi);
|
||||
}
|
||||
if (srcLo != transLo) {
|
||||
dstByte = (uint8_t)((dstByte & 0xF0) | srcLo);
|
||||
}
|
||||
dst[col] = dstByte;
|
||||
}
|
||||
dst += SURFACE_BYTES_PER_ROW;
|
||||
src += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void drawText(SurfaceT *dst, uint8_t bx, uint8_t by, const SurfaceT *fontSurface, const uint16_t *asciiMap, const char *str) {
|
||||
uint16_t entry;
|
||||
uint8_t cx;
|
||||
uint8_t cy;
|
||||
uint8_t ch;
|
||||
uint8_t srcBx;
|
||||
uint8_t srcBy;
|
||||
|
||||
if (dst == NULL || fontSurface == NULL || asciiMap == NULL || str == NULL) {
|
||||
return;
|
||||
}
|
||||
cx = bx;
|
||||
cy = by;
|
||||
while (*str != '\0') {
|
||||
ch = (uint8_t)*str++;
|
||||
entry = asciiMap[ch];
|
||||
if (entry != TILE_NO_GLYPH) {
|
||||
srcBx = (uint8_t)(entry & 0x00FFu);
|
||||
srcBy = (uint8_t)((entry >> 8) & 0x00FFu);
|
||||
tileCopyMasked(dst, cx, cy, fontSurface, srcBx, srcBy, 0u);
|
||||
}
|
||||
cx++;
|
||||
if (cx >= TILE_BLOCKS_PER_ROW) {
|
||||
cx = 0;
|
||||
cy++;
|
||||
if (cy >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void tileCopy(SurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const SurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
uint8_t *dstRow0;
|
||||
const uint8_t *srcRow0;
|
||||
uint16_t dstPixelX;
|
||||
uint16_t dstPixelY;
|
||||
uint16_t srcPixelX;
|
||||
uint16_t srcPixelY;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
}
|
||||
if (dstBx >= TILE_BLOCKS_PER_ROW || dstBy >= TILE_BLOCKS_PER_COL ||
|
||||
srcBx >= TILE_BLOCKS_PER_ROW || srcBy >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
dstPixelX = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
|
||||
dstPixelY = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelX = (uint16_t)((uint16_t)srcBx * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelY = (uint16_t)((uint16_t)srcBy * TILE_PIXELS_PER_SIDE);
|
||||
|
||||
dstRow0 = &dst->pixels[dstPixelY * SURFACE_BYTES_PER_ROW + (dstPixelX >> 1)];
|
||||
srcRow0 = &src->pixels[srcPixelY * SURFACE_BYTES_PER_ROW + (srcPixelX >> 1)];
|
||||
|
||||
if (!halFastTileCopy(dstRow0, srcRow0)) {
|
||||
copyTileOpaque(dstRow0, srcRow0);
|
||||
}
|
||||
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void tileCopyMasked(SurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const SurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
||||
uint8_t *dstRow0;
|
||||
const uint8_t *srcRow0;
|
||||
uint16_t dstPixelX;
|
||||
uint16_t dstPixelY;
|
||||
uint16_t srcPixelX;
|
||||
uint16_t srcPixelY;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
}
|
||||
if (dstBx >= TILE_BLOCKS_PER_ROW || dstBy >= TILE_BLOCKS_PER_COL ||
|
||||
srcBx >= TILE_BLOCKS_PER_ROW || srcBy >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
dstPixelX = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
|
||||
dstPixelY = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelX = (uint16_t)((uint16_t)srcBx * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelY = (uint16_t)((uint16_t)srcBy * TILE_PIXELS_PER_SIDE);
|
||||
|
||||
dstRow0 = &dst->pixels[dstPixelY * SURFACE_BYTES_PER_ROW + (dstPixelX >> 1)];
|
||||
srcRow0 = &src->pixels[srcPixelY * SURFACE_BYTES_PER_ROW + (srcPixelX >> 1)];
|
||||
|
||||
if (!halFastTileCopyMasked(dstRow0, srcRow0, transparentIndex)) {
|
||||
copyTileMasked(dstRow0, srcRow0, transparentIndex);
|
||||
}
|
||||
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void tileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
uint8_t doubled;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
doubled = (uint8_t)(((colorIndex & 0x0F) << 4) | (colorIndex & 0x0F));
|
||||
if (!halFastTileFill(s, bx, by,
|
||||
(uint16_t)((uint16_t)doubled | ((uint16_t)doubled << 8)))) {
|
||||
uint8_t *row = &s->pixels[pixelY * SURFACE_BYTES_PER_ROW + (pixelX >> 1)];
|
||||
uint8_t i;
|
||||
for (i = 0; i < TILE_PIXELS_PER_SIDE; i++) {
|
||||
row[0] = doubled;
|
||||
row[1] = doubled;
|
||||
row[2] = doubled;
|
||||
row[3] = doubled;
|
||||
row += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(s, (int16_t)pixelX, (int16_t)pixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void tilePaste(SurfaceT *dst, uint8_t bx, uint8_t by, const TileT *in) {
|
||||
uint8_t *dstRow;
|
||||
const uint8_t *src;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
uint8_t row;
|
||||
|
||||
if (dst == NULL || in == NULL) {
|
||||
return;
|
||||
}
|
||||
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
dstRow = &dst->pixels[pixelY * SURFACE_BYTES_PER_ROW + (pixelX >> 1)];
|
||||
src = &in->pixels[0];
|
||||
if (!halFastTilePaste(dstRow, src)) {
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dstRow[0] = src[0];
|
||||
dstRow[1] = src[1];
|
||||
dstRow[2] = src[2];
|
||||
dstRow[3] = src[3];
|
||||
dstRow += SURFACE_BYTES_PER_ROW;
|
||||
src += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
surfaceMarkDirtyRect(dst, (int16_t)pixelX, (int16_t)pixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void tileSnap(const SurfaceT *src, uint8_t bx, uint8_t by, TileT *out) {
|
||||
const uint8_t *srcRow;
|
||||
uint8_t *dst;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
uint8_t row;
|
||||
|
||||
if (src == NULL || out == NULL) {
|
||||
return;
|
||||
}
|
||||
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
srcRow = &src->pixels[pixelY * SURFACE_BYTES_PER_ROW + (pixelX >> 1)];
|
||||
dst = &out->pixels[0];
|
||||
if (!halFastTileSnap(dst, srcRow)) {
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dst[0] = srcRow[0];
|
||||
dst[1] = srcRow[1];
|
||||
dst[2] = srcRow[2];
|
||||
dst[3] = srcRow[3];
|
||||
srcRow += SURFACE_BYTES_PER_ROW;
|
||||
dst += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <exec/types.h>
|
||||
|
|
@ -439,7 +440,7 @@ bool halInit(const JoeyConfigT *config) {
|
|||
}
|
||||
// Force COLOR00 to black so the overscan/border region around the
|
||||
// 320x200 display is black until the app's palette load takes over
|
||||
// on the first surfacePresent. Apps that paint a non-black bg need
|
||||
// on the first stagePresent. Apps that paint a non-black bg need
|
||||
// do nothing -- their palette[0] writes the same COLOR00 once the
|
||||
// first LoadRGB4 fires from uploadScbAndPalette.
|
||||
SetRGB4(&gScreen->ViewPort, 0, 0, 0, 0);
|
||||
|
|
@ -453,11 +454,30 @@ const char *halLastError(void) {
|
|||
|
||||
|
||||
void halPresent(const SurfaceT *src) {
|
||||
int16_t y;
|
||||
uint8_t minWord;
|
||||
uint8_t maxWord;
|
||||
uint16_t byteStart;
|
||||
uint16_t byteEnd;
|
||||
|
||||
if (src == NULL || gScreen == NULL) {
|
||||
return;
|
||||
}
|
||||
updateCopperIfNeeded(src);
|
||||
c2pRange(src, 0, SURFACE_HEIGHT, 0, AMIGA_BYTES_PER_ROW);
|
||||
|
||||
// Walk per-row dirty bands: each planar byte covers 8 px = 2 chunky
|
||||
// words, so byteStart = minWord/2 and byteEnd = maxWord/2 + 1
|
||||
// converts dirty-word units to the planar-byte units c2pRange wants.
|
||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||
minWord = gStageMinWord[y];
|
||||
maxWord = gStageMaxWord[y];
|
||||
if (minWord > maxWord) {
|
||||
continue;
|
||||
}
|
||||
byteStart = (uint16_t)(minWord >> 1);
|
||||
byteEnd = (uint16_t)((maxWord >> 1) + 1);
|
||||
c2pRange(src, y, (int16_t)(y + 1), byteStart, byteEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -507,3 +527,183 @@ void halShutdown(void) {
|
|||
gNewUCL = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Amiga has no asm fast paths yet; cross-platform code falls back to
|
||||
// its C implementations whenever these return false.
|
||||
bool halFastSurfaceClear(SurfaceT *s, uint8_t doubled) {
|
||||
(void)s;
|
||||
(void)doubled;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0;
|
||||
(void)srcTilePixels;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawPixel(SurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x0;
|
||||
(void)y0;
|
||||
(void)x1;
|
||||
(void)y1;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastBlitRect(uint8_t *dstRow0, int16_t dstX, const uint8_t *srcRow0, int16_t srcX, int16_t copyW, int16_t copyH, int16_t srcRowBytes, uint16_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)dstX;
|
||||
(void)srcRow0;
|
||||
(void)srcX;
|
||||
(void)copyW;
|
||||
(void)copyH;
|
||||
(void)srcRowBytes;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
(void)s;
|
||||
(void)bx;
|
||||
(void)by;
|
||||
(void)fillWord;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mint/osbind.h>
|
||||
|
|
@ -497,11 +498,30 @@ const char *halLastError(void) {
|
|||
|
||||
|
||||
void halPresent(const SurfaceT *src) {
|
||||
int16_t y;
|
||||
uint8_t minWord;
|
||||
uint8_t maxWord;
|
||||
uint16_t groupStart;
|
||||
uint16_t groupEnd;
|
||||
|
||||
if (src == NULL || !gModeSet) {
|
||||
return;
|
||||
}
|
||||
refreshPaletteStateIfNeeded(src);
|
||||
c2pRange(src, 0, SURFACE_HEIGHT, 0, ST_GROUPS_PER_ROW);
|
||||
|
||||
// Walk per-row dirty bands: each c2p group covers 16 px = 4 chunky
|
||||
// words, so groupStart = minWord/4 and groupEnd = maxWord/4 + 1
|
||||
// converts dirty-word units to c2pRange's group units.
|
||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||
minWord = gStageMinWord[y];
|
||||
maxWord = gStageMaxWord[y];
|
||||
if (minWord > maxWord) {
|
||||
continue;
|
||||
}
|
||||
groupStart = (uint16_t)(minWord >> 2);
|
||||
groupEnd = (uint16_t)((maxWord >> 2) + 1);
|
||||
c2pRange(src, y, (int16_t)(y + 1), groupStart, groupEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -563,3 +583,183 @@ void halShutdown(void) {
|
|||
writeDiagnostics();
|
||||
gModeSet = false;
|
||||
}
|
||||
|
||||
|
||||
// ST has no asm fast paths yet; cross-platform code falls back to its
|
||||
// C implementations when these return false.
|
||||
bool halFastSurfaceClear(SurfaceT *s, uint8_t doubled) {
|
||||
(void)s;
|
||||
(void)doubled;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0;
|
||||
(void)srcTilePixels;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawPixel(SurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x0;
|
||||
(void)y0;
|
||||
(void)x1;
|
||||
(void)y1;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastBlitRect(uint8_t *dstRow0, int16_t dstX, const uint8_t *srcRow0, int16_t srcX, int16_t copyW, int16_t copyH, int16_t srcRowBytes, uint16_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)dstX;
|
||||
(void)srcRow0;
|
||||
(void)srcX;
|
||||
(void)copyW;
|
||||
(void)copyH;
|
||||
(void)srcRowBytes;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
(void)s;
|
||||
(void)bx;
|
||||
(void)by;
|
||||
(void)fillWord;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <dpmi.h>
|
||||
|
|
@ -217,13 +218,28 @@ const char *halLastError(void) {
|
|||
|
||||
void halPresent(const SurfaceT *src) {
|
||||
int16_t y;
|
||||
uint8_t minWord;
|
||||
uint8_t maxWord;
|
||||
int16_t pixelX;
|
||||
uint16_t pixelW;
|
||||
|
||||
if (src == NULL || gVgaMem == NULL) {
|
||||
return;
|
||||
}
|
||||
uploadPaletteIfNeeded(src);
|
||||
|
||||
// Walk per-row dirty bands: each chunky word holds 4 mode-13h
|
||||
// bytes, so pixelX = minWord*4 and pixelW = (maxWord-minWord+1)*4
|
||||
// gives the byte range expandAndWriteLine needs.
|
||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||
expandAndWriteLine(src, y, 0, SURFACE_WIDTH, &gVgaMem[y * VGA_STRIDE]);
|
||||
minWord = gStageMinWord[y];
|
||||
maxWord = gStageMaxWord[y];
|
||||
if (minWord > maxWord) {
|
||||
continue;
|
||||
}
|
||||
pixelX = (int16_t)((uint16_t)minWord << 2);
|
||||
pixelW = (uint16_t)(((uint16_t)maxWord - minWord + 1u) << 2);
|
||||
expandAndWriteLine(src, y, pixelX, pixelW, &gVgaMem[y * VGA_STRIDE]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,3 +293,183 @@ void halShutdown(void) {
|
|||
gCrashLog = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DOS has no asm fast paths yet; cross-platform code falls back to
|
||||
// its C implementations when these return false.
|
||||
bool halFastSurfaceClear(SurfaceT *s, uint8_t doubled) {
|
||||
(void)s;
|
||||
(void)doubled;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0;
|
||||
(void)srcTilePixels;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawPixel(SurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x0;
|
||||
(void)y0;
|
||||
(void)x1;
|
||||
(void)y1;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastBlitRect(uint8_t *dstRow0, int16_t dstX, const uint8_t *srcRow0, int16_t srcX, int16_t copyW, int16_t copyH, int16_t srcRowBytes, uint16_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)dstX;
|
||||
(void)srcRow0;
|
||||
(void)srcX;
|
||||
(void)copyW;
|
||||
(void)copyH;
|
||||
(void)srcRowBytes;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
(void)s;
|
||||
(void)bx;
|
||||
(void)by;
|
||||
(void)fillWord;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
// Apple IIgs audio HAL stub. Real implementation pending.
|
||||
//
|
||||
// Pipeline already in place:
|
||||
// * toolchains/install.sh fetches Ninjaforce NTP source into
|
||||
// toolchains/iigs/ntp/ (CRLF stripped on extraction).
|
||||
// * make/iigs.mk assembles ninjatrackerplus.s with Merlin32 into
|
||||
// build/iigs/audio/ntpplayer.bin (34 KB raw 65816, originally
|
||||
// org $0F0000 but bank-internal so it relocates at any bank-start
|
||||
// load address).
|
||||
// * package-disk.sh bundles ntpplayer.bin onto the IIgs disk image
|
||||
// alongside the demo binaries.
|
||||
//
|
||||
// Why this file is still a stub:
|
||||
// The runtime load path (NewHandle + fopen + fread on NTPPLAYER.BIN)
|
||||
// pulls Memory Manager + ORCA stdio into the link, and ORCA Linker
|
||||
// fails with "Expression too complex in 13/SysLib" when those are
|
||||
// added on top of the existing graphics + input HAL plumbing for
|
||||
// *every* demo (the IIgs build links each binary as one monolithic
|
||||
// image, no static-library culling). Bringing in those tool sets
|
||||
// needs to land alongside the JSL trampoline that actually uses the
|
||||
// loaded NTP -- one combined effort, with the demos that don't need
|
||||
// audio either kept on a thinner audio shim or split out so the
|
||||
// linker isn't asked to resolve everything for everyone.
|
||||
//
|
||||
// Until that combined load + trampoline iteration ships, every entry
|
||||
// here is a safe no-op so the audio API stays callable.
|
||||
|
||||
#include "hal.h"
|
||||
|
||||
|
||||
bool halAudioInit(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void halAudioShutdown(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
(void)data;
|
||||
(void)length;
|
||||
(void)loop;
|
||||
}
|
||||
|
||||
|
||||
void halAudioStopMod(void) {
|
||||
}
|
||||
|
||||
|
||||
bool halAudioIsPlayingMod(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
(void)slot;
|
||||
(void)sample;
|
||||
(void)length;
|
||||
(void)rateHz;
|
||||
}
|
||||
|
||||
|
||||
void halAudioStopSfx(uint8_t slot) {
|
||||
(void)slot;
|
||||
}
|
||||
|
||||
|
||||
void halAudioFrameTick(void) {
|
||||
}
|
||||
|
|
@ -1,7 +1,13 @@
|
|||
// Apple IIgs audio HAL -- full version (linked only into the AUDIO
|
||||
// demo via make/iigs.mk's split source set). audio.c keeps the no-op
|
||||
// stub for every other demo so the monolithic IIgs link budget stays
|
||||
// safe.
|
||||
// Apple IIgs audio HAL -- single source linked into every IIgs demo.
|
||||
// Earlier we split this into an audio.c no-op stub and an audio_full.c
|
||||
// real implementation, filtering audio_full.c out of non-AUDIO source
|
||||
// sets, because pulling Memory Manager + the 34 KB NTP replayer into
|
||||
// every binary blew the ORCA Linker's blank-segment / "Expression too
|
||||
// complex" budget. Now that we know how to name load segments (see
|
||||
// ORCA/C ch. 30 "segment statement"), we put every function in this
|
||||
// file into a named AUDIOIMPL load segment; the GS/OS loader places
|
||||
// it in its own bank, so non-AUDIO binaries pay only for the data
|
||||
// references, not the implementation code.
|
||||
//
|
||||
// The NinjaTrackerPlus replayer is Merlin32-assembled at build time
|
||||
// to ntpplayer.bin and baked into this TU as gNtpPlayerBytes via the
|
||||
|
|
@ -17,7 +23,25 @@
|
|||
|
||||
#include "hal.h"
|
||||
#include "joey/audio.h"
|
||||
#include "ntpplayer_data.h"
|
||||
|
||||
// Place every function defined below in the shared DRAWPRIMS overflow
|
||||
// load segment so the linker keeps the implementation code out of
|
||||
// _ROOT in every binary that includes this TU. (See ORCA/C ch. 30
|
||||
// "segment statement". Reusing the same segment as draw.c / tile.c
|
||||
// rather than picking a unique name keeps the linker's symbol-
|
||||
// resolution expressions flat -- per-name extras nest the
|
||||
// expression and trip the "too complex" threshold on small
|
||||
// binaries.)
|
||||
//
|
||||
// The 34 KB NTP replayer bytes are NOT in this segment -- ORCA/C's
|
||||
// `segment` statement only relocates functions, not data. They live
|
||||
// in their own NTPDATA load segment, declared in build/iigs/audio/
|
||||
// ntpdata.asm (auto-generated from ntpplayer.bin by make/iigs.mk).
|
||||
// We just extern the symbols here.
|
||||
segment "DRAWPRIMS";
|
||||
|
||||
extern const unsigned char gNtpPlayerBytes[];
|
||||
extern const unsigned long gNtpPlayerBytes_len;
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
|
|
|
|||
|
|
@ -10,26 +10,118 @@
|
|||
// ORCA/C must be built with 32-bit pointer mode (-w or equivalent) so
|
||||
// that the long addresses resolve to bank $E1.
|
||||
//
|
||||
// For M1 this is a simple direct-copy present. PEI-slam (in assembly)
|
||||
// arrives as an optimization in a later milestone; the structure here
|
||||
// is unchanged -- only halPresent / halPresentRect get faster inner
|
||||
// loops.
|
||||
// DIRTY-WALK + PEI-SLAM PRESENT
|
||||
// -----------------------------
|
||||
// halPresent walks the per-row dirty bands maintained by drawing
|
||||
// primitives in src/core/*.c. Fully-dirty rows go through the PEI
|
||||
// slam in src/port/iigs/peislam.asm (~530 cyc/row, ~55% faster than
|
||||
// memcpy/MVN); partial-dirty rows use memcpy, which ORCA-C lowers
|
||||
// to MVN (7 cyc/byte) -- the fastest 65816 way to move bytes into
|
||||
// bank $E1 when the dirty band is too narrow to amortize the slam's
|
||||
// per-call AUXWRITE/RAMRD/shadow toggle.
|
||||
//
|
||||
// peislam.asm declares its load segment as DRAWPRIMS so the linker
|
||||
// places it in its own bank, separate from AUDIO's _ROOT (where
|
||||
// audio_full.c + Memory Manager + stdio + NTPstreamsound already
|
||||
// crowd up against the 64 KB-per-bank limit).
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "joey/debug.h"
|
||||
#include "hal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// hal.c is the single TU that calls into joeyDraw.asm. Cross-
|
||||
// platform draw.c / tile.c / etc. dispatch through halFast*
|
||||
// functions defined here; they never reference the asm symbols
|
||||
// directly. This avoids the cumulative ORCA-Linker-Expression-
|
||||
// too-complex-in-13/SysLib failure that hit when each cross-
|
||||
// platform TU brought its own asm extern.
|
||||
JOEYLIB_SEGMENT("DRAWPRIMS")
|
||||
|
||||
// 32 KB stack-slam fill via AUXWRITE. ~25 ms full-screen.
|
||||
extern void iigsSurfaceClearInner(uint8_t *pixels, uint16_t fillWord);
|
||||
// PEI-slam fill of `bytesPerRow` doubled bytes per row across `rows`
|
||||
// rows, advancing 160 bytes per row. firstRow must be in bank $01.
|
||||
// Caller handles partial-nibble edges in C; bytesPerRow is even.
|
||||
extern void iigsFillRectStageInner(uint8_t *firstRow, uint16_t bytesPerRow, uint16_t rows, uint16_t fillWord);
|
||||
// 16 STA abs,X stores at fixed offsets along a 160-byte stride.
|
||||
// ~120 cyc per call.
|
||||
extern void iigsTileFillInner(uint8_t *dstRow0, uint16_t fillWord);
|
||||
// Tile copy / paste / snap inner loops. All take 4-byte large-
|
||||
// model pointers; bank may differ between dst and src (heap
|
||||
// surface vs stage). Stride contracts:
|
||||
// tileCopyInner / tileCopyMaskedInner: dst 160, src 160
|
||||
// tilePasteInner: dst 160, src 4
|
||||
// tileSnapInner: dst 4, src 160
|
||||
extern void iigsTileCopyInner(uint8_t *dstRow0, const uint8_t *srcRow0);
|
||||
extern void iigsTileCopyMaskedInner(uint8_t *dstRow0, const uint8_t *srcRow0, uint16_t transparent);
|
||||
extern void iigsTilePasteInner(uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
extern void iigsTileSnapInner(uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
// Single-pixel and Bresenham line plot. drawLine inner takes
|
||||
// pre-clipped endpoints (caller validates against surface bounds);
|
||||
// it does no per-pixel clipping in the loop.
|
||||
extern void iigsDrawPixelInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t nibble);
|
||||
extern void iigsDrawLineInner(uint8_t *pixels, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t nibble);
|
||||
// Bresenham midpoint circle outline. Caller has verified the entire
|
||||
// bbox is on-surface so no per-pixel clip.
|
||||
extern void iigsDrawCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
|
||||
// Stage-to-SHR full upload: pixels (MVN $01->$E1), SCB, palette.
|
||||
// Asm uses post-MVN DBR=$E1 to do sta abs,Y for SCB/palette.
|
||||
// Replaces ORCA-C's memcpy path which silently fails when called
|
||||
// from halPresent (DBR-state quirk after prior asm primitives).
|
||||
extern void iigsBlitStageToShr(uint8_t *scbPtr, uint16_t *palettePtr);
|
||||
// floodFill row walk: tests seed pixel and walks left/right to find
|
||||
// the matching run. Writes results to gFloodSeedMatch / gFloodLeftX /
|
||||
// gFloodRightX (DRAWPRIMS globals).
|
||||
extern void iigsFloodWalkInner(uint8_t *row, uint16_t startX, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual);
|
||||
extern uint16_t gFloodSeedMatch;
|
||||
extern uint16_t gFloodLeftX;
|
||||
extern uint16_t gFloodRightX;
|
||||
// Per-pixel match scan over [leftX..rightX] of `row`. Writes 1/0 to
|
||||
// markBuf[i] for each pixel. matchEqual selects boundary vs equal mode
|
||||
// (see C srcPixel match logic).
|
||||
extern void iigsFloodScanRowInner(uint8_t *row, uint16_t leftX, uint16_t rightX, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, uint8_t *markBuf);
|
||||
// Per-pixel rect blit (src->dst). transparent == $FFFF means opaque
|
||||
// (always copy); else pixels with src nibble == (transparent & $0F)
|
||||
// are skipped. Dst stride is hardcoded 160 (SURFACE_BYTES_PER_ROW).
|
||||
extern void iigsBlitRectInner(uint8_t *dstRow0, uint16_t dstX, const uint8_t *srcRow0, uint16_t srcX, uint16_t copyW, uint16_t copyH, uint16_t srcRowBytes, uint16_t transparent);
|
||||
// Combined scan + push: matches each pixel, tracks run state, pushes
|
||||
// (x, scanY) to the (stackX, stackY) arrays at *spInOut on every
|
||||
// falling edge and at the end of the row if still in a run. *spInOut
|
||||
// is read on entry and updated with the new top-of-stack on return.
|
||||
extern void iigsFloodScanAndPushInner(uint8_t *row, uint16_t leftX, uint16_t rightX, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, uint16_t scanY, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||
// Single-call per-popped-seed worker: seed test + walk-left + walk-right
|
||||
// + scan-above + scan-below + push, all sharing cached row addr and
|
||||
// match decoders. Outputs to gFloodSeedMatch / gFloodLeftX / gFloodRightX.
|
||||
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||
// One-shot init for the y*160 lookup table (gRowOffsetLut, 400 bytes
|
||||
// in DRAWPRIMS data). Called once from halInit. After this returns,
|
||||
// every asm primitive that needs row offset can do `lda >lut,x` instead
|
||||
// of the 7-instruction shift-add.
|
||||
extern void iigsInitRowLut(void);
|
||||
// Filled circle, scanline-style. fillWord low byte is the doubled
|
||||
// nibble (e.g., 0x33 for nibble 3).
|
||||
extern void iigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t fillWord);
|
||||
|
||||
// ----- Hardware addresses (24-bit / long pointers) -----
|
||||
|
||||
#define IIGS_NEWVIDEO_REG ((volatile uint8_t *)0x00C029L)
|
||||
#define IIGS_BORDER_REG ((volatile uint8_t *)0x00C034L)
|
||||
#define IIGS_SHADOW_REG ((volatile uint8_t *)0x00C035L)
|
||||
#define IIGS_VBL_STATUS ((volatile uint8_t *)0x00C019L)
|
||||
#define IIGS_SHR_PIXELS ((uint8_t *)0xE12000L)
|
||||
#define IIGS_SHR_SCB ((uint8_t *)0xE19D00L)
|
||||
#define IIGS_SHR_PALETTE ((uint16_t *)0xE19E00L)
|
||||
|
||||
// The stage lives at $01/2000 -- the same offset as the SHR display
|
||||
// framebuffer at $E1/2000, but in the fast (2.8 MHz) bank. With SHR
|
||||
// shadow inhibited at $C035, writes here are NOT auto-mirrored to
|
||||
// $E1, so drawing is full-speed and isolated from the displayed
|
||||
// frame until the next stagePresent.
|
||||
#define IIGS_STAGE_PIXELS ((uint8_t *)0x012000L)
|
||||
|
||||
#define VBL_BAR_BIT 0x80
|
||||
|
||||
// NEWVIDEO bit masks
|
||||
|
|
@ -41,6 +133,15 @@
|
|||
// handler) and bumps its "Code: RED" status. Always include this bit.
|
||||
#define NEWVIDEO_RESERVED_BIT 0x01
|
||||
|
||||
// $C035 SHADOW register: bit set = shadow INHIBITED for that range.
|
||||
// Bit 1 = hi-res page 1 ($02000-$03FFF in bank $01)
|
||||
// Bit 2 = hi-res page 2 ($04000-$05FFF in bank $01)
|
||||
// Bit 3 = SHR ($02000-$09FFF in bank $01)
|
||||
// We set 1+2+3 because the SHR pixel range overlaps both hi-res
|
||||
// pages; leaving any of those shadows live would silently mirror
|
||||
// part of the stage to $E1.
|
||||
#define SHADOW_INHIBIT_SHR_MASK 0x0E
|
||||
|
||||
// $C034 BORDER register: high nibble = beep/IRQ enables (preserve),
|
||||
// low nibble = border color index 0..15. Color 0 is the all-zero
|
||||
// palette entry by IIgs convention; we force the low nibble to 0
|
||||
|
|
@ -51,6 +152,7 @@
|
|||
|
||||
static uint8_t gPreviousNewVideo = 0;
|
||||
static uint8_t gPreviousBorder = 0;
|
||||
static uint8_t gPreviousShadow = 0;
|
||||
static bool gModeSet = false;
|
||||
|
||||
// Last-uploaded SCB and palette. Both registers live in bank $E1; on a
|
||||
|
|
@ -62,6 +164,22 @@ static uint8_t gCachedScb [SURFACE_HEIGHT];
|
|||
static uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gCacheValid = false;
|
||||
|
||||
// PEI slam scratch shared with src/port/iigs/peislam.asm. File-scope
|
||||
// non-static so the asm can `ext` them; all accesses inside the slam
|
||||
// use long-mode addressing so they bypass the //e RAMRD redirect the
|
||||
// slam turns on for the duration of the run.
|
||||
volatile uint16_t gPeiOrigSp;
|
||||
volatile uint8_t gPeiOrigShadow;
|
||||
volatile uint16_t gPeiTempRowBase;
|
||||
|
||||
// Defined in src/port/iigs/peislam.asm, in its own load segment
|
||||
// (DRAWPRIMS) so the GS/OS loader places it in a different bank from
|
||||
// AUDIO's _ROOT. PEI-slams the full 80 words of stage row `y` into
|
||||
// the matching $E1 SHR row, ~530 cyc/row vs ~1120 cyc for memcpy/MVN.
|
||||
extern void peiSlamFullRow(int16_t y);
|
||||
|
||||
|
||||
|
||||
// Upload SCB and palette into bank-$E1 SHR memory only when they have
|
||||
// changed since the last call. paletteOrScbChanged returns false when
|
||||
// the cache is already in sync, in which case both memcpys to $E1 are
|
||||
|
|
@ -86,8 +204,18 @@ bool halInit(const JoeyConfigT *config) {
|
|||
(void)config;
|
||||
gPreviousNewVideo = *IIGS_NEWVIDEO_REG;
|
||||
gPreviousBorder = *IIGS_BORDER_REG;
|
||||
gPreviousShadow = *IIGS_SHADOW_REG;
|
||||
*IIGS_NEWVIDEO_REG = (uint8_t)(NEWVIDEO_SHR_ON | NEWVIDEO_LINEARIZE | NEWVIDEO_RESERVED_BIT);
|
||||
*IIGS_BORDER_REG = (uint8_t)(gPreviousBorder & BORDER_COLOR_MASK);
|
||||
// Inhibit shadowing of the stage region. Without this, every
|
||||
// write to $01/2000-9FFF mirrors to $E1 and the off-screen-buffer
|
||||
// illusion breaks (the user would see drawing in progress).
|
||||
*IIGS_SHADOW_REG = (uint8_t)(gPreviousShadow | SHADOW_INHIBIT_SHR_MASK);
|
||||
// SCB and palette are uploaded by halPresent's iigsBlitStageToShr
|
||||
// (asm path, MVN to bank $E1). C-side memset/memcpy to bank $E1
|
||||
// is unreliable from halInit's calling context, so we don't try
|
||||
// it here -- the first present will set up SCB to 320 mode.
|
||||
iigsInitRowLut();
|
||||
gModeSet = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -102,8 +230,13 @@ void halPresent(const SurfaceT *src) {
|
|||
if (src == NULL) {
|
||||
return;
|
||||
}
|
||||
uploadScbAndPaletteIfNeeded(src);
|
||||
memcpy(IIGS_SHR_PIXELS, src->pixels, SURFACE_PIXELS_SIZE);
|
||||
// iigsBlitStageToShr does pixels (MVN $01->$E1) + SCB + palette
|
||||
// upload entirely in asm via DBR=$E1 + sta abs,Y indexed stores.
|
||||
// ORCA-C's C-side memcpy to bank $E1 has been unreliable from
|
||||
// halPresent's calling context, so we route everything through
|
||||
// the asm path. Future: re-introduce per-row dirty-band logic
|
||||
// for partial-screen updates (currently we always blit 32K).
|
||||
iigsBlitStageToShr(src->scb, &src->palette[0][0]);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -134,6 +267,270 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
if (gModeSet) {
|
||||
*IIGS_NEWVIDEO_REG = gPreviousNewVideo;
|
||||
*IIGS_BORDER_REG = gPreviousBorder;
|
||||
*IIGS_SHADOW_REG = gPreviousShadow;
|
||||
gModeSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool halFastSurfaceClear(SurfaceT *s, uint8_t doubled) {
|
||||
uint16_t fillWord;
|
||||
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (s != stageGet()) {
|
||||
return false;
|
||||
}
|
||||
fillWord = (uint16_t)((uint16_t)doubled | ((uint16_t)doubled << 8));
|
||||
iigsSurfaceClearInner(s->pixels, fillWord);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
int16_t pxStart;
|
||||
int16_t pxEnd;
|
||||
int16_t midStart;
|
||||
int16_t midBytes;
|
||||
int16_t trailingByte;
|
||||
int16_t leadingByte;
|
||||
bool hasLeading;
|
||||
bool hasTrailing;
|
||||
int16_t row;
|
||||
uint8_t *line;
|
||||
uint16_t fillWord;
|
||||
uint8_t nibble;
|
||||
uint8_t doubled;
|
||||
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (s != stageGet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pxStart = x;
|
||||
pxEnd = (int16_t)(x + (int16_t)w);
|
||||
leadingByte = (int16_t)(pxStart >> 1);
|
||||
hasLeading = (pxStart & 1) != 0;
|
||||
if (hasLeading) {
|
||||
pxStart++;
|
||||
}
|
||||
midStart = (int16_t)(pxStart >> 1);
|
||||
midBytes = (int16_t)((pxEnd - pxStart) >> 1);
|
||||
hasTrailing = ((pxEnd - pxStart) & 1) != 0;
|
||||
trailingByte = (int16_t)(midStart + midBytes);
|
||||
|
||||
if (midBytes <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nibble = (uint8_t)(colorIndex & 0x0F);
|
||||
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||
|
||||
if (hasLeading || hasTrailing) {
|
||||
for (row = 0; row < (int16_t)h; row++) {
|
||||
line = &s->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||
if (hasLeading) {
|
||||
line[leadingByte] = (uint8_t)((line[leadingByte] & 0xF0) | nibble);
|
||||
}
|
||||
if (hasTrailing) {
|
||||
line[trailingByte] = (uint8_t)((line[trailingByte] & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fillWord = (uint16_t)((uint16_t)doubled | ((uint16_t)doubled << 8));
|
||||
line = &s->pixels[y * SURFACE_BYTES_PER_ROW + midStart];
|
||||
iigsFillRectStageInner(line, (uint16_t)midBytes, h, fillWord);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
iigsTileCopyInner(dstRow0, srcRow0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
iigsTileCopyMaskedInner(dstRow0, srcRow0, (uint16_t)transparent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
iigsTilePasteInner(dstRow0, srcTilePixels);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
iigsTileSnapInner(dstTilePixels, srcRow0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool halFastDrawPixel(SurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsDrawPixelInner(s->pixels, x, y, (uint16_t)(colorIndex & 0x0F));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(SurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsDrawLineInner(s->pixels,
|
||||
(uint16_t)x0, (uint16_t)y0,
|
||||
(uint16_t)x1, (uint16_t)y1,
|
||||
(uint16_t)(colorIndex & 0x0F));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsDrawCircleInner(s->pixels,
|
||||
(uint16_t)cx, (uint16_t)cy, r,
|
||||
(uint16_t)(colorIndex & 0x0F));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(SurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
uint16_t fillWord;
|
||||
uint8_t nibble;
|
||||
uint8_t doubled;
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (s != stageGet()) {
|
||||
return false;
|
||||
}
|
||||
nibble = (uint8_t)(colorIndex & 0x0F);
|
||||
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||
fillWord = (uint16_t)((uint16_t)doubled | ((uint16_t)doubled << 8));
|
||||
iigsFillCircleInner(s->pixels, (uint16_t)cx, (uint16_t)cy, r, fillWord);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
if (row == NULL || seedMatched == NULL || leftXOut == NULL || rightXOut == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsFloodWalkInner(row, (uint16_t)startX,
|
||||
(uint16_t)(matchColor & 0x0F),
|
||||
(uint16_t)(newColor & 0x0F),
|
||||
(uint16_t)(matchEqual ? 1 : 0));
|
||||
*seedMatched = (gFloodSeedMatch != 0);
|
||||
if (*seedMatched) {
|
||||
*leftXOut = (int16_t)gFloodLeftX;
|
||||
*rightXOut = (int16_t)gFloodRightX;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
if (row == NULL || markBuf == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsFloodScanRowInner(row, (uint16_t)leftX, (uint16_t)rightX,
|
||||
(uint16_t)(matchColor & 0x0F),
|
||||
(uint16_t)(newColor & 0x0F),
|
||||
(uint16_t)(matchEqual ? 1 : 0),
|
||||
markBuf);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
if (row == NULL || stackX == NULL || stackY == NULL || spInOut == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsFloodScanAndPushInner(row,
|
||||
(uint16_t)leftX, (uint16_t)rightX,
|
||||
(uint16_t)(matchColor & 0x0F),
|
||||
(uint16_t)(newColor & 0x0F),
|
||||
(uint16_t)(matchEqual ? 1 : 0),
|
||||
(uint16_t)scanY,
|
||||
stackX, stackY,
|
||||
(uint16_t *)spInOut,
|
||||
(uint16_t)maxSp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
if (pixels == NULL || stackX == NULL || stackY == NULL || spInOut == NULL || seedMatched == NULL || leftXOut == NULL || rightXOut == NULL) {
|
||||
return false;
|
||||
}
|
||||
iigsFloodWalkAndScansInner(pixels,
|
||||
(uint16_t)x, (uint16_t)y,
|
||||
(uint16_t)(matchColor & 0x0F),
|
||||
(uint16_t)(newColor & 0x0F),
|
||||
(uint16_t)(matchEqual ? 1 : 0),
|
||||
stackX, stackY,
|
||||
(uint16_t *)spInOut,
|
||||
(uint16_t)maxSp);
|
||||
*seedMatched = (gFloodSeedMatch != 0);
|
||||
*leftXOut = (int16_t)gFloodLeftX;
|
||||
*rightXOut = (int16_t)gFloodRightX;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastBlitRect(uint8_t *dstRow0, int16_t dstX, const uint8_t *srcRow0, int16_t srcX, int16_t copyW, int16_t copyH, int16_t srcRowBytes, uint16_t transparent) {
|
||||
if (dstRow0 == NULL || srcRow0 == NULL || copyW <= 0 || copyH <= 0) {
|
||||
return false;
|
||||
}
|
||||
iigsBlitRectInner(dstRow0, (uint16_t)dstX,
|
||||
srcRow0, (uint16_t)srcX,
|
||||
(uint16_t)copyW, (uint16_t)copyH,
|
||||
(uint16_t)srcRowBytes,
|
||||
transparent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileFill(SurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
uint8_t *row;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
|
||||
if (s == NULL) {
|
||||
return false;
|
||||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * 8u);
|
||||
pixelY = (uint16_t)((uint16_t)by * 8u);
|
||||
row = &s->pixels[pixelY * SURFACE_BYTES_PER_ROW + (pixelX >> 1)];
|
||||
iigsTileFillInner(row, fillWord);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
return IIGS_STAGE_PIXELS;
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
(void)pixels;
|
||||
// Backing memory is hardware-pinned; nothing to free.
|
||||
}
|
||||
|
||||
|
||||
// $C019 RDVBLBAR: bit 7 = 0 during vertical blank, 1 during active
|
||||
// scan. To produce a rising-edge wait (one VBL per call), first spin
|
||||
// while VBL is currently active (bit 7 = 0), then spin until VBL
|
||||
|
|
@ -146,12 +543,3 @@ void halWaitVBL(void) {
|
|||
/* scanning: wait for next VBL */;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
if (gModeSet) {
|
||||
*IIGS_NEWVIDEO_REG = gPreviousNewVideo;
|
||||
*IIGS_BORDER_REG = gPreviousBorder;
|
||||
gModeSet = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3865
src/port/iigs/joeyDraw.asm
Normal file
3865
src/port/iigs/joeyDraw.asm
Normal file
File diff suppressed because it is too large
Load diff
76
src/port/iigs/peislam.asm
Normal file
76
src/port/iigs/peislam.asm
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
* peislam.asm - PEI-slam stage row to bank-$E1 SHR.
|
||||
*
|
||||
* Implements the //e AUXWRITE + RAMRD + SHR-shadow trick that lets
|
||||
* 65816 stack pushes (which are bank-$00-implicit) end up in bank
|
||||
* $E1 SHR display memory:
|
||||
*
|
||||
* - SHR shadow temporarily ENABLED (clear $C035 bit 3) so writes
|
||||
* to bank-$01 in $2000-$9FFF mirror to $E1 SHR.
|
||||
* - AUXWRITE on (any write to $C005) so bank-$00 stack writes
|
||||
* redirect to bank $01, then mirror to $E1 via shadow.
|
||||
* - RAMRD on (any write to $C003) so PEI dp's bank-$00-implicit
|
||||
* reads redirect to bank $01 = the stage source.
|
||||
* - SEI for the duration: stack pointer is hijacked to point at
|
||||
* $E1-mapped stack space, soft-switch state would corrupt any
|
||||
* C code that tried to access bank-$00 globals.
|
||||
*
|
||||
* All scratch reads/writes within the slam use long-mode `>name`
|
||||
* addressing (24-bit, explicit bank) so they bypass RAMRD redirect
|
||||
* and reach the actual bank-$00 global storage.
|
||||
*
|
||||
* Calling convention: ORCA-C memory model 1 (large model, JSL/RTL).
|
||||
* void peiSlamFullRow(int16_t y);
|
||||
* - Caller PHAs y (2 bytes) before JSL.
|
||||
* - JSL pushes 3-byte return address.
|
||||
* - On entry: y_LO at SP+4, y_HI at SP+5 (SP points one below PCL).
|
||||
* - Function preserves DBR; returns via RTL with original SP.
|
||||
* - Caller pops the y arg after RTL.
|
||||
*
|
||||
* Per call: ~50 cyc bracket + 80 PEIs * 6 cyc = ~530 cyc, vs the
|
||||
* memcpy/MVN fallback's 7 cyc/byte * 160 bytes = ~1120 cyc.
|
||||
|
||||
keep PEISLAM
|
||||
case on
|
||||
|
||||
* The operand to START names the LOAD segment this object segment
|
||||
* belongs to (per ORCA/M for IIgs manual, ch. 6 "Load Segments").
|
||||
* Object segments without an operand land in the unnamed "blank
|
||||
* segment" -- which on AUDIO is _ROOT, the very segment whose 64 KB
|
||||
* budget peislam.asm was busting. Naming a load segment forces the
|
||||
* linker to put us in our own segment, which the GS/OS loader then
|
||||
* allocates in its own bank.
|
||||
peiSlamFullRow start IIGSASM
|
||||
* MVN-based row copy. Replaces the PEI-stack-slam approach (which
|
||||
* needs RAMRD/AUXWRITE/SHADOW soft-switches and is sensitive to
|
||||
* DRAWDATA bank placement). MVN copies 160 bytes from the bank-$01
|
||||
* stage row to the matching bank-$E1 SHR row at ~7 cyc/byte; that's
|
||||
* slower than PEI-slam but rock-solid.
|
||||
*
|
||||
* Args after PHP: y (int16) at SP+5..6. Compute rowOffset = $2000
|
||||
* + y*160. MVN $01,$E1 with X=Y=rowOffset, A=159 copies 160 bytes
|
||||
* from $01:rowOffset to $E1:rowOffset.
|
||||
php
|
||||
rep #$30 ; M=16, X=16
|
||||
|
||||
lda 5,s ; y
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a ; A = y << 5 = y*32
|
||||
sta >gPeiTempRowBase
|
||||
asl a
|
||||
asl a ; A = y << 7 = y*128
|
||||
clc
|
||||
adc >gPeiTempRowBase ; A = y*160
|
||||
clc
|
||||
adc #$2000 ; A = $2000 + y*160 = row offset
|
||||
|
||||
tax ; X = source offset (bank $01)
|
||||
tay ; Y = dest offset (bank $E1)
|
||||
lda #159 ; count - 1 (MVN copies count+1 = 160 bytes)
|
||||
mvn $01,$E1
|
||||
|
||||
plp
|
||||
rtl
|
||||
end
|
||||
|
|
@ -1071,6 +1071,7 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
|
||||
install_gsplus() {
|
||||
local base="${SCRIPT_DIR}/emulators/gsplus"
|
||||
local bin="${base}/bin/gsplus"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue