(Untested) Joystick support. Basic sprites.
This commit is contained in:
parent
b10a7802b9
commit
3e9c7f4926
32 changed files with 1698 additions and 55 deletions
7
Makefile
7
Makefile
|
|
@ -35,14 +35,17 @@ ifeq ($(HAVE_DOS),1)
|
|||
ALL_TARGETS += dos
|
||||
endif
|
||||
|
||||
.PHONY: all iigs iigs-disk amiga atarist dos clean help status
|
||||
.PHONY: all iigs iigs-disk amiga atarist dos tools clean help status
|
||||
|
||||
all: $(ALL_TARGETS)
|
||||
all: $(ALL_TARGETS) tools
|
||||
ifeq ($(ALL_TARGETS),)
|
||||
@echo "No toolchains detected. Run ./toolchains/install.sh and source toolchains/env.sh."
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
tools:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/tools.mk
|
||||
|
||||
iigs:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk
|
||||
|
||||
|
|
|
|||
252
examples/joy/joy.c
Normal file
252
examples/joy/joy.c
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// Joystick demo: shows position and button state for both joystick
|
||||
// ports as live, redraw-on-change visualizations. Each stick gets a
|
||||
// frame with a dot indicating the current axis values, plus two
|
||||
// button indicators below. A small status patch shows whether each
|
||||
// port reports a connected stick. Press ESC to quit.
|
||||
//
|
||||
// Like the keys demo, the loop only redraws cells that changed since
|
||||
// last frame so the per-cell rect-present cost stays small.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <joey/joey.h>
|
||||
|
||||
#define COLOR_BACKGROUND 0
|
||||
#define COLOR_FRAME 1
|
||||
#define COLOR_DOT_OFF 2
|
||||
#define COLOR_DOT_ON 3
|
||||
#define COLOR_BUTTON_OFF 4
|
||||
#define COLOR_BUTTON_ON 5
|
||||
#define COLOR_CONNECTED 6
|
||||
#define COLOR_DISCONNECTED 7
|
||||
|
||||
#define FRAME_SIZE 80
|
||||
#define FRAME_INSET 2
|
||||
#define DOT_SIZE 8
|
||||
#define DOT_TRAVEL ((FRAME_SIZE - DOT_SIZE) / 2)
|
||||
|
||||
#define BUTTON_W 24
|
||||
#define BUTTON_H 16
|
||||
#define BUTTON_GAP 4
|
||||
|
||||
#define STATUS_W 80
|
||||
#define STATUS_H 4
|
||||
|
||||
#define STICK_TOP 16
|
||||
#define BUTTON_TOP (STICK_TOP + FRAME_SIZE + 6)
|
||||
#define STATUS_TOP (BUTTON_TOP + BUTTON_H + 6)
|
||||
|
||||
#define STICK0_LEFT 24
|
||||
#define STICK1_LEFT 216
|
||||
|
||||
static void buildPalette(SurfaceT *screen);
|
||||
static void drawFrame(SurfaceT *screen, int16_t left);
|
||||
static void drawAndPresent(SurfaceT *screen, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
|
||||
static int16_t dotXFor(int16_t left, int8_t ax);
|
||||
static int16_t dotYFor(int8_t ay);
|
||||
static int16_t buttonXFor(int16_t left, int idx);
|
||||
static void initialPaint(SurfaceT *screen);
|
||||
static void updateStick(SurfaceT *screen, JoeyJoystickE js, int16_t left);
|
||||
|
||||
typedef struct {
|
||||
int16_t dotX;
|
||||
int16_t dotY;
|
||||
bool btn[JOY_BUTTON_COUNT];
|
||||
bool connected;
|
||||
bool valid;
|
||||
} StickViewT;
|
||||
|
||||
static StickViewT gView[JOYSTICK_COUNT];
|
||||
|
||||
|
||||
static void buildPalette(SurfaceT *screen) {
|
||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||
colors[i] = 0x0000;
|
||||
}
|
||||
colors[COLOR_BACKGROUND] = 0x0000;
|
||||
colors[COLOR_FRAME] = 0x0444;
|
||||
colors[COLOR_DOT_OFF] = 0x0666;
|
||||
colors[COLOR_DOT_ON] = 0x00F0; // green
|
||||
colors[COLOR_BUTTON_OFF] = 0x0333;
|
||||
colors[COLOR_BUTTON_ON] = 0x0FF0; // yellow
|
||||
colors[COLOR_CONNECTED] = 0x00F0;
|
||||
colors[COLOR_DISCONNECTED] = 0x0F00; // red
|
||||
|
||||
paletteSet(screen, 0, colors);
|
||||
}
|
||||
|
||||
|
||||
static void drawAndPresent(SurfaceT *screen, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color) {
|
||||
fillRect(screen, x, y, (uint16_t)w, (uint16_t)h, color);
|
||||
surfacePresentRect(screen, x, y, (uint16_t)w, (uint16_t)h);
|
||||
}
|
||||
|
||||
|
||||
// Draw the static stick frame (just an outlined square -- four edges
|
||||
// with the inset filled background).
|
||||
static void drawFrame(SurfaceT *screen, int16_t left) {
|
||||
int16_t top;
|
||||
|
||||
top = STICK_TOP;
|
||||
fillRect(screen, left, top, FRAME_SIZE, FRAME_SIZE, COLOR_FRAME);
|
||||
fillRect(screen,
|
||||
(int16_t)(left + FRAME_INSET),
|
||||
(int16_t)(top + FRAME_INSET),
|
||||
(uint16_t)(FRAME_SIZE - 2 * FRAME_INSET),
|
||||
(uint16_t)(FRAME_SIZE - 2 * FRAME_INSET),
|
||||
COLOR_BACKGROUND);
|
||||
}
|
||||
|
||||
|
||||
static int16_t dotXFor(int16_t left, int8_t ax) {
|
||||
int32_t cx;
|
||||
int32_t off;
|
||||
|
||||
cx = left + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
||||
off = ((int32_t)ax * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
|
||||
return (int16_t)(cx + off);
|
||||
}
|
||||
|
||||
|
||||
static int16_t dotYFor(int8_t ay) {
|
||||
int32_t cy;
|
||||
int32_t off;
|
||||
|
||||
cy = STICK_TOP + FRAME_SIZE / 2 - DOT_SIZE / 2;
|
||||
off = ((int32_t)ay * DOT_TRAVEL) / JOYSTICK_AXIS_MAX;
|
||||
return (int16_t)(cy + off);
|
||||
}
|
||||
|
||||
|
||||
static int16_t buttonXFor(int16_t left, int idx) {
|
||||
int16_t centerX;
|
||||
|
||||
centerX = (int16_t)(left + FRAME_SIZE / 2);
|
||||
if (idx == 0) {
|
||||
return (int16_t)(centerX - BUTTON_W - BUTTON_GAP / 2);
|
||||
}
|
||||
return (int16_t)(centerX + BUTTON_GAP / 2);
|
||||
}
|
||||
|
||||
|
||||
static void initialPaint(SurfaceT *screen) {
|
||||
int16_t i;
|
||||
|
||||
surfaceClear(screen, COLOR_BACKGROUND);
|
||||
for (i = 0; i < JOYSTICK_COUNT; i++) {
|
||||
int16_t left;
|
||||
left = (i == 0) ? STICK0_LEFT : STICK1_LEFT;
|
||||
drawFrame(screen, left);
|
||||
gView[i].valid = false;
|
||||
gView[i].connected = false;
|
||||
}
|
||||
surfacePresent(screen);
|
||||
}
|
||||
|
||||
|
||||
// Compare current joystick state against gView and redraw / present
|
||||
// only the visual elements that changed.
|
||||
static void updateStick(SurfaceT *screen, JoeyJoystickE js, int16_t left) {
|
||||
StickViewT *v;
|
||||
int8_t ax;
|
||||
int8_t ay;
|
||||
int16_t newDotX;
|
||||
int16_t newDotY;
|
||||
bool newBtn[JOY_BUTTON_COUNT];
|
||||
bool connected;
|
||||
int16_t i;
|
||||
int16_t bx;
|
||||
int16_t by;
|
||||
|
||||
v = &gView[js];
|
||||
connected = joeyJoystickConnected(js);
|
||||
ax = joeyJoystickX(js);
|
||||
ay = joeyJoystickY(js);
|
||||
newDotX = dotXFor(left, ax);
|
||||
newDotY = dotYFor(ay);
|
||||
for (i = 0; i < JOY_BUTTON_COUNT; i++) {
|
||||
newBtn[i] = joeyJoyDown(js, (JoeyJoyButtonE)i);
|
||||
}
|
||||
|
||||
if (!v->valid || v->connected != connected) {
|
||||
drawAndPresent(screen,
|
||||
(int16_t)(left + (FRAME_SIZE - STATUS_W) / 2),
|
||||
STATUS_TOP,
|
||||
STATUS_W, STATUS_H,
|
||||
connected ? COLOR_CONNECTED : COLOR_DISCONNECTED);
|
||||
v->connected = connected;
|
||||
}
|
||||
|
||||
// Move the position dot: erase the old square (paint background),
|
||||
// then stamp the new one. If position didn't change, skip.
|
||||
if (!v->valid || v->dotX != newDotX || v->dotY != newDotY) {
|
||||
if (v->valid) {
|
||||
drawAndPresent(screen, v->dotX, v->dotY, DOT_SIZE, DOT_SIZE, COLOR_BACKGROUND);
|
||||
}
|
||||
drawAndPresent(screen, newDotX, newDotY, DOT_SIZE, DOT_SIZE,
|
||||
(ax != 0 || ay != 0) ? COLOR_DOT_ON : COLOR_DOT_OFF);
|
||||
v->dotX = newDotX;
|
||||
v->dotY = newDotY;
|
||||
} else if (!v->valid) {
|
||||
// First paint of a centered dot: still need to draw it once.
|
||||
drawAndPresent(screen, newDotX, newDotY, DOT_SIZE, DOT_SIZE, COLOR_DOT_OFF);
|
||||
}
|
||||
|
||||
// Button squares.
|
||||
by = BUTTON_TOP;
|
||||
for (i = 0; i < JOY_BUTTON_COUNT; i++) {
|
||||
if (v->valid && v->btn[i] == newBtn[i]) {
|
||||
continue;
|
||||
}
|
||||
bx = buttonXFor(left, i);
|
||||
drawAndPresent(screen, bx, by, BUTTON_W, BUTTON_H,
|
||||
newBtn[i] ? COLOR_BUTTON_ON : COLOR_BUTTON_OFF);
|
||||
v->btn[i] = newBtn[i];
|
||||
}
|
||||
|
||||
v->valid = true;
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
JoeyConfigT config;
|
||||
SurfaceT *screen;
|
||||
|
||||
config.hostMode = HOST_MODE_TAKEOVER;
|
||||
config.codegenBytes = 32 * 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 = surfaceGetScreen();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
buildPalette(screen);
|
||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
||||
initialPaint(screen);
|
||||
joeyInputPoll();
|
||||
|
||||
for (;;) {
|
||||
joeyInputPoll();
|
||||
if (joeyKeyPressed(KEY_ESCAPE)) {
|
||||
break;
|
||||
}
|
||||
updateStick(screen, JOYSTICK_0, STICK0_LEFT);
|
||||
updateStick(screen, JOYSTICK_1, STICK1_LEFT);
|
||||
}
|
||||
|
||||
joeyShutdown();
|
||||
return 0;
|
||||
}
|
||||
148
examples/sprite/sprite.c
Normal file
148
examples/sprite/sprite.c
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Sprite demo: bounces a 16x16 ball sprite around the screen using
|
||||
// surfaceBlitMasked. The ball is embedded as a `const JoeyAssetT`
|
||||
// directly in this file, so no .jas file or runtime allocation is
|
||||
// involved -- this exercises the static / embedded path. Press ESC
|
||||
// to quit.
|
||||
//
|
||||
// Each frame we redraw only the ball's old and new bounding boxes
|
||||
// (and present those two small rects), so the cost stays small even
|
||||
// with the slow 68000-class c2p in the ST and Amiga ports.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <joey/joey.h>
|
||||
|
||||
#define BALL_W 16
|
||||
#define BALL_H 16
|
||||
|
||||
#define BALL_PALETTE_IDX 0
|
||||
|
||||
#define COLOR_BG 0
|
||||
#define COLOR_TRANSPARENT 0 // first palette slot doubles as mask
|
||||
|
||||
// 16x16 ball sprite, 4bpp packed (8 bytes per row):
|
||||
// 0 = transparent (mask)
|
||||
// 2 = ball body (yellow)
|
||||
// 3 = highlight (white)
|
||||
// High nibble of each byte is the LEFT pixel.
|
||||
static const uint8_t gBallPixels[BALL_H * 8] = {
|
||||
0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, // row 0
|
||||
0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, // row 1
|
||||
0x02, 0x22, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20, // row 2
|
||||
0x02, 0x23, 0x32, 0x22, 0x22, 0x22, 0x22, 0x20, // row 3
|
||||
0x22, 0x33, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 4
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 5
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 6
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 7
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 8
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 9
|
||||
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, // row 10
|
||||
0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20, // row 11
|
||||
0x02, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x20, // row 12
|
||||
0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, // row 13
|
||||
0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, // row 14
|
||||
0x00, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0x00 // row 15
|
||||
};
|
||||
|
||||
// Build the embedded ball asset at runtime. We could declare a
|
||||
// `static const JoeyAssetT` with this same data, but ORCA/C 2.1
|
||||
// does not handle a file-scope static struct whose pointer field is
|
||||
// initialized from another static array's address (linker complains
|
||||
// of unresolved references). Building the struct in main() with a
|
||||
// local sidesteps the quirk and costs only a handful of stores.
|
||||
static void buildBallAsset(JoeyAssetT *ball) {
|
||||
uint16_t i;
|
||||
|
||||
ball->width = BALL_W;
|
||||
ball->height = BALL_H;
|
||||
ball->hasPalette = true;
|
||||
ball->pixels = gBallPixels;
|
||||
for (i = 0; i < 16; i++) {
|
||||
ball->palette[i] = 0x0000;
|
||||
}
|
||||
ball->palette[2] = 0x0FF0; // yellow body
|
||||
ball->palette[3] = 0x0FFF; // white highlight
|
||||
}
|
||||
|
||||
|
||||
static void initialPaint(SurfaceT *screen) {
|
||||
surfaceClear(screen, COLOR_BG);
|
||||
surfacePresent(screen);
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
JoeyConfigT config;
|
||||
SurfaceT *screen;
|
||||
JoeyAssetT ball;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t vx;
|
||||
int16_t vy;
|
||||
int16_t lastX;
|
||||
int16_t lastY;
|
||||
|
||||
config.hostMode = HOST_MODE_TAKEOVER;
|
||||
config.codegenBytes = 32 * 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 = surfaceGetScreen();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
buildBallAsset(&ball);
|
||||
joeyAssetApplyPalette(screen, BALL_PALETTE_IDX, &ball);
|
||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, BALL_PALETTE_IDX);
|
||||
initialPaint(screen);
|
||||
|
||||
x = 40;
|
||||
y = 30;
|
||||
vx = 2;
|
||||
vy = 1;
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
surfaceBlitMasked(screen, &ball, x, y, COLOR_TRANSPARENT);
|
||||
surfacePresentRect(screen, x, y, BALL_W, BALL_H);
|
||||
|
||||
for (;;) {
|
||||
joeyWaitVBL();
|
||||
joeyInputPoll();
|
||||
if (joeyKeyPressed(KEY_ESCAPE)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Erase old ball position by clearing its bounding rect. Any
|
||||
// pixel outside the ball that we wrote (we wrote no pixels
|
||||
// outside, since blitMasked respects transparency) stays as
|
||||
// background already.
|
||||
fillRect(screen, lastX, lastY, BALL_W, BALL_H, COLOR_BG);
|
||||
surfacePresentRect(screen, lastX, lastY, BALL_W, BALL_H);
|
||||
|
||||
x = (int16_t)(x + vx);
|
||||
y = (int16_t)(y + vy);
|
||||
if (x <= 0) { x = 0; vx = (int16_t)-vx; }
|
||||
if (x >= SURFACE_WIDTH - BALL_W) { x = SURFACE_WIDTH - BALL_W; vx = (int16_t)-vx; }
|
||||
if (y <= 0) { y = 0; vy = (int16_t)-vy; }
|
||||
if (y >= SURFACE_HEIGHT - BALL_H) { y = SURFACE_HEIGHT - BALL_H; vy = (int16_t)-vy; }
|
||||
|
||||
surfaceBlitMasked(screen, &ball, x, y, COLOR_TRANSPARENT);
|
||||
surfacePresentRect(screen, x, y, BALL_W, BALL_H);
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
joeyShutdown();
|
||||
return 0;
|
||||
}
|
||||
65
include/joey/asset.h
Normal file
65
include/joey/asset.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Sprite-style asset loading.
|
||||
//
|
||||
// A JoeyLib asset is a small bitmap with the same 4bpp packed pixel
|
||||
// format as the rest of the library, plus an optional 16-entry $0RGB
|
||||
// palette. Assets blit onto SurfaceT via surfaceBlit / surfaceBlitMasked
|
||||
// (defined in draw.h).
|
||||
//
|
||||
// Two production paths:
|
||||
//
|
||||
// 1. Embedded -- declare a `const JoeyAssetT` directly in a .c file
|
||||
// and bundle it into the executable. No allocation, no I/O. Best
|
||||
// for small game assets that always ship with the binary.
|
||||
//
|
||||
// 2. File / memory -- joeyAssetLoadFile reads a .jas blob from disk;
|
||||
// joeyAssetFromMem parses one already in RAM. Both allocate the
|
||||
// JoeyAssetT plus its pixel buffer; release with joeyAssetFree.
|
||||
//
|
||||
// .jas binary format (little-endian, byte-stream so endianness of the
|
||||
// host doesn't matter):
|
||||
//
|
||||
// offset bytes field
|
||||
// ------ ----- --------------------------------------------
|
||||
// 0 4 magic "JAS1"
|
||||
// 4 2 width in pixels
|
||||
// 6 2 height in pixels
|
||||
// 8 1 hasPalette (0 or 1)
|
||||
// 9 3 reserved (zero)
|
||||
// 12 32 palette[16] of $0RGB; valid only if hasPalette
|
||||
// 44 ... pixels: rowBytes * height where rowBytes = (width+1)/2
|
||||
|
||||
#ifndef JOEYLIB_ASSET_H
|
||||
#define JOEYLIB_ASSET_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "surface.h"
|
||||
#include "types.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
bool hasPalette;
|
||||
uint16_t palette[16];
|
||||
const uint8_t *pixels;
|
||||
} JoeyAssetT;
|
||||
|
||||
// Allocates a new asset by reading a .jas file. Returns NULL if the
|
||||
// file is missing, malformed, or too large.
|
||||
JoeyAssetT *joeyAssetLoadFile(const char *path);
|
||||
|
||||
// Parses a .jas blob already in memory. Allocates a JoeyAssetT and
|
||||
// copies the pixel run into a fresh buffer so the caller can free its
|
||||
// own copy of the bytes after the call returns.
|
||||
JoeyAssetT *joeyAssetFromMem(const uint8_t *data, uint32_t length);
|
||||
|
||||
// Releases an asset previously returned by a loader. Calling free on
|
||||
// a static / embedded JoeyAssetT is forbidden -- those structs do not
|
||||
// own their pixels and must not be passed here. NULL is OK.
|
||||
void joeyAssetFree(JoeyAssetT *asset);
|
||||
|
||||
// Copies the asset's 16-entry palette into one of the surface's
|
||||
// palette slots so that subsequent blits land on the right colors.
|
||||
// No-op if the asset has no palette.
|
||||
void joeyAssetApplyPalette(SurfaceT *dst, uint8_t paletteIndex, const JoeyAssetT *asset);
|
||||
|
||||
#endif
|
||||
|
|
@ -30,4 +30,11 @@ const char *joeyPlatformName(void);
|
|||
// Returns the library version string (e.g., "1.0.0").
|
||||
const char *joeyVersionString(void);
|
||||
|
||||
// Block the calling thread until the next display vertical blank.
|
||||
// Used to pace game loops to the display's native refresh rate
|
||||
// (~70 Hz on VGA mode 13h, ~50 or ~60 Hz on Amiga/ST PAL/NTSC, ~60 Hz
|
||||
// on IIgs SHR). Cheap on every port since the underlying mechanism is
|
||||
// always a hardware-level wait, not a software timer.
|
||||
void joeyWaitVBL(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#ifndef JOEYLIB_DRAW_H
|
||||
#define JOEYLIB_DRAW_H
|
||||
|
||||
#include "asset.h"
|
||||
#include "platform.h"
|
||||
#include "surface.h"
|
||||
#include "types.h"
|
||||
|
|
@ -25,4 +26,16 @@ uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y);
|
|||
// 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);
|
||||
|
||||
// Blit an asset onto the surface at (x, y). Source nibbles overwrite
|
||||
// destination nibbles verbatim -- the caller is responsible for
|
||||
// matching the asset's palette to the destination palette (typically
|
||||
// via joeyAssetApplyPalette). Clipped to the surface; off-surface
|
||||
// rows / columns are skipped.
|
||||
void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y);
|
||||
|
||||
// Like surfaceBlit, but source pixels equal to transparentIndex are
|
||||
// skipped, leaving the destination pixel unchanged at that position.
|
||||
// The standard convention is transparentIndex = 0.
|
||||
void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y, uint8_t transparentIndex);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
// Keyboard and mouse input polling.
|
||||
// Keyboard, mouse, and joystick input polling.
|
||||
//
|
||||
// Call joeyInputPoll() once per frame (typically right before
|
||||
// drawing) to refresh both keyboard and mouse state. After polling,
|
||||
// the joeyKey* predicates return the current state of every key:
|
||||
// drawing) to refresh keyboard, mouse, and joystick state. After
|
||||
// polling, the joeyKey* predicates return the current state of every
|
||||
// key:
|
||||
//
|
||||
// joeyKeyDown(k) -- is key k held down right now
|
||||
// joeyKeyPressed(k) -- rising edge since the previous poll
|
||||
// joeyKeyReleased(k) -- falling edge since the previous poll
|
||||
//
|
||||
// And the mouse predicates return the pointer state:
|
||||
// The mouse predicates return the pointer state:
|
||||
//
|
||||
// joeyMouseX/Y() -- pointer position in surface
|
||||
// coords (0..SURFACE_WIDTH-1,
|
||||
|
|
@ -17,6 +18,18 @@
|
|||
// joeyMousePressed(b) -- rising edge since last poll
|
||||
// joeyMouseReleased(b) -- falling edge since last poll
|
||||
//
|
||||
// The joystick predicates return per-stick state:
|
||||
//
|
||||
// joeyJoystickConnected(js) -- true if the platform reports a
|
||||
// stick on this port
|
||||
// joeyJoystickX/Y(js) -- axis values, signed -127..+127.
|
||||
// Digital sticks snap to the
|
||||
// extremes; analog sticks return
|
||||
// the raw centered value.
|
||||
// joeyJoyDown(js, b) -- is button b held on stick js
|
||||
// joeyJoyPressed(js, b) -- rising edge since last poll
|
||||
// joeyJoyReleased(js, b) -- falling edge since last poll
|
||||
//
|
||||
// Edge predicates are one-shot: they return true only in the
|
||||
// frame the transition occurred and false thereafter.
|
||||
|
||||
|
|
@ -57,6 +70,23 @@ typedef enum {
|
|||
MOUSE_BUTTON_COUNT
|
||||
} JoeyMouseButtonE;
|
||||
|
||||
typedef enum {
|
||||
JOYSTICK_0 = 0,
|
||||
JOYSTICK_1,
|
||||
|
||||
JOYSTICK_COUNT
|
||||
} JoeyJoystickE;
|
||||
|
||||
typedef enum {
|
||||
JOY_BUTTON_0 = 0,
|
||||
JOY_BUTTON_1,
|
||||
|
||||
JOY_BUTTON_COUNT
|
||||
} JoeyJoyButtonE;
|
||||
|
||||
#define JOYSTICK_AXIS_MAX 127
|
||||
#define JOYSTICK_AXIS_MIN (-127)
|
||||
|
||||
void joeyInputPoll(void);
|
||||
|
||||
bool joeyKeyDown(JoeyKeyE key);
|
||||
|
|
@ -69,4 +99,11 @@ bool joeyMouseDown(JoeyMouseButtonE button);
|
|||
bool joeyMousePressed(JoeyMouseButtonE button);
|
||||
bool joeyMouseReleased(JoeyMouseButtonE button);
|
||||
|
||||
bool joeyJoystickConnected(JoeyJoystickE js);
|
||||
int8_t joeyJoystickX(JoeyJoystickE js);
|
||||
int8_t joeyJoystickY(JoeyJoystickE js);
|
||||
bool joeyJoyDown(JoeyJoystickE js, JoeyJoyButtonE button);
|
||||
bool joeyJoyPressed(JoeyJoystickE js, JoeyJoyButtonE button);
|
||||
bool joeyJoyReleased(JoeyJoystickE js, JoeyJoyButtonE button);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "core.h"
|
||||
#include "surface.h"
|
||||
#include "palette.h"
|
||||
#include "asset.h"
|
||||
#include "draw.h"
|
||||
#include "present.h"
|
||||
#include "input.h"
|
||||
|
|
|
|||
|
|
@ -33,9 +33,13 @@ PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
|||
PATTERN_BIN := $(BINDIR)/Pattern
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/Keys
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
JOY_BIN := $(BINDIR)/Joy
|
||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||
SPRITE_BIN := $(BINDIR)/Sprite
|
||||
|
||||
.PHONY: all amiga clean-amiga
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -69,5 +73,13 @@ $(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
clean-amiga:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
|
|
@ -29,9 +29,13 @@ PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
|||
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
JOY_BIN := $(BINDIR)/JOY.PRG
|
||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||
SPRITE_BIN := $(BINDIR)/SPRITE.PRG
|
||||
|
||||
.PHONY: all atarist clean-atarist
|
||||
all atarist: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
all atarist: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -65,5 +69,13 @@ $(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
clean-atarist:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
16
make/dos.mk
16
make/dos.mk
|
|
@ -27,9 +27,13 @@ PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
|||
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
JOY_BIN := $(BINDIR)/JOY.EXE
|
||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||
SPRITE_BIN := $(BINDIR)/SPRITE.EXE
|
||||
|
||||
.PHONY: all dos clean-dos
|
||||
all dos: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
all dos: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -62,5 +66,15 @@ $(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
|||
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
clean-dos:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
20
make/iigs.mk
20
make/iigs.mk
|
|
@ -22,6 +22,10 @@ PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
|||
PATTERN_BIN := $(BINDIR)/PATTERN
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS
|
||||
JOY_SRC := $(EXAMPLES)/joy/joy.c
|
||||
JOY_BIN := $(BINDIR)/JOY
|
||||
SPRITE_SRC := $(EXAMPLES)/sprite/sprite.c
|
||||
SPRITE_BIN := $(BINDIR)/SPRITE
|
||||
DISK_IMG := $(BINDIR)/joey.2mg
|
||||
|
||||
IIGS_PACKAGE := $(REPO_DIR)/toolchains/iigs/package-disk.sh
|
||||
|
|
@ -33,7 +37,7 @@ IIX_INCLUDES := \
|
|||
-I $(SRC_CORE)
|
||||
|
||||
.PHONY: all iigs iigs-disk clean-iigs
|
||||
all iigs: $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
all iigs: $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN)
|
||||
|
||||
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
||||
# #pragma noroot). The example source supplies main(); libjoey sources
|
||||
|
|
@ -55,13 +59,23 @@ $(KEYS_BIN): $(KEYS_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
|||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(KEYS_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(JOY_BIN): $(JOY_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(JOY_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
$(SPRITE_BIN): $(SPRITE_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(SPRITE_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
# Assemble an 800KB ProDOS 2img containing the examples, ready to
|
||||
# mount in GSplus alongside a GS/OS boot volume.
|
||||
iigs-disk: $(DISK_IMG)
|
||||
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(IIGS_PACKAGE)
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN) $(IIGS_PACKAGE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(JOY_BIN) $(SPRITE_BIN)
|
||||
|
||||
clean-iigs:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
26
make/tools.mk
Normal file
26
make/tools.mk
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Host-side tools for the JoeyLib asset pipeline.
|
||||
#
|
||||
# These build with the host's default `cc` (no cross-toolchain) into
|
||||
# build/tools/. Currently:
|
||||
# joeyasset -- PPM (P6) -> .jas converter
|
||||
|
||||
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||
|
||||
BUILD_DIR := $(REPO_DIR)/build/tools
|
||||
TOOLS_DIR := $(REPO_DIR)/tools
|
||||
|
||||
HOST_CC ?= cc
|
||||
HOST_CFLAGS := -std=c99 -Wall -Wextra -O2
|
||||
|
||||
JOEYASSET_SRC := $(TOOLS_DIR)/joeyasset/joeyasset.c
|
||||
JOEYASSET_BIN := $(BUILD_DIR)/joeyasset
|
||||
|
||||
.PHONY: all tools clean-tools
|
||||
all tools: $(JOEYASSET_BIN)
|
||||
|
||||
$(JOEYASSET_BIN): $(JOEYASSET_SRC)
|
||||
@mkdir -p $(dir $@)
|
||||
$(HOST_CC) $(HOST_CFLAGS) $< -o $@
|
||||
|
||||
clean-tools:
|
||||
rm -rf $(BUILD_DIR)
|
||||
|
|
@ -30,7 +30,9 @@ case $prog in
|
|||
hello) file=Hello ;;
|
||||
pattern) file=Pattern ;;
|
||||
keys) file=Keys ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
joy) file=Joy ;;
|
||||
sprite) file=Sprite ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
|
|
@ -56,6 +58,8 @@ mkdir -p "$work/s"
|
|||
cp "$bin_dir/Hello" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Pattern" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Keys" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Joy" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Sprite" "$work/" 2>/dev/null || true
|
||||
# ':' prefix anchors to the root of the current volume; otherwise
|
||||
# AmigaDOS looks in C: and the command is not found.
|
||||
echo ":$file" > "$work/s/startup-sequence"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ case $prog in
|
|||
hello) file=HELLO.PRG ;;
|
||||
pattern) file=PATTERN.PRG ;;
|
||||
keys) file=KEYS.PRG ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
joy) file=JOY.PRG ;;
|
||||
sprite) file=SPRITE.PRG ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ case $prog in
|
|||
hello) file=HELLO.EXE ;;
|
||||
pattern) file=PATTERN.EXE ;;
|
||||
keys) file=KEYS.EXE ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
joy) file=JOY.EXE ;;
|
||||
sprite) file=SPRITE.EXE ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
# scripts/run-iigs.sh # boots (Pattern hint)
|
||||
# scripts/run-iigs.sh hello # boots, hints HELLO
|
||||
# scripts/run-iigs.sh keys # boots, hints KEYS
|
||||
# scripts/run-iigs.sh joy # boots, hints JOY
|
||||
# scripts/run-iigs.sh sprite # boots, hints SPRITE
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -25,8 +27,8 @@ data_disk=$repo/build/iigs/bin/joey.2mg
|
|||
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
||||
|
||||
case $prog in
|
||||
hello|pattern|keys) ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
hello|pattern|keys|joy|sprite) ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys|joy|sprite]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
|
||||
|
|
|
|||
158
src/core/asset.c
Normal file
158
src/core/asset.c
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// Asset loader: parses .jas blobs into JoeyAssetT, with companion
|
||||
// helpers for file I/O and palette application.
|
||||
//
|
||||
// The .jas format is little-endian byte-stream so the same binary
|
||||
// works on every target -- 6502/x86 (LE) read it directly, 68k (BE)
|
||||
// reassembles via byte-by-byte decode in jasDecodeWord.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "joey/asset.h"
|
||||
#include "joey/palette.h"
|
||||
|
||||
#define JAS_HEADER_SIZE 44
|
||||
#define JAS_PIXELS_OFFSET JAS_HEADER_SIZE
|
||||
#define JAS_PALETTE_OFFSET 12
|
||||
#define JAS_PALETTE_ENTRIES 16
|
||||
#define JAS_MAX_BYTES (1L << 24) // 16MB sanity cap on file size
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static uint16_t jasDecodeWord(const uint8_t *p);
|
||||
static bool jasMagicMatches(const uint8_t *data);
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
static uint16_t jasDecodeWord(const uint8_t *p) {
|
||||
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
||||
}
|
||||
|
||||
|
||||
static bool jasMagicMatches(const uint8_t *data) {
|
||||
return data[0] == 'J' && data[1] == 'A' && data[2] == 'S' && data[3] == '1';
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void joeyAssetApplyPalette(SurfaceT *dst, uint8_t paletteIndex, const JoeyAssetT *asset) {
|
||||
if (dst == NULL || asset == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!asset->hasPalette) {
|
||||
return;
|
||||
}
|
||||
paletteSet(dst, paletteIndex, asset->palette);
|
||||
}
|
||||
|
||||
|
||||
void joeyAssetFree(JoeyAssetT *asset) {
|
||||
if (asset == NULL) {
|
||||
return;
|
||||
}
|
||||
// The const cast is intentional: loader-owned assets carry
|
||||
// pixels we allocated. Static / embedded assets point at .rodata
|
||||
// and the contract documents that they must not be passed here.
|
||||
free((void *)asset->pixels);
|
||||
free(asset);
|
||||
}
|
||||
|
||||
|
||||
JoeyAssetT *joeyAssetFromMem(const uint8_t *data, uint32_t length) {
|
||||
JoeyAssetT *asset;
|
||||
uint8_t *pixelBuf;
|
||||
uint32_t rowBytes;
|
||||
uint32_t pixelLen;
|
||||
uint16_t i;
|
||||
|
||||
if (data == NULL || length < JAS_HEADER_SIZE) {
|
||||
return NULL;
|
||||
}
|
||||
if (!jasMagicMatches(data)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
asset = (JoeyAssetT *)malloc(sizeof(JoeyAssetT));
|
||||
if (asset == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memset(asset, 0, sizeof(*asset));
|
||||
|
||||
asset->width = jasDecodeWord(data + 4);
|
||||
asset->height = jasDecodeWord(data + 6);
|
||||
asset->hasPalette = data[8] != 0;
|
||||
for (i = 0; i < JAS_PALETTE_ENTRIES; i++) {
|
||||
asset->palette[i] = jasDecodeWord(data + JAS_PALETTE_OFFSET + (i * 2));
|
||||
}
|
||||
|
||||
if (asset->width == 0 || asset->height == 0) {
|
||||
free(asset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rowBytes = (uint32_t)((asset->width + 1) >> 1);
|
||||
pixelLen = rowBytes * (uint32_t)asset->height;
|
||||
if (length < (JAS_PIXELS_OFFSET + pixelLen)) {
|
||||
free(asset);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pixelBuf = (uint8_t *)malloc(pixelLen);
|
||||
if (pixelBuf == NULL) {
|
||||
free(asset);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(pixelBuf, data + JAS_PIXELS_OFFSET, pixelLen);
|
||||
asset->pixels = pixelBuf;
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
|
||||
JoeyAssetT *joeyAssetLoadFile(const char *path) {
|
||||
FILE *fp;
|
||||
uint8_t *buf;
|
||||
long size;
|
||||
size_t readBytes;
|
||||
JoeyAssetT *asset;
|
||||
|
||||
if (path == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(fp, 0L, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
size = ftell(fp);
|
||||
if (size <= 0 || size > JAS_MAX_BYTES) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
if (fseek(fp, 0L, SEEK_SET) != 0) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = (uint8_t *)malloc((size_t)size);
|
||||
if (buf == NULL) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
readBytes = fread(buf, 1, (size_t)size, fp);
|
||||
fclose(fp);
|
||||
if (readBytes != (size_t)size) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
asset = joeyAssetFromMem(buf, (uint32_t)size);
|
||||
free(buf);
|
||||
return asset;
|
||||
}
|
||||
135
src/core/draw.c
135
src/core/draw.c
|
|
@ -12,11 +12,50 @@
|
|||
|
||||
// ----- Prototypes -----
|
||||
|
||||
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 bool blitClip(int16_t *dstX, int16_t *dstY, int16_t *srcX, int16_t *srcY, int16_t *w, int16_t *h, int16_t srcW, int16_t srcH);
|
||||
static void clipRect(int16_t *x, int16_t *y, int16_t *w, int16_t *h, bool *outVisible);
|
||||
static void fillRectClipped(SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
|
||||
static uint8_t srcPixel(const uint8_t *row, int16_t x);
|
||||
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble);
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
// Clip a sprite blit to the destination surface. dst* is where the
|
||||
// sprite would land at full size; src* is offset into the sprite's
|
||||
// own pixel grid (incremented when clipping the left/top edges); w,h
|
||||
// are reduced to fit the visible region. Returns false if the blit
|
||||
// is entirely off-surface.
|
||||
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) {
|
||||
*srcX = 0;
|
||||
*srcY = 0;
|
||||
*w = srcW;
|
||||
*h = srcH;
|
||||
if (*w <= 0 || *h <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (*dstX < 0) {
|
||||
*srcX = -(*dstX);
|
||||
*w -= *srcX;
|
||||
*dstX = 0;
|
||||
}
|
||||
if (*dstY < 0) {
|
||||
*srcY = -(*dstY);
|
||||
*h -= *srcY;
|
||||
*dstY = 0;
|
||||
}
|
||||
if (*dstX >= SURFACE_WIDTH || *dstY >= SURFACE_HEIGHT) {
|
||||
return false;
|
||||
}
|
||||
if (*dstX + *w > SURFACE_WIDTH) {
|
||||
*w = SURFACE_WIDTH - *dstX;
|
||||
}
|
||||
if (*dstY + *h > SURFACE_HEIGHT) {
|
||||
*h = SURFACE_HEIGHT - *dstY;
|
||||
}
|
||||
return (*w > 0 && *h > 0);
|
||||
}
|
||||
|
||||
|
||||
// Clip a rectangle to the surface bounds. Outputs *outVisible = false
|
||||
// if the rect is entirely off-surface.
|
||||
static void clipRect(int16_t *x, int16_t *y, int16_t *w, int16_t *h, bool *outVisible) {
|
||||
|
|
@ -78,6 +117,29 @@ static void fillRectClipped(SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_
|
|||
}
|
||||
|
||||
|
||||
static void dstPixel(uint8_t *row, int16_t x, uint8_t nibble) {
|
||||
uint8_t *byte;
|
||||
|
||||
byte = &row[x >> 1];
|
||||
if (x & 1) {
|
||||
*byte = (uint8_t)((*byte & 0xF0) | nibble);
|
||||
} else {
|
||||
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint8_t srcPixel(const uint8_t *row, int16_t x) {
|
||||
uint8_t byte;
|
||||
|
||||
byte = row[x >> 1];
|
||||
if (x & 1) {
|
||||
return (uint8_t)(byte & 0x0F);
|
||||
}
|
||||
return (uint8_t)(byte >> 4);
|
||||
}
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||
|
|
@ -142,6 +204,75 @@ uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y) {
|
|||
}
|
||||
|
||||
|
||||
void surfaceBlit(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y) {
|
||||
int16_t srcX0;
|
||||
int16_t srcY0;
|
||||
int16_t copyW;
|
||||
int16_t copyH;
|
||||
int16_t srcRowBytes;
|
||||
int16_t row;
|
||||
int16_t col;
|
||||
const uint8_t *srcRow;
|
||||
uint8_t *dstRow;
|
||||
uint8_t nibble;
|
||||
|
||||
if (dst == NULL || src == NULL || src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!blitClip(&x, &y, &srcX0, &srcY0, ©W, ©H,
|
||||
(int16_t)src->width, (int16_t)src->height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
||||
for (row = 0; row < copyH; row++) {
|
||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||
for (col = 0; col < copyW; col++) {
|
||||
nibble = srcPixel(srcRow, srcX0 + col);
|
||||
dstPixel(dstRow, x + col, nibble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void surfaceBlitMasked(SurfaceT *dst, const JoeyAssetT *src, int16_t x, int16_t y, uint8_t transparentIndex) {
|
||||
int16_t srcX0;
|
||||
int16_t srcY0;
|
||||
int16_t copyW;
|
||||
int16_t copyH;
|
||||
int16_t srcRowBytes;
|
||||
int16_t row;
|
||||
int16_t col;
|
||||
const uint8_t *srcRow;
|
||||
uint8_t *dstRow;
|
||||
uint8_t nibble;
|
||||
uint8_t transparent;
|
||||
|
||||
if (dst == NULL || src == NULL || src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!blitClip(&x, &y, &srcX0, &srcY0, ©W, ©H,
|
||||
(int16_t)src->width, (int16_t)src->height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
transparent = (uint8_t)(transparentIndex & 0x0F);
|
||||
srcRowBytes = (int16_t)((src->width + 1) >> 1);
|
||||
for (row = 0; row < copyH; row++) {
|
||||
srcRow = &src->pixels[(srcY0 + row) * srcRowBytes];
|
||||
dstRow = &dst->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||
for (col = 0; col < copyW; col++) {
|
||||
nibble = srcPixel(srcRow, srcX0 + col);
|
||||
if (nibble == transparent) {
|
||||
continue;
|
||||
}
|
||||
dstPixel(dstRow, x + col, nibble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void surfaceClear(SurfaceT *s, uint8_t colorIndex) {
|
||||
uint8_t nibble;
|
||||
uint8_t doubled;
|
||||
|
|
|
|||
|
|
@ -45,4 +45,9 @@ void halInputInit(void);
|
|||
void halInputShutdown(void);
|
||||
void halInputPoll(void);
|
||||
|
||||
// Block until the next display vertical blank. Each port implements
|
||||
// this with whatever native wait the hardware provides (VGA $3DA,
|
||||
// graphics.library WaitTOF, XBIOS Vsync, $C019 polling).
|
||||
void halWaitVBL(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -95,3 +95,8 @@ void joeyShutdown(void) {
|
|||
const char *joeyVersionString(void) {
|
||||
return JOEYLIB_VERSION_STRING;
|
||||
}
|
||||
|
||||
|
||||
void joeyWaitVBL(void) {
|
||||
halWaitVBL();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// Public input API. Maintains current/previous state buffers for both
|
||||
// the keyboard (gKeyState/gKeyPrev) and the mouse buttons
|
||||
// (gMouseButtonState/gMouseButtonPrev). joeyInputPoll snapshots the
|
||||
// previous state, then asks the port HAL to refresh the current state;
|
||||
// the predicates derive held/edge values from those two buffers.
|
||||
// Public input API. Maintains current/previous state buffers for the
|
||||
// keyboard (gKeyState/gKeyPrev), mouse buttons (gMouseButtonState/
|
||||
// gMouseButtonPrev), and joystick buttons (gJoyButtonState/
|
||||
// gJoyButtonPrev). joeyInputPoll snapshots the previous state, then
|
||||
// asks the port HAL to refresh the current state; the predicates
|
||||
// derive held/edge values from those two buffers.
|
||||
//
|
||||
// Mouse position (gMouseX/gMouseY) is plain current state -- there is
|
||||
// no "previous position" exposed; games that want delta movement can
|
||||
// remember the previous joeyMouseX/Y themselves.
|
||||
// Mouse position and joystick axes are plain current state with no
|
||||
// edge predicates -- games that want deltas track the previous values
|
||||
// themselves.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -22,10 +23,17 @@ int16_t gMouseY = 0;
|
|||
bool gMouseButtonState[MOUSE_BUTTON_COUNT];
|
||||
bool gMouseButtonPrev [MOUSE_BUTTON_COUNT];
|
||||
|
||||
bool gJoyConnected [JOYSTICK_COUNT];
|
||||
int8_t gJoyAxisX [JOYSTICK_COUNT];
|
||||
int8_t gJoyAxisY [JOYSTICK_COUNT];
|
||||
bool gJoyButtonState[JOYSTICK_COUNT][JOY_BUTTON_COUNT];
|
||||
bool gJoyButtonPrev [JOYSTICK_COUNT][JOY_BUTTON_COUNT];
|
||||
|
||||
|
||||
void joeyInputPoll(void) {
|
||||
memcpy(gKeyPrev, gKeyState, sizeof(gKeyState));
|
||||
memcpy(gMouseButtonPrev, gMouseButtonState, sizeof(gMouseButtonState));
|
||||
memcpy(gJoyButtonPrev, gJoyButtonState, sizeof(gJoyButtonState));
|
||||
halInputPoll();
|
||||
}
|
||||
|
||||
|
|
@ -86,3 +94,60 @@ int16_t joeyMouseX(void) {
|
|||
int16_t joeyMouseY(void) {
|
||||
return gMouseY;
|
||||
}
|
||||
|
||||
|
||||
bool joeyJoystickConnected(JoeyJoystickE js) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return gJoyConnected[js];
|
||||
}
|
||||
|
||||
|
||||
int8_t joeyJoystickX(JoeyJoystickE js) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return gJoyAxisX[js];
|
||||
}
|
||||
|
||||
|
||||
int8_t joeyJoystickY(JoeyJoystickE js) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return gJoyAxisY[js];
|
||||
}
|
||||
|
||||
|
||||
bool joeyJoyDown(JoeyJoystickE js, JoeyJoyButtonE button) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return false;
|
||||
}
|
||||
if ((int)button < 0 || (int)button >= JOY_BUTTON_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return gJoyButtonState[js][button];
|
||||
}
|
||||
|
||||
|
||||
bool joeyJoyPressed(JoeyJoystickE js, JoeyJoyButtonE button) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return false;
|
||||
}
|
||||
if ((int)button < 0 || (int)button >= JOY_BUTTON_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return gJoyButtonState[js][button] && !gJoyButtonPrev[js][button];
|
||||
}
|
||||
|
||||
|
||||
bool joeyJoyReleased(JoeyJoystickE js, JoeyJoyButtonE button) {
|
||||
if ((int)js < 0 || (int)js >= JOYSTICK_COUNT) {
|
||||
return false;
|
||||
}
|
||||
if ((int)button < 0 || (int)button >= JOY_BUTTON_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return !gJoyButtonState[js][button] && gJoyButtonPrev[js][button];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// Internal input state shared between core and per-port HAL.
|
||||
//
|
||||
// Per-port halInputPoll() writes directly into gKeyState[] and the
|
||||
// gMouse* globals: set key entries to true for keys currently held,
|
||||
// store pointer position in gMouseX/gMouseY (surface-space pixels),
|
||||
// and set gMouseButtonState[] entries true for buttons currently held.
|
||||
// The core compares against the *Prev shadow each joeyInputPoll to
|
||||
// derive edge events.
|
||||
// Per-port halInputPoll() writes directly into the public state
|
||||
// globals: gKeyState[] for keyboard, gMouse* for mouse, gJoy* for
|
||||
// joysticks. The core compares against the matching *Prev shadow
|
||||
// each joeyInputPoll to derive edge events for keys/buttons. Mouse
|
||||
// position and joystick axes have no edge semantics, so no shadow.
|
||||
|
||||
#ifndef JOEYLIB_INPUT_INTERNAL_H
|
||||
#define JOEYLIB_INPUT_INTERNAL_H
|
||||
|
|
@ -21,4 +20,10 @@ extern int16_t gMouseY;
|
|||
extern bool gMouseButtonState[MOUSE_BUTTON_COUNT];
|
||||
extern bool gMouseButtonPrev [MOUSE_BUTTON_COUNT];
|
||||
|
||||
extern bool gJoyConnected[JOYSTICK_COUNT];
|
||||
extern int8_t gJoyAxisX [JOYSTICK_COUNT];
|
||||
extern int8_t gJoyAxisY [JOYSTICK_COUNT];
|
||||
extern bool gJoyButtonState[JOYSTICK_COUNT][JOY_BUTTON_COUNT];
|
||||
extern bool gJoyButtonPrev [JOYSTICK_COUNT][JOY_BUTTON_COUNT];
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -433,6 +433,14 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
}
|
||||
|
||||
|
||||
// WaitTOF() blocks the calling task until the next "top of frame"
|
||||
// VBlank interrupt -- 50 Hz on PAL, 60 Hz on NTSC. graphics.library
|
||||
// is auto-opened by libnix so no extra plumbing is needed.
|
||||
void halWaitVBL(void) {
|
||||
WaitTOF();
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
if (gScreen != NULL) {
|
||||
// CloseScreen should free attached UCopList, but be explicit
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@
|
|||
#include <intuition/intuition.h>
|
||||
#include <intuition/screens.h>
|
||||
#include <devices/inputevent.h>
|
||||
#include <libraries/lowlevel.h>
|
||||
|
||||
#include <proto/exec.h>
|
||||
#include <proto/intuition.h>
|
||||
#include <proto/lowlevel.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
|
@ -44,6 +46,7 @@ extern struct Screen *gScreen;
|
|||
// ----- Prototypes -----
|
||||
|
||||
static void drainMessages(void);
|
||||
static void pollJoysticks(void);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
|
|
@ -111,7 +114,15 @@ static const uint8_t gRawKeyToKey[AMIGA_KEY_TABLE_SIZE] = {
|
|||
[0x64] = KEY_LALT,
|
||||
};
|
||||
|
||||
static struct Window *gWindow = NULL;
|
||||
static struct Window *gWindow = NULL;
|
||||
static struct Library *gLowLevelBase = NULL;
|
||||
|
||||
// AmigaOS exposes joystick port 1 (the dedicated stick) via lowlevel
|
||||
// ReadJoyPort(1); port 0 shares the mouse port. We map our public
|
||||
// JOYSTICK_0 to Amiga port 1 so the primary stick is what retro games
|
||||
// expect.
|
||||
#define AMIGA_PORT_FOR_JS0 1
|
||||
#define AMIGA_PORT_FOR_JS1 0
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
|
|
@ -169,6 +180,50 @@ static void drainMessages(void) {
|
|||
}
|
||||
|
||||
|
||||
// Read both joystick ports via lowlevel.library. Devices that report
|
||||
// JP_TYPE_JOYSTK or JP_TYPE_GAMECTLR populate gJoy*; anything else
|
||||
// (mouse on port 0, no device, unknown) leaves the stick disconnected
|
||||
// with zeroed state.
|
||||
static void pollJoysticks(void) {
|
||||
static const int amigaPortForStick[JOYSTICK_COUNT] = {
|
||||
AMIGA_PORT_FOR_JS0,
|
||||
AMIGA_PORT_FOR_JS1
|
||||
};
|
||||
ULONG state;
|
||||
ULONG type;
|
||||
int stick;
|
||||
|
||||
if (gLowLevelBase == NULL) {
|
||||
return;
|
||||
}
|
||||
for (stick = 0; stick < JOYSTICK_COUNT; stick++) {
|
||||
state = ReadJoyPort((UWORD)amigaPortForStick[stick]);
|
||||
type = state & JP_TYPE_MASK;
|
||||
if (type != JP_TYPE_JOYSTK && type != JP_TYPE_GAMECTLR) {
|
||||
gJoyConnected[stick] = false;
|
||||
gJoyAxisX[stick] = 0;
|
||||
gJoyAxisY[stick] = 0;
|
||||
gJoyButtonState[stick][JOY_BUTTON_0] = false;
|
||||
gJoyButtonState[stick][JOY_BUTTON_1] = false;
|
||||
continue;
|
||||
}
|
||||
gJoyConnected[stick] = true;
|
||||
|
||||
gJoyAxisX[stick] = 0;
|
||||
if (state & JPF_JOY_LEFT) { gJoyAxisX[stick] = JOYSTICK_AXIS_MIN; }
|
||||
if (state & JPF_JOY_RIGHT) { gJoyAxisX[stick] = JOYSTICK_AXIS_MAX; }
|
||||
gJoyAxisY[stick] = 0;
|
||||
if (state & JPF_JOY_UP) { gJoyAxisY[stick] = JOYSTICK_AXIS_MIN; }
|
||||
if (state & JPF_JOY_DOWN) { gJoyAxisY[stick] = JOYSTICK_AXIS_MAX; }
|
||||
|
||||
// Standard 1-button stick reports JPF_BUTTON_RED only. CD32
|
||||
// game controllers also report BLUE for the second face button.
|
||||
gJoyButtonState[stick][JOY_BUTTON_0] = (state & JPF_BUTTON_RED) != 0;
|
||||
gJoyButtonState[stick][JOY_BUTTON_1] = (state & JPF_BUTTON_BLUE) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
|
|
@ -200,15 +255,25 @@ void halInputInit(void) {
|
|||
gMouseX = (int16_t)(gWindow->Width / 2);
|
||||
gMouseY = (int16_t)(gWindow->Height / 2);
|
||||
}
|
||||
|
||||
// lowlevel.library shipped with AmigaOS 2.05+. If absent (very old
|
||||
// Kickstart, or stripped AROS), the joystick API silently reports
|
||||
// disconnected sticks rather than failing init.
|
||||
gLowLevelBase = OpenLibrary((CONST_STRPTR)"lowlevel.library", 0);
|
||||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
drainMessages();
|
||||
pollJoysticks();
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
if (gLowLevelBase != NULL) {
|
||||
CloseLibrary(gLowLevelBase);
|
||||
gLowLevelBase = NULL;
|
||||
}
|
||||
if (gWindow == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -497,6 +497,13 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
}
|
||||
|
||||
|
||||
// Vsync() is XBIOS opcode 37; mintlib exposes it directly. It blocks
|
||||
// until the next 50 Hz (PAL) or 60 Hz (NTSC) vertical blank.
|
||||
void halWaitVBL(void) {
|
||||
Vsync();
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
if (!gModeSet) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -51,10 +51,26 @@
|
|||
#define MOUSE_HDR_LEFT_BTN 0x02
|
||||
#define MOUSE_HDR_RIGHT_BTN 0x01
|
||||
|
||||
// IKBD joystick byte: bit set = direction held / fire pressed.
|
||||
#define JOY_BIT_UP 0x01
|
||||
#define JOY_BIT_DOWN 0x02
|
||||
#define JOY_BIT_LEFT 0x04
|
||||
#define JOY_BIT_RIGHT 0x08
|
||||
#define JOY_BIT_FIRE 0x80
|
||||
|
||||
// Packet kinds for the ISR's small state machine.
|
||||
#define PKT_KIND_NONE 0
|
||||
#define PKT_KIND_DISCARD 1
|
||||
#define PKT_KIND_REL_MOUSE 2
|
||||
#define PKT_KIND_JOY0 3 // 0xFE -- 1 byte for ST joy port 0 (mouse port)
|
||||
#define PKT_KIND_JOY1 4 // 0xFF -- 1 byte for ST joy port 1 (dedicated)
|
||||
#define PKT_KIND_JOY_BOTH 5 // 0xFD -- 2 bytes (joy0 then joy1)
|
||||
|
||||
// Mapping from ST joystick port -> JoeyJoystickE. Port 1 is the
|
||||
// dedicated stick that retro games use; expose it as the primary
|
||||
// JOYSTICK_0. Port 0 (mouse port) becomes JOYSTICK_1.
|
||||
#define ST_PORT0_AS_JS JOYSTICK_1
|
||||
#define ST_PORT1_AS_JS JOYSTICK_0
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
|
|
@ -146,6 +162,11 @@ static volatile uint8_t gIsrMouseButtons = 0;
|
|||
static int16_t gMouseAbsX = SURFACE_WIDTH / 2;
|
||||
static int16_t gMouseAbsY = SURFACE_HEIGHT / 2;
|
||||
|
||||
// Latched joystick state, written by the IKBD ISR. The byte stays
|
||||
// unchanged between packets while a direction or fire is held, so
|
||||
// halInputPoll can simply read the latest value.
|
||||
static volatile uint8_t gIsrJoyByte[JOYSTICK_COUNT];
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
// Runs in MFP ACIA interrupt context. Reads one byte from the ACIA,
|
||||
|
|
@ -160,15 +181,35 @@ static long ikbdHandler(void) {
|
|||
byte = *ST_ACIA_DATA;
|
||||
|
||||
if (gPacketRemaining != 0) {
|
||||
if (gPacketKind == PKT_KIND_REL_MOUSE) {
|
||||
// mouse-packet payload: byte 0 = dx (signed), byte 1 = dy
|
||||
if (gMousePacketByte == 0) {
|
||||
gIsrMouseDx += (int8_t)byte;
|
||||
gMousePacketByte = 1;
|
||||
} else {
|
||||
gIsrMouseDy += (int8_t)byte;
|
||||
gMousePacketByte = 0;
|
||||
}
|
||||
switch (gPacketKind) {
|
||||
case PKT_KIND_REL_MOUSE:
|
||||
// mouse-packet payload: byte 0 = dx, byte 1 = dy
|
||||
if (gMousePacketByte == 0) {
|
||||
gIsrMouseDx += (int8_t)byte;
|
||||
gMousePacketByte = 1;
|
||||
} else {
|
||||
gIsrMouseDy += (int8_t)byte;
|
||||
gMousePacketByte = 0;
|
||||
}
|
||||
break;
|
||||
case PKT_KIND_JOY0:
|
||||
gIsrJoyByte[ST_PORT0_AS_JS] = byte;
|
||||
break;
|
||||
case PKT_KIND_JOY1:
|
||||
gIsrJoyByte[ST_PORT1_AS_JS] = byte;
|
||||
break;
|
||||
case PKT_KIND_JOY_BOTH:
|
||||
// 0xFD payload is joy0 then joy1.
|
||||
if (gMousePacketByte == 0) {
|
||||
gIsrJoyByte[ST_PORT0_AS_JS] = byte;
|
||||
gMousePacketByte = 1;
|
||||
} else {
|
||||
gIsrJoyByte[ST_PORT1_AS_JS] = byte;
|
||||
gMousePacketByte = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gPacketRemaining = (uint8_t)(gPacketRemaining - 1);
|
||||
if (gPacketRemaining == 0) {
|
||||
|
|
@ -192,10 +233,15 @@ static long ikbdHandler(void) {
|
|||
break;
|
||||
case PKT_JOY_BOTH:
|
||||
gPacketRemaining = 2;
|
||||
gPacketKind = PKT_KIND_JOY_BOTH;
|
||||
break;
|
||||
case PKT_JOY0:
|
||||
gPacketRemaining = 1;
|
||||
gPacketKind = PKT_KIND_JOY0;
|
||||
break;
|
||||
case PKT_JOY1:
|
||||
gPacketRemaining = 1;
|
||||
gPacketKind = PKT_KIND_JOY1;
|
||||
break;
|
||||
default:
|
||||
if (byte >= PKT_REL_MOUSE_MIN && byte <= PKT_REL_MOUSE_MAX) {
|
||||
|
|
@ -236,9 +282,10 @@ static long restoreIkbdVector(void) {
|
|||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
memset((void *)gIsrJoyByte, 0, sizeof(gIsrJoyByte));
|
||||
|
||||
gMouseAbsX = SURFACE_WIDTH / 2;
|
||||
gMouseAbsY = SURFACE_HEIGHT / 2;
|
||||
|
|
@ -248,6 +295,11 @@ void halInputInit(void) {
|
|||
gIsrMouseDy = 0;
|
||||
gIsrMouseButtons = 0;
|
||||
|
||||
// Both ports always announce themselves on the ST -- packets
|
||||
// arrive even when no stick is plugged in (state stays at zero).
|
||||
gJoyConnected[JOYSTICK_0] = true;
|
||||
gJoyConnected[JOYSTICK_1] = true;
|
||||
|
||||
gKbdvecs = (_KBDVECS *)Kbdvbase();
|
||||
gPacketRemaining = 0;
|
||||
gPacketKind = PKT_KIND_NONE;
|
||||
|
|
@ -256,16 +308,18 @@ void halInputInit(void) {
|
|||
}
|
||||
|
||||
|
||||
// The ACIA ISR writes gIsrState (keys) and the gIsrMouse* deltas at
|
||||
// ~100 Hz max; the ~60-byte memcpy is essentially never racing a write.
|
||||
// Worst case is a single key or one mouse packet lagging one frame --
|
||||
// well under perceptible.
|
||||
// The ACIA ISR writes gIsrState (keys), gIsrMouse* deltas, and
|
||||
// gIsrJoyByte[] at ~100 Hz max; the ~60-byte memcpy is essentially
|
||||
// never racing a write. Worst case is a single key or one packet
|
||||
// lagging one frame -- well under perceptible.
|
||||
void halInputPoll(void) {
|
||||
int32_t dx;
|
||||
int32_t dy;
|
||||
int32_t newX;
|
||||
int32_t newY;
|
||||
uint8_t btn;
|
||||
uint8_t joy;
|
||||
uint16_t i;
|
||||
|
||||
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
||||
|
||||
|
|
@ -291,6 +345,20 @@ void halInputPoll(void) {
|
|||
gMouseButtonState[MOUSE_BUTTON_LEFT] = (btn & MOUSE_HDR_LEFT_BTN) != 0;
|
||||
gMouseButtonState[MOUSE_BUTTON_RIGHT] = (btn & MOUSE_HDR_RIGHT_BTN) != 0;
|
||||
gMouseButtonState[MOUSE_BUTTON_MIDDLE] = false;
|
||||
|
||||
// Decode latest joystick byte for each stick. Direction is digital
|
||||
// on the ST, so axes snap to the JOYSTICK_AXIS_* extremes.
|
||||
for (i = 0; i < JOYSTICK_COUNT; i++) {
|
||||
joy = gIsrJoyByte[i];
|
||||
gJoyAxisX[i] = 0;
|
||||
gJoyAxisY[i] = 0;
|
||||
if (joy & JOY_BIT_LEFT) { gJoyAxisX[i] = JOYSTICK_AXIS_MIN; }
|
||||
if (joy & JOY_BIT_RIGHT) { gJoyAxisX[i] = JOYSTICK_AXIS_MAX; }
|
||||
if (joy & JOY_BIT_UP) { gJoyAxisY[i] = JOYSTICK_AXIS_MIN; }
|
||||
if (joy & JOY_BIT_DOWN) { gJoyAxisY[i] = JOYSTICK_AXIS_MAX; }
|
||||
gJoyButtonState[i][JOY_BUTTON_0] = (joy & JOY_BIT_FIRE) != 0;
|
||||
gJoyButtonState[i][JOY_BUTTON_1] = false; // ST has 1 fire button
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
#define VGA_STRIDE 320u
|
||||
#define DAC_INDEX_PORT 0x3C8
|
||||
#define DAC_DATA_PORT 0x3C9
|
||||
#define VGA_INPUT_STAT_1 0x3DA
|
||||
#define VGA_VRETRACE_BIT 0x08
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
|
|
@ -231,6 +233,22 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
}
|
||||
|
||||
|
||||
// VGA mode 13h vertical refresh on a real CRT runs at ~70 Hz. We
|
||||
// detect the start of vertical retrace by polling input status
|
||||
// register 1 ($3DA) bit 3: 1 = currently in vretrace. To get a
|
||||
// rising-edge wait (so back-to-back calls each yield one frame),
|
||||
// first wait while we're already in retrace, then wait until we
|
||||
// re-enter retrace.
|
||||
void halWaitVBL(void) {
|
||||
while (inportb(VGA_INPUT_STAT_1) & VGA_VRETRACE_BIT) {
|
||||
/* spin while still in current retrace */;
|
||||
}
|
||||
while (!(inportb(VGA_INPUT_STAT_1) & VGA_VRETRACE_BIT)) {
|
||||
/* spin until next retrace begins */;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
__dpmi_regs regs;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,11 +56,34 @@
|
|||
#define MOUSE_BTN_RIGHT 0x0002
|
||||
#define MOUSE_BTN_MIDDLE 0x0004
|
||||
|
||||
// INT 15h game-port BIOS functions:
|
||||
// AH=84h, DX=0: read switches (AL bits 4-7 = buttons, 0=pressed)
|
||||
// AH=84h, DX=1: read positions (AX/BX/CX/DX = stickA X/Y, stickB X/Y)
|
||||
// The position values are raw R-C timer counts; magnitude depends on
|
||||
// the joystick's potentiometers and the host BIOS calibration. We
|
||||
// threshold around a nominal center to derive digital direction; for
|
||||
// DOSBox-mapped sticks 128/384 covers the default emulated range and
|
||||
// gives clean L/R/U/D snaps.
|
||||
#define BIOS_INT 0x15
|
||||
#define BIOS_FN_GAMEPORT 0x84
|
||||
#define BIOS_GAMEPORT_DX_SWITCHES 0x0000
|
||||
#define BIOS_GAMEPORT_DX_POS 0x0001
|
||||
#define BIOS_FLAGS_CARRY 0x0001
|
||||
#define BIOS_BTN_A1 0x10
|
||||
#define BIOS_BTN_A2 0x20
|
||||
#define BIOS_BTN_B1 0x40
|
||||
#define BIOS_BTN_B2 0x80
|
||||
#define JOY_AXIS_LO_THRESHOLD 128
|
||||
#define JOY_AXIS_HI_THRESHOLD 384
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void keyboardIsr(void);
|
||||
static bool mouseInit(void);
|
||||
static void mousePoll(void);
|
||||
static bool joystickInit(void);
|
||||
static void joystickPoll(void);
|
||||
static void keyboardIsr(void);
|
||||
static bool mouseInit(void);
|
||||
static void mousePoll(void);
|
||||
static int8_t scaleAxis(uint16_t v);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
|
|
@ -131,7 +154,8 @@ static _go32_dpmi_seginfo gNewHandler;
|
|||
static bool gHooked = false;
|
||||
static volatile bool gIsrState[KEY_COUNT];
|
||||
|
||||
static bool gMousePresent = false;
|
||||
static bool gMousePresent = false;
|
||||
static bool gJoystickPresent = false;
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
|
|
@ -192,6 +216,74 @@ static bool mouseInit(void) {
|
|||
}
|
||||
|
||||
|
||||
static int8_t scaleAxis(uint16_t v) {
|
||||
if (v < JOY_AXIS_LO_THRESHOLD) {
|
||||
return JOYSTICK_AXIS_MIN;
|
||||
}
|
||||
if (v > JOY_AXIS_HI_THRESHOLD) {
|
||||
return JOYSTICK_AXIS_MAX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Detect a game-port BIOS handler. Some BIOSes return CF=1 for "no
|
||||
// joystick port" -- we treat that as no joystick. DOSBox-Staging with
|
||||
// any host stick mapped will return CF=0.
|
||||
static bool joystickInit(void) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.h.ah = BIOS_FN_GAMEPORT;
|
||||
r.x.dx = BIOS_GAMEPORT_DX_SWITCHES;
|
||||
__dpmi_int(BIOS_INT, &r);
|
||||
if (r.x.flags & BIOS_FLAGS_CARRY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void joystickPoll(void) {
|
||||
__dpmi_regs r;
|
||||
uint8_t btn;
|
||||
|
||||
if (!gJoystickPresent) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.h.ah = BIOS_FN_GAMEPORT;
|
||||
r.x.dx = BIOS_GAMEPORT_DX_SWITCHES;
|
||||
__dpmi_int(BIOS_INT, &r);
|
||||
if (r.x.flags & BIOS_FLAGS_CARRY) {
|
||||
gJoystickPresent = false;
|
||||
return;
|
||||
}
|
||||
btn = r.h.al;
|
||||
// Active-low: bit set in AL means button is *not* pressed.
|
||||
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_0] = (btn & BIOS_BTN_A1) == 0;
|
||||
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_1] = (btn & BIOS_BTN_A2) == 0;
|
||||
gJoyButtonState[JOYSTICK_1][JOY_BUTTON_0] = (btn & BIOS_BTN_B1) == 0;
|
||||
gJoyButtonState[JOYSTICK_1][JOY_BUTTON_1] = (btn & BIOS_BTN_B2) == 0;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.h.ah = BIOS_FN_GAMEPORT;
|
||||
r.x.dx = BIOS_GAMEPORT_DX_POS;
|
||||
__dpmi_int(BIOS_INT, &r);
|
||||
if (r.x.flags & BIOS_FLAGS_CARRY) {
|
||||
return;
|
||||
}
|
||||
gJoyAxisX[JOYSTICK_0] = scaleAxis(r.x.ax);
|
||||
gJoyAxisY[JOYSTICK_0] = scaleAxis(r.x.bx);
|
||||
gJoyAxisX[JOYSTICK_1] = scaleAxis(r.x.cx);
|
||||
gJoyAxisY[JOYSTICK_1] = scaleAxis(r.x.dx);
|
||||
|
||||
gJoyConnected[JOYSTICK_0] = true;
|
||||
gJoyConnected[JOYSTICK_1] = true;
|
||||
}
|
||||
|
||||
|
||||
static void mousePoll(void) {
|
||||
__dpmi_regs r;
|
||||
uint16_t btn;
|
||||
|
|
@ -232,7 +324,8 @@ void halInputInit(void) {
|
|||
_go32_dpmi_set_protected_mode_interrupt_vector(9, &gNewHandler);
|
||||
gHooked = true;
|
||||
|
||||
gMousePresent = mouseInit();
|
||||
gMousePresent = mouseInit();
|
||||
gJoystickPresent = joystickInit();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -241,6 +334,7 @@ void halInputPoll(void) {
|
|||
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
||||
enable();
|
||||
mousePoll();
|
||||
joystickPoll();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,13 @@
|
|||
// ----- Hardware addresses (24-bit / long pointers) -----
|
||||
|
||||
#define IIGS_NEWVIDEO_REG ((volatile uint8_t *)0x00C029L)
|
||||
#define IIGS_VBL_STATUS ((volatile uint8_t *)0x00C019L)
|
||||
#define IIGS_SHR_PIXELS ((uint8_t *)0xE12000L)
|
||||
#define IIGS_SHR_SCB ((uint8_t *)0xE19D00L)
|
||||
#define IIGS_SHR_PALETTE ((uint16_t *)0xE19E00L)
|
||||
|
||||
#define VBL_BAR_BIT 0x80
|
||||
|
||||
// NEWVIDEO bit masks
|
||||
#define NEWVIDEO_SHR_ON 0x80
|
||||
#define NEWVIDEO_LINEARIZE 0x40
|
||||
|
|
@ -100,6 +103,20 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
}
|
||||
|
||||
|
||||
// $C019 RDVBLBAR: bit 7 = 0 during vertical blank, 1 during active
|
||||
// scan. To produce a rising-edge wait (one VBL per call), first spin
|
||||
// while VBL is currently active (bit 7 = 0), then spin until VBL
|
||||
// fires again (bit 7 returns to 0). The IIgs SHR refresh is 60 Hz.
|
||||
void halWaitVBL(void) {
|
||||
while ((*IIGS_VBL_STATUS & VBL_BAR_BIT) == 0) {
|
||||
/* already in VBL: wait for active scan */;
|
||||
}
|
||||
while ((*IIGS_VBL_STATUS & VBL_BAR_BIT) != 0) {
|
||||
/* scanning: wait for next VBL */;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
if (gModeSet) {
|
||||
*IIGS_NEWVIDEO_REG = gPreviousNewVideo;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,18 @@
|
|||
#define IIGS_MODIFIERS ((volatile uint8_t *)0x00C025L)
|
||||
#define IIGS_KMSTATUS ((volatile uint8_t *)0x00C027L)
|
||||
|
||||
// Joystick / paddle softswitches.
|
||||
#define IIGS_BTN0 ((volatile uint8_t *)0x00C061L)
|
||||
#define IIGS_BTN1 ((volatile uint8_t *)0x00C062L)
|
||||
#define IIGS_PADDLE0 ((volatile uint8_t *)0x00C064L)
|
||||
#define IIGS_PADDLE1 ((volatile uint8_t *)0x00C065L)
|
||||
#define IIGS_PTRIG ((volatile uint8_t *)0x00C070L)
|
||||
#define IIGS_BUTTON_BIT 0x80
|
||||
#define IIGS_PADDLE_BUSY 0x80
|
||||
#define PADDLE_TIMEOUT 256
|
||||
#define PADDLE_LO_THRESHOLD 64
|
||||
#define PADDLE_HI_THRESHOLD 192
|
||||
|
||||
#define KBD_STROBE_BIT 0x80
|
||||
#define KBD_ASCII_MASK 0x7F
|
||||
|
||||
|
|
@ -85,9 +97,11 @@
|
|||
// ----- Prototypes -----
|
||||
|
||||
static void buildAsciiTable(void);
|
||||
static void pollJoystick(void);
|
||||
static void pollMouse(void);
|
||||
static void readModifierKeys(void);
|
||||
static int8_t signExtend7(uint8_t raw);
|
||||
static int8_t thresholdPaddle(uint8_t v);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
|
|
@ -152,6 +166,68 @@ static int8_t signExtend7(uint8_t raw) {
|
|||
}
|
||||
|
||||
|
||||
// Threshold a 0..255 paddle reading into a digital direction so the
|
||||
// IIgs analog stick presents the same axis semantics as the digital
|
||||
// sticks on ST/Amiga/DOS. Center range is treated as zero.
|
||||
static int8_t thresholdPaddle(uint8_t v) {
|
||||
if (v < PADDLE_LO_THRESHOLD) {
|
||||
return JOYSTICK_AXIS_MIN;
|
||||
}
|
||||
if (v > PADDLE_HI_THRESHOLD) {
|
||||
return JOYSTICK_AXIS_MAX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Read the Apple IIgs joystick (paddle 0/1 + buttons 0/1). Buttons at
|
||||
// $C061/$C062 are tied to the Open-Apple/Closed-Apple keys, so holding
|
||||
// either modifier key looks like a fire press -- intentional Apple
|
||||
// behavior, accept it. Only one stick is exposed; the IIgs second
|
||||
// "stick" wiring (paddles 2/3) is rarely used by retro games.
|
||||
//
|
||||
// Each paddle read triggers an RC scan via $C070 and then polls the
|
||||
// paddle softswitch until bit 7 clears; the iteration count
|
||||
// approximates the paddle's 0..255 position (the Apple firmware
|
||||
// PREAD routine works the same way). The two reads are inlined here
|
||||
// rather than factored into a helper because ORCA/C 2.1 trips over
|
||||
// `volatile uint8_t *` function parameters.
|
||||
static void pollJoystick(void) {
|
||||
uint16_t count;
|
||||
uint8_t px;
|
||||
uint8_t py;
|
||||
uint8_t byte;
|
||||
|
||||
byte = *IIGS_PTRIG;
|
||||
px = 255;
|
||||
for (count = 0; count < PADDLE_TIMEOUT; count++) {
|
||||
byte = *IIGS_PADDLE0;
|
||||
if ((byte & IIGS_PADDLE_BUSY) == 0) {
|
||||
px = (uint8_t)count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
byte = *IIGS_PTRIG;
|
||||
py = 255;
|
||||
for (count = 0; count < PADDLE_TIMEOUT; count++) {
|
||||
byte = *IIGS_PADDLE1;
|
||||
if ((byte & IIGS_PADDLE_BUSY) == 0) {
|
||||
py = (uint8_t)count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gJoyAxisX[JOYSTICK_0] = thresholdPaddle(px);
|
||||
gJoyAxisY[JOYSTICK_0] = thresholdPaddle(py);
|
||||
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_0] = (*IIGS_BTN0 & IIGS_BUTTON_BIT) != 0;
|
||||
gJoyButtonState[JOYSTICK_0][JOY_BUTTON_1] = (*IIGS_BTN1 & IIGS_BUTTON_BIT) != 0;
|
||||
|
||||
gJoyConnected[JOYSTICK_0] = true;
|
||||
gJoyConnected[JOYSTICK_1] = false;
|
||||
}
|
||||
|
||||
|
||||
// Drain one X+Y delta pair from the ADB mouse FIFO. $C027 bit 1 tells
|
||||
// us which coordinate the next $C024 read will return; we honor that
|
||||
// rather than assuming an order, so we stay in sync even if a stray
|
||||
|
|
@ -241,6 +317,7 @@ void halInputPoll(void) {
|
|||
|
||||
readModifierKeys();
|
||||
pollMouse();
|
||||
pollJoystick();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
306
tools/joeyasset/joeyasset.c
Normal file
306
tools/joeyasset/joeyasset.c
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
// joeyasset: convert a PPM (P6) bitmap into JoeyLib's .jas asset
|
||||
// format. Designed for hosts with a C99 compiler; no external library
|
||||
// dependencies.
|
||||
//
|
||||
// PPM is a stable, trivially parseable RGB format. ImageMagick / GIMP /
|
||||
// Photoshop / paint.net all export it directly, so the conversion
|
||||
// pipeline from a PNG is one extra command:
|
||||
//
|
||||
// convert sprite.png sprite.ppm # ImageMagick / GraphicsMagick
|
||||
// joeyasset sprite.ppm sprite.jas
|
||||
//
|
||||
// Quantization rule for v1: every input RGB triple is reduced to the
|
||||
// IIgs-style 4-bit-per-channel $0RGB form by taking the high nibble of
|
||||
// each channel. After that reduction the input is required to use no
|
||||
// more than 16 distinct $0RGB colors. We do NOT dither or do real
|
||||
// colorspace quantization yet -- inputs are expected to already use a
|
||||
// 16-color palette. If you need dithering, run the input through a
|
||||
// quantizer (gimp, pngquant, etc.) first.
|
||||
//
|
||||
// Output format is documented in include/joey/asset.h.
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define JAS_MAGIC0 'J'
|
||||
#define JAS_MAGIC1 'A'
|
||||
#define JAS_MAGIC2 'S'
|
||||
#define JAS_MAGIC3 '1'
|
||||
|
||||
#define JAS_PALETTE_ENTRIES 16
|
||||
#define JAS_HEADER_SIZE 44
|
||||
|
||||
#define PPM_TOKEN_MAX 64
|
||||
|
||||
static int parsePpmToken(FILE *fp, char *out, int outLen);
|
||||
static int loadPpm(const char *path, int *outWidth, int *outHeight, uint8_t **outPixels);
|
||||
static int buildPalette(const uint8_t *rgb, int width, int height, uint16_t *outPalette, uint8_t *outIndices);
|
||||
static int writeJas(const char *path, int width, int height, const uint16_t *palette, const uint8_t *indices);
|
||||
static void writeLE16(uint8_t *p, uint16_t v);
|
||||
|
||||
|
||||
static void writeLE16(uint8_t *p, uint16_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFF);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
// Reads a single whitespace-separated token from a PPM header,
|
||||
// skipping comments (# to end-of-line).
|
||||
static int parsePpmToken(FILE *fp, char *out, int outLen) {
|
||||
int c;
|
||||
int pos;
|
||||
|
||||
pos = 0;
|
||||
for (;;) {
|
||||
c = fgetc(fp);
|
||||
if (c == EOF) {
|
||||
return -1;
|
||||
}
|
||||
if (isspace(c)) {
|
||||
continue;
|
||||
}
|
||||
if (c == '#') {
|
||||
while ((c = fgetc(fp)) != EOF && c != '\n') {
|
||||
/* skip */;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
while (c != EOF && !isspace(c) && c != '#') {
|
||||
if (pos < outLen - 1) {
|
||||
out[pos++] = (char)c;
|
||||
}
|
||||
c = fgetc(fp);
|
||||
}
|
||||
out[pos] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int loadPpm(const char *path, int *outWidth, int *outHeight, uint8_t **outPixels) {
|
||||
FILE *fp;
|
||||
char tok[PPM_TOKEN_MAX];
|
||||
int width;
|
||||
int height;
|
||||
int maxval;
|
||||
size_t pixelBytes;
|
||||
uint8_t *buf;
|
||||
size_t read;
|
||||
|
||||
fp = fopen(path, "rb");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "joeyasset: cannot open %s: %s\n", path, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (parsePpmToken(fp, tok, sizeof(tok)) != 0 || strcmp(tok, "P6") != 0) {
|
||||
fprintf(stderr, "joeyasset: %s is not a PPM (P6) file\n", path);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
if (parsePpmToken(fp, tok, sizeof(tok)) != 0) {
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
width = atoi(tok);
|
||||
if (parsePpmToken(fp, tok, sizeof(tok)) != 0) {
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
height = atoi(tok);
|
||||
if (parsePpmToken(fp, tok, sizeof(tok)) != 0) {
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
maxval = atoi(tok);
|
||||
if (width <= 0 || height <= 0) {
|
||||
fprintf(stderr, "joeyasset: %s has non-positive dimensions\n", path);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
if (maxval != 255) {
|
||||
fprintf(stderr, "joeyasset: %s maxval %d unsupported (must be 255)\n", path, maxval);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
// PPM raster begins after exactly one whitespace byte following the
|
||||
// maxval token (already consumed by parsePpmToken's trailing read).
|
||||
// Re-read one byte? No -- parsePpmToken stops after consuming the
|
||||
// whitespace, so we are positioned at the first raster byte.
|
||||
|
||||
pixelBytes = (size_t)width * (size_t)height * 3u;
|
||||
buf = (uint8_t *)malloc(pixelBytes);
|
||||
if (buf == NULL) {
|
||||
fprintf(stderr, "joeyasset: out of memory (%zu bytes)\n", pixelBytes);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
read = fread(buf, 1, pixelBytes, fp);
|
||||
fclose(fp);
|
||||
if (read != pixelBytes) {
|
||||
fprintf(stderr, "joeyasset: short raster in %s (got %zu, need %zu)\n",
|
||||
path, read, pixelBytes);
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
*outWidth = width;
|
||||
*outHeight = height;
|
||||
*outPixels = buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Reduce every input RGB triple to a 12-bit $0RGB color. Up to 16
|
||||
// distinct colors are accepted; the first color encountered is
|
||||
// assigned palette index 0, the second index 1, etc. Returns the
|
||||
// number of distinct colors found, or -1 if more than 16.
|
||||
static int buildPalette(const uint8_t *rgb, int width, int height, uint16_t *outPalette, uint8_t *outIndices) {
|
||||
int total;
|
||||
int paletteCount;
|
||||
int i;
|
||||
int j;
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint16_t color;
|
||||
|
||||
total = width * height;
|
||||
paletteCount = 0;
|
||||
for (i = 0; i < JAS_PALETTE_ENTRIES; i++) {
|
||||
outPalette[i] = 0x0000;
|
||||
}
|
||||
for (i = 0; i < total; i++) {
|
||||
r = rgb[i * 3 + 0] >> 4;
|
||||
g = rgb[i * 3 + 1] >> 4;
|
||||
b = rgb[i * 3 + 2] >> 4;
|
||||
color = (uint16_t)((r << 8) | (g << 4) | b);
|
||||
for (j = 0; j < paletteCount; j++) {
|
||||
if (outPalette[j] == color) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == paletteCount) {
|
||||
if (paletteCount >= JAS_PALETTE_ENTRIES) {
|
||||
return -1;
|
||||
}
|
||||
outPalette[paletteCount] = color;
|
||||
paletteCount++;
|
||||
}
|
||||
outIndices[i] = (uint8_t)j;
|
||||
}
|
||||
return paletteCount;
|
||||
}
|
||||
|
||||
|
||||
static int writeJas(const char *path, int width, int height, const uint16_t *palette, const uint8_t *indices) {
|
||||
FILE *fp;
|
||||
uint8_t header[JAS_HEADER_SIZE];
|
||||
int rowBytes;
|
||||
int x;
|
||||
int y;
|
||||
uint8_t byte;
|
||||
int written;
|
||||
|
||||
memset(header, 0, sizeof(header));
|
||||
header[0] = JAS_MAGIC0;
|
||||
header[1] = JAS_MAGIC1;
|
||||
header[2] = JAS_MAGIC2;
|
||||
header[3] = JAS_MAGIC3;
|
||||
writeLE16(header + 4, (uint16_t)width);
|
||||
writeLE16(header + 6, (uint16_t)height);
|
||||
header[8] = 1;
|
||||
for (x = 0; x < JAS_PALETTE_ENTRIES; x++) {
|
||||
writeLE16(header + 12 + (x * 2), palette[x]);
|
||||
}
|
||||
|
||||
fp = fopen(path, "wb");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "joeyasset: cannot create %s: %s\n", path, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (fwrite(header, 1, sizeof(header), fp) != sizeof(header)) {
|
||||
fprintf(stderr, "joeyasset: short write to %s\n", path);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
rowBytes = (width + 1) >> 1;
|
||||
written = 0;
|
||||
for (y = 0; y < height; y++) {
|
||||
for (x = 0; x < rowBytes; x++) {
|
||||
int p0;
|
||||
int p1;
|
||||
p0 = x * 2;
|
||||
p1 = p0 + 1;
|
||||
byte = (uint8_t)((indices[y * width + p0] & 0x0F) << 4);
|
||||
if (p1 < width) {
|
||||
byte = (uint8_t)(byte | (indices[y * width + p1] & 0x0F));
|
||||
}
|
||||
if (fputc(byte, fp) == EOF) {
|
||||
fprintf(stderr, "joeyasset: short write to %s\n", path);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
written++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int width;
|
||||
int height;
|
||||
uint8_t *rgb;
|
||||
uint8_t *indices;
|
||||
uint16_t palette[JAS_PALETTE_ENTRIES];
|
||||
int paletteCount;
|
||||
int rc;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "usage: joeyasset INPUT.ppm OUTPUT.jas\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (loadPpm(argv[1], &width, &height, &rgb) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
indices = (uint8_t *)malloc((size_t)width * (size_t)height);
|
||||
if (indices == NULL) {
|
||||
fprintf(stderr, "joeyasset: out of memory for index buffer\n");
|
||||
free(rgb);
|
||||
return 1;
|
||||
}
|
||||
|
||||
paletteCount = buildPalette(rgb, width, height, palette, indices);
|
||||
if (paletteCount < 0) {
|
||||
fprintf(stderr,
|
||||
"joeyasset: input has more than 16 distinct $0RGB colors after\n"
|
||||
" 4-bit-per-channel quantization. Reduce the input palette and\n"
|
||||
" retry (e.g. pngquant --nofs 16, or GIMP -> Image -> Mode ->\n"
|
||||
" Indexed... with 16 colors and no dithering).\n");
|
||||
free(rgb);
|
||||
free(indices);
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = writeJas(argv[2], width, height, palette, indices);
|
||||
if (rc == 0) {
|
||||
printf("joeyasset: wrote %s (%d x %d, %d color%s)\n",
|
||||
argv[2], width, height, paletteCount,
|
||||
paletteCount == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
free(rgb);
|
||||
free(indices);
|
||||
return rc;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue