Basic rendering working.
This commit is contained in:
commit
b3d9961e78
37 changed files with 4284 additions and 0 deletions
53
.gitignore
vendored
Normal file
53
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Build outputs
|
||||||
|
build/
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
*.exe
|
||||||
|
*.prg
|
||||||
|
*.tos
|
||||||
|
*.sys16
|
||||||
|
*.gs
|
||||||
|
*.bin
|
||||||
|
|
||||||
|
# Toolchain installations (everything fetched/built by install.sh)
|
||||||
|
toolchains/iigs/*
|
||||||
|
toolchains/amiga/*
|
||||||
|
toolchains/atarist/*
|
||||||
|
toolchains/dos/*
|
||||||
|
toolchains/emulators/*
|
||||||
|
toolchains/.install_state/*
|
||||||
|
toolchains/cache/*
|
||||||
|
|
||||||
|
# Disk images
|
||||||
|
*.adf
|
||||||
|
*.hdf
|
||||||
|
*.st
|
||||||
|
*.2mg
|
||||||
|
*.po
|
||||||
|
*.img
|
||||||
|
*.dsk
|
||||||
|
|
||||||
|
# Editor / OS cruft
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Generated asset blobs (if running tools locally)
|
||||||
|
*.SUR
|
||||||
|
*.TIL
|
||||||
|
*.SPR
|
||||||
|
*.PAL
|
||||||
|
*.MAP
|
||||||
|
*.FNT
|
||||||
|
*.SND
|
||||||
|
*.ANI
|
||||||
|
|
||||||
|
# Crap I added
|
||||||
|
stuff/*
|
||||||
|
docs/*
|
||||||
73
Makefile
Normal file
73
Makefile
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# JoeyLib top-level Makefile.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source toolchains/env.sh (first, every shell session)
|
||||||
|
# make builds every target whose toolchain is present
|
||||||
|
# make iigs builds Apple IIgs only
|
||||||
|
# make amiga builds Commodore Amiga only
|
||||||
|
# make atarist builds Atari ST only
|
||||||
|
# make dos builds MS-DOS only
|
||||||
|
# make clean removes all build outputs
|
||||||
|
#
|
||||||
|
# The build references cross tools exclusively from toolchains/, set up
|
||||||
|
# by sourcing toolchains/env.sh. Do not invoke make without sourcing
|
||||||
|
# env.sh first or builds will fail with missing tool errors.
|
||||||
|
|
||||||
|
REPO_DIR := $(abspath $(dir $(firstword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
# Detect which toolchains are actually available.
|
||||||
|
HAVE_IIGS := $(shell [ -x "$(IIGS_CC)" ] && echo 1)
|
||||||
|
HAVE_AMIGA := $(shell [ -x "$(AMIGA_CC)" ] && echo 1)
|
||||||
|
HAVE_ATARIST := $(shell [ -x "$(ST_CC)" ] && echo 1)
|
||||||
|
HAVE_DOS := $(shell [ -x "$(DOS_CC)" ] && echo 1)
|
||||||
|
|
||||||
|
ALL_TARGETS :=
|
||||||
|
ifeq ($(HAVE_IIGS),1)
|
||||||
|
ALL_TARGETS += iigs
|
||||||
|
endif
|
||||||
|
ifeq ($(HAVE_AMIGA),1)
|
||||||
|
ALL_TARGETS += amiga
|
||||||
|
endif
|
||||||
|
ifeq ($(HAVE_ATARIST),1)
|
||||||
|
ALL_TARGETS += atarist
|
||||||
|
endif
|
||||||
|
ifeq ($(HAVE_DOS),1)
|
||||||
|
ALL_TARGETS += dos
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all iigs iigs-disk amiga atarist dos clean help status
|
||||||
|
|
||||||
|
all: $(ALL_TARGETS)
|
||||||
|
ifeq ($(ALL_TARGETS),)
|
||||||
|
@echo "No toolchains detected. Run ./toolchains/install.sh and source toolchains/env.sh."
|
||||||
|
@exit 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
iigs:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk
|
||||||
|
|
||||||
|
iigs-disk:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-disk
|
||||||
|
|
||||||
|
amiga:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/amiga.mk
|
||||||
|
|
||||||
|
atarist:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/atarist.mk
|
||||||
|
|
||||||
|
dos:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/dos.mk
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(REPO_DIR)/build
|
||||||
|
|
||||||
|
status:
|
||||||
|
@echo "Toolchain availability:"
|
||||||
|
@echo " iigs: $(if $(HAVE_IIGS),yes,NO)"
|
||||||
|
@echo " amiga: $(if $(HAVE_AMIGA),yes,NO)"
|
||||||
|
@echo " atarist: $(if $(HAVE_ATARIST),yes,NO)"
|
||||||
|
@echo " dos: $(if $(HAVE_DOS),yes,NO)"
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets: iigs amiga atarist dos all clean status help"
|
||||||
|
@echo "Source toolchains/env.sh first."
|
||||||
62
README.md
Normal file
62
README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# JoeyLib
|
||||||
|
|
||||||
|
A unified C game-development library targeting four early 16-bit
|
||||||
|
platforms from a single codebase:
|
||||||
|
|
||||||
|
- Apple IIgs (reference platform)
|
||||||
|
- Commodore Amiga (A500 / 68000 baseline)
|
||||||
|
- Atari ST (STF / 68000 baseline)
|
||||||
|
- MS-DOS (386 / VGA, DJGPP)
|
||||||
|
|
||||||
|
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
|
||||||
|
Hot paths are hand-written assembly per port; the public API is C.
|
||||||
|
|
||||||
|
See `docs/DESIGN.md` for the full 1.0 design.
|
||||||
|
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone <repo> joeylib
|
||||||
|
cd joeylib
|
||||||
|
./toolchains/install.sh
|
||||||
|
# follow any non-free-tool placement instructions the script prints
|
||||||
|
source toolchains/env.sh
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds `libjoey.a` for every target whose toolchain is installed,
|
||||||
|
plus the `examples/hello` program for each.
|
||||||
|
|
||||||
|
|
||||||
|
## Building for a single target
|
||||||
|
|
||||||
|
```
|
||||||
|
source toolchains/env.sh
|
||||||
|
make iigs
|
||||||
|
make amiga
|
||||||
|
make atarist
|
||||||
|
make dos
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/ design and reference documentation
|
||||||
|
include/joey/ public headers
|
||||||
|
src/core/ portable library code
|
||||||
|
src/codegen/ runtime sprite codegen (per-CPU emitters)
|
||||||
|
src/port/<plat>/ per-platform HAL implementations
|
||||||
|
src/shared68k/ assembly shared by Amiga and Atari ST
|
||||||
|
tools/joeytool/ asset pipeline and packaging
|
||||||
|
examples/ example programs
|
||||||
|
toolchains/ self-contained cross-build tools
|
||||||
|
make/ per-target Makefile fragments
|
||||||
|
build/<plat>/ per-target build outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
TBD.
|
||||||
29
examples/hello/hello.c
Normal file
29
examples/hello/hello.c
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
// JoeyLib hello-world example.
|
||||||
|
//
|
||||||
|
// Validates that the C API headers, library, and per-platform link
|
||||||
|
// path all work end-to-end. Runs in HOST_MODE_OS so it can use stdio.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <joey/joey.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
JoeyConfigT config;
|
||||||
|
|
||||||
|
config.hostMode = HOST_MODE_OS;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("JoeyLib %s\n", joeyVersionString());
|
||||||
|
printf("Platform: %s\n", joeyPlatformName());
|
||||||
|
printf("Hello from JoeyLib.\n");
|
||||||
|
|
||||||
|
joeyShutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
147
examples/pattern/pattern.c
Normal file
147
examples/pattern/pattern.c
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
// M1 deliverable: visible test pattern exercising surfaces, palettes,
|
||||||
|
// SCBs, fillRect, and present.
|
||||||
|
//
|
||||||
|
// Screen is divided into 8 horizontal bands, each assigned its own
|
||||||
|
// palette. The pattern draws 16 vertical color-index stripes across
|
||||||
|
// the entire height; as a result each band displays its palette's
|
||||||
|
// gradient. Color index 0 in every palette is forced to black by the
|
||||||
|
// library contract, so the leftmost stripe is black in every band.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <joey/joey.h>
|
||||||
|
|
||||||
|
#define BAND_COUNT 8
|
||||||
|
#define BAND_HEIGHT (SURFACE_HEIGHT / BAND_COUNT)
|
||||||
|
#define STRIPE_COUNT 16
|
||||||
|
#define STRIPE_WIDTH (SURFACE_WIDTH / STRIPE_COUNT)
|
||||||
|
#define DISPLAY_SECONDS 5
|
||||||
|
|
||||||
|
static void buildPalettes(SurfaceT *screen);
|
||||||
|
static void buildScbs(SurfaceT *screen);
|
||||||
|
static void drawStripes(SurfaceT *screen);
|
||||||
|
static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn);
|
||||||
|
static void waitSeconds(int seconds);
|
||||||
|
|
||||||
|
|
||||||
|
static void buildPalettes(SurfaceT *screen) {
|
||||||
|
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
||||||
|
|
||||||
|
// Palette 0: grayscale
|
||||||
|
makeGradient(colors, 1, 1, 1);
|
||||||
|
paletteSet(screen, 0, colors);
|
||||||
|
|
||||||
|
// Palette 1: red
|
||||||
|
makeGradient(colors, 1, 0, 0);
|
||||||
|
paletteSet(screen, 1, colors);
|
||||||
|
|
||||||
|
// Palette 2: yellow
|
||||||
|
makeGradient(colors, 1, 1, 0);
|
||||||
|
paletteSet(screen, 2, colors);
|
||||||
|
|
||||||
|
// Palette 3: green
|
||||||
|
makeGradient(colors, 0, 1, 0);
|
||||||
|
paletteSet(screen, 3, colors);
|
||||||
|
|
||||||
|
// Palette 4: cyan
|
||||||
|
makeGradient(colors, 0, 1, 1);
|
||||||
|
paletteSet(screen, 4, colors);
|
||||||
|
|
||||||
|
// Palette 5: blue
|
||||||
|
makeGradient(colors, 0, 0, 1);
|
||||||
|
paletteSet(screen, 5, colors);
|
||||||
|
|
||||||
|
// Palette 6: magenta
|
||||||
|
makeGradient(colors, 1, 0, 1);
|
||||||
|
paletteSet(screen, 6, colors);
|
||||||
|
|
||||||
|
// Palette 7: white-only (same as grayscale but serves as sanity check)
|
||||||
|
makeGradient(colors, 1, 1, 1);
|
||||||
|
paletteSet(screen, 7, colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void buildScbs(SurfaceT *screen) {
|
||||||
|
uint16_t band;
|
||||||
|
uint16_t first;
|
||||||
|
uint16_t last;
|
||||||
|
|
||||||
|
for (band = 0; band < BAND_COUNT; band++) {
|
||||||
|
first = (uint16_t)(band * BAND_HEIGHT);
|
||||||
|
last = (uint16_t)(first + BAND_HEIGHT - 1);
|
||||||
|
scbSetRange(screen, first, last, (uint8_t)band);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void drawStripes(SurfaceT *screen) {
|
||||||
|
uint8_t colorIndex;
|
||||||
|
int16_t x;
|
||||||
|
|
||||||
|
surfaceClear(screen, 0);
|
||||||
|
for (colorIndex = 0; colorIndex < STRIPE_COUNT; colorIndex++) {
|
||||||
|
x = (int16_t)(colorIndex * STRIPE_WIDTH);
|
||||||
|
fillRect(screen, x, 0, STRIPE_WIDTH, SURFACE_HEIGHT, colorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void makeGradient(uint16_t *out16, int redOn, int greenOn, int blueOn) {
|
||||||
|
uint8_t i;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
|
||||||
|
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
r = (uint8_t)(redOn ? i : 0);
|
||||||
|
g = (uint8_t)(greenOn ? i : 0);
|
||||||
|
b = (uint8_t)(blueOn ? i : 0);
|
||||||
|
out16[i] = (uint16_t)((r << 8) | (g << 4) | b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPalettes(screen);
|
||||||
|
buildScbs(screen);
|
||||||
|
drawStripes(screen);
|
||||||
|
surfacePresent(screen);
|
||||||
|
|
||||||
|
waitSeconds(DISPLAY_SECONDS);
|
||||||
|
|
||||||
|
joeyShutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
33
include/joey/core.h
Normal file
33
include/joey/core.h
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
// JoeyLib lifecycle: configuration, initialization, shutdown.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_CORE_H
|
||||||
|
#define JOEYLIB_CORE_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HostModeE hostMode; // takeover or cooperate with host OS
|
||||||
|
uint32_t codegenBytes; // runtime compiled-sprite cache size
|
||||||
|
uint16_t maxSurfaces; // maximum concurrent surfaces
|
||||||
|
uint32_t audioBytes; // audio sample and module RAM pool
|
||||||
|
uint32_t assetBytes; // tileset / sprite / map RAM pool
|
||||||
|
} JoeyConfigT;
|
||||||
|
|
||||||
|
// Initialize the library. Returns true on success.
|
||||||
|
// On failure, joeyLastError() returns a human-readable description.
|
||||||
|
bool joeyInit(const JoeyConfigT *config);
|
||||||
|
|
||||||
|
// Shut down the library, releasing all resources.
|
||||||
|
void joeyShutdown(void);
|
||||||
|
|
||||||
|
// Returns the most recent error message, or NULL if none.
|
||||||
|
const char *joeyLastError(void);
|
||||||
|
|
||||||
|
// Returns the platform identifier string (e.g., "Apple IIgs", "MS-DOS").
|
||||||
|
const char *joeyPlatformName(void);
|
||||||
|
|
||||||
|
// Returns the library version string (e.g., "1.0.0").
|
||||||
|
const char *joeyVersionString(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
28
include/joey/draw.h
Normal file
28
include/joey/draw.h
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Drawing primitives.
|
||||||
|
//
|
||||||
|
// All primitives clip to the surface rectangle. Out-of-bounds draws are
|
||||||
|
// silent no-ops, not errors. samplePixel returns 0 for off-surface reads.
|
||||||
|
// Primitives operate on color indices; per-scanline palette resolution
|
||||||
|
// happens only at display time.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_DRAW_H
|
||||||
|
#define JOEYLIB_DRAW_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "surface.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Fill the entire surface with a single color index. Writes color 0 is
|
||||||
|
// a legitimate clear-to-black and does not skip.
|
||||||
|
void surfaceClear(SurfaceT *s, uint8_t colorIndex);
|
||||||
|
|
||||||
|
// Plot a single pixel. Off-surface coordinates are no-ops.
|
||||||
|
void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex);
|
||||||
|
|
||||||
|
// Read a pixel value. Off-surface coordinates return 0.
|
||||||
|
uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
#endif
|
||||||
17
include/joey/joey.h
Normal file
17
include/joey/joey.h
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// JoeyLib umbrella header.
|
||||||
|
//
|
||||||
|
// Game code includes this single file:
|
||||||
|
// #include <joey/joey.h>
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_H
|
||||||
|
#define JOEYLIB_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "surface.h"
|
||||||
|
#include "palette.h"
|
||||||
|
#include "draw.h"
|
||||||
|
#include "present.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
31
include/joey/palette.h
Normal file
31
include/joey/palette.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Palette and SCB (scanline control byte) accessors.
|
||||||
|
//
|
||||||
|
// Colors are 12-bit $0RGB (bits 11..8 red, 7..4 green, 3..0 blue).
|
||||||
|
// The library forces color 0 of every palette to black; paletteSet
|
||||||
|
// silently masks that entry to $000 regardless of what the caller
|
||||||
|
// passed. See docs/DESIGN.md sections 5 and 10.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_PALETTE_H
|
||||||
|
#define JOEYLIB_PALETTE_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "surface.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Load all 16 colors of a palette. colors16 must point to exactly 16
|
||||||
|
// uint16_t values in $0RGB format.
|
||||||
|
void paletteSet(SurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16);
|
||||||
|
|
||||||
|
// Read all 16 colors of a palette into out16.
|
||||||
|
void paletteGet(const SurfaceT *s, uint8_t paletteIndex, uint16_t *out16);
|
||||||
|
|
||||||
|
// Assign a single palette index (0..15) to a single scanline (0..199).
|
||||||
|
void scbSet(SurfaceT *s, uint16_t line, uint8_t paletteIndex);
|
||||||
|
|
||||||
|
// Assign a palette index to an inclusive range of scanlines.
|
||||||
|
void scbSetRange(SurfaceT *s, uint16_t firstLine, uint16_t lastLine, uint8_t paletteIndex);
|
||||||
|
|
||||||
|
// Read the palette index assigned to a scanline.
|
||||||
|
uint8_t scbGet(const SurfaceT *s, uint16_t line);
|
||||||
|
|
||||||
|
#endif
|
||||||
73
include/joey/platform.h
Normal file
73
include/joey/platform.h
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
// JoeyLib platform, CPU, endian, and capability defines.
|
||||||
|
//
|
||||||
|
// Exactly one JOEYLIB_PLATFORM_* must be defined per build. The build
|
||||||
|
// system normally sets this via -D; if absent, the header attempts to
|
||||||
|
// auto-detect from compiler-predefined macros.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_PLATFORM_H
|
||||||
|
#define JOEYLIB_PLATFORM_H
|
||||||
|
|
||||||
|
// ----- Auto-detect platform from compiler macros if not preset -----
|
||||||
|
|
||||||
|
#if !defined(JOEYLIB_PLATFORM_IIGS) && \
|
||||||
|
!defined(JOEYLIB_PLATFORM_AMIGA) && \
|
||||||
|
!defined(JOEYLIB_PLATFORM_ATARIST) && \
|
||||||
|
!defined(JOEYLIB_PLATFORM_DOS)
|
||||||
|
|
||||||
|
#if defined(__DJGPP__) || defined(__MSDOS__)
|
||||||
|
#define JOEYLIB_PLATFORM_DOS
|
||||||
|
#elif defined(__amigaos__) || defined(AMIGA) || defined(__amigaos4__)
|
||||||
|
#define JOEYLIB_PLATFORM_AMIGA
|
||||||
|
#elif defined(__atarist__) || defined(__MINT__) || defined(__TOS__)
|
||||||
|
#define JOEYLIB_PLATFORM_ATARIST
|
||||||
|
#elif defined(__ORCAC__) || defined(__APPLE2GS__) || defined(__GNO__)
|
||||||
|
#define JOEYLIB_PLATFORM_IIGS
|
||||||
|
#else
|
||||||
|
#error "JoeyLib: unknown platform; define JOEYLIB_PLATFORM_<TARGET> explicitly via -D"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Validate exactly one platform is defined -----
|
||||||
|
|
||||||
|
#if (defined(JOEYLIB_PLATFORM_IIGS) + \
|
||||||
|
defined(JOEYLIB_PLATFORM_AMIGA) + \
|
||||||
|
defined(JOEYLIB_PLATFORM_ATARIST) + \
|
||||||
|
defined(JOEYLIB_PLATFORM_DOS)) != 1
|
||||||
|
#error "JoeyLib: exactly one JOEYLIB_PLATFORM_* must be defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Derived CPU markers -----
|
||||||
|
|
||||||
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
|
#define JOEYLIB_CPU_65816
|
||||||
|
#define JOEYLIB_ENDIAN_LITTLE
|
||||||
|
#define JOEYLIB_NATIVE_CHUNKY
|
||||||
|
#define JOEYLIB_PLATFORM_NAME "Apple IIgs"
|
||||||
|
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
||||||
|
#define JOEYLIB_CPU_68000
|
||||||
|
#define JOEYLIB_ENDIAN_BIG
|
||||||
|
#define JOEYLIB_NATIVE_PLANAR
|
||||||
|
#define JOEYLIB_HAS_BLITTER
|
||||||
|
#define JOEYLIB_HAS_COPPER
|
||||||
|
#define JOEYLIB_PLATFORM_NAME "Commodore Amiga"
|
||||||
|
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
|
#define JOEYLIB_CPU_68000
|
||||||
|
#define JOEYLIB_ENDIAN_BIG
|
||||||
|
#define JOEYLIB_NATIVE_PLANAR
|
||||||
|
#define JOEYLIB_PLATFORM_NAME "Atari ST"
|
||||||
|
#elif defined(JOEYLIB_PLATFORM_DOS)
|
||||||
|
#define JOEYLIB_CPU_X86
|
||||||
|
#define JOEYLIB_ENDIAN_LITTLE
|
||||||
|
#define JOEYLIB_NATIVE_CHUNKY
|
||||||
|
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Library version -----
|
||||||
|
|
||||||
|
#define JOEYLIB_VERSION_MAJOR 1
|
||||||
|
#define JOEYLIB_VERSION_MINOR 0
|
||||||
|
#define JOEYLIB_VERSION_PATCH 0
|
||||||
|
#define JOEYLIB_VERSION_STRING "1.0.0"
|
||||||
|
|
||||||
|
#endif
|
||||||
23
include/joey/present.h
Normal file
23
include/joey/present.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Present / slam.
|
||||||
|
//
|
||||||
|
// surfacePresent copies pixels, SCBs, and palettes from a source
|
||||||
|
// surface to the visible display. On chunky platforms (IIgs, DOS) this
|
||||||
|
// is a direct copy; on planar platforms (Amiga, Atari ST) this is a
|
||||||
|
// chunky-to-planar conversion. See docs/DESIGN.md section 7.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_PRESENT_H
|
||||||
|
#define JOEYLIB_PRESENT_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "surface.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Present the entire source surface to the display.
|
||||||
|
void surfacePresent(const SurfaceT *src);
|
||||||
|
|
||||||
|
// Present a rectangular region of the source surface to the display.
|
||||||
|
// The rect is clipped to the surface. Negative or zero dimensions are
|
||||||
|
// no-ops.
|
||||||
|
void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||||
|
|
||||||
|
#endif
|
||||||
46
include/joey/surface.h
Normal file
46
include/joey/surface.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Surface type and allocation.
|
||||||
|
//
|
||||||
|
// All surfaces are 320x200 pixels, 4 bits per pixel packed (two pixels
|
||||||
|
// per byte, high nibble is the left pixel). Each surface carries its
|
||||||
|
// own 200-entry SCB (scanline control byte) table and a 16-by-16 $0RGB
|
||||||
|
// palette set. See docs/DESIGN.md section 6.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_SURFACE_H
|
||||||
|
#define JOEYLIB_SURFACE_H
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// ----- Fixed surface dimensions (library contract) -----
|
||||||
|
|
||||||
|
#define SURFACE_WIDTH 320
|
||||||
|
#define SURFACE_HEIGHT 200
|
||||||
|
#define SURFACE_BYTES_PER_ROW 160
|
||||||
|
#define SURFACE_PIXELS_SIZE (SURFACE_BYTES_PER_ROW * SURFACE_HEIGHT)
|
||||||
|
|
||||||
|
#define SURFACE_PALETTE_COUNT 16
|
||||||
|
#define SURFACE_COLORS_PER_PALETTE 16
|
||||||
|
#define SURFACE_PALETTE_ENTRIES (SURFACE_PALETTE_COUNT * SURFACE_COLORS_PER_PALETTE)
|
||||||
|
|
||||||
|
// ----- Opaque public type -----
|
||||||
|
|
||||||
|
typedef struct SurfaceT SurfaceT;
|
||||||
|
|
||||||
|
// Allocate a new offscreen surface. Returns NULL on failure (joeyLastError
|
||||||
|
// describes the reason).
|
||||||
|
SurfaceT *surfaceCreate(void);
|
||||||
|
|
||||||
|
// Release an offscreen surface previously returned by surfaceCreate.
|
||||||
|
// Passing NULL is a no-op. Passing the screen surface is a no-op.
|
||||||
|
void surfaceDestroy(SurfaceT *s);
|
||||||
|
|
||||||
|
// The library's pre-allocated screen surface. This is the surface the
|
||||||
|
// library presents to the display. Always valid between joeyInit and
|
||||||
|
// joeyShutdown.
|
||||||
|
SurfaceT *surfaceGetScreen(void);
|
||||||
|
|
||||||
|
// Copy pixels, SCBs, and palettes from src into dst. Both must be valid
|
||||||
|
// surfaces.
|
||||||
|
void surfaceCopy(SurfaceT *dst, const SurfaceT *src);
|
||||||
|
|
||||||
|
#endif
|
||||||
20
include/joey/types.h
Normal file
20
include/joey/types.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// JoeyLib shared types and enums.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_TYPES_H
|
||||||
|
#define JOEYLIB_TYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HOST_MODE_TAKEOVER,
|
||||||
|
HOST_MODE_OS
|
||||||
|
} HostModeE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
VIDEO_REGION_NTSC,
|
||||||
|
VIDEO_REGION_PAL,
|
||||||
|
VIDEO_REGION_VGA
|
||||||
|
} VideoRegionE;
|
||||||
|
|
||||||
|
#endif
|
||||||
67
make/amiga.mk
Normal file
67
make/amiga.mk
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Amiga (Bebbo m68k-amigaos-gcc) build rules.
|
||||||
|
|
||||||
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
PLATFORM := amiga
|
||||||
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
|
LIBDIR := $(BUILD)/lib
|
||||||
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
|
CFLAGS := $(COMMON_CFLAGS) -DJOEYLIB_PLATFORM_AMIGA -m68000 -fomit-frame-pointer -noixemul
|
||||||
|
ASFLAGS := -Felf -m68000 -quiet
|
||||||
|
# --allow-multiple-definition lets our user-space tzset stub
|
||||||
|
# (src/port/amiga/libinit.c) win over libnix's version in
|
||||||
|
# __gmtoffset.o. libnix's tzset dereferences a possibly-NULL
|
||||||
|
# LocaleBase; our no-op skips the deref.
|
||||||
|
LDFLAGS := -noixemul -Wl,--allow-multiple-definition
|
||||||
|
|
||||||
|
PORT_C_SRCS := $(wildcard $(SRC_PORT)/amiga/*.c)
|
||||||
|
PORT_S_SRCS := $(wildcard $(SRC_PORT)/amiga/*.s)
|
||||||
|
SHARED_S := $(wildcard $(SRC_68K)/*.s)
|
||||||
|
|
||||||
|
LIB_OBJS := \
|
||||||
|
$(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/amiga/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/amiga/%.s,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||||
|
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S))
|
||||||
|
|
||||||
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
|
HELLO_BIN := $(BINDIR)/Hello
|
||||||
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
|
PATTERN_BIN := $(BINDIR)/Pattern
|
||||||
|
|
||||||
|
.PHONY: all amiga clean-amiga
|
||||||
|
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||||
|
|
||||||
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/amiga/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/amiga/%.s
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/68k/%.o: $(SRC_68K)/%.s
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(LIB): $(LIB_OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_AR) rcs $@ $^
|
||||||
|
|
||||||
|
$(HELLO_BIN): $(HELLO_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
clean-amiga:
|
||||||
|
rm -rf $(BUILD)
|
||||||
63
make/atarist.mk
Normal file
63
make/atarist.mk
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Atari ST (m68k-atari-mint-gcc) build rules.
|
||||||
|
|
||||||
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
PLATFORM := atarist
|
||||||
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
|
LIBDIR := $(BUILD)/lib
|
||||||
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
|
CFLAGS := $(COMMON_CFLAGS) -DJOEYLIB_PLATFORM_ATARIST -m68000 -fomit-frame-pointer
|
||||||
|
ASFLAGS := -Felf -m68000 -quiet
|
||||||
|
LDFLAGS :=
|
||||||
|
|
||||||
|
PORT_C_SRCS := $(wildcard $(SRC_PORT)/atarist/*.c)
|
||||||
|
PORT_S_SRCS := $(wildcard $(SRC_PORT)/atarist/*.s)
|
||||||
|
SHARED_S := $(wildcard $(SRC_68K)/*.s)
|
||||||
|
|
||||||
|
LIB_OBJS := \
|
||||||
|
$(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/atarist/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/atarist/%.s,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||||
|
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S))
|
||||||
|
|
||||||
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
|
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
||||||
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
|
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||||
|
|
||||||
|
.PHONY: all atarist clean-atarist
|
||||||
|
all atarist: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||||
|
|
||||||
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/atarist/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/atarist/%.s
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/68k/%.o: $(SRC_68K)/%.s
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(LIB): $(LIB_OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_AR) rcs $@ $^
|
||||||
|
|
||||||
|
$(HELLO_BIN): $(HELLO_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
clean-atarist:
|
||||||
|
rm -rf $(BUILD)
|
||||||
18
make/common.mk
Normal file
18
make/common.mk
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Shared variables used by per-target Makefile fragments.
|
||||||
|
#
|
||||||
|
# Source this only via the per-target make file (make/iigs.mk etc).
|
||||||
|
|
||||||
|
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
||||||
|
INCLUDE_DIR := $(REPO_DIR)/include
|
||||||
|
SRC_CORE := $(REPO_DIR)/src/core
|
||||||
|
SRC_PORT := $(REPO_DIR)/src/port
|
||||||
|
SRC_CG := $(REPO_DIR)/src/codegen
|
||||||
|
SRC_68K := $(REPO_DIR)/src/shared68k
|
||||||
|
EXAMPLES := $(REPO_DIR)/examples
|
||||||
|
|
||||||
|
# Portable C sources for libjoey -- present on every target.
|
||||||
|
CORE_C_SRCS := $(wildcard $(SRC_CORE)/*.c)
|
||||||
|
|
||||||
|
# Common include flags. Per-port code can include hal.h / surfaceInternal.h
|
||||||
|
# directly because SRC_CORE is in the include path.
|
||||||
|
COMMON_CFLAGS := -I$(INCLUDE_DIR) -I$(SRC_CORE) -Wall -Wextra -O2
|
||||||
59
make/dos.mk
Normal file
59
make/dos.mk
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# DOS (DJGPP) build rules.
|
||||||
|
|
||||||
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
PLATFORM := dos
|
||||||
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
|
LIBDIR := $(BUILD)/lib
|
||||||
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
|
CFLAGS := $(COMMON_CFLAGS) -DJOEYLIB_PLATFORM_DOS -march=i386 -m32
|
||||||
|
ASFLAGS := -f coff
|
||||||
|
LDFLAGS :=
|
||||||
|
|
||||||
|
PORT_C_SRCS := $(wildcard $(SRC_PORT)/dos/*.c)
|
||||||
|
PORT_S_SRCS := $(wildcard $(SRC_PORT)/dos/*.asm)
|
||||||
|
|
||||||
|
LIB_OBJS := \
|
||||||
|
$(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/dos/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_PORT)/dos/%.asm,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS))
|
||||||
|
|
||||||
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
|
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
||||||
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
|
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||||
|
|
||||||
|
.PHONY: all dos clean-dos
|
||||||
|
all dos: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||||
|
|
||||||
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/dos/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(SRC_PORT)/dos/%.asm
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(LIB): $(LIB_OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_AR) rcs $@ $^
|
||||||
|
|
||||||
|
$(HELLO_BIN): $(HELLO_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||||
|
$(DOS_EMBED_DPMI) $@
|
||||||
|
|
||||||
|
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||||
|
$(DOS_EMBED_DPMI) $@
|
||||||
|
|
||||||
|
clean-dos:
|
||||||
|
rm -rf $(BUILD)
|
||||||
60
make/iigs.mk
Normal file
60
make/iigs.mk
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Apple IIgs build rules.
|
||||||
|
#
|
||||||
|
# Uses GoldenGate's iix to drive ORCA/C 2.1.0 + ORCA Linker. The
|
||||||
|
# toolchains/iigs/iix-build.sh wrapper handles ORCA's case-sensitivity
|
||||||
|
# quirks (lowercase .a/.root/.sym vs linker demands for uppercase),
|
||||||
|
# multi-source compile/link, and pragma injection for include paths
|
||||||
|
# and the stdint/stdbool shim headers.
|
||||||
|
|
||||||
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
PLATFORM := iigs
|
||||||
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
|
BINDIR := $(BUILD)/bin
|
||||||
|
|
||||||
|
PORT_C_SRCS := $(wildcard $(SRC_PORT)/iigs/*.c)
|
||||||
|
|
||||||
|
LIB_SRCS := $(CORE_C_SRCS) $(PORT_C_SRCS)
|
||||||
|
|
||||||
|
HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
||||||
|
HELLO_BIN := $(BINDIR)/HELLO
|
||||||
|
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||||
|
PATTERN_BIN := $(BINDIR)/PATTERN
|
||||||
|
DISK_IMG := $(BINDIR)/joey.2mg
|
||||||
|
|
||||||
|
IIGS_PACKAGE := $(REPO_DIR)/toolchains/iigs/package-disk.sh
|
||||||
|
|
||||||
|
IIX_INCLUDES := \
|
||||||
|
-I $(IIGS_INCLUDE_SHIM) \
|
||||||
|
-I $(INCLUDE_DIR) \
|
||||||
|
-I $(INCLUDE_DIR)/joey \
|
||||||
|
-I $(SRC_CORE)
|
||||||
|
|
||||||
|
.PHONY: all iigs iigs-disk clean-iigs
|
||||||
|
all iigs: $(HELLO_BIN) $(PATTERN_BIN)
|
||||||
|
|
||||||
|
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
||||||
|
# #pragma noroot). The example source supplies main(); libjoey sources
|
||||||
|
# are the extras. The chtyp post-step tags the output as GS/OS S16
|
||||||
|
# ($B3) so GS/OS recognizes it as launchable; the file-type lives in
|
||||||
|
# a user.com.apple.FinderInfo xattr that iix and profuse preserve.
|
||||||
|
$(HELLO_BIN): $(HELLO_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(HELLO_SRC) $(LIB_SRCS)
|
||||||
|
$(IIGS_IIX) chtyp -t S16 $@
|
||||||
|
|
||||||
|
$(PATTERN_BIN): $(PATTERN_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(PATTERN_SRC) $(LIB_SRCS)
|
||||||
|
$(IIGS_IIX) chtyp -t S16 $@
|
||||||
|
|
||||||
|
# Assemble an 800KB ProDOS 2img containing both examples, ready to
|
||||||
|
# mount in GSplus alongside a GS/OS boot volume.
|
||||||
|
iigs-disk: $(DISK_IMG)
|
||||||
|
|
||||||
|
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(IIGS_PACKAGE)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN)
|
||||||
|
|
||||||
|
clean-iigs:
|
||||||
|
rm -rf $(BUILD)
|
||||||
110
scripts/run-amiga.sh
Executable file
110
scripts/run-amiga.sh
Executable file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch the built Amiga example in FS-UAE. Defaults to PATTERN; pass
|
||||||
|
# "hello" to run HELLO instead.
|
||||||
|
#
|
||||||
|
# Kickstart and Workbench:
|
||||||
|
# - If toolchains/emulators/support/kickstart.rom is present, it is
|
||||||
|
# used as the Kickstart ROM. Otherwise FS-UAE's built-in AROS ROM
|
||||||
|
# is used (less compatible with OCS Intuition but requires no
|
||||||
|
# licensed files).
|
||||||
|
# - If toolchains/emulators/support/workbench.adf is present, it is
|
||||||
|
# inserted into DF0: and the Amiga boots Workbench. The user
|
||||||
|
# launches the example from the JOEYLIB drawer on the desktop.
|
||||||
|
# - If Workbench is not present, AmigaDOS boots directly from DH0:,
|
||||||
|
# executes s/startup-sequence, and auto-runs the requested example.
|
||||||
|
#
|
||||||
|
# Always on A500 hardware (OCS chipset). Our HAL only uses OCS
|
||||||
|
# features so this matches real-hardware behavior.
|
||||||
|
#
|
||||||
|
# scripts/run-amiga.sh # runs Pattern
|
||||||
|
# scripts/run-amiga.sh hello # runs Hello
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
prog=${1:-pattern}
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
bin_dir=$repo/build/amiga/bin
|
||||||
|
support=$repo/toolchains/emulators/support
|
||||||
|
|
||||||
|
case $prog in
|
||||||
|
hello) file=Hello ;;
|
||||||
|
pattern) file=Pattern ;;
|
||||||
|
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
|
echo "$bin_dir/$file not built. Run 'make amiga' first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
kickstart=$support/kickstart.rom
|
||||||
|
workbench=$support/workbench.adf
|
||||||
|
|
||||||
|
# Stage a fresh hard-drive directory containing both example binaries
|
||||||
|
# plus an s/startup-sequence that auto-invokes the chosen one. The
|
||||||
|
# startup-sequence only runs when booting directly from the hard
|
||||||
|
# drive (i.e. no Workbench floppy); Workbench takes over boot if
|
||||||
|
# present and the user launches manually.
|
||||||
|
work=$(mktemp -d -t joeylib-amiga.XXXXXX)
|
||||||
|
# Preserve any diagnostic dumps Pattern writes to the virtual HD
|
||||||
|
# (copper.txt, etc.) before the temp dir is removed on script exit.
|
||||||
|
dump_keep=/tmp/joeylib-amiga-dump
|
||||||
|
trap 'mkdir -p "$dump_keep"; cp "$work"/*.txt "$dump_keep"/ 2>/dev/null; rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
mkdir -p "$work/s"
|
||||||
|
cp "$bin_dir/Hello" "$work/" 2>/dev/null || true
|
||||||
|
cp "$bin_dir/Pattern" "$work/" 2>/dev/null || true
|
||||||
|
# ':' 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"
|
||||||
|
|
||||||
|
fsargs=(
|
||||||
|
--amiga_model=A500
|
||||||
|
--fast_memory=2048
|
||||||
|
--hard_drive_0="$work"
|
||||||
|
--hard_drive_0_label=JOEYLIB
|
||||||
|
# Mouse-handling overrides for VM hosts where FS-UAE's default
|
||||||
|
# relative-mode grab fights with the hypervisor's own pointer
|
||||||
|
# integration. automatic_input_grab=0 stops FS-UAE from grabbing
|
||||||
|
# the pointer on focus; middle_click_ungrab=1 is a fallback so a
|
||||||
|
# middle-click releases any grab that does occur; mouse_integration=1
|
||||||
|
# asks FS-UAE to track host pointer position directly instead of
|
||||||
|
# sampling relative deltas.
|
||||||
|
--automatic_input_grab=0
|
||||||
|
--middle_click_ungrab=1
|
||||||
|
--mouse_integration=1
|
||||||
|
# Speed overrides: floppy_drive_speed=800 runs the floppy at 8x so
|
||||||
|
# the Workbench boot finishes in a few seconds instead of a minute.
|
||||||
|
# accuracy=0 and fast_copper=1 are deliberately NOT set -- they
|
||||||
|
# compromise copper timing, which our per-scanline palette swaps
|
||||||
|
# depend on.
|
||||||
|
--floppy_drive_speed=800
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -f $kickstart ]]; then
|
||||||
|
fsargs+=(--kickstart_file="$kickstart")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f $workbench ]]; then
|
||||||
|
fsargs+=(--floppy_drive_0="$workbench")
|
||||||
|
cat <<EOF
|
||||||
|
FS-UAE booting Workbench from DF0:. Once the desktop appears:
|
||||||
|
1. Double-click the JOEYLIB disk icon.
|
||||||
|
2. From the Window menu, pick "Show > All Files" (Workbench hides
|
||||||
|
files that have no .info icon by default).
|
||||||
|
3. Double-click $file to run it.
|
||||||
|
Alternatively open a Shell (Workbench > Tools > Shell), type 'cd
|
||||||
|
JOEYLIB:' then '$file'.
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
cat <<EOF
|
||||||
|
FS-UAE booting directly from the JOEYLIB hard drive. $file will
|
||||||
|
auto-run via s/startup-sequence. Drop a Workbench 3.1 ADF at
|
||||||
|
$workbench to boot the GUI instead.
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Intentionally no exec: we need the bash EXIT trap to fire so the
|
||||||
|
# scratch dir gets cleaned up (and any diagnostic dumps preserved).
|
||||||
|
# exec would replace the shell and skip trap execution.
|
||||||
|
fs-uae "${fsargs[@]}"
|
||||||
40
scripts/run-atarist.sh
Executable file
40
scripts/run-atarist.sh
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch the built Atari ST example in Hatari. Defaults to PATTERN;
|
||||||
|
# pass "hello" to run HELLO instead. Hatari autostarts the .PRG via
|
||||||
|
# the --auto flag; EmuTOS (toolchains/emulators/support/emutos-512k.img)
|
||||||
|
# provides the TOS ROM since the Ubuntu hatari package does not bundle
|
||||||
|
# one.
|
||||||
|
#
|
||||||
|
# scripts/run-atarist.sh # runs PATTERN.PRG
|
||||||
|
# scripts/run-atarist.sh hello # runs HELLO.PRG
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
prog=${1:-pattern}
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
bin_dir=$repo/build/atarist/bin
|
||||||
|
|
||||||
|
case $prog in
|
||||||
|
hello) file=HELLO.PRG ;;
|
||||||
|
pattern) file=PATTERN.PRG ;;
|
||||||
|
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||||
|
|
||||||
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
|
echo "$bin_dir/$file not built. Run 'make atarist' first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f $tos ]]; then
|
||||||
|
echo "TOS ROM missing: $tos" >&2
|
||||||
|
echo "run ./toolchains/install.sh (EmuTOS should have been staged)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Hatari's --auto needs the target on a mounted GEMDOS drive.
|
||||||
|
exec hatari \
|
||||||
|
--tos "$tos" \
|
||||||
|
--harddrive "$bin_dir" \
|
||||||
|
--gemdos-drive C \
|
||||||
|
--auto "C:\\$file"
|
||||||
25
scripts/run-dos.sh
Executable file
25
scripts/run-dos.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch the built DOS example in DOSBox. Defaults to PATTERN; pass
|
||||||
|
# "hello" to run HELLO instead.
|
||||||
|
#
|
||||||
|
# scripts/run-dos.sh # runs PATTERN
|
||||||
|
# scripts/run-dos.sh hello # runs HELLO
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
prog=${1:-pattern}
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
bin_dir=$repo/build/dos/bin
|
||||||
|
|
||||||
|
case $prog in
|
||||||
|
hello) file=HELLO.EXE ;;
|
||||||
|
pattern) file=PATTERN.EXE ;;
|
||||||
|
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||||
|
echo "$bin_dir/$file not built. Run 'make dos' first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec dosbox -c "C:" -c "$file" -c "pause" --exit "$bin_dir"
|
||||||
65
scripts/run-iigs.sh
Executable file
65
scripts/run-iigs.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Launch the built Apple IIgs examples in GSplus. GSplus is booted from
|
||||||
|
# a GS/OS 6.0.4 System disk (toolchains/emulators/support/gsos-system.po)
|
||||||
|
# with joey.2mg mounted as the data disk on slot 5 drive 2. The user
|
||||||
|
# navigates to the JOEYLIB volume in Finder and double-clicks HELLO or
|
||||||
|
# PATTERN to run it.
|
||||||
|
#
|
||||||
|
# Unlike the other emulators, GS/OS does not auto-run on boot -- it
|
||||||
|
# drops to Finder. Pass "hello" or "pattern" to print a reminder of
|
||||||
|
# which example to launch.
|
||||||
|
#
|
||||||
|
# scripts/run-iigs.sh # boots and waits for user (Pattern hint)
|
||||||
|
# scripts/run-iigs.sh hello # same, but hints to click HELLO
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
prog=${1:-pattern}
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
|
||||||
|
gsplus=$repo/toolchains/emulators/gsplus/bin/gsplus
|
||||||
|
rom=$repo/toolchains/emulators/support/apple-iigs.rom
|
||||||
|
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
||||||
|
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||||
|
|
||||||
|
case $prog in
|
||||||
|
hello|pattern) ;;
|
||||||
|
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk"; do
|
||||||
|
if [[ ! -f $f ]]; then
|
||||||
|
echo "missing: $f" >&2
|
||||||
|
if [[ $f == "$data_disk" ]]; then
|
||||||
|
echo "run 'make iigs-disk' to build it." >&2
|
||||||
|
else
|
||||||
|
echo "run ./toolchains/install.sh (ROM/system disk should have been staged)." >&2
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# GSplus writes back to disk images during the session; stage writable
|
||||||
|
# copies so repeated runs do not mutate the originals.
|
||||||
|
work=$(mktemp -d -t joeylib-iigs.XXXXXX)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
cp "$sys_disk" "$work/boot.po"
|
||||||
|
cp "$data_disk" "$work/joey.2mg"
|
||||||
|
|
||||||
|
target=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
||||||
|
cat <<EOF
|
||||||
|
GSplus launching GS/OS 6.0.4.
|
||||||
|
Once Finder is up:
|
||||||
|
1. Open the JOEYLIB disk on the desktop.
|
||||||
|
2. Double-click $target to run.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# GSplus auto-creates config.kegs in its cwd on first run. cd into
|
||||||
|
# the scratch dir so that file lands there and gets cleaned up with
|
||||||
|
# the rest of the temp state, rather than polluting the directory
|
||||||
|
# the user invoked the script from.
|
||||||
|
cd "$work"
|
||||||
|
# No exec: let the bash EXIT trap fire so the scratch dir (and the
|
||||||
|
# config.kegs GSplus auto-creates) gets cleaned up.
|
||||||
|
"$gsplus" -rom "$rom" -s5d1 "$work/boot.po" -s5d2 "$work/joey.2mg"
|
||||||
155
src/core/draw.c
Normal file
155
src/core/draw.c
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Drawing primitives on chunky 4bpp packed surfaces.
|
||||||
|
//
|
||||||
|
// Byte layout: two pixels per byte, high nibble is the LEFT pixel.
|
||||||
|
// Clipping is handled at the C API boundary so ASM inner loops (future)
|
||||||
|
// can run with branch-free, pre-validated parameters.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/draw.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- 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);
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if (*w <= 0 || *h <= 0) {
|
||||||
|
*outVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (*x < 0) {
|
||||||
|
*w += *x;
|
||||||
|
*x = 0;
|
||||||
|
}
|
||||||
|
if (*y < 0) {
|
||||||
|
*h += *y;
|
||||||
|
*y = 0;
|
||||||
|
}
|
||||||
|
if (*x >= SURFACE_WIDTH || *y >= SURFACE_HEIGHT) {
|
||||||
|
*outVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (*x + *w > SURFACE_WIDTH) {
|
||||||
|
*w = SURFACE_WIDTH - *x;
|
||||||
|
}
|
||||||
|
if (*y + *h > SURFACE_HEIGHT) {
|
||||||
|
*h = SURFACE_HEIGHT - *y;
|
||||||
|
}
|
||||||
|
*outVisible = (*w > 0 && *h > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void fillRectClipped(SurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex) {
|
||||||
|
uint8_t nibble = colorIndex & 0x0F;
|
||||||
|
uint8_t doubled = (uint8_t)((nibble << 4) | nibble);
|
||||||
|
int16_t row;
|
||||||
|
int16_t pxStart;
|
||||||
|
int16_t pxEnd;
|
||||||
|
int16_t midBytes;
|
||||||
|
uint8_t *line;
|
||||||
|
|
||||||
|
for (row = 0; row < h; row++) {
|
||||||
|
line = &s->pixels[(y + row) * SURFACE_BYTES_PER_ROW];
|
||||||
|
pxStart = x;
|
||||||
|
pxEnd = x + w;
|
||||||
|
|
||||||
|
if (pxStart & 1) {
|
||||||
|
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0xF0) | nibble);
|
||||||
|
pxStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
midBytes = (pxEnd - pxStart) >> 1;
|
||||||
|
if (midBytes > 0) {
|
||||||
|
memset(&line[pxStart >> 1], doubled, (size_t)midBytes);
|
||||||
|
pxStart += midBytes << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pxStart < pxEnd) {
|
||||||
|
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0x0F) | (nibble << 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
void drawPixel(SurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
||||||
|
uint8_t *byte;
|
||||||
|
uint8_t nibble;
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte = &s->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
||||||
|
nibble = colorIndex & 0x0F;
|
||||||
|
if (x & 1) {
|
||||||
|
*byte = (uint8_t)((*byte & 0xF0) | nibble);
|
||||||
|
} else {
|
||||||
|
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fillRect(SurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||||
|
int16_t sx;
|
||||||
|
int16_t sy;
|
||||||
|
int16_t sw;
|
||||||
|
int16_t sh;
|
||||||
|
bool visible;
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sx = x;
|
||||||
|
sy = y;
|
||||||
|
sw = (int16_t)w;
|
||||||
|
sh = (int16_t)h;
|
||||||
|
clipRect(&sx, &sy, &sw, &sh, &visible);
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fillRectClipped(s, sx, sy, sw, sh, colorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t samplePixel(const SurfaceT *s, int16_t x, int16_t y) {
|
||||||
|
uint8_t byte;
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte = s->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
||||||
|
if (x & 1) {
|
||||||
|
return (uint8_t)(byte & 0x0F);
|
||||||
|
}
|
||||||
|
return (uint8_t)(byte >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void surfaceClear(SurfaceT *s, uint8_t colorIndex) {
|
||||||
|
uint8_t nibble;
|
||||||
|
uint8_t doubled;
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nibble = colorIndex & 0x0F;
|
||||||
|
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||||
|
memset(s->pixels, doubled, SURFACE_PIXELS_SIZE);
|
||||||
|
}
|
||||||
37
src/core/hal.h
Normal file
37
src/core/hal.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Internal HAL (hardware abstraction layer) interface.
|
||||||
|
//
|
||||||
|
// This header is included by src/core/*.c and by per-port source under
|
||||||
|
// src/port/<platform>/. It is NOT part of the public API and must not
|
||||||
|
// be installed or exposed to game code.
|
||||||
|
//
|
||||||
|
// Each port must implement every function declared here.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_HAL_H
|
||||||
|
#define JOEYLIB_HAL_H
|
||||||
|
|
||||||
|
#include "joey/core.h"
|
||||||
|
#include "joey/surface.h"
|
||||||
|
|
||||||
|
// Per-port one-shot initialization. Called from joeyInit after config
|
||||||
|
// has been stored but before any surfaces are created. The port sets up
|
||||||
|
// the display mode, allocates any HW-adjacent buffers (chip RAM on
|
||||||
|
// Amiga, VGA mode on DOS, SHR on IIgs), and prepares for presents.
|
||||||
|
// Returns true on success. On failure, halLastError may be set.
|
||||||
|
bool halInit(const JoeyConfigT *config);
|
||||||
|
|
||||||
|
// Per-port teardown. Restores display mode, frees HW-adjacent buffers.
|
||||||
|
void halShutdown(void);
|
||||||
|
|
||||||
|
// Present the entire source surface to the display.
|
||||||
|
void halPresent(const SurfaceT *src);
|
||||||
|
|
||||||
|
// Present a rectangular region of the source surface. The caller has
|
||||||
|
// already validated and clipped the rect to be fully inside the
|
||||||
|
// surface bounds and to have positive extents.
|
||||||
|
void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||||
|
|
||||||
|
// Optional: returns a port-specific error message string for the last
|
||||||
|
// HAL failure, or NULL if none. Ports may return NULL always.
|
||||||
|
const char *halLastError(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
94
src/core/init.c
Normal file
94
src/core/init.c
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// JoeyLib lifecycle: init, shutdown, error reporting.
|
||||||
|
//
|
||||||
|
// joeyInit stores configuration, allocates the library-owned screen
|
||||||
|
// surface, and asks the port HAL to set up the display mode.
|
||||||
|
// joeyShutdown tears those down in reverse order.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/core.h"
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void clearError(void);
|
||||||
|
static void setError(const char *message);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static JoeyConfigT gConfig;
|
||||||
|
static bool gInitialized = false;
|
||||||
|
static const char *gLastError = NULL;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
static void clearError(void) {
|
||||||
|
gLastError = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void setError(const char *message) {
|
||||||
|
gLastError = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
bool joeyInit(const JoeyConfigT *config) {
|
||||||
|
clearError();
|
||||||
|
|
||||||
|
if (gInitialized) {
|
||||||
|
setError("joeyInit called while already initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == NULL) {
|
||||||
|
setError("joeyInit called with NULL config");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&gConfig, config, sizeof(gConfig));
|
||||||
|
|
||||||
|
if (!surfaceAllocScreen()) {
|
||||||
|
setError("failed to allocate screen surface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!halInit(&gConfig)) {
|
||||||
|
const char *halMsg = halLastError();
|
||||||
|
setError(halMsg != NULL ? halMsg : "halInit failed");
|
||||||
|
surfaceFreeScreen();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gInitialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *joeyLastError(void) {
|
||||||
|
return gLastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *joeyPlatformName(void) {
|
||||||
|
return JOEYLIB_PLATFORM_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void joeyShutdown(void) {
|
||||||
|
if (!gInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
halShutdown();
|
||||||
|
surfaceFreeScreen();
|
||||||
|
gInitialized = false;
|
||||||
|
clearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *joeyVersionString(void) {
|
||||||
|
return JOEYLIB_VERSION_STRING;
|
||||||
|
}
|
||||||
40
src/core/palette.c
Normal file
40
src/core/palette.c
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Palette accessors.
|
||||||
|
//
|
||||||
|
// Library contract: color 0 of every palette is forced to black ($000).
|
||||||
|
// paletteSet silently masks that entry regardless of what the caller
|
||||||
|
// provides.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/palette.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
void paletteGet(const SurfaceT *s, uint8_t paletteIndex, uint16_t *out16) {
|
||||||
|
if (s == NULL || out16 == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(out16, s->palette[paletteIndex], SURFACE_COLORS_PER_PALETTE * sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void paletteSet(SurfaceT *s, uint8_t paletteIndex, const uint16_t *colors16) {
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
if (s == NULL || colors16 == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->palette[paletteIndex][0] = 0x0000;
|
||||||
|
for (i = 1; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
s->palette[paletteIndex][i] = colors16[i] & 0x0FFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/core/present.c
Normal file
63
src/core/present.c
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Present / slam dispatcher.
|
||||||
|
//
|
||||||
|
// Validates and clips the source rectangle, then routes to the port's
|
||||||
|
// HAL implementation for the actual pixel format conversion and
|
||||||
|
// display-memory write.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "joey/present.h"
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
void surfacePresent(const SurfaceT *src) {
|
||||||
|
if (src == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
halPresent(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void surfacePresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||||
|
int16_t sx;
|
||||||
|
int16_t sy;
|
||||||
|
int16_t sw;
|
||||||
|
int16_t sh;
|
||||||
|
|
||||||
|
if (src == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sx = x;
|
||||||
|
sy = y;
|
||||||
|
sw = (int16_t)w;
|
||||||
|
sh = (int16_t)h;
|
||||||
|
|
||||||
|
if (sw <= 0 || sh <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sx < 0) {
|
||||||
|
sw += sx;
|
||||||
|
sx = 0;
|
||||||
|
}
|
||||||
|
if (sy < 0) {
|
||||||
|
sh += sy;
|
||||||
|
sy = 0;
|
||||||
|
}
|
||||||
|
if (sx >= SURFACE_WIDTH || sy >= SURFACE_HEIGHT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sx + sw > SURFACE_WIDTH) {
|
||||||
|
sw = SURFACE_WIDTH - sx;
|
||||||
|
}
|
||||||
|
if (sy + sh > SURFACE_HEIGHT) {
|
||||||
|
sh = SURFACE_HEIGHT - sy;
|
||||||
|
}
|
||||||
|
if (sw <= 0 || sh <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
halPresentRect(src, sx, sy, (uint16_t)sw, (uint16_t)sh);
|
||||||
|
}
|
||||||
57
src/core/scb.c
Normal file
57
src/core/scb.c
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Scanline control byte (SCB) accessors.
|
||||||
|
//
|
||||||
|
// Each scanline holds one uint8_t SCB value in range 0..15 selecting
|
||||||
|
// which of the 16 palettes that scanline uses at display time.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "joey/palette.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
uint8_t scbGet(const SurfaceT *s, uint16_t line) {
|
||||||
|
if (s == NULL || line >= SURFACE_HEIGHT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return s->scb[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void scbSet(SurfaceT *s, uint16_t line, uint8_t paletteIndex) {
|
||||||
|
if (s == NULL || line >= SURFACE_HEIGHT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s->scb[line] = paletteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void scbSetRange(SurfaceT *s, uint16_t firstLine, uint16_t lastLine, uint8_t paletteIndex) {
|
||||||
|
uint16_t line;
|
||||||
|
uint16_t last;
|
||||||
|
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paletteIndex >= SURFACE_PALETTE_COUNT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (firstLine >= SURFACE_HEIGHT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = lastLine;
|
||||||
|
if (last >= SURFACE_HEIGHT) {
|
||||||
|
last = SURFACE_HEIGHT - 1;
|
||||||
|
}
|
||||||
|
if (last < firstLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (line = firstLine; line <= last; line++) {
|
||||||
|
s->scb[line] = paletteIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/core/surface.c
Normal file
68
src/core/surface.c
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Surface allocation, destruction, and the library-owned screen surface.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/surface.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
// (public API declared in joey/surface.h)
|
||||||
|
// (internal prototypes declared in surfaceInternal.h)
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static SurfaceT *gScreen = NULL;
|
||||||
|
|
||||||
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
|
void surfaceCopy(SurfaceT *dst, const SurfaceT *src) {
|
||||||
|
if (dst == NULL || src == NULL || dst == src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(dst, src, sizeof(SurfaceT));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SurfaceT *surfaceCreate(void) {
|
||||||
|
SurfaceT *s = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void surfaceDestroy(SurfaceT *s) {
|
||||||
|
if (s == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s == gScreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SurfaceT *surfaceGetScreen(void) {
|
||||||
|
return gScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Internal (alphabetical) -----
|
||||||
|
|
||||||
|
bool surfaceAllocScreen(void) {
|
||||||
|
if (gScreen != NULL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
gScreen = (SurfaceT *)calloc(1, sizeof(SurfaceT));
|
||||||
|
return gScreen != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void surfaceFreeScreen(void) {
|
||||||
|
if (gScreen == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(gScreen);
|
||||||
|
gScreen = NULL;
|
||||||
|
}
|
||||||
21
src/core/surfaceInternal.h
Normal file
21
src/core/surfaceInternal.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Internal surface definition shared across core and port code.
|
||||||
|
|
||||||
|
#ifndef JOEYLIB_SURFACE_INTERNAL_H
|
||||||
|
#define JOEYLIB_SURFACE_INTERNAL_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "joey/surface.h"
|
||||||
|
|
||||||
|
struct SurfaceT {
|
||||||
|
uint8_t pixels[SURFACE_PIXELS_SIZE];
|
||||||
|
uint8_t scb[SURFACE_HEIGHT];
|
||||||
|
uint16_t palette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate and free the library's pre-allocated screen surface. Called
|
||||||
|
// from init.c during joeyInit / joeyShutdown.
|
||||||
|
bool surfaceAllocScreen(void);
|
||||||
|
void surfaceFreeScreen(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
411
src/port/amiga/hal.c
Normal file
411
src/port/amiga/hal.c
Normal file
|
|
@ -0,0 +1,411 @@
|
||||||
|
// Commodore Amiga HAL for M2 + M2.5.
|
||||||
|
//
|
||||||
|
// M2 scope:
|
||||||
|
// * OpenScreen (Intuition) for a CUSTOMSCREEN at 320x200x4 bitplanes.
|
||||||
|
// * Chunky 4bpp to 4 separate bitplanes c2p at present time.
|
||||||
|
// * Partial-rect present covers only the dirty scanlines.
|
||||||
|
//
|
||||||
|
// M2.5 scope (per-scanline palette / SCB emulation):
|
||||||
|
// * Build a user copper list that WAITs for each display scanline
|
||||||
|
// and MOVEs the 16 color registers with that line's palette.
|
||||||
|
// * Install it via ViewPort.UCopIns + MakeScreen + RethinkDisplay.
|
||||||
|
// * Rebuild on every halPresent since SCB and palettes may have
|
||||||
|
// changed anywhere.
|
||||||
|
//
|
||||||
|
// Deferred:
|
||||||
|
// * Blitter-assisted c2p for speed on A500.
|
||||||
|
// * Takeover mode (LoadView(NULL) + OwnBlitter + direct hardware).
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <exec/types.h>
|
||||||
|
#include <intuition/intuition.h>
|
||||||
|
#include <intuition/screens.h>
|
||||||
|
#include <graphics/copper.h>
|
||||||
|
#include <graphics/gfxbase.h>
|
||||||
|
#include <graphics/gfxmacros.h>
|
||||||
|
#include <graphics/displayinfo.h>
|
||||||
|
#include <graphics/modeid.h>
|
||||||
|
#include <graphics/rastport.h>
|
||||||
|
#include <graphics/view.h>
|
||||||
|
|
||||||
|
#include <hardware/custom.h>
|
||||||
|
|
||||||
|
#include <proto/exec.h>
|
||||||
|
#include <proto/intuition.h>
|
||||||
|
#include <proto/graphics.h>
|
||||||
|
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
extern struct Custom custom;
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define AMIGA_BITPLANES 4
|
||||||
|
#define AMIGA_BYTES_PER_ROW 40
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void buildCopperList(const SurfaceT *src);
|
||||||
|
static void c2pRange(const SurfaceT *src, int16_t y0, int16_t y1);
|
||||||
|
static void dumpCopperList(void);
|
||||||
|
static void installCopperList(void);
|
||||||
|
static void uploadFirstBandPalette(const SurfaceT *src);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static struct Screen *gScreen = NULL;
|
||||||
|
static struct BitMap *gBitMap = NULL;
|
||||||
|
static UBYTE *gPlanes[AMIGA_BITPLANES];
|
||||||
|
static struct UCopList *gNewUCL = NULL; // built but not yet installed
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// Convert a range of chunky scanlines [y0, y1) to Amiga planar.
|
||||||
|
// Each plane scanline is 40 bytes (1 bit per pixel x 320 pixels).
|
||||||
|
// For each destination byte, 8 pixels' worth of 4bpp chunky source is
|
||||||
|
// read and split into one bit per plane.
|
||||||
|
static void c2pRange(const SurfaceT *src, int16_t y0, int16_t y1) {
|
||||||
|
const uint8_t *srcLine;
|
||||||
|
UBYTE *p0;
|
||||||
|
UBYTE *p1;
|
||||||
|
UBYTE *p2;
|
||||||
|
UBYTE *p3;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t planarByte;
|
||||||
|
uint16_t px;
|
||||||
|
uint16_t pixel;
|
||||||
|
uint8_t srcByte;
|
||||||
|
uint8_t nibble;
|
||||||
|
uint8_t bit;
|
||||||
|
uint8_t b0;
|
||||||
|
uint8_t b1;
|
||||||
|
uint8_t b2;
|
||||||
|
uint8_t b3;
|
||||||
|
|
||||||
|
for (y = y0; y < y1; y++) {
|
||||||
|
srcLine = &src->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||||
|
p0 = &gPlanes[0][y * AMIGA_BYTES_PER_ROW];
|
||||||
|
p1 = &gPlanes[1][y * AMIGA_BYTES_PER_ROW];
|
||||||
|
p2 = &gPlanes[2][y * AMIGA_BYTES_PER_ROW];
|
||||||
|
p3 = &gPlanes[3][y * AMIGA_BYTES_PER_ROW];
|
||||||
|
|
||||||
|
for (planarByte = 0; planarByte < AMIGA_BYTES_PER_ROW; planarByte++) {
|
||||||
|
b0 = 0;
|
||||||
|
b1 = 0;
|
||||||
|
b2 = 0;
|
||||||
|
b3 = 0;
|
||||||
|
for (px = 0; px < 8; px++) {
|
||||||
|
pixel = (uint16_t)(planarByte * 8 + px);
|
||||||
|
srcByte = srcLine[pixel >> 1];
|
||||||
|
nibble = (uint8_t)((pixel & 1) ? (srcByte & 0x0F) : (srcByte >> 4));
|
||||||
|
bit = (uint8_t)(7 - px);
|
||||||
|
b0 = (uint8_t)(b0 | (((nibble >> 0) & 1) << bit));
|
||||||
|
b1 = (uint8_t)(b1 | (((nibble >> 1) & 1) << bit));
|
||||||
|
b2 = (uint8_t)(b2 | (((nibble >> 2) & 1) << bit));
|
||||||
|
b3 = (uint8_t)(b3 | (((nibble >> 3) & 1) << bit));
|
||||||
|
}
|
||||||
|
p0[planarByte] = b0;
|
||||||
|
p1[planarByte] = b1;
|
||||||
|
p2[planarByte] = b2;
|
||||||
|
p3[planarByte] = b3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Build a user copper list for per-scanline palette (SCB emulation).
|
||||||
|
// One WAIT + 16 MOVEs per displayed scanline + one CEND. The list is
|
||||||
|
// stored in gNewUCL until installCopperList swaps it onto the screen.
|
||||||
|
// DyOffset tells us where display line 0 sits in hardware coordinates
|
||||||
|
// so the WAITs line up with the real visible region regardless of
|
||||||
|
// PAL/NTSC or any overscan the user may have configured.
|
||||||
|
static void buildCopperList(const SurfaceT *src) {
|
||||||
|
struct UCopList *ucl;
|
||||||
|
UWORD line;
|
||||||
|
UWORD col;
|
||||||
|
UBYTE palIdx;
|
||||||
|
UWORD prevPalIdx;
|
||||||
|
UWORD vpos;
|
||||||
|
UWORD topBorder;
|
||||||
|
UWORD bandCount;
|
||||||
|
|
||||||
|
ucl = (struct UCopList *)AllocMem(sizeof(struct UCopList),
|
||||||
|
MEMF_PUBLIC | MEMF_CLEAR);
|
||||||
|
if (ucl == NULL) {
|
||||||
|
gNewUCL = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worst-case reservation is one band-change per scanline (16 MOVEs
|
||||||
|
// + 1 WAIT per change), plus the terminal wait. For realistic SCB
|
||||||
|
// tables the actual count is far smaller, but CINIT only takes a
|
||||||
|
// single number so we size for the cap.
|
||||||
|
CINIT(ucl, (SURFACE_HEIGHT * 17) + 1);
|
||||||
|
|
||||||
|
// Hardware scanline where display line 0 lives. 0x2C is the
|
||||||
|
// standard top border for a PAL screen at TopEdge=0; we hardcode
|
||||||
|
// rather than reading ViewPort.DyOffset because DyOffset is a
|
||||||
|
// signed +/- adjustment around the standard value, not the
|
||||||
|
// absolute hardware line.
|
||||||
|
// User-copper vpos values are DISPLAY-RELATIVE -- graphics.lib
|
||||||
|
// MrgCop adds the active View's DyOffset to each WAIT before
|
||||||
|
// emitting, so a vp=0 user WAIT lands at beam line DyOffset,
|
||||||
|
// which is where Intuition places display line 0. Emitting at
|
||||||
|
// vpos=line keeps merged vpos under 256 even for the last band
|
||||||
|
// (175 + 44 = 219 < 256), avoiding MrgCop's destructive wrap-
|
||||||
|
// handling path that would otherwise disable bitplane DMA at
|
||||||
|
// the viewport end.
|
||||||
|
topBorder = 0;
|
||||||
|
prevPalIdx = 0xFFFF;
|
||||||
|
bandCount = 0;
|
||||||
|
|
||||||
|
for (line = 0; line < SURFACE_HEIGHT; line++) {
|
||||||
|
palIdx = src->scb[line];
|
||||||
|
if (palIdx >= SURFACE_PALETTE_COUNT) {
|
||||||
|
palIdx = 0;
|
||||||
|
}
|
||||||
|
if ((UWORD)palIdx == prevPalIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vpos = (UWORD)(line + topBorder);
|
||||||
|
CWAIT(ucl, vpos, 0);
|
||||||
|
for (col = 0; col < SURFACE_COLORS_PER_PALETTE; col++) {
|
||||||
|
CMOVE(ucl, custom.color[col], src->palette[palIdx][col]);
|
||||||
|
}
|
||||||
|
prevPalIdx = (UWORD)palIdx;
|
||||||
|
bandCount++;
|
||||||
|
}
|
||||||
|
(void)bandCount;
|
||||||
|
CEND(ucl);
|
||||||
|
|
||||||
|
gNewUCL = ucl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Swap the freshly built user copper list onto the screen's ViewPort
|
||||||
|
// and force a full graphics-library recomputation of the hardware
|
||||||
|
// copper list. MakeScreen regenerates the viewport copper to include
|
||||||
|
// our UCopIns; MrgCop merges every viewport's copper into one hardware
|
||||||
|
// list; LoadView swaps the live copper pointers. Calling the graphics
|
||||||
|
// primitives directly (rather than only Intuition's RethinkDisplay /
|
||||||
|
// RemakeDisplay) was observed here to be the step that actually makes
|
||||||
|
// the user copper list visible -- Intuition's wrappers sometimes
|
||||||
|
// skipped the merge.
|
||||||
|
static void installCopperList(void) {
|
||||||
|
struct View *view;
|
||||||
|
|
||||||
|
if (gNewUCL == NULL || gScreen == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Forbid();
|
||||||
|
if (gScreen->ViewPort.UCopIns != NULL) {
|
||||||
|
FreeVPortCopLists(&gScreen->ViewPort);
|
||||||
|
}
|
||||||
|
gScreen->ViewPort.UCopIns = gNewUCL;
|
||||||
|
gNewUCL = NULL;
|
||||||
|
Permit();
|
||||||
|
|
||||||
|
MakeScreen(gScreen);
|
||||||
|
|
||||||
|
view = ViewAddress();
|
||||||
|
Forbid();
|
||||||
|
MrgCop(view);
|
||||||
|
LoadView(view);
|
||||||
|
Permit();
|
||||||
|
WaitTOF();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Diagnostic: dump the merged hardware copper list (LOFCprList) to a
|
||||||
|
// text file on the current volume. Written once per halPresent after
|
||||||
|
// MrgCop, so the host can inspect exactly what the copper is being
|
||||||
|
// asked to execute. Each line is either MOVE (destination offset +
|
||||||
|
// data) or WAIT (vp, hp, mask). The dump stops at the first "end of
|
||||||
|
// copper" marker (0xFFFF + any mask with bit15 clear would be a wait
|
||||||
|
// past frame end).
|
||||||
|
static void dumpCopperList(void) {
|
||||||
|
FILE *fp;
|
||||||
|
struct View *view;
|
||||||
|
struct cprlist *cl;
|
||||||
|
UWORD *p;
|
||||||
|
WORD i;
|
||||||
|
WORD count;
|
||||||
|
UWORD w1;
|
||||||
|
UWORD w2;
|
||||||
|
|
||||||
|
fp = fopen("copper.txt", "w");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
view = ViewAddress();
|
||||||
|
if (view == NULL) {
|
||||||
|
fprintf(fp, "view is NULL\n");
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cl = view->LOFCprList;
|
||||||
|
if (cl == NULL) {
|
||||||
|
fprintf(fp, "LOFCprList is NULL\n");
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = cl->start;
|
||||||
|
count = cl->MaxCount;
|
||||||
|
fprintf(fp, "LOFCprList.start=0x%08lx MaxCount=%d\n",
|
||||||
|
(unsigned long)p, (int)count);
|
||||||
|
fprintf(fp, "vp.DyOffset=%d vp.DxOffset=%d\n",
|
||||||
|
(int)gScreen->ViewPort.DyOffset,
|
||||||
|
(int)gScreen->ViewPort.DxOffset);
|
||||||
|
fprintf(fp, "view.DyOffset=%d view.DxOffset=%d\n",
|
||||||
|
(int)view->DyOffset, (int)view->DxOffset);
|
||||||
|
fprintf(fp, "--\n");
|
||||||
|
|
||||||
|
if (p == NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
w1 = p[i * 2];
|
||||||
|
w2 = p[i * 2 + 1];
|
||||||
|
if (w1 == 0xFFFF && w2 == 0xFFFE) {
|
||||||
|
fprintf(fp, "%4d: END %04x %04x\n", (int)i, w1, w2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (w1 & 1) {
|
||||||
|
fprintf(fp, "%4d: %s vp=%3d hp=%3d mask=%04x\n",
|
||||||
|
(int)i,
|
||||||
|
(w2 & 0x8000) ? "SKIP" : "WAIT",
|
||||||
|
(int)(w1 >> 8),
|
||||||
|
(int)(w1 & 0xFE),
|
||||||
|
(unsigned)w2);
|
||||||
|
} else {
|
||||||
|
fprintf(fp, "%4d: MOVE dst=%03x data=%04x\n",
|
||||||
|
(int)i,
|
||||||
|
(unsigned)(w1 & 0x1FE),
|
||||||
|
(unsigned)w2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Load the first band's palette into the screen's ColorMap so the
|
||||||
|
// Intuition-generated frame-start copper writes those values on each
|
||||||
|
// frame. This acts as a safety net: even if our user copper list does
|
||||||
|
// not fire (or fires late) for the very first band, the top of the
|
||||||
|
// display still shows the correct colors because Intuition's own
|
||||||
|
// COLORxx loads happen before any user copper instruction.
|
||||||
|
static void uploadFirstBandPalette(const SurfaceT *src) {
|
||||||
|
UWORD aPalette[SURFACE_COLORS_PER_PALETTE];
|
||||||
|
UWORD i;
|
||||||
|
UBYTE palIdx;
|
||||||
|
|
||||||
|
palIdx = src->scb[0];
|
||||||
|
if (palIdx >= SURFACE_PALETTE_COUNT) {
|
||||||
|
palIdx = 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
aPalette[i] = (UWORD)src->palette[palIdx][i];
|
||||||
|
}
|
||||||
|
LoadRGB4(&gScreen->ViewPort, aPalette, SURFACE_COLORS_PER_PALETTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
bool halInit(const JoeyConfigT *config) {
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
(void)config;
|
||||||
|
|
||||||
|
// SA_DisplayID pins us to OCS PAL low-res so Intuition opens a
|
||||||
|
// real planar screen rather than an RTG substitute.
|
||||||
|
gScreen = OpenScreenTags(NULL,
|
||||||
|
(ULONG)SA_Width, (ULONG)SURFACE_WIDTH,
|
||||||
|
(ULONG)SA_Height, (ULONG)SURFACE_HEIGHT,
|
||||||
|
(ULONG)SA_Depth, (ULONG)AMIGA_BITPLANES,
|
||||||
|
(ULONG)SA_DisplayID, (ULONG)(PAL_MONITOR_ID | LORES_KEY),
|
||||||
|
(ULONG)SA_DetailPen, (ULONG)0,
|
||||||
|
(ULONG)SA_BlockPen, (ULONG)1,
|
||||||
|
(ULONG)SA_Title, (ULONG)"JoeyLib",
|
||||||
|
(ULONG)SA_Type, (ULONG)CUSTOMSCREEN,
|
||||||
|
(ULONG)SA_Quiet, (ULONG)TRUE,
|
||||||
|
TAG_DONE);
|
||||||
|
|
||||||
|
if (gScreen == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gBitMap = gScreen->RastPort.BitMap;
|
||||||
|
for (i = 0; i < AMIGA_BITPLANES; i++) {
|
||||||
|
gPlanes[i] = gBitMap->Planes[i];
|
||||||
|
if (gPlanes[i] == NULL) {
|
||||||
|
CloseScreen(gScreen);
|
||||||
|
gScreen = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *halLastError(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresent(const SurfaceT *src) {
|
||||||
|
if (src == NULL || gScreen == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadFirstBandPalette(src);
|
||||||
|
buildCopperList(src);
|
||||||
|
installCopperList();
|
||||||
|
dumpCopperList();
|
||||||
|
c2pRange(src, 0, SURFACE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||||
|
(void)x;
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
if (src == NULL || gScreen == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Whole-scanline c2p for M2; rebuild the entire copper list since
|
||||||
|
// SCB entries outside the rect may have changed too.
|
||||||
|
uploadFirstBandPalette(src);
|
||||||
|
buildCopperList(src);
|
||||||
|
installCopperList();
|
||||||
|
c2pRange(src, y, y + (int16_t)h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halShutdown(void) {
|
||||||
|
if (gScreen != NULL) {
|
||||||
|
// CloseScreen should free attached UCopList, but be explicit
|
||||||
|
// to catch any case where the screen close path skips it.
|
||||||
|
Forbid();
|
||||||
|
if (gScreen->ViewPort.UCopIns != NULL) {
|
||||||
|
FreeVPortCopLists(&gScreen->ViewPort);
|
||||||
|
}
|
||||||
|
Permit();
|
||||||
|
CloseScreen(gScreen);
|
||||||
|
gScreen = NULL;
|
||||||
|
gBitMap = NULL;
|
||||||
|
}
|
||||||
|
if (gNewUCL != NULL) {
|
||||||
|
FreeMem(gNewUCL, sizeof(struct UCopList));
|
||||||
|
gNewUCL = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/port/amiga/libinit.c
Normal file
83
src/port/amiga/libinit.c
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Tolerant replacement for libnix's __initlibraries / __exitlibraries.
|
||||||
|
//
|
||||||
|
// libnix's version treats any OpenLibrary failure as fatal: it walks the
|
||||||
|
// auto-open list, and if any entry cannot be opened, it writes
|
||||||
|
// "<name> failed to load" to the CLI and exits(20) before main() runs.
|
||||||
|
//
|
||||||
|
// That behavior kills the examples on AROS, which does not provide
|
||||||
|
// locale.library as a disk library. libnix unconditionally pulls the
|
||||||
|
// locale.library entry into the auto-open list because C runtime
|
||||||
|
// utilities (e.g. tzset via time()) take LocaleBase as an argument to
|
||||||
|
// ConvToLower -- even when the runtime does not actually need a real
|
||||||
|
// Locale to run.
|
||||||
|
//
|
||||||
|
// Our replacement walks the same list but records NULL for any
|
||||||
|
// library that cannot be opened, then continues. Code that actually
|
||||||
|
// needs the library will crash when it dereferences the NULL base,
|
||||||
|
// but none of the joeylib examples do. Since these symbols are
|
||||||
|
// defined here in a user object, the linker satisfies references
|
||||||
|
// from this file instead of pulling in libnix's version.
|
||||||
|
|
||||||
|
#include <proto/exec.h>
|
||||||
|
#include <stabs.h>
|
||||||
|
|
||||||
|
extern long __LIB_LIST__;
|
||||||
|
|
||||||
|
|
||||||
|
static int endsWithResource(const char *name) {
|
||||||
|
const char *p = name;
|
||||||
|
while (*p) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
p -= 9;
|
||||||
|
if (p < name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return p[0] == '.' && p[1] == 'r' && p[2] == 'e' && p[3] == 's' &&
|
||||||
|
p[4] == 'o' && p[5] == 'u' && p[6] == 'r' && p[7] == 'c' &&
|
||||||
|
p[8] == 'e';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void __initlibraries(void) {
|
||||||
|
long *entry = &__LIB_LIST__ + 1;
|
||||||
|
while (*entry) {
|
||||||
|
long *base = entry++;
|
||||||
|
const char *name = *(const char **)entry++;
|
||||||
|
long lib;
|
||||||
|
|
||||||
|
if (endsWithResource(name)) {
|
||||||
|
lib = (long)OpenResource((STRPTR)name);
|
||||||
|
} else {
|
||||||
|
lib = (long)OldOpenLibrary((STRPTR)name);
|
||||||
|
}
|
||||||
|
*base = lib;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void __exitlibraries(void) {
|
||||||
|
long *entry = &__LIB_LIST__ + 1;
|
||||||
|
while (*entry) {
|
||||||
|
long *base = entry++;
|
||||||
|
const char *name = *(const char **)entry++;
|
||||||
|
if (*base != 0 && *base != -1 && !endsWithResource(name)) {
|
||||||
|
CloseLibrary((struct Library *)*base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ADD2INIT(__initlibraries, -79);
|
||||||
|
ADD2EXIT(__exitlibraries, -79);
|
||||||
|
|
||||||
|
|
||||||
|
// libnix's tzset dereferences LocaleBase to call locale.library's
|
||||||
|
// ConvToLower. On AROS (where locale.library is unavailable), our
|
||||||
|
// tolerant __initlibraries leaves LocaleBase NULL, which would crash
|
||||||
|
// the first time time() is called (via tzset). Our pattern example
|
||||||
|
// uses time() for the 5-second display; override tzset with a no-op
|
||||||
|
// so time() can run. Difference-based timing is unaffected because
|
||||||
|
// any DST correction applied consistently cancels out.
|
||||||
|
void tzset(void) {
|
||||||
|
}
|
||||||
479
src/port/atarist/hal.c
Normal file
479
src/port/atarist/hal.c
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
// Atari ST HAL for M2 + M2.5.
|
||||||
|
//
|
||||||
|
// M2 scope:
|
||||||
|
// * XBIOS Setscreen to ST low-res (320x200x16, mode 0).
|
||||||
|
// * Chunky 4bpp to word-interleaved ST planar c2p at present time.
|
||||||
|
//
|
||||||
|
// M2.5 scope (per-band palette / SCB emulation):
|
||||||
|
// * halPresent scans the SurfaceT's SCB array and builds a compact
|
||||||
|
// transitions table: each entry is (start_line, palette_index)
|
||||||
|
// for a new palette region. For pattern.c's 8 uniform bands this
|
||||||
|
// is 8 entries; in the worst case it is 200 (one per scanline).
|
||||||
|
// * VBL ISR pre-loads the first band's palette, then programs
|
||||||
|
// MFP Timer B (event-count mode, TBDR = HBL delta to first
|
||||||
|
// transition) to fire at the END of the last scanline before
|
||||||
|
// the next band starts.
|
||||||
|
// * Timer B ISR writes the current band's palette, advances the
|
||||||
|
// transition index, and (stop/reload TBDR/restart) reprograms
|
||||||
|
// Timer B to fire at the next transition. With 8 transitions per
|
||||||
|
// frame the ISR runs 8 times instead of 313 -- well under the
|
||||||
|
// ~147-HBL-fires-per-frame cap Hatari's MFP emulation imposes on
|
||||||
|
// event-count mode, and ~0.2% CPU overhead vs ~60% for per-HBL.
|
||||||
|
// * gLinePalettes is a flat pre-quantized (line, color)->$0RGB
|
||||||
|
// table built in halPresent by flattenScbPalettes; the ISR uses
|
||||||
|
// its first row per band as the source of 16 shifter writes.
|
||||||
|
//
|
||||||
|
// Deferred:
|
||||||
|
// * Takeover mode (direct shifter programming without TOS).
|
||||||
|
// * STE's extended palette bits (we drop to STF 9-bit for now).
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <mint/osbind.h>
|
||||||
|
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
// Word-interleaved ST planar uses the same 160 bytes/scanline as our
|
||||||
|
// chunky source, but organized as 20 groups of 4 words per scanline,
|
||||||
|
// with each word holding the 16 one-bit samples for one bitplane.
|
||||||
|
#define ST_BYTES_PER_ROW 160
|
||||||
|
#define ST_GROUPS_PER_ROW 20
|
||||||
|
#define ST_SCREEN_ALIGN 256
|
||||||
|
|
||||||
|
// Shifter palette registers: 16 words at $FFFF8240..$FFFF825F.
|
||||||
|
#define ST_PALETTE_REGS ((volatile uint16_t *)0xFFFF8240L)
|
||||||
|
|
||||||
|
// MFP hardware addresses.
|
||||||
|
#define ST_MFP_TBCR ((volatile uint8_t *)0xFFFFFA1BL) // Timer B control
|
||||||
|
#define ST_MFP_TBDR ((volatile uint8_t *)0xFFFFFA21L) // Timer B data
|
||||||
|
#define ST_MFP_ISRA ((volatile uint8_t *)0xFFFFFA0FL) // In-service A
|
||||||
|
#define MFP_TBCR_STOP 0x00
|
||||||
|
#define MFP_TBCR_EVENT 0x08
|
||||||
|
#define MFP_TB_CLEAR 0xFE // clear bit 0 of ISRA (Timer B)
|
||||||
|
|
||||||
|
// Exception-vector numbers passed to Setexc (= vector offset / 4).
|
||||||
|
#define VEC_VBL (0x70 / 4) // 68k autovector IRQ 4
|
||||||
|
#define VEC_MFP_TB (0x120 / 4) // MFP Timer B
|
||||||
|
#define INT_TIMER_B 8
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static uint16_t quantizeColorToSt(uint16_t orgb);
|
||||||
|
static void c2pRow(const uint8_t *src, uint16_t *dst);
|
||||||
|
static void c2pRange(const SurfaceT *src, int16_t y0, int16_t y1);
|
||||||
|
static void flattenScbPalettes(const SurfaceT *src);
|
||||||
|
static void writeDiagnostics(void);
|
||||||
|
static long writePrevPaletteRegs(void);
|
||||||
|
|
||||||
|
static __attribute__((interrupt_handler)) void timerBIsr(void);
|
||||||
|
static __attribute__((interrupt_handler)) void vblIsr(void);
|
||||||
|
static void buildTransitions(const SurfaceT *src);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
// Screen buffer: enough for 320x200x4bpp planar plus padding for
|
||||||
|
// runtime 256-byte alignment. TOS .PRG format only supports 2-byte
|
||||||
|
// object-file alignment, so we overallocate and align the pointer
|
||||||
|
// manually in halInit.
|
||||||
|
static uint8_t gScreenBuffer[SURFACE_PIXELS_SIZE + ST_SCREEN_ALIGN];
|
||||||
|
|
||||||
|
static uint8_t *gScreenBase = NULL;
|
||||||
|
static void *gPrevPhysbase = NULL;
|
||||||
|
static void *gPrevLogbase = NULL;
|
||||||
|
static int16_t gPrevRez = 0;
|
||||||
|
static uint16_t gPrevPalette[SURFACE_COLORS_PER_PALETTE];
|
||||||
|
static bool gModeSet = false;
|
||||||
|
|
||||||
|
// Per-scanline pre-quantized palette table. Indexed by display line;
|
||||||
|
// each row is a 16-word palette ready to be copied straight into the
|
||||||
|
// shifter registers. Written at present() time, read by the Timer B
|
||||||
|
// ISR with no CPU-side math beyond a counter subtract.
|
||||||
|
static uint16_t gLinePalettes[SURFACE_HEIGHT][SURFACE_COLORS_PER_PALETTE];
|
||||||
|
|
||||||
|
// Band-transition table. Each entry is one palette change: at
|
||||||
|
// display line gBandStart[i], load palette indexed by gBandPalIdx[i].
|
||||||
|
// Built once per halPresent from the SurfaceT's SCB array.
|
||||||
|
#define MAX_BANDS SURFACE_HEIGHT
|
||||||
|
static uint16_t gBandStart [MAX_BANDS];
|
||||||
|
static uint8_t gBandPalIdx[MAX_BANDS];
|
||||||
|
static uint16_t gBandCount = 0;
|
||||||
|
|
||||||
|
// Index of the band the Timer B ISR is currently scheduling TO. At
|
||||||
|
// VBL this is 0 (band 0 palette pre-loaded, Timer B scheduled to
|
||||||
|
// fire when it's time to transition to band 1). Each ISR fire writes
|
||||||
|
// the palette for gCurrentBand and advances to the next.
|
||||||
|
static volatile uint16_t gCurrentBand = 0;
|
||||||
|
|
||||||
|
// Diagnostic captures.
|
||||||
|
static volatile int16_t gFrameCount = 0;
|
||||||
|
static volatile uint16_t gLastBandCount = 0;
|
||||||
|
|
||||||
|
// Saved exception vectors for restore on shutdown.
|
||||||
|
static void (*gOldVblVec)(void) = NULL;
|
||||||
|
static void (*gOldTimerBVec)(void) = NULL;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// Convert 16 chunky pixels (8 bytes 4bpp packed) to 4 ST planar words.
|
||||||
|
// The output word order is plane 0, 1, 2, 3 (low bit = plane 0).
|
||||||
|
static void c2pRow(const uint8_t *src, uint16_t *dst) {
|
||||||
|
uint16_t group;
|
||||||
|
uint16_t px;
|
||||||
|
uint16_t plane0;
|
||||||
|
uint16_t plane1;
|
||||||
|
uint16_t plane2;
|
||||||
|
uint16_t plane3;
|
||||||
|
uint8_t byte;
|
||||||
|
uint8_t nibble;
|
||||||
|
uint16_t bit;
|
||||||
|
|
||||||
|
for (group = 0; group < ST_GROUPS_PER_ROW; group++) {
|
||||||
|
plane0 = 0;
|
||||||
|
plane1 = 0;
|
||||||
|
plane2 = 0;
|
||||||
|
plane3 = 0;
|
||||||
|
|
||||||
|
for (px = 0; px < 16; px++) {
|
||||||
|
byte = src[(group * 8) + (px >> 1)];
|
||||||
|
nibble = (uint8_t)((px & 1) ? (byte & 0x0F) : (byte >> 4));
|
||||||
|
bit = (uint16_t)(15 - px);
|
||||||
|
plane0 = (uint16_t)(plane0 | (((nibble >> 0) & 1) << bit));
|
||||||
|
plane1 = (uint16_t)(plane1 | (((nibble >> 1) & 1) << bit));
|
||||||
|
plane2 = (uint16_t)(plane2 | (((nibble >> 2) & 1) << bit));
|
||||||
|
plane3 = (uint16_t)(plane3 | (((nibble >> 3) & 1) << bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[(group * 4) + 0] = plane0;
|
||||||
|
dst[(group * 4) + 1] = plane1;
|
||||||
|
dst[(group * 4) + 2] = plane2;
|
||||||
|
dst[(group * 4) + 3] = plane3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void c2pRange(const SurfaceT *src, int16_t y0, int16_t y1) {
|
||||||
|
int16_t y;
|
||||||
|
const uint8_t *srcLine;
|
||||||
|
uint16_t *dstLine;
|
||||||
|
|
||||||
|
for (y = y0; y < y1; y++) {
|
||||||
|
srcLine = &src->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||||
|
dstLine = (uint16_t *)&gScreenBase[y * ST_BYTES_PER_ROW];
|
||||||
|
c2pRow(srcLine, dstLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Scan the surface's SCB and record one transition entry for each
|
||||||
|
// run of the same palette index. gBandCount is the number of
|
||||||
|
// distinct bands; gBandStart[i] is the display line where band i
|
||||||
|
// begins; gBandPalIdx[i] is the palette index that band uses.
|
||||||
|
static void buildTransitions(const SurfaceT *src) {
|
||||||
|
uint16_t line;
|
||||||
|
uint8_t idx;
|
||||||
|
uint8_t prev;
|
||||||
|
|
||||||
|
gBandCount = 0;
|
||||||
|
prev = 0xFF;
|
||||||
|
|
||||||
|
for (line = 0; line < SURFACE_HEIGHT; line++) {
|
||||||
|
idx = src->scb[line];
|
||||||
|
if (idx >= SURFACE_PALETTE_COUNT) {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
if (idx != prev) {
|
||||||
|
if (gBandCount < MAX_BANDS) {
|
||||||
|
gBandStart [gBandCount] = line;
|
||||||
|
gBandPalIdx[gBandCount] = idx;
|
||||||
|
gBandCount++;
|
||||||
|
}
|
||||||
|
prev = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gLastBandCount = gBandCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pre-quantize every palette row indexed by scanline through the SCB
|
||||||
|
// into gLinePalettes, so the Timer B ISR can do a flat indexed copy
|
||||||
|
// without any surface-level lookups. Called once per halPresent.
|
||||||
|
static void flattenScbPalettes(const SurfaceT *src) {
|
||||||
|
uint16_t line;
|
||||||
|
uint16_t col;
|
||||||
|
uint8_t idx;
|
||||||
|
|
||||||
|
for (line = 0; line < SURFACE_HEIGHT; line++) {
|
||||||
|
idx = src->scb[line];
|
||||||
|
if (idx >= SURFACE_PALETTE_COUNT) {
|
||||||
|
idx = 0;
|
||||||
|
}
|
||||||
|
for (col = 0; col < SURFACE_COLORS_PER_PALETTE; col++) {
|
||||||
|
gLinePalettes[line][col] = quantizeColorToSt(src->palette[idx][col]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 12-bit $0RGB to STF 9-bit palette register (drops the low bit of
|
||||||
|
// each 4-bit channel).
|
||||||
|
static uint16_t quantizeColorToSt(uint16_t orgb) {
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t g;
|
||||||
|
uint16_t b;
|
||||||
|
|
||||||
|
r = (orgb >> 8) & 0x0F;
|
||||||
|
g = (orgb >> 4) & 0x0F;
|
||||||
|
b = orgb & 0x0F;
|
||||||
|
r = r >> 1;
|
||||||
|
g = g >> 1;
|
||||||
|
b = b >> 1;
|
||||||
|
return (uint16_t)((r << 8) | (g << 4) | b);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Timer B interrupt handler. Fires once at each band transition;
|
||||||
|
// writes the band's palette to the shifter and lets Timer B's
|
||||||
|
// auto-reload keep counting for the next fire. We deliberately do
|
||||||
|
// NOT stop/reload/restart the timer here: that sequence would cost
|
||||||
|
// 1-2 HBL edges each fire, and those losses compound across 7+
|
||||||
|
// transitions into a visible "last band short" drift. Updating
|
||||||
|
// TBDR in place is enough for variable-length bands -- the new
|
||||||
|
// value takes effect on the fire AFTER next, which is acceptable
|
||||||
|
// when adjacent bands have similar lengths; uniform bands (like
|
||||||
|
// pattern.c) don't need TBDR updates at all so stay perfectly
|
||||||
|
// aligned with no drift.
|
||||||
|
static void timerBIsr(void) {
|
||||||
|
uint16_t band;
|
||||||
|
uint8_t palIdx;
|
||||||
|
const uint16_t *src;
|
||||||
|
volatile uint16_t *dst;
|
||||||
|
uint16_t nextDelta;
|
||||||
|
|
||||||
|
band = gCurrentBand + 1;
|
||||||
|
gCurrentBand = band;
|
||||||
|
|
||||||
|
if (band < gBandCount) {
|
||||||
|
palIdx = gBandPalIdx[band];
|
||||||
|
if (palIdx >= SURFACE_PALETTE_COUNT) {
|
||||||
|
palIdx = 0;
|
||||||
|
}
|
||||||
|
src = &gLinePalettes[gBandStart[band]][0];
|
||||||
|
dst = ST_PALETTE_REGS;
|
||||||
|
dst[ 0] = src[ 0];
|
||||||
|
dst[ 1] = src[ 1];
|
||||||
|
dst[ 2] = src[ 2];
|
||||||
|
dst[ 3] = src[ 3];
|
||||||
|
dst[ 4] = src[ 4];
|
||||||
|
dst[ 5] = src[ 5];
|
||||||
|
dst[ 6] = src[ 6];
|
||||||
|
dst[ 7] = src[ 7];
|
||||||
|
dst[ 8] = src[ 8];
|
||||||
|
dst[ 9] = src[ 9];
|
||||||
|
dst[10] = src[10];
|
||||||
|
dst[11] = src[11];
|
||||||
|
dst[12] = src[12];
|
||||||
|
dst[13] = src[13];
|
||||||
|
dst[14] = src[14];
|
||||||
|
dst[15] = src[15];
|
||||||
|
|
||||||
|
if (band + 1 < gBandCount) {
|
||||||
|
// Update TBDR for the fire-after-next (auto-reload at
|
||||||
|
// the NEXT fire still uses the old value). Don't stop
|
||||||
|
// the timer.
|
||||||
|
nextDelta = gBandStart[band + 1] - gBandStart[band];
|
||||||
|
if (nextDelta == 0 || nextDelta > 255) {
|
||||||
|
nextDelta = 1;
|
||||||
|
}
|
||||||
|
*ST_MFP_TBDR = (uint8_t)nextDelta;
|
||||||
|
*ST_MFP_ISRA = MFP_TB_CLEAR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No further transitions this frame; stopping Timer B here only
|
||||||
|
// affects the (never-used) next fire, not timing of any band
|
||||||
|
// we've already scheduled.
|
||||||
|
*ST_MFP_TBCR = MFP_TBCR_STOP;
|
||||||
|
*ST_MFP_ISRA = MFP_TB_CLEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Vertical blank handler. Pre-loads band 0's palette so the first
|
||||||
|
// visible scanline is correct, then programs Timer B to fire at
|
||||||
|
// the HBL delta to the next band transition (if any).
|
||||||
|
static void vblIsr(void) {
|
||||||
|
uint16_t delta;
|
||||||
|
const uint16_t *src;
|
||||||
|
volatile uint16_t *dst;
|
||||||
|
|
||||||
|
gFrameCount = gFrameCount + 1;
|
||||||
|
gCurrentBand = 0;
|
||||||
|
|
||||||
|
if (gBandCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage band 0's palette into the shifter registers.
|
||||||
|
src = &gLinePalettes[gBandStart[0]][0];
|
||||||
|
dst = ST_PALETTE_REGS;
|
||||||
|
dst[ 0] = src[ 0];
|
||||||
|
dst[ 1] = src[ 1];
|
||||||
|
dst[ 2] = src[ 2];
|
||||||
|
dst[ 3] = src[ 3];
|
||||||
|
dst[ 4] = src[ 4];
|
||||||
|
dst[ 5] = src[ 5];
|
||||||
|
dst[ 6] = src[ 6];
|
||||||
|
dst[ 7] = src[ 7];
|
||||||
|
dst[ 8] = src[ 8];
|
||||||
|
dst[ 9] = src[ 9];
|
||||||
|
dst[10] = src[10];
|
||||||
|
dst[11] = src[11];
|
||||||
|
dst[12] = src[12];
|
||||||
|
dst[13] = src[13];
|
||||||
|
dst[14] = src[14];
|
||||||
|
dst[15] = src[15];
|
||||||
|
|
||||||
|
// Program Timer B for the next band transition.
|
||||||
|
if (gBandCount > 1) {
|
||||||
|
delta = gBandStart[1] - gBandStart[0];
|
||||||
|
if (delta == 0 || delta > 255) {
|
||||||
|
delta = 1;
|
||||||
|
}
|
||||||
|
*ST_MFP_TBCR = MFP_TBCR_STOP;
|
||||||
|
*ST_MFP_TBDR = (uint8_t)delta;
|
||||||
|
*ST_MFP_ISRA = MFP_TB_CLEAR;
|
||||||
|
*ST_MFP_TBCR = MFP_TBCR_EVENT;
|
||||||
|
} else {
|
||||||
|
*ST_MFP_TBCR = MFP_TBCR_STOP;
|
||||||
|
*ST_MFP_ISRA = MFP_TB_CLEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static long writePrevPaletteRegs(void) {
|
||||||
|
uint16_t i;
|
||||||
|
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
ST_PALETTE_REGS[i] = gPrevPalette[i];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void writeDiagnostics(void) {
|
||||||
|
FILE *fp;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
fp = fopen("diag.txt", "w");
|
||||||
|
if (fp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fprintf(fp, "frames observed: %d\n", (int)gFrameCount);
|
||||||
|
fprintf(fp, "band count: %d\n", (int)gLastBandCount);
|
||||||
|
for (i = 0; i < gLastBandCount && i < 16; i++) {
|
||||||
|
fprintf(fp, " band %2d: start line %3d, palIdx %d\n",
|
||||||
|
(int)i, (int)gBandStart[i], (int)gBandPalIdx[i]);
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
bool halInit(const JoeyConfigT *config) {
|
||||||
|
uintptr_t addr;
|
||||||
|
|
||||||
|
(void)config;
|
||||||
|
|
||||||
|
// Align screen buffer to 256 bytes inside the static storage.
|
||||||
|
addr = (uintptr_t)gScreenBuffer;
|
||||||
|
addr = (addr + (ST_SCREEN_ALIGN - 1)) & ~((uintptr_t)ST_SCREEN_ALIGN - 1);
|
||||||
|
gScreenBase = (uint8_t *)addr;
|
||||||
|
|
||||||
|
memset(gScreenBase, 0, SURFACE_PIXELS_SIZE);
|
||||||
|
|
||||||
|
gPrevPhysbase = Physbase();
|
||||||
|
gPrevLogbase = Logbase();
|
||||||
|
gPrevRez = Getrez();
|
||||||
|
|
||||||
|
// Capture current palette so we can restore exactly on shutdown.
|
||||||
|
{
|
||||||
|
uint16_t i;
|
||||||
|
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
gPrevPalette[i] = (uint16_t)Setcolor((int16_t)i, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to ST low-res: 320x200x16, mode 0.
|
||||||
|
Setscreen((long)gScreenBase, (long)gScreenBase, 0);
|
||||||
|
gModeSet = true;
|
||||||
|
|
||||||
|
// Save previous VBL + Timer B vectors, install ours. Timer B
|
||||||
|
// is at MFP vector $120; vector installed by Xbtimer below.
|
||||||
|
gOldVblVec = (void (*)(void))Setexc(VEC_VBL, -1L);
|
||||||
|
gOldTimerBVec = (void (*)(void))Setexc(VEC_MFP_TB, -1L);
|
||||||
|
(void)Setexc(VEC_VBL, (long)vblIsr);
|
||||||
|
|
||||||
|
// Program MFP Timer B: event-count (HBL) mode, initial TBDR=1
|
||||||
|
// (a placeholder -- VBL ISR reprograms it for the first real
|
||||||
|
// transition per frame), vector=timerBIsr, then enable in IMRA.
|
||||||
|
Xbtimer(1, MFP_TBCR_EVENT, 1, timerBIsr);
|
||||||
|
Jenabint(INT_TIMER_B);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *halLastError(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresent(const SurfaceT *src) {
|
||||||
|
if (src == NULL || !gModeSet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flattenScbPalettes(src);
|
||||||
|
buildTransitions(src);
|
||||||
|
c2pRange(src, 0, SURFACE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||||
|
(void)x;
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
if (src == NULL || !gModeSet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flattenScbPalettes(src);
|
||||||
|
buildTransitions(src);
|
||||||
|
c2pRange(src, y, y + (int16_t)h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halShutdown(void) {
|
||||||
|
if (!gModeSet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable MFP Timer B and restore the exception vectors before
|
||||||
|
// changing the screen -- a late ISR firing mid-Setscreen would
|
||||||
|
// write palette into whatever buffer TOS remapped.
|
||||||
|
Jdisint(INT_TIMER_B);
|
||||||
|
if (gOldTimerBVec != NULL) {
|
||||||
|
(void)Setexc(VEC_MFP_TB, (long)gOldTimerBVec);
|
||||||
|
}
|
||||||
|
if (gOldVblVec != NULL) {
|
||||||
|
(void)Setexc(VEC_VBL, (long)gOldVblVec);
|
||||||
|
}
|
||||||
|
|
||||||
|
Setscreen((long)gPrevLogbase, (long)gPrevPhysbase, gPrevRez);
|
||||||
|
Supexec(writePrevPaletteRegs);
|
||||||
|
writeDiagnostics();
|
||||||
|
gModeSet = false;
|
||||||
|
}
|
||||||
150
src/port/dos/hal.c
Normal file
150
src/port/dos/hal.c
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
// DOS HAL: VGA mode 13h, 8-bit DAC, nibble-expand present.
|
||||||
|
//
|
||||||
|
// JoeyLib surfaces are 4bpp chunky with a 200-entry SCB table selecting
|
||||||
|
// one of 16 palettes per scanline. Mode 13h is 8bpp; the three pairs of
|
||||||
|
// operations map cleanly:
|
||||||
|
// * All 16 palettes x 16 colors = 256 entries fill the VGA DAC once.
|
||||||
|
// * Pixel byte = (scb[y] << 4) | (source nibble).
|
||||||
|
// * No per-scanline palette programming needed at display time.
|
||||||
|
//
|
||||||
|
// Access to VGA memory uses DJGPP's nearptr mechanism for speed.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <dpmi.h>
|
||||||
|
#include <go32.h>
|
||||||
|
#include <pc.h>
|
||||||
|
#include <sys/nearptr.h>
|
||||||
|
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define VGA_LINEAR_ADDR 0xA0000u
|
||||||
|
#define VGA_STRIDE 320u
|
||||||
|
#define DAC_INDEX_PORT 0x3C8
|
||||||
|
#define DAC_DATA_PORT 0x3C9
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void expandAndWriteLine(const SurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst);
|
||||||
|
static void uploadPalette(const SurfaceT *src);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static uint8_t *gVgaMem = NULL;
|
||||||
|
static bool gNearEnabled = false;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
static void expandAndWriteLine(const SurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst) {
|
||||||
|
uint8_t palBase;
|
||||||
|
const uint8_t *srcBytes;
|
||||||
|
int16_t i;
|
||||||
|
int16_t xEnd;
|
||||||
|
uint8_t byte;
|
||||||
|
uint8_t nibble;
|
||||||
|
|
||||||
|
palBase = (uint8_t)(src->scb[y] << 4);
|
||||||
|
srcBytes = &src->pixels[y * SURFACE_BYTES_PER_ROW];
|
||||||
|
xEnd = x + (int16_t)w;
|
||||||
|
|
||||||
|
for (i = x; i < xEnd; i++) {
|
||||||
|
byte = srcBytes[i >> 1];
|
||||||
|
nibble = (uint8_t)((i & 1) ? (byte & 0x0F) : (byte >> 4));
|
||||||
|
dst[i] = (uint8_t)(palBase | nibble);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void uploadPalette(const SurfaceT *src) {
|
||||||
|
uint16_t p;
|
||||||
|
uint16_t c;
|
||||||
|
uint16_t rgb;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
|
||||||
|
outportb(DAC_INDEX_PORT, 0);
|
||||||
|
for (p = 0; p < SURFACE_PALETTE_COUNT; p++) {
|
||||||
|
for (c = 0; c < SURFACE_COLORS_PER_PALETTE; c++) {
|
||||||
|
rgb = src->palette[p][c];
|
||||||
|
r = (uint8_t)((rgb >> 8) & 0x0F);
|
||||||
|
g = (uint8_t)((rgb >> 4) & 0x0F);
|
||||||
|
b = (uint8_t)(rgb & 0x0F);
|
||||||
|
outportb(DAC_DATA_PORT, (uint8_t)((r << 2) | (r >> 2)));
|
||||||
|
outportb(DAC_DATA_PORT, (uint8_t)((g << 2) | (g >> 2)));
|
||||||
|
outportb(DAC_DATA_PORT, (uint8_t)((b << 2) | (b >> 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
bool halInit(const JoeyConfigT *config) {
|
||||||
|
__dpmi_regs regs;
|
||||||
|
|
||||||
|
(void)config;
|
||||||
|
|
||||||
|
memset(®s, 0, sizeof(regs));
|
||||||
|
regs.x.ax = 0x0013;
|
||||||
|
__dpmi_int(0x10, ®s);
|
||||||
|
|
||||||
|
if (!__djgpp_nearptr_enable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gNearEnabled = true;
|
||||||
|
gVgaMem = (uint8_t *)(__djgpp_conventional_base + VGA_LINEAR_ADDR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *halLastError(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresent(const SurfaceT *src) {
|
||||||
|
int16_t y;
|
||||||
|
|
||||||
|
if (src == NULL || gVgaMem == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadPalette(src);
|
||||||
|
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||||
|
expandAndWriteLine(src, y, 0, SURFACE_WIDTH, &gVgaMem[y * VGA_STRIDE]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||||
|
int16_t py;
|
||||||
|
int16_t yEnd;
|
||||||
|
|
||||||
|
if (src == NULL || gVgaMem == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uploadPalette(src);
|
||||||
|
yEnd = y + (int16_t)h;
|
||||||
|
for (py = y; py < yEnd; py++) {
|
||||||
|
expandAndWriteLine(src, py, x, w, &gVgaMem[py * VGA_STRIDE]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halShutdown(void) {
|
||||||
|
__dpmi_regs regs;
|
||||||
|
|
||||||
|
if (gNearEnabled) {
|
||||||
|
__djgpp_nearptr_disable();
|
||||||
|
gNearEnabled = false;
|
||||||
|
}
|
||||||
|
gVgaMem = NULL;
|
||||||
|
|
||||||
|
memset(®s, 0, sizeof(regs));
|
||||||
|
regs.x.ax = 0x0003;
|
||||||
|
__dpmi_int(0x10, ®s);
|
||||||
|
}
|
||||||
103
src/port/iigs/hal.c
Normal file
103
src/port/iigs/hal.c
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Apple IIgs HAL: enable SHR, write pixels / SCBs / palettes into the
|
||||||
|
// $E1 bank at the stock addresses the shifter reads from.
|
||||||
|
//
|
||||||
|
// Memory map in bank $E1:
|
||||||
|
// $2000 - $9CFF pixel data (32000 bytes, 160 bytes per scanline)
|
||||||
|
// $9D00 - $9DC7 SCB bytes (200 used)
|
||||||
|
// $9E00 - $9FFF 16 palettes x 16 colors x 2 bytes, $0RGB
|
||||||
|
//
|
||||||
|
// NEWVIDEO register at $00C029 controls SHR enable. Bit 7 turns SHR on.
|
||||||
|
// ORCA/C must be built with 32-bit pointer mode (-w or equivalent) so
|
||||||
|
// that the long addresses resolve to bank $E1.
|
||||||
|
//
|
||||||
|
// For M1 this is a simple direct-copy present. PEI-slam (in assembly)
|
||||||
|
// arrives as an optimization in a later milestone; the structure here
|
||||||
|
// is unchanged -- only halPresent / halPresentRect get faster inner
|
||||||
|
// loops.
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "hal.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// ----- Hardware addresses (24-bit / long pointers) -----
|
||||||
|
|
||||||
|
#define IIGS_NEWVIDEO_REG ((volatile uint8_t *)0x00C029L)
|
||||||
|
#define IIGS_SHR_PIXELS ((uint8_t *)0xE12000L)
|
||||||
|
#define IIGS_SHR_SCB ((uint8_t *)0xE19D00L)
|
||||||
|
#define IIGS_SHR_PALETTE ((uint16_t *)0xE19E00L)
|
||||||
|
|
||||||
|
// NEWVIDEO bit masks
|
||||||
|
#define NEWVIDEO_SHR_ON 0x80
|
||||||
|
#define NEWVIDEO_LINEARIZE 0x40
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static uint8_t gPreviousNewVideo = 0;
|
||||||
|
static bool gModeSet = false;
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
bool halInit(const JoeyConfigT *config) {
|
||||||
|
(void)config;
|
||||||
|
gPreviousNewVideo = *IIGS_NEWVIDEO_REG;
|
||||||
|
*IIGS_NEWVIDEO_REG = (uint8_t)(NEWVIDEO_SHR_ON | NEWVIDEO_LINEARIZE);
|
||||||
|
gModeSet = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char *halLastError(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresent(const SurfaceT *src) {
|
||||||
|
if (src == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(IIGS_SHR_PIXELS, src->pixels, SURFACE_PIXELS_SIZE);
|
||||||
|
memcpy(IIGS_SHR_SCB, src->scb, SURFACE_HEIGHT);
|
||||||
|
memcpy(IIGS_SHR_PALETTE, src->palette, sizeof(src->palette));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||||
|
int16_t py;
|
||||||
|
int16_t yEnd;
|
||||||
|
uint16_t copyBytes;
|
||||||
|
int16_t byteStart;
|
||||||
|
|
||||||
|
if (src == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCBs and palettes track the whole surface, not just the rect --
|
||||||
|
// cheap enough on IIgs (200 bytes + 512 bytes) and avoids tracking
|
||||||
|
// which palette/SCB regions changed. A future optimization can
|
||||||
|
// limit these to the affected scanlines.
|
||||||
|
memcpy(IIGS_SHR_SCB, src->scb, SURFACE_HEIGHT);
|
||||||
|
memcpy(IIGS_SHR_PALETTE, src->palette, sizeof(src->palette));
|
||||||
|
|
||||||
|
// Pixel copy: byte-aligned runs per scanline. x is always even
|
||||||
|
// after API-level clipping for 4bpp packed if caller aligned it;
|
||||||
|
// otherwise we include the byte containing the leftmost pixel.
|
||||||
|
byteStart = x >> 1;
|
||||||
|
copyBytes = (uint16_t)(((x + (int16_t)w + 1) >> 1) - byteStart);
|
||||||
|
yEnd = y + (int16_t)h;
|
||||||
|
|
||||||
|
for (py = y; py < yEnd; py++) {
|
||||||
|
memcpy(&IIGS_SHR_PIXELS[py * SURFACE_BYTES_PER_ROW + byteStart],
|
||||||
|
&src->pixels[py * SURFACE_BYTES_PER_ROW + byteStart],
|
||||||
|
copyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void halShutdown(void) {
|
||||||
|
if (gModeSet) {
|
||||||
|
*IIGS_NEWVIDEO_REG = gPreviousNewVideo;
|
||||||
|
gModeSet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
toolchains/env.sh
Normal file
83
toolchains/env.sh
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# JoeyLib toolchain environment.
|
||||||
|
#
|
||||||
|
# Source this file before invoking make:
|
||||||
|
# source toolchains/env.sh
|
||||||
|
#
|
||||||
|
# All build commands resolve cross-compilers and assemblers exclusively
|
||||||
|
# from toolchains/ regardless of what is installed on the host. The
|
||||||
|
# Makefiles reference these variables by name; never hardcode paths.
|
||||||
|
|
||||||
|
JOEY_TOOLCHAINS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
export JOEY_TOOLCHAINS
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# IIgs
|
||||||
|
#
|
||||||
|
# Two separate trees:
|
||||||
|
# - toolchains/iigs/gg-tools/bin/ Unix host binaries (iix, dumpobj, ...)
|
||||||
|
# built from the GoldenGate source repo.
|
||||||
|
# - toolchains/iigs/goldengate/ The IIgs filesystem iix emulates.
|
||||||
|
# $GOLDEN_GATE points here.
|
||||||
|
#
|
||||||
|
# iix is the host-side command shell that runs ORCA tools (cc, Linker,
|
||||||
|
# Asm65816, ...) via 65816 emulation against the $GOLDEN_GATE tree.
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export GOLDEN_GATE="${JOEY_TOOLCHAINS}/iigs/goldengate"
|
||||||
|
export ORCA_ROOT="${GOLDEN_GATE}"
|
||||||
|
|
||||||
|
export IIGS_AS="${JOEY_TOOLCHAINS}/iigs/merlin32/bin/merlin32"
|
||||||
|
export IIGS_IIX="${JOEY_TOOLCHAINS}/iigs/gg-tools/bin/iix"
|
||||||
|
export IIGS_BUILD="${JOEY_TOOLCHAINS}/iigs/iix-build.sh"
|
||||||
|
export IIGS_INCLUDE_SHIM="${JOEY_TOOLCHAINS}/iigs/include-shim"
|
||||||
|
# Kept as an alias for IIGS_IIX so the top-level Makefile's HAVE_IIGS
|
||||||
|
# availability probe (`[ -x $IIGS_CC ]`) works with the common pattern
|
||||||
|
# used for the other platforms.
|
||||||
|
export IIGS_CC="${IIGS_IIX}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Amiga
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export AMIGA_PREFIX="${JOEY_TOOLCHAINS}/amiga/gcc"
|
||||||
|
export AMIGA_CC="${AMIGA_PREFIX}/bin/m68k-amigaos-gcc"
|
||||||
|
export AMIGA_AR="${AMIGA_PREFIX}/bin/m68k-amigaos-ar"
|
||||||
|
export AMIGA_LD="${AMIGA_PREFIX}/bin/m68k-amigaos-ld"
|
||||||
|
export AMIGA_AS="${JOEY_TOOLCHAINS}/amiga/vasm/bin/vasmm68k_mot"
|
||||||
|
export AMIGA_NDK="${AMIGA_PREFIX}/m68k-amigaos/sys-include"
|
||||||
|
export AMIGA_PTPLAYER="${JOEY_TOOLCHAINS}/amiga/ptplayer"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Atari ST
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export ST_PREFIX="${JOEY_TOOLCHAINS}/atarist/gcc"
|
||||||
|
export ST_CC="${ST_PREFIX}/bin/m68k-atari-mint-gcc"
|
||||||
|
export ST_AR="${ST_PREFIX}/bin/m68k-atari-mint-ar"
|
||||||
|
export ST_LD="${ST_PREFIX}/bin/m68k-atari-mint-ld"
|
||||||
|
export ST_AS="${JOEY_TOOLCHAINS}/atarist/vasm/bin/vasmm68k_mot"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# DOS
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export DOS_PREFIX="${JOEY_TOOLCHAINS}/dos/djgpp"
|
||||||
|
export DOS_CC="${DOS_PREFIX}/bin/i586-pc-msdosdjgpp-gcc"
|
||||||
|
export DOS_AR="${DOS_PREFIX}/bin/i586-pc-msdosdjgpp-ar"
|
||||||
|
export DOS_LD="${DOS_PREFIX}/bin/i586-pc-msdosdjgpp-ld"
|
||||||
|
# DJGPP's DOS-target utilities live under the i586-...-msdosdjgpp
|
||||||
|
# sysroot bin, not the host bin that holds the cross-gcc.
|
||||||
|
export DOS_STUBEDIT="${DOS_PREFIX}/i586-pc-msdosdjgpp/bin/stubedit"
|
||||||
|
export DOS_STUBIFY="${DOS_PREFIX}/i586-pc-msdosdjgpp/bin/stubify"
|
||||||
|
export DOS_EXE2COFF="${DOS_PREFIX}/i586-pc-msdosdjgpp/bin/exe2coff"
|
||||||
|
export DOS_AS="${JOEY_TOOLCHAINS}/dos/nasm/bin/nasm"
|
||||||
|
export DOS_CWSDPMI="${JOEY_TOOLCHAINS}/dos/cwsdpmi/bin/bin"
|
||||||
|
export DOS_CWSDSTUB="${DOS_CWSDPMI}/CWSDSTUB.EXE"
|
||||||
|
export DOS_EMBED_DPMI="${JOEY_TOOLCHAINS}/dos/embed-dpmi.sh"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# PATH augmentation (in case sub-tools shell out unprefixed names)
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
PATH="${AMIGA_PREFIX}/bin:${ST_PREFIX}/bin:${DOS_PREFIX}/bin:${JOEY_TOOLCHAINS}/iigs/merlin32/bin:${JOEY_TOOLCHAINS}/amiga/vasm/bin:${JOEY_TOOLCHAINS}/dos/nasm/bin:${PATH}"
|
||||||
|
export PATH
|
||||||
1298
toolchains/install.sh
Executable file
1298
toolchains/install.sh
Executable file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue