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);
|
surfaceClear(screen, COLOR_BG);
|
||||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
|
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H,
|
||||||
audioOk ? COLOR_HINT : COLOR_BG);
|
audioOk ? COLOR_HINT : COLOR_BG);
|
||||||
surfacePresent(screen);
|
stagePresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,9 +128,9 @@ int main(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen = surfaceGetScreen();
|
screen = stageGet();
|
||||||
if (screen == NULL) {
|
if (screen == NULL) {
|
||||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
fprintf(stderr, "stageGet returned NULL\n");
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -171,11 +171,11 @@ int main(void) {
|
||||||
|
|
||||||
if (flashFrames > 0) {
|
if (flashFrames > 0) {
|
||||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_BAR);
|
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--;
|
flashFrames--;
|
||||||
if (flashFrames == 0) {
|
if (flashFrames == 0) {
|
||||||
fillRect(screen, BAR_X, BAR_Y, BAR_W, BAR_H, COLOR_HINT);
|
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) {
|
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);
|
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].valid = false;
|
||||||
gView[i].connected = false;
|
gView[i].connected = false;
|
||||||
}
|
}
|
||||||
surfacePresent(screen);
|
stagePresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -226,9 +226,9 @@ int main(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen = surfaceGetScreen();
|
screen = stageGet();
|
||||||
if (screen == NULL) {
|
if (screen == NULL) {
|
||||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
fprintf(stderr, "stageGet returned NULL\n");
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ static void initialPaint(SurfaceT *screen) {
|
||||||
gCellLit[row][col] = false;
|
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);
|
drawCell(screen, col, row, lit);
|
||||||
x = (int16_t)(MARGIN_X + col * (CELL_W + GAP));
|
x = (int16_t)(MARGIN_X + col * (CELL_W + GAP));
|
||||||
y = (int16_t)(MARGIN_Y + row * (CELL_H + 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;
|
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 (gLastCursorX != mouseX || gLastCursorY != mouseY) {
|
||||||
if (gLastCursorCol != CELL_NONE) {
|
if (gLastCursorCol != CELL_NONE) {
|
||||||
drawCell(screen, gLastCursorCol, gLastCursorRow, gCellLit[gLastCursorRow][gLastCursorCol]);
|
drawCell(screen, gLastCursorCol, gLastCursorRow, gCellLit[gLastCursorRow][gLastCursorCol]);
|
||||||
surfacePresentRect(screen,
|
stagePresentRect(
|
||||||
(int16_t)(MARGIN_X + gLastCursorCol * (CELL_W + GAP)),
|
(int16_t)(MARGIN_X + gLastCursorCol * (CELL_W + GAP)),
|
||||||
(int16_t)(MARGIN_Y + gLastCursorRow * (CELL_H + GAP)),
|
(int16_t)(MARGIN_Y + gLastCursorRow * (CELL_H + GAP)),
|
||||||
CELL_W, CELL_H);
|
CELL_W, CELL_H);
|
||||||
} else if (gLastCursorX >= 0 && gLastCursorY >= 0) {
|
} else if (gLastCursorX >= 0 && gLastCursorY >= 0) {
|
||||||
// Old cursor was in a gap region. Stamp background over it.
|
// Old cursor was in a gap region. Stamp background over it.
|
||||||
fillRect(screen, gLastCursorX, gLastCursorY, CURSOR_W, CURSOR_H, COLOR_BACKGROUND);
|
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);
|
drawCursor(screen, mouseX, mouseY);
|
||||||
surfacePresentRect(screen, mouseX, mouseY, CURSOR_W, CURSOR_H);
|
stagePresentRect(mouseX, mouseY, CURSOR_W, CURSOR_H);
|
||||||
|
|
||||||
gLastCursorX = mouseX;
|
gLastCursorX = mouseX;
|
||||||
gLastCursorY = mouseY;
|
gLastCursorY = mouseY;
|
||||||
|
|
@ -233,9 +233,9 @@ int main(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen = surfaceGetScreen();
|
screen = stageGet();
|
||||||
if (screen == NULL) {
|
if (screen == NULL) {
|
||||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
fprintf(stderr, "stageGet returned NULL\n");
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
// library contract, so the leftmost stripe is black in every band.
|
// library contract, so the leftmost stripe is black in every band.
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include <joey/joey.h>
|
#include <joey/joey.h>
|
||||||
|
|
||||||
|
|
@ -16,13 +15,11 @@
|
||||||
#define BAND_HEIGHT (SURFACE_HEIGHT / BAND_COUNT)
|
#define BAND_HEIGHT (SURFACE_HEIGHT / BAND_COUNT)
|
||||||
#define STRIPE_COUNT 16
|
#define STRIPE_COUNT 16
|
||||||
#define STRIPE_WIDTH (SURFACE_WIDTH / STRIPE_COUNT)
|
#define STRIPE_WIDTH (SURFACE_WIDTH / STRIPE_COUNT)
|
||||||
#define DISPLAY_SECONDS 5
|
|
||||||
|
|
||||||
static void buildPalettes(SurfaceT *screen);
|
static void buildPalettes(SurfaceT *screen);
|
||||||
static void buildScbs(SurfaceT *screen);
|
static void buildScbs(SurfaceT *screen);
|
||||||
static void drawStripes(SurfaceT *screen);
|
static void drawStripes(SurfaceT *screen);
|
||||||
static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn);
|
static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn);
|
||||||
static void waitSeconds(int seconds);
|
|
||||||
|
|
||||||
|
|
||||||
static void buildPalettes(SurfaceT *screen) {
|
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) {
|
int main(void) {
|
||||||
|
|
@ -128,9 +116,9 @@ int main(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen = surfaceGetScreen();
|
screen = stageGet();
|
||||||
if (screen == NULL) {
|
if (screen == NULL) {
|
||||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
fprintf(stderr, "stageGet returned NULL\n");
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -138,9 +126,9 @@ int main(void) {
|
||||||
buildPalettes(screen);
|
buildPalettes(screen);
|
||||||
buildScbs(screen);
|
buildScbs(screen);
|
||||||
drawStripes(screen);
|
drawStripes(screen);
|
||||||
surfacePresent(screen);
|
stagePresent();
|
||||||
|
|
||||||
waitSeconds(DISPLAY_SECONDS);
|
joeyWaitForAnyKey();
|
||||||
|
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
#define BALL_TILES_X (BALL_W / 8)
|
#define BALL_TILES_X (BALL_W / 8)
|
||||||
#define BALL_TILES_Y (BALL_H / 8)
|
#define BALL_TILES_Y (BALL_H / 8)
|
||||||
|
|
||||||
#define TILE_BYTES 32
|
|
||||||
#define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES)
|
#define BALL_TILE_BYTES (BALL_TILES_X * BALL_TILES_Y * TILE_BYTES)
|
||||||
// SaveUnder must store rounded-up byte boundaries: x rounded down to
|
// SaveUnder must store rounded-up byte boundaries: x rounded down to
|
||||||
// even, width rounded up to even. Worst case for BALL_W=16 (already
|
// even, width rounded up to even. Worst case for BALL_W=16 (already
|
||||||
|
|
@ -122,9 +121,9 @@ int main(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen = surfaceGetScreen();
|
screen = stageGet();
|
||||||
if (screen == NULL) {
|
if (screen == NULL) {
|
||||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
fprintf(stderr, "stageGet returned NULL\n");
|
||||||
joeyShutdown();
|
joeyShutdown();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +144,7 @@ int main(void) {
|
||||||
buildPalette(screen);
|
buildPalette(screen);
|
||||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
||||||
surfaceClear(screen, COLOR_BG);
|
surfaceClear(screen, COLOR_BG);
|
||||||
surfacePresent(screen);
|
stagePresent();
|
||||||
|
|
||||||
backup.bytes = gBallBackup;
|
backup.bytes = gBallBackup;
|
||||||
|
|
||||||
|
|
@ -157,7 +156,7 @@ int main(void) {
|
||||||
|
|
||||||
spriteSaveUnder(screen, ball, x, y, &backup);
|
spriteSaveUnder(screen, ball, x, y, &backup);
|
||||||
spriteDraw(screen, ball, x, y);
|
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;
|
haveBackup = true;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
@ -168,7 +167,7 @@ int main(void) {
|
||||||
|
|
||||||
// Stash the prior ball's region before restoring the bytes
|
// Stash the prior ball's region before restoring the bytes
|
||||||
// under it. Do all off-screen work (restore + move + draw)
|
// 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
|
// old and new regions. Putting waitVBL immediately before the
|
||||||
// present lets the present land inside the VBL window so the
|
// present lets the present land inside the VBL window so the
|
||||||
// CRT never sees a half-updated framebuffer (matters most on
|
// CRT never sees a half-updated framebuffer (matters most on
|
||||||
|
|
@ -206,7 +205,7 @@ int main(void) {
|
||||||
: (backup.y + backup.height));
|
: (backup.y + backup.height));
|
||||||
|
|
||||||
joeyWaitVBL();
|
joeyWaitVBL();
|
||||||
surfacePresentRect(screen, unionX, unionY,
|
stagePresentRect(unionX, unionY,
|
||||||
(uint16_t)(unionRight - unionX),
|
(uint16_t)(unionRight - unionX),
|
||||||
(uint16_t)(unionBottom - unionY));
|
(uint16_t)(unionBottom - unionY));
|
||||||
haveBackup = true;
|
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.
|
// Read a pixel value. Off-surface coordinates return 0.
|
||||||
uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y);
|
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.
|
// 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);
|
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
|
// Blit an asset onto the surface at (x, y). Source nibbles overwrite
|
||||||
// destination nibbles verbatim -- the caller is responsible for
|
// destination nibbles verbatim -- the caller is responsible for
|
||||||
// matching the asset's palette to the destination palette (typically
|
// matching the asset's palette to the destination palette (typically
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,12 @@ typedef enum {
|
||||||
|
|
||||||
void joeyInputPoll(void);
|
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 joeyKeyDown(JoeyKeyE key);
|
||||||
bool joeyKeyPressed(JoeyKeyE key);
|
bool joeyKeyPressed(JoeyKeyE key);
|
||||||
bool joeyKeyReleased(JoeyKeyE key);
|
bool joeyKeyReleased(JoeyKeyE key);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "palette.h"
|
#include "palette.h"
|
||||||
#include "asset.h"
|
#include "asset.h"
|
||||||
#include "draw.h"
|
#include "draw.h"
|
||||||
|
#include "tile.h"
|
||||||
#include "present.h"
|
#include "present.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,20 @@
|
||||||
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
||||||
#endif
|
#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 -----
|
// ----- Library version -----
|
||||||
|
|
||||||
#define JOEYLIB_VERSION_MAJOR 1
|
#define JOEYLIB_VERSION_MAJOR 1
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
// Present / slam.
|
// Stage present.
|
||||||
//
|
//
|
||||||
// surfacePresent copies pixels, SCBs, and palettes from a source
|
// stagePresent flips the library-owned stage (back-buffer) to the
|
||||||
// surface to the visible display. On chunky platforms (IIgs, DOS) this
|
// display. On chunky platforms (IIgs, DOS) this is a direct copy; on
|
||||||
// is a direct copy; on planar platforms (Amiga, Atari ST) this is a
|
// planar platforms (Amiga, Atari ST) this is a chunky-to-planar
|
||||||
// chunky-to-planar conversion. See docs/DESIGN.md section 7.
|
// 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
|
#ifndef JOEYLIB_PRESENT_H
|
||||||
#define JOEYLIB_PRESENT_H
|
#define JOEYLIB_PRESENT_H
|
||||||
|
|
@ -12,12 +14,15 @@
|
||||||
#include "surface.h"
|
#include "surface.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
// Present the entire source surface to the display.
|
// Flip the dirty regions of the stage to the display, then clear the
|
||||||
void surfacePresent(const SurfaceT *src);
|
// 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.
|
// Flip a specific rectangular region of the stage to the display,
|
||||||
// The rect is clipped to the surface. Negative or zero dimensions are
|
// regardless of dirty state. Coordinates are clipped to the surface;
|
||||||
// no-ops.
|
// negative or zero dimensions are no-ops. Does not consult or modify
|
||||||
void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
// 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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,15 @@ typedef struct SurfaceT SurfaceT;
|
||||||
SurfaceT *surfaceCreate(void);
|
SurfaceT *surfaceCreate(void);
|
||||||
|
|
||||||
// Release an offscreen surface previously returned by surfaceCreate.
|
// 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);
|
void surfaceDestroy(SurfaceT *s);
|
||||||
|
|
||||||
// The library's pre-allocated screen surface. This is the surface the
|
// The library-owned stage: the back-buffer surface that stagePresent
|
||||||
// library presents to the display. Always valid between joeyInit and
|
// flips to the display. Always valid between joeyInit and joeyShutdown.
|
||||||
// joeyShutdown.
|
// On IIgs the stage's pixel buffer is pinned to bank $01 SHR space with
|
||||||
SurfaceT *surfaceGetScreen(void);
|
// 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
|
// Copy pixels, SCBs, and palettes from src into dst. Both must be valid
|
||||||
// surfaces.
|
// 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
|
HELLO_BIN := $(BINDIR)/Hello
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/Pattern
|
PATTERN_BIN := $(BINDIR)/Pattern
|
||||||
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
|
DRAW_BIN := $(BINDIR)/Draw
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/Keys
|
KEYS_BIN := $(BINDIR)/Keys
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
|
|
@ -76,7 +78,7 @@ DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
.PHONY: all amiga clean-amiga
|
.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
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -118,6 +120,10 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
$(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)
|
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||||
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
|
DRAW_BIN := $(BINDIR)/DRAW.PRG
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
|
|
@ -61,7 +63,7 @@ DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
.PHONY: all atarist clean-atarist
|
.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
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -110,6 +112,10 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
$(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)
|
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
$(ST_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@ $(LDFLAGS)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||||
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
|
DRAW_BIN := $(BINDIR)/DRAW.EXE
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
|
|
@ -54,7 +56,7 @@ DATA_DIR := $(BINDIR)/DATA
|
||||||
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
DATA_FILES := $(DATA_DIR)/test.mod $(DATA_DIR)/test.sfx
|
||||||
|
|
||||||
.PHONY: all dos clean-dos
|
.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
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -94,6 +96,11 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
||||||
$(DOS_EMBED_DPMI) $@
|
$(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)
|
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(DOS_CC) $(CFLAGS) $< $(LIB) $(LIBXMP_AR) -o $@
|
$(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
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
PORT_C_SRCS_ALL := $(wildcard $(SRC_PORT)/iigs/*.c)
|
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
|
# audio_full.c declares its functions in the AUDIOIMPL load segment
|
||||||
# real implementation (NewHandle / fopen / JSL trampoline) and links
|
# (`segment "AUDIOIMPL"` at file scope, see ORCA/C ch. 30) so the
|
||||||
# only into AUDIO -- the IIgs build is monolithic, so pulling Memory
|
# implementation code lives in its own bank, not _ROOT. That lets
|
||||||
# Manager + ORCA stdio into every binary blows the linker's
|
# the same source link into every binary, replacing the earlier
|
||||||
# "Expression too complex" budget. The two files define the same
|
# audio.c-stub vs audio_full.c-real split. The 34 KB NTP replayer
|
||||||
# halAudio* symbols; iigs/audio.c is filtered out of the AUDIO source
|
# bytes still ride along via the xxd-baked header.
|
||||||
# set, audio_full.c is filtered out of the everyone-else set.
|
PORT_C_SRCS := $(PORT_C_SRCS_ALL)
|
||||||
PORT_C_SRCS := $(filter-out %/audio_full.c, $(PORT_C_SRCS_ALL))
|
|
||||||
PORT_C_SRCS_AUDIO := $(filter-out %/audio.c, $(PORT_C_SRCS_ALL))
|
|
||||||
|
|
||||||
# IIgs uses NTPstreamsound for SFX, not the libxmp+overlay combo that
|
# IIgs uses NTPstreamsound for SFX, not the libxmp+overlay combo that
|
||||||
# DOS and ST share, so src/core/audioSfxMix.c is unused here. Filter
|
# 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 \
|
CODEGEN_SRCS := $(REPO_DIR)/src/codegen/spriteEmitIigs.c \
|
||||||
$(REPO_DIR)/src/codegen/spriteCompile.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
|
# NinjaTrackerPlus replayer. Assembled with Merlin32 from the staged
|
||||||
# source at toolchains/iigs/ntp/ninjatrackerplus.s. Output is a 34 KB
|
# source at toolchains/iigs/ntp/ninjatrackerplus.s. Output is a 34 KB
|
||||||
# raw 65816 binary that the IIgs audio HAL loads at runtime via
|
# 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`.
|
# load address even though it was assembled with `org $0F0000`.
|
||||||
NTP_SRC := $(REPO_DIR)/toolchains/iigs/ntp/ninjatrackerplus.s
|
NTP_SRC := $(REPO_DIR)/toolchains/iigs/ntp/ninjatrackerplus.s
|
||||||
NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
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
|
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_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
HELLO_BIN := $(BINDIR)/HELLO
|
HELLO_BIN := $(BINDIR)/HELLO
|
||||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
PATTERN_BIN := $(BINDIR)/PATTERN
|
PATTERN_BIN := $(BINDIR)/PATTERN
|
||||||
|
DRAW_SRC := $(EXAMPLES)/draw/draw.c
|
||||||
|
DRAW_BIN := $(BINDIR)/DRAW
|
||||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||||
KEYS_BIN := $(BINDIR)/KEYS
|
KEYS_BIN := $(BINDIR)/KEYS
|
||||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||||
|
|
@ -77,7 +82,11 @@ IIX_INCLUDES := \
|
||||||
-I $(REPO_DIR)/src/codegen
|
-I $(REPO_DIR)/src/codegen
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk clean-iigs
|
.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)
|
$(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
@ -85,51 +94,55 @@ $(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
||||||
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
cd $(BUILD)/audio && $(IIGS_MERLIN) . ninjatrackerplus.s
|
||||||
mv $(BUILD)/audio/ntpplayer $@
|
mv $(BUILD)/audio/ntpplayer $@
|
||||||
|
|
||||||
# Bake the NTP replayer bytes into a C header so audio_full.c can link
|
# Bake the NTP replayer bytes into an ORCA-M asm file. The asm declares
|
||||||
# the player into the AUDIO binary instead of fopen'ing a separate
|
# the bytes in a `data NTPDATA` segment; ORCA's linker groups same-
|
||||||
# NTPPLAYER.BIN at runtime. NTP is bank-internal / PIC, so the linked
|
# name object segments into one load segment, and the GS/OS loader
|
||||||
# bytes still BlockMove cleanly into the Memory Manager handle the HAL
|
# places it in its own bank. Net effect: the 34 KB of NTP bytes don't
|
||||||
# allocates. Same xxd-i pattern as test_assets.h.
|
# crowd _ROOT in any binary, so audio_full.c can link into every demo
|
||||||
$(NTP_HEADER): $(NTP_BIN)
|
# (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 $@)
|
@mkdir -p $(dir $@)
|
||||||
@echo "// Generated by make/iigs.mk -- NinjaTrackerPlus replayer bytes." > $@
|
$(REPO_DIR)/toolchains/iigs/bin-to-asm.sh $(NTP_BIN) $@ NTPDATA gNtpPlayerBytes gNtpPlayerBytes_len
|
||||||
@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" >> $@
|
|
||||||
|
|
||||||
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
||||||
# #pragma noroot). The example source supplies main(); libjoey sources
|
# #pragma noroot). The example source supplies main(); libjoey sources
|
||||||
# are the extras. The chtyp post-step tags the output as GS/OS S16
|
# 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
|
# ($B3) so GS/OS recognizes it as launchable; the file-type lives in
|
||||||
# a user.com.apple.FinderInfo xattr that iix and profuse preserve.
|
# 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 $@)
|
@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 $@
|
$(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 $@)
|
@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 $@
|
$(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 $@)
|
@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 $@
|
$(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 $@)
|
@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 $@
|
$(IIGS_IIX) chtyp -t S16 $@
|
||||||
|
|
||||||
# Sprite demo uses ORCA-C large memory model (-b) so pointers are
|
$(JOY_BIN): $(JOY_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||||
# 32-bit and the codegen-arena JSL stub can call cross-bank into the
|
@mkdir -p $(dir $@)
|
||||||
# arena. Without -b, ORCA-C's 16-bit pointers would lose the bank
|
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(JOY_SRC) $(LIB_SRCS)
|
||||||
# byte and the stub would JSL into bank 0 (system memory) -> crash.
|
$(IIGS_IIX) chtyp -t S16 $@
|
||||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
|
||||||
|
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB_SRCS) $(NTP_ASM) $(IIGS_BUILD)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(SPRITE_SRC) $(LIB_SRCS)
|
$(IIGS_BUILD) -b $(IIX_INCLUDES) -o $@ $(SPRITE_SRC) $(LIB_SRCS)
|
||||||
$(IIGS_IIX) chtyp -t S16 $@
|
$(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)
|
AUDIO_DATA_FILES := $(AUDIO_SFX)
|
||||||
endif
|
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 $@)
|
@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 $@
|
$(IIGS_IIX) chtyp -t S16 $@
|
||||||
|
|
||||||
# Assemble an 800KB ProDOS 2img containing the examples, ready to
|
# Assemble an 800KB ProDOS 2img containing the examples, ready to
|
||||||
# mount in GSplus alongside a GS/OS boot volume.
|
# mount in GSplus alongside a GS/OS boot volume.
|
||||||
iigs-disk: $(DISK_IMG)
|
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 $@)
|
@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:
|
clean-iigs:
|
||||||
rm -rf $(BUILD)
|
rm -rf $(BUILD)
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,31 @@
|
||||||
#
|
#
|
||||||
# scripts/run-amiga.sh # runs Pattern
|
# scripts/run-amiga.sh # runs Pattern
|
||||||
# scripts/run-amiga.sh hello # runs Hello
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
echo "usage: $0 [example-name]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
prog=${1:-pattern}
|
prog=${1:-pattern}
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
bin_dir=$repo/build/amiga/bin
|
bin_dir=$repo/build/amiga/bin
|
||||||
support=$repo/toolchains/emulators/support
|
support=$repo/toolchains/emulators/support
|
||||||
|
|
||||||
case $prog in
|
prog_lower=${prog,,}
|
||||||
hello) file=Hello ;;
|
file=${prog_lower^}
|
||||||
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
|
|
||||||
|
|
||||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
echo "$bin_dir/$file not built. Run 'make amiga' first." >&2
|
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
|
exit 1
|
||||||
fi
|
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
|
trap 'mkdir -p "$dump_keep"; cp "$work"/*.txt "$dump_keep"/ 2>/dev/null; rm -rf "$work"' EXIT
|
||||||
|
|
||||||
mkdir -p "$work/s"
|
mkdir -p "$work/s"
|
||||||
cp "$bin_dir/Hello" "$work/" 2>/dev/null || true
|
# Stage every built binary (executable file at top of bin_dir, no
|
||||||
cp "$bin_dir/Pattern" "$work/" 2>/dev/null || true
|
# extension on Amiga). DATA/ is copied separately below.
|
||||||
cp "$bin_dir/Keys" "$work/" 2>/dev/null || true
|
find "$bin_dir" -maxdepth 1 -type f -executable -exec cp -t "$work/" {} +
|
||||||
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 the DATA folder (test.mod, test.sfx) the audio demo loads from
|
# Stage the DATA folder (test.mod, test.sfx) the audio demo loads from
|
||||||
# the boot volume at runtime.
|
# the boot volume at runtime.
|
||||||
if [[ -d "$bin_dir/DATA" ]]; then
|
if [[ -d "$bin_dir/DATA" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -6,28 +6,31 @@
|
||||||
#
|
#
|
||||||
# scripts/run-atarist.sh # runs PATTERN.PRG
|
# scripts/run-atarist.sh # runs PATTERN.PRG
|
||||||
# scripts/run-atarist.sh hello # runs HELLO.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
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
echo "usage: $0 [example-name]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
prog=${1:-pattern}
|
prog=${1:-pattern}
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
bin_dir=$repo/build/atarist/bin
|
bin_dir=$repo/build/atarist/bin
|
||||||
|
file=${prog^^}.PRG
|
||||||
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
|
|
||||||
|
|
||||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||||
|
|
||||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
echo "$bin_dir/$file not built. Run 'make atarist' first." >&2
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ ! -f $tos ]]; then
|
if [[ ! -f $tos ]]; then
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,29 @@
|
||||||
#
|
#
|
||||||
# scripts/run-dos.sh # runs PATTERN
|
# scripts/run-dos.sh # runs PATTERN
|
||||||
# scripts/run-dos.sh hello # runs HELLO
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
echo "usage: $0 [example-name]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
prog=${1:-pattern}
|
prog=${1:-pattern}
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
bin_dir=$repo/build/dos/bin
|
bin_dir=$repo/build/dos/bin
|
||||||
|
file=${prog^^}.EXE
|
||||||
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
|
|
||||||
|
|
||||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
echo "$bin_dir/$file not built. Run 'make dos' first." >&2
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,28 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
echo "usage: $0 [example-name]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
prog=${1:-pattern}
|
prog=${1:-pattern}
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
|
||||||
case $prog in
|
bin_dir=$repo/build/iigs/bin
|
||||||
hello|pattern|keys|joy|sprite|audio) ;;
|
target=${prog^^}
|
||||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
if [[ ! -f "$bin_dir/$target" ]]; then
|
||||||
esac
|
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
|
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
|
for f in "$sys_disk" "$data_disk"; do
|
||||||
if [[ ! -f $f ]]; then
|
if [[ ! -f $f ]]; then
|
||||||
|
|
@ -42,24 +54,30 @@ mkdir -p "$out"
|
||||||
cp "$sys_disk" "$work/boot.po"
|
cp "$sys_disk" "$work/boot.po"
|
||||||
cp "$data_disk" "$work/joey.2mg"
|
cp "$data_disk" "$work/joey.2mg"
|
||||||
|
|
||||||
# Lua script: on every CPU stop (BRK, breakpoint, watchpoint, manual
|
# Lua script: drives Finder via natural keyboard + macadb key fields
|
||||||
# halt), append a state snapshot to crash.txt. This way we don't need
|
# to launch the requested example, dumps register state on any halt
|
||||||
# the user to type anything at the debugger window -- whatever halts
|
# (BRK, breakpoint, watchpoint, manual stop). Field names for keys
|
||||||
# the CPU lands a record in crash.txt.
|
# come from MAME's apple2gs macadb input definitions:
|
||||||
cat > "$work/crash-hook.lua" <<'LUA'
|
# :macadb:KEY0 -> "d D"
|
||||||
-- Crash diagnostics for IIgs demos. Auto-resumes the initial debug
|
# :macadb:KEY1 -> "o O"
|
||||||
-- pause so the user doesn't need to type "go". On any subsequent halt
|
# :macadb:KEY2 -> "j J", "p P"
|
||||||
-- (BRK, watchpoint, breakpoint) outside ROM, dumps registers + bytes
|
# :macadb:KEY3 -> "Command / Open Apple"
|
||||||
-- around PC to crash.txt. ROM halts (PB == 0xFE/0xFF) are skipped so
|
# Letters not on KEY0..2 fall back to natkeyboard:post() (which
|
||||||
-- we don't fill the file with normal IIgs ROM stack walking.
|
# 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 cpu = manager.machine.devices[":maincpu"]
|
||||||
local prog = cpu.spaces["program"]
|
local prog = cpu.spaces["program"]
|
||||||
local outpath = "/tmp/mame-iigs/crash.txt"
|
local outpath = "/tmp/mame-iigs/crash.txt"
|
||||||
|
|
||||||
local function in_rom(pb)
|
|
||||||
return pb == 0xFE or pb == 0xFF
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dump(label)
|
local function dump(label)
|
||||||
local f = io.open(outpath, "a")
|
local f = io.open(outpath, "a")
|
||||||
if f == nil then return end
|
if f == nil then return end
|
||||||
|
|
@ -75,16 +93,19 @@ local function dump(label)
|
||||||
f:write(string.format(" %02X", b))
|
f:write(string.format(" %02X", b))
|
||||||
end
|
end
|
||||||
f:write("\n")
|
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()
|
f:close()
|
||||||
end
|
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 done_marker = "/tmp/mame-iigs/.done"
|
||||||
local started = false
|
local started = false
|
||||||
local crashed = false
|
local crashed = false
|
||||||
local boot_frames = 0
|
local boot_frames = 0
|
||||||
|
|
||||||
local function signal_done(reason)
|
local function signal_done(reason)
|
||||||
local f = io.open(done_marker, "w")
|
local f = io.open(done_marker, "w")
|
||||||
if f ~= nil then
|
if f ~= nil then
|
||||||
|
|
@ -93,13 +114,86 @@ local function signal_done(reason)
|
||||||
end
|
end
|
||||||
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()
|
emu.register_periodic(function()
|
||||||
local dbg = manager.machine.debugger
|
local dbg = manager.machine.debugger
|
||||||
if dbg == nil then return end
|
if dbg == nil then return end
|
||||||
if dbg.execution_state == "stop" then
|
if dbg.execution_state == "stop" then
|
||||||
if not started then
|
if not started then
|
||||||
-- First halt is the -debug startup pause; auto-resume so
|
|
||||||
-- emulation begins without manual input.
|
|
||||||
started = true
|
started = true
|
||||||
dbg.execution_state = "run"
|
dbg.execution_state = "run"
|
||||||
return
|
return
|
||||||
|
|
@ -111,10 +205,12 @@ emu.register_periodic(function()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
boot_frames = boot_frames + 1
|
boot_frames = boot_frames + 1
|
||||||
-- Watchdog: ~30 wall-sec at 60 Hz. If nothing crashes by
|
check_ckpt()
|
||||||
-- then, dump current state (likely the demo running fine)
|
while step_idx <= #steps and boot_frames >= steps[step_idx][1] do
|
||||||
-- and tell the launcher to shut down so we can grab joeylog.
|
steps[step_idx][2]()
|
||||||
if boot_frames > 1800 and not crashed then
|
step_idx = step_idx + 1
|
||||||
|
end
|
||||||
|
if boot_frames > 18000 and not crashed then
|
||||||
crashed = true
|
crashed = true
|
||||||
dump("watchdog")
|
dump("watchdog")
|
||||||
signal_done("watchdog")
|
signal_done("watchdog")
|
||||||
|
|
@ -130,7 +226,8 @@ cat <<EOF
|
||||||
MAME apple2gs (auto-launch ${prog^^}):
|
MAME apple2gs (auto-launch ${prog^^}):
|
||||||
Boot disk: $work/boot.po (flop3)
|
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:
|
On crash the MAME debugger halts and Lua dumps state to:
|
||||||
$out/crash.txt
|
$out/crash.txt
|
||||||
|
|
||||||
|
|
@ -146,6 +243,11 @@ cleanup() {
|
||||||
if [[ -f $work/debug.log ]]; then
|
if [[ -f $work/debug.log ]]; then
|
||||||
mv -f "$work/debug.log" "$out/debug.log"
|
mv -f "$work/debug.log" "$out/debug.log"
|
||||||
fi
|
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 profuse=$repo/toolchains/iigs/gg-tools/bin/profuse
|
||||||
local mnt=$work/_mnt
|
local mnt=$work/_mnt
|
||||||
if [[ -x $profuse && -f $work/joey.2mg ]]; then
|
if [[ -x $profuse && -f $work/joey.2mg ]]; then
|
||||||
|
|
@ -167,11 +269,11 @@ cleanup() {
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Headless by default (-video none). Set MAME_WINDOW=1 to get a real
|
# Visible by default. Set MAME_HEADLESS=1 to suppress the video window
|
||||||
# emulator window for interactive use.
|
# (CI / batch runs that only care about crash.txt).
|
||||||
video_arg="-video none"
|
video_arg="-window"
|
||||||
if [[ "${MAME_WINDOW:-0}" = "1" ]]; then
|
if [[ "${MAME_HEADLESS:-0}" = "1" ]]; then
|
||||||
video_arg="-window"
|
video_arg="-video none"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clear the done-marker the Lua hook uses to signal shutdown.
|
# Clear the done-marker the Lua hook uses to signal shutdown.
|
||||||
|
|
@ -182,13 +284,14 @@ mame apple2gs \
|
||||||
-flop3 "$work/boot.po" \
|
-flop3 "$work/boot.po" \
|
||||||
-flop4 "$work/joey.2mg" \
|
-flop4 "$work/joey.2mg" \
|
||||||
$video_arg -sound none \
|
$video_arg -sound none \
|
||||||
|
-nothrottle \
|
||||||
-debug -debuglog \
|
-debug -debuglog \
|
||||||
-autoboot_script "$work/crash-hook.lua" &
|
-autoboot_script "$work/crash-hook.lua" &
|
||||||
mame_pid=$!
|
mame_pid=$!
|
||||||
|
|
||||||
# Poll for the done-marker. Kill MAME once Lua signals it. Cap total
|
# 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.
|
# wall-clock at 6 min so we don't outlive the Lua-side watchdog (5 min).
|
||||||
deadline=$((SECONDS + 60))
|
deadline=$((SECONDS + 360))
|
||||||
while kill -0 "$mame_pid" 2>/dev/null; do
|
while kill -0 "$mame_pid" 2>/dev/null; do
|
||||||
if [[ -f $out/.done ]]; then
|
if [[ -f $out/.done ]]; then
|
||||||
kill "$mame_pid" 2>/dev/null
|
kill "$mame_pid" 2>/dev/null
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,18 @@
|
||||||
#
|
#
|
||||||
# scripts/run-iigs.sh # boots (Pattern hint)
|
# scripts/run-iigs.sh # boots (Pattern hint)
|
||||||
# scripts/run-iigs.sh hello # boots, hints HELLO
|
# scripts/run-iigs.sh hello # boots, hints HELLO
|
||||||
# scripts/run-iigs.sh keys # boots, hints KEYS
|
# scripts/run-iigs.sh draw # boots, hints DRAW
|
||||||
# scripts/run-iigs.sh joy # boots, hints JOY
|
#
|
||||||
# scripts/run-iigs.sh sprite # boots, hints SPRITE
|
# Argument is any built example name (case-insensitive); upper-case
|
||||||
# scripts/run-iigs.sh audio # boots, hints AUDIO
|
# it for the Finder hint and existence-check.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -gt 1 ]]; then
|
||||||
|
echo "usage: $0 [example-name]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
prog=${1:-pattern}
|
prog=${1:-pattern}
|
||||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
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
|
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||||
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
||||||
|
|
||||||
case $prog in
|
target=${prog^^}
|
||||||
hello|pattern|keys|joy|sprite|audio) ;;
|
bin_dir=$repo/build/iigs/bin
|
||||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite|audio]" >&2; exit 2 ;;
|
if [[ ! -f "$bin_dir/$target" ]]; then
|
||||||
esac
|
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
|
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
|
||||||
if [[ ! -f $f ]]; then
|
if [[ ! -f $f ]]; then
|
||||||
|
|
@ -107,7 +119,6 @@ cp "$data_disk" "$work/joey.2mg"
|
||||||
# install_support_iigs_null_c600.
|
# install_support_iigs_null_c600.
|
||||||
cp "$null_c600" "$work/c600.rom"
|
cp "$null_c600" "$work/c600.rom"
|
||||||
|
|
||||||
target=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
GSplus launching GS/OS 6.0.4.
|
GSplus launching GS/OS 6.0.4.
|
||||||
Once Finder is up:
|
Once Finder is up:
|
||||||
|
|
|
||||||
483
src/core/draw.c
483
src/core/draw.c
|
|
@ -8,13 +8,31 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "joey/draw.h"
|
#include "joey/draw.h"
|
||||||
|
#include "joey/debug.h"
|
||||||
|
#include "hal.h"
|
||||||
#include "surfaceInternal.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 -----
|
// ----- 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 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 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 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 uint8_t srcPixel(const uint8_t *row, int16_t x);
|
||||||
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble);
|
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) {
|
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble) {
|
||||||
uint8_t *byte;
|
uint8_t *byte;
|
||||||
|
|
||||||
|
|
@ -142,6 +361,131 @@ static uint8_t srcPixel(const uint8_t *row, int16_t x) {
|
||||||
|
|
||||||
// ----- Public API (alphabetical) -----
|
// ----- 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) {
|
void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||||
uint8_t *byte;
|
uint8_t *byte;
|
||||||
uint8_t nibble;
|
uint8_t nibble;
|
||||||
|
|
@ -153,6 +497,7 @@ void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!halFastDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex)) {
|
||||||
byte = &s->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
byte = &s->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
||||||
nibble = colorIndex & 0x0F;
|
nibble = colorIndex & 0x0F;
|
||||||
if (x & 1) {
|
if (x & 1) {
|
||||||
|
|
@ -160,6 +505,83 @@ void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||||
} else {
|
} else {
|
||||||
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
*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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -182,7 +604,53 @@ void fillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!halFastFillRect(s, sx, sy, (uint16_t)sw, (uint16_t)sh, colorIndex)) {
|
||||||
fillRectClipped(s, sx, sy, sw, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -225,6 +693,10 @@ void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
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++) {
|
for (row = 0; row < copyH; row++) {
|
||||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||||
|
|
@ -233,6 +705,8 @@ void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y) {
|
||||||
dstPixel(dstRow, x + col, nibble);
|
dstPixel(dstRow, x + col, nibble);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
surfaceMarkDirtyRect(dst, x, y, copyW, copyH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -259,6 +733,10 @@ void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t
|
||||||
|
|
||||||
transparent = (uint8_t)(transparentIndex & 0x0F);
|
transparent = (uint8_t)(transparentIndex & 0x0F);
|
||||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
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++) {
|
for (row = 0; row < copyH; row++) {
|
||||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||||
|
|
@ -270,6 +748,8 @@ void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t
|
||||||
dstPixel(dstRow, x + col, nibble);
|
dstPixel(dstRow, x + col, nibble);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
surfaceMarkDirtyRect(dst, x, y, copyW, copyH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -282,5 +762,8 @@ void surfaceClear(SurfaceT *s, uint8_t colorIndex) {
|
||||||
}
|
}
|
||||||
nibble = colorIndex & 0x0F;
|
nibble = colorIndex & 0x0F;
|
||||||
doubled = (uint8_t)((nibble << 4) | nibble);
|
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||||
|
if (!halFastSurfaceClear(s, doubled)) {
|
||||||
memset(s->pixels, doubled, SURFACE_PIXELS_SIZE);
|
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.
|
// Per-port teardown. Restores display mode, frees HW-adjacent buffers.
|
||||||
void halShutdown(void);
|
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.
|
// Present the entire source surface to the display.
|
||||||
void halPresent(const SurfaceT *src);
|
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 halAudioStopSfx(uint8_t slot);
|
||||||
void halAudioFrameTick(void);
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -57,23 +57,26 @@ bool joeyInit(const JoeyConfigT *config) {
|
||||||
|
|
||||||
memcpy(&gConfig, config, sizeof(gConfig));
|
memcpy(&gConfig, config, sizeof(gConfig));
|
||||||
|
|
||||||
if (!surfaceAllocScreen()) {
|
// halInit must run before stageAlloc: on IIgs the stage's pixel
|
||||||
setError("failed to allocate screen surface");
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!codegenArenaInit(gConfig.codegenBytes != 0 ? gConfig.codegenBytes
|
if (!codegenArenaInit(gConfig.codegenBytes != 0 ? gConfig.codegenBytes
|
||||||
: DEFAULT_CODEGEN_BYTES)) {
|
: DEFAULT_CODEGEN_BYTES)) {
|
||||||
setError("failed to allocate codegen arena");
|
setError("failed to allocate codegen arena");
|
||||||
surfaceFreeScreen();
|
stageFree();
|
||||||
return false;
|
halShutdown();
|
||||||
}
|
|
||||||
|
|
||||||
if (!halInit(&gConfig)) {
|
|
||||||
const char *halMsg = halLastError();
|
|
||||||
setError(halMsg != NULL ? halMsg : "halInit failed");
|
|
||||||
codegenArenaShutdown();
|
|
||||||
surfaceFreeScreen();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,9 +102,9 @@ void joeyShutdown(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
halInputShutdown();
|
halInputShutdown();
|
||||||
halShutdown();
|
|
||||||
codegenArenaShutdown();
|
codegenArenaShutdown();
|
||||||
surfaceFreeScreen();
|
stageFree();
|
||||||
|
halShutdown();
|
||||||
gInitialized = false;
|
gInitialized = false;
|
||||||
clearError();
|
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) {
|
bool joeyKeyDown(JoeyKeyE key) {
|
||||||
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,40 @@
|
||||||
// Present / slam dispatcher.
|
// Stage present dispatcher.
|
||||||
//
|
//
|
||||||
// Validates and clips the source rectangle, then routes to the port's
|
// stagePresent walks the per-row dirty bands set by drawing primitives
|
||||||
// HAL implementation for the actual pixel format conversion and
|
// and asks the port HAL to flip just those rows to the display, then
|
||||||
// display-memory write.
|
// resets the dirty state. stagePresentRect bypasses dirty tracking
|
||||||
|
// entirely and slams a caller-specified rectangle (after clipping).
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "joey/debug.h"
|
||||||
#include "joey/present.h"
|
#include "joey/present.h"
|
||||||
#include "hal.h"
|
#include "hal.h"
|
||||||
#include "surfaceInternal.h"
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
// ----- Public API (alphabetical) -----
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
void surfacePresent(const SurfaceT *src) {
|
void stagePresent(void) {
|
||||||
if (src == NULL) {
|
SurfaceT *stage;
|
||||||
|
|
||||||
|
stage = stageGet();
|
||||||
|
if (stage == NULL) {
|
||||||
return;
|
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 sx;
|
||||||
int16_t sy;
|
int16_t sy;
|
||||||
int16_t sw;
|
int16_t sw;
|
||||||
int16_t sh;
|
int16_t sh;
|
||||||
|
|
||||||
if (src == NULL) {
|
stage = stageGet();
|
||||||
|
if (stage == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,5 +67,5 @@ void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, u
|
||||||
return;
|
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);
|
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).
|
// need clip math (they walk fixed offsets).
|
||||||
if (sp->slot != NULL && isFullyOnSurface(x, y, widthPx, heightPx)) {
|
if (sp->slot != NULL && isFullyOnSurface(x, y, widthPx, heightPx)) {
|
||||||
spriteCompiledDraw(s, sp, x, y);
|
spriteCompiledDraw(s, sp, x, y);
|
||||||
|
surfaceMarkDirtyRect(s, x, y, (int16_t)widthPx, (int16_t)heightPx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
spriteDrawInterpreted(s, sp, x, y);
|
spriteDrawInterpreted(s, sp, x, y);
|
||||||
|
|
@ -556,6 +558,8 @@ void spriteRestoreUnder(SurfaceT *s, const SpriteBackupT *backup) {
|
||||||
shift = (copyBytes == (int16_t)spriteBytesPerRow) ? 0 : 1;
|
shift = (copyBytes == (int16_t)spriteBytesPerRow) ? 0 : 1;
|
||||||
if (sp->routineOffsets[shift][SPRITE_OP_RESTORE] != SPRITE_NOT_COMPILED) {
|
if (sp->routineOffsets[shift][SPRITE_OP_RESTORE] != SPRITE_NOT_COMPILED) {
|
||||||
spriteCompiledRestoreUnder(s, backup);
|
spriteCompiledRestoreUnder(s, backup);
|
||||||
|
surfaceMarkDirtyRect(s, backup->x, backup->y,
|
||||||
|
(int16_t)backup->width, (int16_t)backup->height);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -568,6 +572,8 @@ void spriteRestoreUnder(SurfaceT *s, const SpriteBackupT *backup) {
|
||||||
&backup->bytes[(uint16_t)row * (uint16_t)copyBytes],
|
&backup->bytes[(uint16_t)row * (uint16_t)copyBytes],
|
||||||
(size_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
|
// Surface allocation, destruction, persistence, and the library-owned
|
||||||
// screen surface.
|
// stage (the back-buffer surface).
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "joey/surface.h"
|
#include "joey/surface.h"
|
||||||
|
#include "hal.h"
|
||||||
#include "surfaceInternal.h"
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
#define SURFACE_PALETTE_BYTES (SURFACE_PALETTE_ENTRIES * (uint32_t)sizeof(uint16_t))
|
#define SURFACE_PALETTE_BYTES (SURFACE_PALETTE_ENTRIES * (uint32_t)sizeof(uint16_t))
|
||||||
|
|
@ -19,20 +20,52 @@
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- 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) -----
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
SurfaceT *stageGet(void) {
|
||||||
|
return gStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void surfaceCopy(SurfaceT *dst, const SurfaceT *src) {
|
void surfaceCopy(SurfaceT *dst, const SurfaceT *src) {
|
||||||
if (dst == NULL || src == NULL || dst == src) {
|
if (dst == NULL || src == NULL || dst == src) {
|
||||||
return;
|
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 *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;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,18 +74,14 @@ void surfaceDestroy(SurfaceT *s) {
|
||||||
if (s == NULL) {
|
if (s == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (s == gScreen) {
|
if (s == gStage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
free(s->pixels);
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SurfaceT *surfaceGetScreen(void) {
|
|
||||||
return gScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool surfaceLoadFile(SurfaceT *dst, const char *path) {
|
bool surfaceLoadFile(SurfaceT *dst, const char *path) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
long fileSize;
|
long fileSize;
|
||||||
|
|
@ -90,6 +119,7 @@ bool surfaceLoadFile(SurfaceT *dst, const char *path) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
surfaceMarkDirtyAll(dst);
|
||||||
return true;
|
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 (s != gStage) {
|
||||||
if (gScreen != NULL) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
gScreen = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
|
||||||
return gScreen != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void surfaceFreeScreen(void) {
|
|
||||||
if (gScreen == NULL) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
free(gScreen);
|
for (row = 0; row < SURFACE_HEIGHT; row++) {
|
||||||
gScreen = NULL;
|
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"
|
#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 {
|
struct SurfaceT {
|
||||||
uint8_t pixels[SURFACE_PIXELS_SIZE];
|
uint8_t *pixels;
|
||||||
uint8_t scb[SURFACE_HEIGHT];
|
uint8_t scb[SURFACE_HEIGHT];
|
||||||
uint16_t palette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
uint16_t palette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Allocate and free the library's pre-allocated screen surface. Called
|
// 16-bit words per scanline. SHR / chunky 4bpp packed = 2 px per byte,
|
||||||
// from init.c during joeyInit / joeyShutdown.
|
// 4 px per 16-bit word. SURFACE_BYTES_PER_ROW (160) / 2 = 80 words.
|
||||||
bool surfaceAllocScreen(void);
|
// Dirty tracking grain is 16-bit words because that matches the IIgs
|
||||||
void surfaceFreeScreen(void);
|
// 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
|
#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 <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <exec/types.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
|
// Force COLOR00 to black so the overscan/border region around the
|
||||||
// 320x200 display is black until the app's palette load takes over
|
// 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
|
// do nothing -- their palette[0] writes the same COLOR00 once the
|
||||||
// first LoadRGB4 fires from uploadScbAndPalette.
|
// first LoadRGB4 fires from uploadScbAndPalette.
|
||||||
SetRGB4(&gScreen->ViewPort, 0, 0, 0, 0);
|
SetRGB4(&gScreen->ViewPort, 0, 0, 0, 0);
|
||||||
|
|
@ -453,11 +454,30 @@ const char *halLastError(void) {
|
||||||
|
|
||||||
|
|
||||||
void halPresent(const SurfaceT *src) {
|
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) {
|
if (src == NULL || gScreen == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateCopperIfNeeded(src);
|
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;
|
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 <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <mint/osbind.h>
|
#include <mint/osbind.h>
|
||||||
|
|
@ -497,11 +498,30 @@ const char *halLastError(void) {
|
||||||
|
|
||||||
|
|
||||||
void halPresent(const SurfaceT *src) {
|
void halPresent(const SurfaceT *src) {
|
||||||
|
int16_t y;
|
||||||
|
uint8_t minWord;
|
||||||
|
uint8_t maxWord;
|
||||||
|
uint16_t groupStart;
|
||||||
|
uint16_t groupEnd;
|
||||||
|
|
||||||
if (src == NULL || !gModeSet) {
|
if (src == NULL || !gModeSet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshPaletteStateIfNeeded(src);
|
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();
|
writeDiagnostics();
|
||||||
gModeSet = false;
|
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 <signal.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <dpmi.h>
|
#include <dpmi.h>
|
||||||
|
|
@ -217,13 +218,28 @@ const char *halLastError(void) {
|
||||||
|
|
||||||
void halPresent(const SurfaceT *src) {
|
void halPresent(const SurfaceT *src) {
|
||||||
int16_t y;
|
int16_t y;
|
||||||
|
uint8_t minWord;
|
||||||
|
uint8_t maxWord;
|
||||||
|
int16_t pixelX;
|
||||||
|
uint16_t pixelW;
|
||||||
|
|
||||||
if (src == NULL || gVgaMem == NULL) {
|
if (src == NULL || gVgaMem == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uploadPaletteIfNeeded(src);
|
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++) {
|
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;
|
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
|
// Apple IIgs audio HAL -- single source linked into every IIgs demo.
|
||||||
// demo via make/iigs.mk's split source set). audio.c keeps the no-op
|
// Earlier we split this into an audio.c no-op stub and an audio_full.c
|
||||||
// stub for every other demo so the monolithic IIgs link budget stays
|
// real implementation, filtering audio_full.c out of non-AUDIO source
|
||||||
// safe.
|
// 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
|
// The NinjaTrackerPlus replayer is Merlin32-assembled at build time
|
||||||
// to ntpplayer.bin and baked into this TU as gNtpPlayerBytes via the
|
// to ntpplayer.bin and baked into this TU as gNtpPlayerBytes via the
|
||||||
|
|
@ -17,7 +23,25 @@
|
||||||
|
|
||||||
#include "hal.h"
|
#include "hal.h"
|
||||||
#include "joey/audio.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 -----
|
// ----- Constants -----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,26 +10,118 @@
|
||||||
// ORCA/C must be built with 32-bit pointer mode (-w or equivalent) so
|
// ORCA/C must be built with 32-bit pointer mode (-w or equivalent) so
|
||||||
// that the long addresses resolve to bank $E1.
|
// that the long addresses resolve to bank $E1.
|
||||||
//
|
//
|
||||||
// For M1 this is a simple direct-copy present. PEI-slam (in assembly)
|
// DIRTY-WALK + PEI-SLAM PRESENT
|
||||||
// arrives as an optimization in a later milestone; the structure here
|
// -----------------------------
|
||||||
// is unchanged -- only halPresent / halPresentRect get faster inner
|
// halPresent walks the per-row dirty bands maintained by drawing
|
||||||
// loops.
|
// 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 <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/debug.h"
|
||||||
#include "hal.h"
|
#include "hal.h"
|
||||||
#include "surfaceInternal.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) -----
|
// ----- Hardware addresses (24-bit / long pointers) -----
|
||||||
|
|
||||||
#define IIGS_NEWVIDEO_REG ((volatile uint8_t *)0x00C029L)
|
#define IIGS_NEWVIDEO_REG ((volatile uint8_t *)0x00C029L)
|
||||||
#define IIGS_BORDER_REG ((volatile uint8_t *)0x00C034L)
|
#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_VBL_STATUS ((volatile uint8_t *)0x00C019L)
|
||||||
#define IIGS_SHR_PIXELS ((uint8_t *)0xE12000L)
|
#define IIGS_SHR_PIXELS ((uint8_t *)0xE12000L)
|
||||||
#define IIGS_SHR_SCB ((uint8_t *)0xE19D00L)
|
#define IIGS_SHR_SCB ((uint8_t *)0xE19D00L)
|
||||||
#define IIGS_SHR_PALETTE ((uint16_t *)0xE19E00L)
|
#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
|
#define VBL_BAR_BIT 0x80
|
||||||
|
|
||||||
// NEWVIDEO bit masks
|
// NEWVIDEO bit masks
|
||||||
|
|
@ -41,6 +133,15 @@
|
||||||
// handler) and bumps its "Code: RED" status. Always include this bit.
|
// handler) and bumps its "Code: RED" status. Always include this bit.
|
||||||
#define NEWVIDEO_RESERVED_BIT 0x01
|
#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),
|
// $C034 BORDER register: high nibble = beep/IRQ enables (preserve),
|
||||||
// low nibble = border color index 0..15. Color 0 is the all-zero
|
// 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
|
// palette entry by IIgs convention; we force the low nibble to 0
|
||||||
|
|
@ -51,6 +152,7 @@
|
||||||
|
|
||||||
static uint8_t gPreviousNewVideo = 0;
|
static uint8_t gPreviousNewVideo = 0;
|
||||||
static uint8_t gPreviousBorder = 0;
|
static uint8_t gPreviousBorder = 0;
|
||||||
|
static uint8_t gPreviousShadow = 0;
|
||||||
static bool gModeSet = false;
|
static bool gModeSet = false;
|
||||||
|
|
||||||
// Last-uploaded SCB and palette. Both registers live in bank $E1; on a
|
// 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 uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||||
static bool gCacheValid = false;
|
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
|
// Upload SCB and palette into bank-$E1 SHR memory only when they have
|
||||||
// changed since the last call. paletteOrScbChanged returns false when
|
// changed since the last call. paletteOrScbChanged returns false when
|
||||||
// the cache is already in sync, in which case both memcpys to $E1 are
|
// 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;
|
(void)config;
|
||||||
gPreviousNewVideo = *IIGS_NEWVIDEO_REG;
|
gPreviousNewVideo = *IIGS_NEWVIDEO_REG;
|
||||||
gPreviousBorder = *IIGS_BORDER_REG;
|
gPreviousBorder = *IIGS_BORDER_REG;
|
||||||
|
gPreviousShadow = *IIGS_SHADOW_REG;
|
||||||
*IIGS_NEWVIDEO_REG = (uint8_t)(NEWVIDEO_SHR_ON | NEWVIDEO_LINEARIZE | NEWVIDEO_RESERVED_BIT);
|
*IIGS_NEWVIDEO_REG = (uint8_t)(NEWVIDEO_SHR_ON | NEWVIDEO_LINEARIZE | NEWVIDEO_RESERVED_BIT);
|
||||||
*IIGS_BORDER_REG = (uint8_t)(gPreviousBorder & BORDER_COLOR_MASK);
|
*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;
|
gModeSet = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +230,13 @@ void halPresent(const SurfaceT *src) {
|
||||||
if (src == NULL) {
|
if (src == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uploadScbAndPaletteIfNeeded(src);
|
// iigsBlitStageToShr does pixels (MVN $01->$E1) + SCB + palette
|
||||||
memcpy(IIGS_SHR_PIXELS, src->pixels, SURFACE_PIXELS_SIZE);
|
// 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
|
// $C019 RDVBLBAR: bit 7 = 0 during vertical blank, 1 during active
|
||||||
// scan. To produce a rising-edge wait (one VBL per call), first spin
|
// 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
|
// while VBL is currently active (bit 7 = 0), then spin until VBL
|
||||||
|
|
@ -146,12 +543,3 @@ void halWaitVBL(void) {
|
||||||
/* scanning: wait for next VBL */;
|
/* 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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
install_gsplus() {
|
install_gsplus() {
|
||||||
local base="${SCRIPT_DIR}/emulators/gsplus"
|
local base="${SCRIPT_DIR}/emulators/gsplus"
|
||||||
local bin="${base}/bin/gsplus"
|
local bin="${base}/bin/gsplus"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue