Major refactoring
This commit is contained in:
parent
1972f6df90
commit
add3f3dd0d
68 changed files with 3455 additions and 2241 deletions
14
Makefile
14
Makefile
|
|
@ -35,7 +35,7 @@ ifeq ($(HAVE_DOS),1)
|
|||
ALL_TARGETS += dos
|
||||
endif
|
||||
|
||||
.PHONY: all iigs iigs-disk amiga atarist dos tools clean help status
|
||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos blank-check tools clean help status
|
||||
|
||||
all: $(ALL_TARGETS) tools
|
||||
ifeq ($(ALL_TARGETS),)
|
||||
|
|
@ -52,6 +52,9 @@ iigs:
|
|||
iigs-disk:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-disk
|
||||
|
||||
iigs-verify:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/iigs.mk iigs-verify
|
||||
|
||||
amiga:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/amiga.mk
|
||||
|
||||
|
|
@ -61,6 +64,11 @@ atarist:
|
|||
dos:
|
||||
@$(MAKE) -f $(REPO_DIR)/make/dos.mk
|
||||
|
||||
# Compile + link-check the new-port template (src/blank/blank.c) with the host
|
||||
# cc. No toolchain needed; keeps the template from bit-rotting as the lib grows.
|
||||
blank-check:
|
||||
@$(REPO_DIR)/scripts/check-blank.sh
|
||||
|
||||
clean:
|
||||
rm -rf $(REPO_DIR)/build
|
||||
|
||||
|
|
@ -73,4 +81,6 @@ status:
|
|||
|
||||
help:
|
||||
@echo "Targets: iigs amiga atarist dos all clean status help"
|
||||
@echo "Source toolchains/env.sh first."
|
||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||
@echo " blank-check (host-cc smoke test of the new-port template)"
|
||||
@echo "Source toolchains/env.sh first (blank-check needs no toolchain)."
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ void jlWaitVBL(void);
|
|||
// Monotonic 16-bit frame counter. Caller must poll faster than
|
||||
// 2 * jlFrameHz() so no edge is missed. Used by benchmarks and
|
||||
// frame-rate-independent animation. (Per-port edge-detect / VBL-ISR
|
||||
// mechanism is documented at halFrameCount in src/core/hal.h.)
|
||||
// mechanism is documented at jlpFrameCount in src/core/port.h.)
|
||||
uint16_t jlFrameCount(void);
|
||||
|
||||
// Nominal display frame rate in Hz: 50 (Amiga PAL), 60 (IIgs / ST
|
||||
|
|
@ -62,7 +62,7 @@ uint16_t jlFrameHz(void);
|
|||
// from frame rate -- two consecutive calls separated by a long
|
||||
// render report the true elapsed time, not the number of VBLs that
|
||||
// fired. (Per-port millisecond timer sources are documented at
|
||||
// halMillisElapsed in src/core/hal.h.)
|
||||
// jlpMillisElapsed in src/core/port.h.)
|
||||
uint32_t jlMillisElapsed(void);
|
||||
|
||||
// Deterministic, seedable pseudo-random generator. Bit-identical
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ typedef enum {
|
|||
void jlInputPoll(void);
|
||||
|
||||
// Block until the user presses any key. Internally polls via
|
||||
// jlInputPoll, so per-port halInputPoll machinery (including
|
||||
// jlInputPoll, so per-port jlpInputPoll machinery (including
|
||||
// audio-friendly IRQ-driven samplers) keeps working while the
|
||||
// wait loop runs.
|
||||
void jlWaitForAnyKey(void);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
#if !defined(JOEYLIB_PLATFORM_IIGS) && \
|
||||
!defined(JOEYLIB_PLATFORM_AMIGA) && \
|
||||
!defined(JOEYLIB_PLATFORM_ATARIST) && \
|
||||
!defined(JOEYLIB_PLATFORM_DOS)
|
||||
!defined(JOEYLIB_PLATFORM_DOS) && \
|
||||
!defined(JOEYLIB_PLATFORM_BLANK)
|
||||
|
||||
#if defined(__DJGPP__) || defined(__MSDOS__)
|
||||
#define JOEYLIB_PLATFORM_DOS
|
||||
|
|
@ -28,12 +29,17 @@
|
|||
|
||||
#endif
|
||||
|
||||
// JOEYLIB_PLATFORM_BLANK is the copy-to-start template for a new port (see
|
||||
// src/blank/blank.c). It has no auto-detect macro -- bring it up by building
|
||||
// with -DJOEYLIB_PLATFORM_BLANK.
|
||||
|
||||
// ----- Validate exactly one platform is defined -----
|
||||
|
||||
#if (defined(JOEYLIB_PLATFORM_IIGS) + \
|
||||
defined(JOEYLIB_PLATFORM_AMIGA) + \
|
||||
defined(JOEYLIB_PLATFORM_ATARIST) + \
|
||||
defined(JOEYLIB_PLATFORM_DOS)) != 1
|
||||
defined(JOEYLIB_PLATFORM_DOS) + \
|
||||
defined(JOEYLIB_PLATFORM_BLANK)) != 1
|
||||
#error "JoeyLib: exactly one JOEYLIB_PLATFORM_* must be defined"
|
||||
#endif
|
||||
|
||||
|
|
@ -61,6 +67,227 @@
|
|||
#define JOEYLIB_ENDIAN_LITTLE
|
||||
#define JOEYLIB_NATIVE_CHUNKY
|
||||
#define JOEYLIB_PLATFORM_NAME "MS-DOS"
|
||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||
// Chunky little-endian is the simplest baseline for a new port: the generic
|
||||
// backend gives you fully-working software rendering out of the box. Adjust
|
||||
// these (or switch to JOEYLIB_NATIVE_PLANAR) once you know your hardware.
|
||||
#define JOEYLIB_ENDIAN_LITTLE
|
||||
#define JOEYLIB_NATIVE_CHUNKY
|
||||
#define JOEYLIB_PLATFORM_NAME "Blank Template"
|
||||
#endif
|
||||
|
||||
// ----- Per-machine op override registry (JL_HAS_<OP>) -----
|
||||
//
|
||||
// A machine #define-s JL_HAS_<OP> for each overridable platform op it replaces
|
||||
// with its own (asm / native) implementation in src/<machine>/. Ops a machine
|
||||
// does NOT claim fall back to the portable-C default in src/generic/, selected
|
||||
// at compile time (no runtime dispatch). The jlp<Op> macros/prototypes in
|
||||
// src/core/port.h read this table. This is the new lib's equivalent of the
|
||||
// original joeylib's joey.h JL_HAS_* table.
|
||||
|
||||
#if defined(JOEYLIB_PLATFORM_IIGS)
|
||||
#define JL_HAS_SURFACE_CLEAR // iigsSurfaceClearFastInner (stage), macro
|
||||
#define JL_HAS_DRAW_PIXEL // iigsDrawPixelInner, macro
|
||||
#define JL_HAS_DRAW_LINE // iigsDrawLineInner, macro
|
||||
#define JL_HAS_DRAW_CIRCLE // iigsDrawCircleInner, macro
|
||||
#define JL_HAS_FILL_CIRCLE // iigsFillCircleInner (stage), macro -- returns bool
|
||||
#define JL_HAS_FILL_RECT // iigsFillRectInner (stage), macro -> generic else
|
||||
#define JL_HAS_TILE_FILL // iigsTileFillInner, macro
|
||||
#define JL_HAS_TILE_COPY // iigsTileCopyInner, macro
|
||||
#define JL_HAS_TILE_COPY_MASKED // iigsTileCopyMaskedInner, macro
|
||||
#define JL_HAS_TILE_PASTE // iigsTilePasteInner, macro
|
||||
#define JL_HAS_TILE_SNAP // iigsTileSnapInner, macro
|
||||
#define JL_HAS_SPRITE_DRAW // chunky -> codegen; planes mirror is noop macro
|
||||
#define JL_HAS_SPRITE_SAVE // noop macro (codegen path saves)
|
||||
#define JL_HAS_SPRITE_RESTORE // noop macro
|
||||
#define JL_HAS_FLOOD_WALK_AND_SCANS // iigsFloodWalkAndScansInner (all-in-one), macro
|
||||
#define JL_HAS_BIG_ALLOC // Memory Manager NewHandle/DisposeHandle, function
|
||||
#define JL_HAS_STAGE_ALLOC_PIXELS // pinned $01:2000 display memory, function
|
||||
#define JL_HAS_STAGE_FREE_PIXELS // no-op (pinned), function
|
||||
#define JL_HAS_FRAME_HZ // runtime 50/60 from battery RAM, function
|
||||
#define JL_HAS_FRAME_COUNT // GetTick word, function
|
||||
#define JL_HAS_WAIT_VBL // $C019 VBL poll, function
|
||||
// audio: every real port implements the full engine
|
||||
#define JL_HAS_AUDIO_INIT
|
||||
#define JL_HAS_AUDIO_SHUTDOWN
|
||||
#define JL_HAS_AUDIO_PLAY_MOD
|
||||
#define JL_HAS_AUDIO_STOP_MOD
|
||||
#define JL_HAS_AUDIO_IS_PLAYING_MOD
|
||||
#define JL_HAS_AUDIO_PLAY_SFX
|
||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||
#define JL_HAS_AUDIO_STOP_SFX
|
||||
#define JL_HAS_AUDIO_TONE
|
||||
#define JL_HAS_AUDIO_VOICE
|
||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||
#define JL_HAS_AUDIO_FRAME_TICK
|
||||
// lifecycle / present / input (every real port implements)
|
||||
#define JL_HAS_INIT
|
||||
#define JL_HAS_SHUTDOWN
|
||||
#define JL_HAS_PRESENT
|
||||
#define JL_HAS_INPUT_INIT
|
||||
#define JL_HAS_INPUT_SHUTDOWN
|
||||
#define JL_HAS_INPUT_POLL
|
||||
#define JL_HAS_JOYSTICK_RESET
|
||||
// millisElapsed: generic frameCount*1000/frameHz (no override)
|
||||
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
||||
#define JL_HAS_SURFACE_CLEAR // planar clear, function
|
||||
#define JL_HAS_DRAW_PIXEL // amigaPlanarSetPixel, function
|
||||
#define JL_HAS_DRAW_LINE // amigaPlanarLine, function
|
||||
#define JL_HAS_DRAW_CIRCLE // amiga planar circle, function
|
||||
#define JL_HAS_FILL_CIRCLE // amiga planar fill circle, function -- returns bool
|
||||
#define JL_HAS_FILL_RECT // amiga planar fill rect, function
|
||||
#define JL_HAS_TILE_FILL // amiga planar tile fill, function
|
||||
#define JL_HAS_TILE_COPY // amiga planar tile copy, function
|
||||
#define JL_HAS_TILE_COPY_MASKED // amiga planar masked copy, function
|
||||
#define JL_HAS_TILE_PASTE // amiga planar tile paste, function
|
||||
#define JL_HAS_TILE_SNAP // amiga planar tile snap, function
|
||||
#define JL_HAS_TILE_PASTE_MONO // amiga planar mono colorize+paste, function
|
||||
#define JL_HAS_SPRITE_DRAW // amiga planar sprite draw, function
|
||||
#define JL_HAS_SPRITE_SAVE // amiga planar sprite save, function
|
||||
#define JL_HAS_SPRITE_RESTORE // amiga planar sprite restore, function
|
||||
#define JL_HAS_FLOOD_WALK_PLANES // amiga planar flood walk-out, function
|
||||
#define JL_HAS_FLOOD_SCAN_ROW_PLANES // amiga planar flood scan-row, function
|
||||
#define JL_HAS_SURFACE_COPY_PLANES // amiga blitter plane copy, function
|
||||
#define JL_HAS_SAMPLE_PIXEL // amiga planar nibble decode, function
|
||||
#define JL_HAS_SURFACE_HASH // amiga planar hash, function
|
||||
#define JL_HAS_SURFACE_LOAD_FILE // amiga planar load, function
|
||||
#define JL_HAS_SURFACE_SAVE_FILE // amiga planar save, function
|
||||
#define JL_HAS_STAGE_ALLOC_PIXELS // amiga: NULL (no chunky stage shadow), function
|
||||
#define JL_HAS_SURFACE_ALLOC_PIXELS // amiga: NULL (planar storage), function
|
||||
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // amiga AmigaPlanarT + chip planes, function
|
||||
#define JL_HAS_SURFACE_FREE_PORT_DATA // amiga FreeMem planes, function
|
||||
#define JL_HAS_SURFACE_PLANE_PTR // amiga plane base pointer, function
|
||||
#define JL_HAS_FRAME_HZ // PAL 50, function
|
||||
#define JL_HAS_FRAME_COUNT // VBL-server counter, function
|
||||
#define JL_HAS_WAIT_VBL // WaitTOF, function
|
||||
// audio: every real port implements the full engine
|
||||
#define JL_HAS_AUDIO_INIT
|
||||
#define JL_HAS_AUDIO_SHUTDOWN
|
||||
#define JL_HAS_AUDIO_PLAY_MOD
|
||||
#define JL_HAS_AUDIO_STOP_MOD
|
||||
#define JL_HAS_AUDIO_IS_PLAYING_MOD
|
||||
#define JL_HAS_AUDIO_PLAY_SFX
|
||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||
#define JL_HAS_AUDIO_STOP_SFX
|
||||
#define JL_HAS_AUDIO_TONE
|
||||
#define JL_HAS_AUDIO_VOICE
|
||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||
#define JL_HAS_AUDIO_FRAME_TICK
|
||||
// lifecycle / present / input (every real port implements)
|
||||
#define JL_HAS_INIT
|
||||
#define JL_HAS_SHUTDOWN
|
||||
#define JL_HAS_PRESENT
|
||||
#define JL_HAS_INPUT_INIT
|
||||
#define JL_HAS_INPUT_SHUTDOWN
|
||||
#define JL_HAS_INPUT_POLL
|
||||
#define JL_HAS_JOYSTICK_RESET
|
||||
// millisElapsed: generic frameCount*1000/frameHz (no override)
|
||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||
#define JL_HAS_SURFACE_CLEAR // planar clear, function
|
||||
#define JL_HAS_DRAW_PIXEL // stPlanarSetPixel, function
|
||||
#define JL_HAS_DRAW_LINE // stPlanarLine, function
|
||||
#define JL_HAS_DRAW_CIRCLE // st planar circle, function
|
||||
#define JL_HAS_FILL_CIRCLE // st planar fill circle, function -- returns bool
|
||||
#define JL_HAS_FILL_RECT // st planar fill rect, function
|
||||
#define JL_HAS_TILE_FILL // st planar tile fill, function
|
||||
#define JL_HAS_TILE_COPY // st planar tile copy, function
|
||||
#define JL_HAS_TILE_COPY_MASKED // st planar masked copy, function
|
||||
#define JL_HAS_TILE_PASTE // st planar tile paste, function
|
||||
#define JL_HAS_TILE_SNAP // st planar tile snap, function
|
||||
#define JL_HAS_TILE_PASTE_MONO // st planar mono colorize+paste, function
|
||||
#define JL_HAS_SPRITE_DRAW // st planar sprite draw, function
|
||||
#define JL_HAS_SPRITE_SAVE // st planar sprite save, function
|
||||
#define JL_HAS_SPRITE_RESTORE // st planar sprite restore, function
|
||||
#define JL_HAS_SURFACE_COPY_PLANES // st planar buffer memcpy, function
|
||||
#define JL_HAS_SAMPLE_PIXEL // st planar nibble decode, function
|
||||
#define JL_HAS_SURFACE_HASH // st planar hash, function
|
||||
#define JL_HAS_SURFACE_LOAD_FILE // st planar load, function
|
||||
#define JL_HAS_SURFACE_SAVE_FILE // st planar save, function
|
||||
#define JL_HAS_STAGE_ALLOC_PIXELS // st: NULL (no chunky stage shadow), function
|
||||
#define JL_HAS_SURFACE_ALLOC_PIXELS // st: NULL (planar storage), function
|
||||
#define JL_HAS_SURFACE_ALLOC_PORT_DATA // st StPlanarT + interleaved buffer, function
|
||||
#define JL_HAS_SURFACE_FREE_PORT_DATA // st free buffer, function
|
||||
#define JL_HAS_SURFACE_PLANE_PTR // st interleaved buffer base, function
|
||||
#define JL_HAS_FRAME_HZ // PAL 50, function
|
||||
#define JL_HAS_FRAME_COUNT // VBL-ISR counter, function
|
||||
#define JL_HAS_WAIT_VBL // frame-counter wait, function
|
||||
#define JL_HAS_MILLIS_ELAPSED // Supexec 200 Hz tick, function
|
||||
// audio: every real port implements the full engine
|
||||
#define JL_HAS_AUDIO_INIT
|
||||
#define JL_HAS_AUDIO_SHUTDOWN
|
||||
#define JL_HAS_AUDIO_PLAY_MOD
|
||||
#define JL_HAS_AUDIO_STOP_MOD
|
||||
#define JL_HAS_AUDIO_IS_PLAYING_MOD
|
||||
#define JL_HAS_AUDIO_PLAY_SFX
|
||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||
#define JL_HAS_AUDIO_STOP_SFX
|
||||
#define JL_HAS_AUDIO_TONE
|
||||
#define JL_HAS_AUDIO_VOICE
|
||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||
#define JL_HAS_AUDIO_FRAME_TICK
|
||||
// lifecycle / present / input (every real port implements)
|
||||
#define JL_HAS_INIT
|
||||
#define JL_HAS_SHUTDOWN
|
||||
#define JL_HAS_PRESENT
|
||||
#define JL_HAS_INPUT_INIT
|
||||
#define JL_HAS_INPUT_SHUTDOWN
|
||||
#define JL_HAS_INPUT_POLL
|
||||
#define JL_HAS_JOYSTICK_RESET
|
||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
||||
// chunky generics: surface clear, draw pixel/line/circle, fill circle/rect,
|
||||
// tiles, surface readers, allocation -- DOS overrides only the timing services.
|
||||
#define JL_HAS_FRAME_HZ // VGA ~70, function
|
||||
#define JL_HAS_FRAME_COUNT // $3DA retrace edge counter, function
|
||||
#define JL_HAS_WAIT_VBL // $3DA retrace poll, function
|
||||
#define JL_HAS_MILLIS_ELAPSED // uclock / audio-ISR tick, function
|
||||
// audio: every real port implements the full engine
|
||||
#define JL_HAS_AUDIO_INIT
|
||||
#define JL_HAS_AUDIO_SHUTDOWN
|
||||
#define JL_HAS_AUDIO_PLAY_MOD
|
||||
#define JL_HAS_AUDIO_STOP_MOD
|
||||
#define JL_HAS_AUDIO_IS_PLAYING_MOD
|
||||
#define JL_HAS_AUDIO_PLAY_SFX
|
||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||
#define JL_HAS_AUDIO_STOP_SFX
|
||||
#define JL_HAS_AUDIO_TONE
|
||||
#define JL_HAS_AUDIO_VOICE
|
||||
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_ENTER
|
||||
#define JL_HAS_AUDIO_CRITICAL_EXIT
|
||||
#define JL_HAS_AUDIO_FRAME_TICK
|
||||
// lifecycle / present / input (every real port implements)
|
||||
#define JL_HAS_INIT
|
||||
#define JL_HAS_SHUTDOWN
|
||||
#define JL_HAS_PRESENT
|
||||
#define JL_HAS_INPUT_INIT
|
||||
#define JL_HAS_INPUT_SHUTDOWN
|
||||
#define JL_HAS_INPUT_POLL
|
||||
#define JL_HAS_JOYSTICK_RESET
|
||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||
// Copy-to-start template. It overrides ONLY the platform SERVICES (TODO
|
||||
// stubs in src/blank/blank.c). Everything graphical -- draw / tile / sprite
|
||||
// primitives, surface readers, allocation, bigAlloc, millisElapsed -- comes
|
||||
// from the portable-C generics for free, so a chunky port renders correctly
|
||||
// before you write a line of graphics code. Add a JL_HAS_<OP> here + an
|
||||
// override in blank.c only when you want to accelerate a specific op.
|
||||
#define JL_HAS_INIT // bring up your display mode
|
||||
#define JL_HAS_SHUTDOWN // restore it
|
||||
#define JL_HAS_PRESENT // blit the chunky stage to your framebuffer
|
||||
#define JL_HAS_INPUT_INIT
|
||||
#define JL_HAS_INPUT_SHUTDOWN
|
||||
#define JL_HAS_INPUT_POLL // read your keyboard into gKeyState[]
|
||||
#define JL_HAS_JOYSTICK_RESET
|
||||
#define JL_HAS_WAIT_VBL // pace to the display
|
||||
#define JL_HAS_FRAME_COUNT // a monotonic frame/refresh counter
|
||||
#define JL_HAS_FRAME_HZ // your refresh rate in Hz
|
||||
// audio: left to the no-op generics (your port links + runs silent). Add
|
||||
// JL_HAS_AUDIO_* + overrides in blank.c when you wire up sound.
|
||||
#endif
|
||||
|
||||
// ----- Library version -----
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ BINDIR := $(BUILD)/bin
|
|||
# PTPlayer is staged by toolchains/install.sh into PTPLAYER_DIR; we
|
||||
# reference its ptplayer.asm + header from there rather than copying
|
||||
# them into the source tree, so install.sh can refresh / version it
|
||||
# independently. -I on $(SRC_PORT)/amiga lets ptplayer.h resolve
|
||||
# <SDI_compiler.h> from the port-local shim alongside our HAL code.
|
||||
# independently. -I on $(SRC_DIR)/amiga lets ptplayer.h resolve
|
||||
# <SDI_compiler.h> from the machine-local shim alongside our code.
|
||||
PTPLAYER_DIR := $(REPO_DIR)/toolchains/amiga/ptplayer
|
||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_PORT)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP $(CFLAGS_EXTRA)
|
||||
CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT -I$(SRC_DIR)/amiga -I$(SRC_68K) -I$(PTPLAYER_DIR) -MMD -MP $(CFLAGS_EXTRA)
|
||||
# OSCOMPAT=1 selects PTPlayer's audio.device-friendly variant (uses
|
||||
# CIA-B + audio.device interrupts via the OS rather than taking over
|
||||
# Paula directly), matching the way our HAL cooperates with Intuition.
|
||||
|
|
@ -27,13 +27,13 @@ CFLAGS := $(COMMON_CFLAGS) -m68000 -fomit-frame-pointer -noixemul -D__OSCOMPAT
|
|||
# and surfaces as undefined references at every binary's link step).
|
||||
PTPLAYER_ASFLAGS := -Fhunk -m68000 -quiet -DOSCOMPAT=1
|
||||
# --allow-multiple-definition lets our user-space tzset stub
|
||||
# (src/port/amiga/libinit.c) win over libnix's version in
|
||||
# (src/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)
|
||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/amiga/*.c)
|
||||
PORT_S_SRCS := $(wildcard $(SRC_DIR)/amiga/*.s)
|
||||
SHARED_S := $(wildcard $(SRC_68K)/*.s)
|
||||
|
||||
# Amiga uses PTPlayer's mt_playfx for SFX, not the libxmp+overlay
|
||||
|
|
@ -42,17 +42,20 @@ SHARED_S := $(wildcard $(SRC_68K)/*.s)
|
|||
# code out of every Amiga binary.
|
||||
CORE_C_SRCS_AMIGA := $(filter-out %/audioSfxMix.c, $(CORE_C_SRCS))
|
||||
|
||||
# Sprite codegen: 68k emitter shared with the ST port.
|
||||
# Sprite codegen: the 68k emitters (spriteEmit68k.c shared with ST, plus the
|
||||
# Amiga-planar spriteEmitPlanar68k.c) live in src/m68k/ and build into obj/68k/;
|
||||
# only the CPU-independent dispatch stays in src/codegen.
|
||||
CODEGEN_DIR := $(REPO_DIR)/src/codegen
|
||||
|
||||
LIB_OBJS := \
|
||||
$(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS_AMIGA)) \
|
||||
$(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_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/amiga/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/amiga/%.s,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S)) \
|
||||
$(BUILD)/obj/port/ptplayer.o \
|
||||
$(BUILD)/obj/codegen/spriteEmit68k.o \
|
||||
$(BUILD)/obj/codegen/spriteEmitPlanar68k.o \
|
||||
$(BUILD)/obj/68k/spriteEmit68k.o \
|
||||
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
||||
$(BUILD)/obj/codegen/spriteCompile.o
|
||||
|
||||
LIB := $(LIBDIR)/libjoey.a
|
||||
|
|
@ -129,13 +132,17 @@ $(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
|||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/amiga/%.c
|
||||
$(BUILD)/obj/generic/%.o: $(SRC_DIR)/generic/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/amiga/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Hand-written 68k assembly: GAS syntax, fed through the gcc driver
|
||||
# so the bundled m68k-amigaos-as (binutils) does the work.
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/amiga/%.s
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/amiga/%.s
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
|
@ -153,6 +160,12 @@ $(BUILD)/obj/68k/%.o: $(SRC_68K)/%.s
|
|||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# 68k sprite emitters (spriteEmit68k.c, spriteEmitPlanar68k.c) now live in
|
||||
# src/m68k/; -I$(CODEGEN_DIR) lets them resolve spriteEmitter.h.
|
||||
$(BUILD)/obj/68k/%.o: $(SRC_68K)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||
|
||||
$(LIB): $(LIB_OBJS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_AR) rcs $@ $^
|
||||
|
|
|
|||
|
|
@ -24,20 +24,23 @@ LIBXMP_AR := $(LIBDIR)/libxmplite.a
|
|||
# support has no impact on JoeyLib's audio.
|
||||
LIBXMP_CFLAGS := -DLIBXMP_CORE_PLAYER -DLIBXMP_CORE_DISABLE_IT -DHAVE_FNMATCH=0 -I$(LIBXMP_DIR)/include -I$(LIBXMP_DIR)/include/libxmp-lite -I$(LIBXMP_DIR)/src -Wno-error -w
|
||||
|
||||
PORT_C_SRCS := $(wildcard $(SRC_PORT)/atarist/*.c)
|
||||
PORT_S_SRCS := $(wildcard $(SRC_PORT)/atarist/*.s)
|
||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/atarist/*.c)
|
||||
PORT_S_SRCS := $(wildcard $(SRC_DIR)/atarist/*.s)
|
||||
SHARED_S := $(wildcard $(SRC_68K)/*.s)
|
||||
|
||||
# Sprite codegen: 68k emitter shared with the Amiga port.
|
||||
# Sprite codegen: the 68k emitters (spriteEmit68k.c shared with Amiga, plus the
|
||||
# ST-interleaved spriteEmitInterleaved68k.c) live in src/m68k/ and build into
|
||||
# obj/68k/; only the CPU-independent dispatch stays in src/codegen.
|
||||
CODEGEN_DIR := $(REPO_DIR)/src/codegen
|
||||
|
||||
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_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/atarist/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/atarist/%.s,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S)) \
|
||||
$(BUILD)/obj/codegen/spriteEmit68k.o \
|
||||
$(BUILD)/obj/codegen/spriteEmitInterleaved68k.o \
|
||||
$(BUILD)/obj/68k/spriteEmit68k.o \
|
||||
$(BUILD)/obj/68k/spriteEmitInterleaved68k.o \
|
||||
$(BUILD)/obj/codegen/spriteCompile.o
|
||||
|
||||
LIB := $(LIBDIR)/libjoey.a
|
||||
|
|
@ -105,14 +108,18 @@ $(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
|||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/atarist/%.c
|
||||
$(BUILD)/obj/generic/%.o: $(SRC_DIR)/generic/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/atarist/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Hand-written 68k assembly: GAS syntax, fed through the gcc driver
|
||||
# so the bundled m68k-atari-mint-as (binutils) does the work. No vasm
|
||||
# dependency on the ST path.
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/atarist/%.s
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/atarist/%.s
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
|
@ -120,6 +127,12 @@ $(BUILD)/obj/68k/%.o: $(SRC_68K)/%.s
|
|||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# 68k sprite emitters (spriteEmit68k.c, spriteEmitInterleaved68k.c) now live in
|
||||
# src/m68k/; -I$(CODEGEN_DIR) lets them resolve spriteEmitter.h.
|
||||
$(BUILD)/obj/68k/%.o: $(SRC_68K)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/codegen/%.o: $(CODEGEN_DIR)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||
|
|
|
|||
|
|
@ -4,15 +4,23 @@
|
|||
|
||||
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
||||
INCLUDE_DIR := $(REPO_DIR)/include
|
||||
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos);
|
||||
# code shared across machines of one CPU lives in a processor folder (src/m68k).
|
||||
SRC_DIR := $(REPO_DIR)/src
|
||||
SRC_CORE := $(REPO_DIR)/src/core
|
||||
SRC_PORT := $(REPO_DIR)/src/port
|
||||
SRC_CG := $(REPO_DIR)/src/codegen
|
||||
SRC_68K := $(REPO_DIR)/src/shared68k
|
||||
SRC_68K := $(REPO_DIR)/src/m68k
|
||||
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
|
||||
# Generic portable-C op defaults (the src/generic backend) -- present on every
|
||||
# target. spriteEmitStub.c is excluded: it is the no-codegen template and
|
||||
# redefines jlSpriteCompile / spriteCompiledDraw, which the real
|
||||
# src/codegen/spriteCompile.c already provides.
|
||||
GENERIC_C_SRCS := $(filter-out %/spriteEmitStub.c, $(wildcard $(SRC_DIR)/generic/*.c))
|
||||
|
||||
# Common include flags. Per-port code can include port.h / surfaceInternal.h
|
||||
# directly because SRC_CORE is in the include path.
|
||||
COMMON_CFLAGS := -I$(INCLUDE_DIR) -I$(SRC_CORE) -Wall -Wextra -Werror -O2
|
||||
|
|
|
|||
20
make/dos.mk
20
make/dos.mk
|
|
@ -22,15 +22,17 @@ LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP
|
|||
LIBXMP_AR := $(LIBDIR)/libxmplite.a
|
||||
LIBXMP_CFLAGS := -DJOEY_LIBXMP_LITE -DLIBXMP_CORE_PLAYER -DHAVE_FNMATCH=0 -I$(LIBXMP_DIR)/include -I$(LIBXMP_DIR)/include/libxmp-lite -I$(LIBXMP_DIR)/src -Wno-error -w
|
||||
|
||||
PORT_C_SRCS := $(wildcard $(SRC_PORT)/dos/*.c)
|
||||
PORT_S_SRCS := $(wildcard $(SRC_PORT)/dos/*.asm)
|
||||
# The x86 sprite emitter (spriteEmitX86.c) now lives in src/dos/ and is built
|
||||
# by the port rule below; only the CPU-independent dispatch stays in src/codegen.
|
||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/dos/*.c)
|
||||
PORT_S_SRCS := $(wildcard $(SRC_DIR)/dos/*.asm)
|
||||
CODEGEN_DIR := $(REPO_DIR)/src/codegen
|
||||
|
||||
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)) \
|
||||
$(BUILD)/obj/codegen/spriteEmitX86.o \
|
||||
$(patsubst $(SRC_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/dos/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||
$(patsubst $(SRC_DIR)/dos/%.asm,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||
$(BUILD)/obj/codegen/spriteCompile.o
|
||||
|
||||
LIB := $(LIBDIR)/libjoey.a
|
||||
|
|
@ -119,11 +121,15 @@ $(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
|||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/dos/%.c
|
||||
$(BUILD)/obj/generic/%.o: $(SRC_DIR)/generic/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_PORT)/dos/%.asm
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/dos/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||
|
||||
$(BUILD)/obj/port/%.o: $(SRC_DIR)/dos/%.asm
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_AS) $(ASFLAGS) $< -o $@
|
||||
|
||||
|
|
|
|||
30
make/iigs.mk
30
make/iigs.mk
|
|
@ -2,7 +2,7 @@
|
|||
#
|
||||
# toolchains/iigs/clang-build.sh drives compile (clang) / assemble
|
||||
# (llvm-mc) / link (link816) against the prebuilt w65816 runtime.
|
||||
# Hand-rolled asm lives in src/port/iigs/*.s (llvm-mc GAS syntax);
|
||||
# Hand-rolled asm lives in src/iigs/*.s (llvm-mc GAS syntax);
|
||||
# bank placement is handled by -ffunction-sections + link816
|
||||
# dead-stripping.
|
||||
#
|
||||
|
|
@ -26,19 +26,20 @@ RT_INC := $(LLVM816_ROOT)/runtime/include
|
|||
CFLAGS := --target=w65816 -O2 -ffreestanding -ffunction-sections
|
||||
INCLUDES := -I$(RT_INC) -I$(INCLUDE_DIR) -I$(INCLUDE_DIR)/joey -I$(SRC_CORE) -I$(REPO_DIR)/src/codegen
|
||||
|
||||
PORT_C_SRCS := $(wildcard $(SRC_PORT)/iigs/*.c)
|
||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/iigs/*.c)
|
||||
# Hand-rolled asm, migrated to llvm-mc GAS .s.
|
||||
PORT_ASM_SRCS := $(wildcard $(SRC_PORT)/iigs/*.s)
|
||||
PORT_ASM_SRCS := $(wildcard $(SRC_DIR)/iigs/*.s)
|
||||
|
||||
# IIgs uses NTPstreamsound for SFX, not the libxmp+overlay combo that
|
||||
# DOS and ST share, so src/core/audioSfxMix.c is unused here.
|
||||
CORE_C_SRCS_IIGS := $(filter-out %/audioSfxMix.c, $(CORE_C_SRCS))
|
||||
|
||||
# Sprite codegen: 65816 emitter + cross-platform compile dispatch.
|
||||
CODEGEN_SRCS := $(REPO_DIR)/src/codegen/spriteEmitIigs.c \
|
||||
$(REPO_DIR)/src/codegen/spriteCompile.c
|
||||
# Sprite codegen: the 65816 emitter (spriteEmitIigs.c) now lives in src/iigs/
|
||||
# and is picked up by PORT_C_SRCS; only the CPU-independent compile dispatch
|
||||
# remains in src/codegen.
|
||||
CODEGEN_SRCS := $(REPO_DIR)/src/codegen/spriteCompile.c
|
||||
|
||||
LIB_C_SRCS := $(CORE_C_SRCS_IIGS) $(PORT_C_SRCS) $(CODEGEN_SRCS)
|
||||
LIB_C_SRCS := $(CORE_C_SRCS_IIGS) $(GENERIC_C_SRCS) $(PORT_C_SRCS) $(CODEGEN_SRCS)
|
||||
LIB_SRCS := $(LIB_C_SRCS) $(PORT_ASM_SRCS)
|
||||
|
||||
# Example sources (single-TU unless noted).
|
||||
|
|
@ -64,7 +65,7 @@ NTP_BIN := $(BUILD)/audio/ntpplayer.bin
|
|||
NTP_ASM := $(BUILD)/audio/ntpdata.s
|
||||
IIGS_MERLIN := $(REPO_DIR)/toolchains/iigs/merlin32/bin/merlin32
|
||||
|
||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples clean-iigs
|
||||
.PHONY: all iigs iigs-lib iigs-clang-smoke iigs-examples iigs-disk iigs-verify clean-iigs
|
||||
|
||||
# Default: compile-check the library + run the end-to-end smoke test.
|
||||
all iigs: iigs-lib iigs-clang-smoke
|
||||
|
|
@ -76,6 +77,19 @@ iigs-examples: $(BINDIR)/PATTERN $(BINDIR)/DRAW $(BINDIR)/KEYS $(BINDIR)/JOY \
|
|||
$(BINDIR)/SPRITE $(BINDIR)/UBER $(BINDIR)/ADV $(BINDIR)/ADV2 \
|
||||
$(BINDIR)/AGI
|
||||
|
||||
# Build the JOEYLIB data disk (build/iigs/bin/joey.2mg) that run-iigs-mame.sh /
|
||||
# run-iigs.sh mount on flop4. Packs the launchable examples as ProDOS S16 apps
|
||||
# (an 800KB floppy can't hold every example; see make-iigs-disk.sh).
|
||||
iigs-disk: iigs-examples
|
||||
BINDIR="$(BINDIR)" $(REPO_DIR)/scripts/make-iigs-disk.sh $(BINDIR)/joey.2mg
|
||||
|
||||
# Headless visual-verification gate: boot GS/OS under MAME, launch the DRAW
|
||||
# example off joey.2mg, and assert the SHR framebuffer was actually rendered
|
||||
# (many distinct colors -> pixels/lines/circles/tiles/flood all drew). Pass
|
||||
# VERIFY_EXAMPLE=name to check a different example.
|
||||
iigs-verify: iigs-disk
|
||||
$(REPO_DIR)/scripts/verify-iigs.sh $(or $(VERIFY_EXAMPLE),draw)
|
||||
|
||||
$(NTP_BIN): $(NTP_SRC) $(IIGS_MERLIN)
|
||||
@mkdir -p $(BUILD)/audio
|
||||
@cp $(NTP_SRC) $(BUILD)/audio/ninjatrackerplus.s
|
||||
|
|
|
|||
92
scripts/bench-amiga.sh
Executable file
92
scripts/bench-amiga.sh
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env bash
|
||||
# Headless UBER perf capture for the Amiga (FS-UAE).
|
||||
#
|
||||
# Stages the built Amiga binaries + DATA into a scratch directory-HD
|
||||
# with an s/startup-sequence that auto-runs Uber, boots A500/OCS from
|
||||
# the real Kickstart on a private Xvfb display, polls the staged
|
||||
# joeylog.txt until UBER prints its "press any key to exit" completion
|
||||
# marker (or a wall-clock timeout), then kills the emulator and copies
|
||||
# the log to build/amiga/bin/joeylog.txt for tools/uber-perf-table.
|
||||
#
|
||||
# scripts/bench-amiga.sh # default 150s timeout
|
||||
# TIMEOUT=240 scripts/bench-amiga.sh # override
|
||||
#
|
||||
# Real Kickstart (toolchains/emulators/support/kickstart.rom) is
|
||||
# required -- the HAL drives OCS directly and AROS is unreliable for it.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/amiga/bin
|
||||
support=$repo/toolchains/emulators/support
|
||||
kickstart=$support/kickstart.rom
|
||||
timeout_s=${TIMEOUT:-150}
|
||||
|
||||
if [[ ! -f $bin_dir/Uber ]]; then
|
||||
echo "Uber not built. Run 'make amiga' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $kickstart ]]; then
|
||||
echo "Kickstart ROM missing: $kickstart" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
work=$(mktemp -d -t joeylib-amiga-bench.XXXXXX)
|
||||
mkdir -p "$work/s"
|
||||
find "$bin_dir" -maxdepth 1 -type f -executable -exec cp -t "$work/" {} +
|
||||
if [[ -d "$bin_dir/DATA" ]]; then
|
||||
cp -r "$bin_dir/DATA" "$work/"
|
||||
fi
|
||||
# ':' anchors to the boot volume root.
|
||||
echo ":Uber" > "$work/s/startup-sequence"
|
||||
|
||||
log=$work/joeylog.txt
|
||||
|
||||
display=:$((90 + RANDOM % 9))
|
||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||
xvfb_pid=$!
|
||||
sleep 0.4
|
||||
cleanup() {
|
||||
kill "$fs_pid" 2>/dev/null || true
|
||||
kill "$xvfb_pid" 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
rm -rf "$work"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "bench-amiga: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||
DISPLAY=$display fs-uae \
|
||||
--amiga_model=A500 \
|
||||
--fast_memory=2048 \
|
||||
--hard_drive_0="$work" \
|
||||
--hard_drive_0_label=JOEYLIB \
|
||||
--kickstart_file="$kickstart" \
|
||||
--automatic_input_grab=0 \
|
||||
--fullscreen=0 \
|
||||
--fade_out_duration=0 \
|
||||
>/tmp/bench-amiga-fsuae.log 2>&1 &
|
||||
fs_pid=$!
|
||||
|
||||
waited=0
|
||||
while (( waited < timeout_s )); do
|
||||
if [[ -f $log ]] && grep -q "press any key to exit" "$log" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
(( waited += 2 )) || true
|
||||
done
|
||||
|
||||
if [[ ! -f $log ]]; then
|
||||
echo "bench-amiga: FAIL -- no joeylog.txt produced" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$log" "$bin_dir/joeylog.txt"
|
||||
ops=$(grep -c "ops/sec" "$log" || true)
|
||||
done_marker=$(grep -c "press any key to exit" "$log" || true)
|
||||
if (( done_marker == 0 )); then
|
||||
echo "bench-amiga: WARNING -- timed out before completion marker (partial capture)" >&2
|
||||
fi
|
||||
echo "bench-amiga: captured $ops op lines (complete=$done_marker) in ${waited}s" >&2
|
||||
echo " -> $bin_dir/joeylog.txt" >&2
|
||||
grep "UBER:" "$log" | sed 's/ | hash=.*//' || true
|
||||
84
scripts/bench-atarist.sh
Executable file
84
scripts/bench-atarist.sh
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env bash
|
||||
# Headless UBER perf capture for the Atari ST (Hatari).
|
||||
#
|
||||
# Runs build/atarist/bin/UBER.PRG under Hatari (fast-forward, no sound)
|
||||
# on a private Xvfb display, polling joeylog.txt until UBER prints its
|
||||
# "press any key to exit" completion marker (or a wall-clock timeout),
|
||||
# then kills the emulator.
|
||||
#
|
||||
# UBER writes "UBER: <op>: N iters / M frames = K ops/sec" lines to
|
||||
# joeylog.txt in its CWD == the GEMDOS drive C: == the bin dir, so the
|
||||
# log lands directly in build/atarist/bin/joeylog.txt where
|
||||
# tools/uber-perf-table expects it.
|
||||
#
|
||||
# scripts/bench-atarist.sh # default 90s timeout
|
||||
# TIMEOUT=120 scripts/bench-atarist.sh # override
|
||||
#
|
||||
# NOTE: requires the ST supervisor-mode fix in src/atarist/hal.c
|
||||
# (jlpInit Super(0L)); without it UBER bus-errors on the first present.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/atarist/bin
|
||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||
log=$bin_dir/joeylog.txt
|
||||
timeout_s=${TIMEOUT:-90}
|
||||
|
||||
if [[ ! -f $bin_dir/UBER.PRG ]]; then
|
||||
echo "UBER.PRG not built. Run 'make atarist' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f $tos ]]; then
|
||||
echo "TOS ROM missing: $tos" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$log"
|
||||
|
||||
display=:$((90 + RANDOM % 9))
|
||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||
xvfb_pid=$!
|
||||
sleep 0.4
|
||||
cleanup() {
|
||||
kill "$hat_pid" 2>/dev/null || true
|
||||
kill "$xvfb_pid" 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "bench-atarist: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||
DISPLAY=$display hatari \
|
||||
--tos "$tos" \
|
||||
--harddrive "$bin_dir" \
|
||||
--gemdos-drive C \
|
||||
--drive-a off --drive-b off \
|
||||
--sound off \
|
||||
--fast-forward on \
|
||||
--frameskips 4 \
|
||||
--auto "C:\\UBER.PRG" >/tmp/bench-atarist-hatari.log 2>&1 &
|
||||
hat_pid=$!
|
||||
|
||||
# Poll for the completion marker UBER flushes just before its key wait.
|
||||
waited=0
|
||||
while (( waited < timeout_s )); do
|
||||
if [[ -f $log ]] && grep -q "press any key to exit" "$log"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
(( waited++ )) || true
|
||||
done
|
||||
|
||||
if [[ ! -f $log ]]; then
|
||||
echo "bench-atarist: FAIL -- no joeylog.txt produced" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ops=$(grep -c "ops/sec" "$log" || true)
|
||||
done_marker=$(grep -c "press any key to exit" "$log" || true)
|
||||
if (( done_marker == 0 )); then
|
||||
echo "bench-atarist: WARNING -- timed out before completion marker (partial capture)" >&2
|
||||
fi
|
||||
echo "bench-atarist: captured $ops op lines (complete=$done_marker) in ${waited}s" >&2
|
||||
echo " -> $log" >&2
|
||||
grep "UBER:" "$log" | sed 's/ | hash=.*//' || true
|
||||
71
scripts/bench-dos.sh
Executable file
71
scripts/bench-dos.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env bash
|
||||
# Headless UBER perf capture for DOS (DOSBox, 386SX-16 timing).
|
||||
#
|
||||
# Mounts build/dos/bin as C: in a headless DOSBox (Xvfb), runs UBER.EXE
|
||||
# (which writes JOEYLOG.TXT to its CWD == C: == the host bin dir), polls
|
||||
# that log for UBER's "press any key to exit" completion marker, then
|
||||
# kills DOSBox. tools/uber-perf-table reads build/dos/bin/JOEYLOG.TXT.
|
||||
#
|
||||
# Uses the same dosbox-386sx16.conf as run-dos.sh so the cycle budget
|
||||
# matches the PERF.md reference machine.
|
||||
#
|
||||
# scripts/bench-dos.sh # default 90s timeout
|
||||
# TIMEOUT=120 scripts/bench-dos.sh # override
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bin_dir=$repo/build/dos/bin
|
||||
conf=$repo/scripts/dosbox-386sx16.conf
|
||||
log=$bin_dir/JOEYLOG.TXT
|
||||
timeout_s=${TIMEOUT:-90}
|
||||
|
||||
if [[ ! -f $bin_dir/UBER.EXE ]]; then
|
||||
echo "UBER.EXE not built. Run 'make dos' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$log" "$bin_dir/joeylog.txt"
|
||||
|
||||
display=:$((90 + RANDOM % 9))
|
||||
Xvfb "$display" -screen 0 800x600x24 -nolisten tcp >/dev/null 2>&1 &
|
||||
xvfb_pid=$!
|
||||
sleep 0.4
|
||||
cleanup() {
|
||||
kill "$dosbox_pid" 2>/dev/null || true
|
||||
kill "$xvfb_pid" 2>/dev/null || true
|
||||
wait 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "bench-dos: running UBER headless (timeout ${timeout_s}s)..." >&2
|
||||
DISPLAY=$display dosbox \
|
||||
-conf "$conf" \
|
||||
-c "C:" \
|
||||
-c "UBER.EXE" \
|
||||
"$bin_dir" >/tmp/bench-dos-dosbox.log 2>&1 &
|
||||
dosbox_pid=$!
|
||||
|
||||
waited=0
|
||||
while (( waited < timeout_s )); do
|
||||
if [[ -f $log ]] && grep -q "press any key to exit" "$log" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
(( waited++ )) || true
|
||||
done
|
||||
|
||||
if [[ ! -f $log ]]; then
|
||||
echo "bench-dos: FAIL -- no JOEYLOG.TXT produced" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# uber-perf-table reads build/dos/bin/JOEYLOG.TXT directly (uppercase).
|
||||
ops=$(grep -c "ops/sec" "$log" || true)
|
||||
done_marker=$(grep -c "press any key to exit" "$log" || true)
|
||||
if (( done_marker == 0 )); then
|
||||
echo "bench-dos: WARNING -- timed out before completion marker (partial capture)" >&2
|
||||
fi
|
||||
echo "bench-dos: captured $ops op lines (complete=$done_marker) in ${waited}s" >&2
|
||||
echo " -> $log" >&2
|
||||
grep "UBER:" "$log" | sed 's/ | hash=.*//' || true
|
||||
57
scripts/check-blank.sh
Executable file
57
scripts/check-blank.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env bash
|
||||
# check-blank.sh - Smoke-test the new-port template (src/blank/blank.c).
|
||||
#
|
||||
# A blank port has no toolchain, so we compile it with the HOST cc just to
|
||||
# prove two things stay true as the library evolves:
|
||||
# 1. The template + the JOEYLIB_PLATFORM_BLANK blocks in platform.h still
|
||||
# compile (catches a new platform-#if dispatch point that forgot a BLANK
|
||||
# case -- e.g. assetLoad.c's tile target, the sprite emitter selector).
|
||||
# 2. blank.c + the generics LINK with no unresolved jlp* ops -- i.e. a new
|
||||
# chunky port really does get everything except the platform services for
|
||||
# free from src/generic/.
|
||||
#
|
||||
# Source set mirrors a real blank-port build: core + ALL of src/generic
|
||||
# (including the stub sprite emitter) + blank.c; NOT src/codegen/spriteCompile.c
|
||||
# (that's only for ports with a per-CPU JIT emitter).
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cc=${CC:-cc}
|
||||
work=$(mktemp -d -t joeylib-blank.XXXXXX)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
inc=(-Iinclude -Iinclude/joey -Isrc/core -Isrc/codegen)
|
||||
cd "$repo"
|
||||
|
||||
fail=0
|
||||
for f in src/core/*.c src/generic/*.c src/blank/blank.c; do
|
||||
o="$work/$(echo "$f" | tr / _).o"
|
||||
if ! "$cc" -DJOEYLIB_PLATFORM_BLANK "${inc[@]}" -Wall -c "$f" -o "$o" 2>"$work/err"; then
|
||||
echo "check-blank: COMPILE FAIL: $f" >&2
|
||||
sed 's/^/ /' "$work/err" >&2
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
[ "$fail" = 0 ] || { echo "check-blank: FAIL (template does not compile for BLANK)" >&2; exit 1; }
|
||||
|
||||
# Link-completeness: any JoeyLib symbol undefined across the whole object set
|
||||
# (and not defined by any of them) is a real gap in blank.c or the generics.
|
||||
missing=$(python3 - "$work" <<'PY'
|
||||
import subprocess, sys, glob
|
||||
defs=set(); und=set()
|
||||
for o in glob.glob(sys.argv[1]+"/*.o"):
|
||||
for line in subprocess.run(["nm",o],capture_output=True,text=True).stdout.splitlines():
|
||||
p=line.split()
|
||||
if len(p)>=3 and p[1] in "TDBRtdbr": defs.add(p[2])
|
||||
elif len(p)>=3 and p[1]=="U": und.add(p[2])
|
||||
elif len(p)==2 and p[0]=="U": und.add(p[1])
|
||||
print(" ".join(sorted(s for s in und-defs if s.startswith(("jlp","jlSprite","spriteCompiledDraw")))))
|
||||
PY
|
||||
)
|
||||
if [ -n "$missing" ]; then
|
||||
echo "check-blank: FAIL - unresolved JoeyLib symbols (blank.c / generics gap):" >&2
|
||||
echo " $missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check-blank: PASS (blank template compiles + links; all ops resolve via blank.c + generics)"
|
||||
68
scripts/make-iigs-disk.sh
Executable file
68
scripts/make-iigs-disk.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
# make-iigs-disk.sh - Build the JOEYLIB ProDOS disk image (joey.2mg) that
|
||||
# run-iigs-mame.sh and run-iigs.sh mount as the data disk (flop4). It holds
|
||||
# the launchable example binaries as ProDOS type $B3 (S16 / GS-OS apps) in a
|
||||
# volume named JOEYLIB, so GS/OS Finder lists and launches them.
|
||||
#
|
||||
# A 3.5" floppy caps at 800KB, which cannot hold every example at once.
|
||||
# Examples are added in priority order (small primitive demos first); any that
|
||||
# would overflow the volume are skipped with a warning rather than corrupting
|
||||
# the image. Override the set with JOEY_DISK_EXAMPLES="DRAW SPRITE ...".
|
||||
#
|
||||
# Usage: scripts/make-iigs-disk.sh [output.2mg]
|
||||
# Requires: toolchains/env.sh sourced (LLVM816_ROOT), built IIgs examples.
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
: "${LLVM816_ROOT:?make-iigs-disk.sh: source toolchains/env.sh first}"
|
||||
CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}"
|
||||
BINDIR="${BINDIR:-$repo/build/iigs/bin}"
|
||||
OUT="${1:-$BINDIR/joey.2mg}"
|
||||
VOL=JOEYLIB
|
||||
|
||||
[ -x "$CADIUS" ] || { echo "make-iigs-disk.sh: cadius not found at $CADIUS" >&2; exit 2; }
|
||||
|
||||
# Priority order: small primitive demos first so DRAW/PATTERN/etc. always fit;
|
||||
# the large AGI build lands last and is dropped if the floppy is full.
|
||||
order=(${JOEY_DISK_EXAMPLES:-DRAW PATTERN KEYS JOY SPRITE UBER ADV2 ADV AGI})
|
||||
|
||||
work=$(mktemp -d -t joeylib-disk.XXXXXX)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
rm -f "$OUT"
|
||||
"$CADIUS" CREATEVOLUME "$OUT" "$VOL" 800KB >/dev/null
|
||||
|
||||
freeBlocks() {
|
||||
"$CADIUS" CATALOG "$OUT" 2>/dev/null \
|
||||
| grep -oE 'Free : [0-9]+' | grep -oE '[0-9]+' | head -1
|
||||
}
|
||||
|
||||
added=()
|
||||
skipped=()
|
||||
for name in "${order[@]}"; do
|
||||
bin="$BINDIR/$name"
|
||||
[ -f "$bin" ] || continue
|
||||
sz=$(stat -c%s "$bin")
|
||||
# data blocks + one index block per 256 data blocks + a little dir slack
|
||||
need=$(( (sz + 511) / 512 + (sz + 131071) / 131072 + 2 ))
|
||||
if [ "$need" -gt "$(freeBlocks)" ]; then
|
||||
skipped+=("$name")
|
||||
continue
|
||||
fi
|
||||
# ProDOS type $B3 (S16/GS-OS application), aux $0000.
|
||||
cp "$bin" "$work/$name#B30000"
|
||||
"$CADIUS" ADDFILE "$OUT" "/$VOL" "$work/$name#B30000" >/dev/null
|
||||
rm -f "$work/$name#B30000"
|
||||
if "$CADIUS" CATALOG "$OUT" 2>/dev/null | grep -qE "^ $name +S16"; then
|
||||
added+=("$name")
|
||||
else
|
||||
skipped+=("$name")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "iigs-disk: $OUT (volume /$VOL)"
|
||||
echo " added: ${added[*]:-none}"
|
||||
if [ ${#skipped[@]} -gt 0 ]; then
|
||||
echo " SKIPPED (800KB floppy full): ${skipped[*]}"
|
||||
echo " -> mount a single-example disk via JOEY_DISK_EXAMPLES=\"NAME\" $0"
|
||||
fi
|
||||
|
|
@ -251,17 +251,32 @@ cleanup() {
|
|||
trap cleanup EXIT
|
||||
|
||||
# Visible by default. Set MAME_HEADLESS=1 to suppress the video window
|
||||
# (CI / batch runs that only care about crash.txt).
|
||||
# (CI / batch runs that only care about crash.txt / snapshots). The crash
|
||||
# hook drives MAME's debugger, which needs a Qt platform; headless boxes
|
||||
# have no X display, so force Qt's offscreen plugin and SDL's dummy
|
||||
# video/audio drivers so MAME never tries to open a window.
|
||||
video_arg="-window"
|
||||
if [[ "${MAME_HEADLESS:-0}" = "1" ]]; then
|
||||
video_arg="-video none"
|
||||
# Use the software renderer (not "-video none"): MAME's video:snapshot()
|
||||
# needs a real render target, so "-video none" silently drops snapshots.
|
||||
# "-video soft" renders to an offscreen surface that snapshot() can read.
|
||||
video_arg="-video soft"
|
||||
export QT_QPA_PLATFORM=offscreen
|
||||
export SDL_VIDEODRIVER=dummy
|
||||
export SDL_AUDIODRIVER=dummy
|
||||
fi
|
||||
|
||||
# apple2gs ROM set. Default to the user's MAME roms; override via MAME_ROMPATH.
|
||||
# (A bare `mame apple2gs` only finds ROMs if rompath is already configured;
|
||||
# passing it explicitly keeps this script self-contained.)
|
||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
||||
|
||||
# Clear the done-marker the Lua hook uses to signal shutdown.
|
||||
rm -f "$out/.done"
|
||||
|
||||
cd "$work"
|
||||
mame apple2gs \
|
||||
-rompath "$rompath" \
|
||||
-flop3 "$work/boot.po" \
|
||||
-flop4 "$work/joey.2mg" \
|
||||
$video_arg -sound none \
|
||||
|
|
|
|||
127
scripts/verify-iigs.sh
Executable file
127
scripts/verify-iigs.sh
Executable file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env bash
|
||||
# verify-iigs.sh - Headless, deterministic visual-verification gate for the
|
||||
# IIgs port. Boots GS/OS under MAME, drives the Finder to launch one example
|
||||
# off the JOEYLIB data disk, then reads the Super Hi-Res framebuffer directly
|
||||
# out of emulated memory ($E1/2000..$E1/9CFF) and reports how many distinct
|
||||
# 4-bit pixel values were drawn plus a checksum. A blank/Finder screen yields
|
||||
# a tiny distinct count; a real rendered demo (pixels, lines, circles, flood
|
||||
# fills) lights up many of the 16 colors.
|
||||
#
|
||||
# Why memory reads instead of PNG snapshots: MAME's video:snapshot() needs a
|
||||
# real render target, which a headless `-video none` run does not have. Reading
|
||||
# the SHR bytes the asm actually wrote is render-target-independent and exact.
|
||||
#
|
||||
# Usage: scripts/verify-iigs.sh [example] [minDistinct]
|
||||
# example lowercase name (default: draw). Must be on joey.2mg (run
|
||||
# `make iigs-disk` first). The volume is JOEYLIB.
|
||||
# minDistinct PASS threshold for distinct nibble values (default: 4).
|
||||
#
|
||||
# Requires: toolchains/env.sh sourced; build/iigs/bin/joey.2mg built.
|
||||
set -euo pipefail
|
||||
|
||||
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
example=${1:-draw}
|
||||
minDistinct=${2:-4}
|
||||
NAME=${example^^}
|
||||
|
||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"
|
||||
readFrame="${MAME_READ_FRAME:-9000}"
|
||||
|
||||
for f in "$sys_disk" "$data_disk"; do
|
||||
[ -f "$f" ] || { echo "verify-iigs: missing $f (run 'make iigs-disk')" >&2; exit 2; }
|
||||
done
|
||||
|
||||
work=$(mktemp -d -t joeylib-verify.XXXXXX)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
cp "$sys_disk" "$work/boot.po"
|
||||
cp "$data_disk" "$work/joey.2mg"
|
||||
|
||||
# Finder keystroke timeline (frame counts calibrated against gsos-system.po):
|
||||
# select JOEYLIB volume (J), Cmd-O to open it, type the program name, Cmd-O to
|
||||
# launch. Then at readFrame, sum the SHR framebuffer and report. No -debug: we
|
||||
# drive input with natkeyboard and read memory via the program space directly,
|
||||
# and exit cleanly from Lua (so MAME never lingers).
|
||||
cat > "$work/verify.lua" <<LUA
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local mem = cpu.spaces["program"]
|
||||
local nat = manager.machine.natkeyboard
|
||||
local frame = 0
|
||||
local idx = 1
|
||||
|
||||
local function field(port, name)
|
||||
local p = manager.machine.ioport.ports[port]
|
||||
if p == nil then return nil end
|
||||
return p.fields[name]
|
||||
end
|
||||
local key_cmd = field(":macadb:KEY3", "Command / Open Apple")
|
||||
local function press(f) if f then f:set_value(1) end end
|
||||
local function release(f) if f then f:set_value(0) end end
|
||||
|
||||
local function report()
|
||||
-- SHR pixels: \$E1/2000 .. \$E1/9CFF = 32000 bytes, 2 pixels/byte (4bpp).
|
||||
local base = 0xE12000
|
||||
local count = 32000
|
||||
local seen = {}
|
||||
local distinct = 0
|
||||
local nonzero = 0
|
||||
local sum = 0
|
||||
for i = 0, count - 1 do
|
||||
local b = mem:read_u8(base + i)
|
||||
if b ~= 0 then nonzero = nonzero + 1 end
|
||||
sum = (sum + b) & 0xFFFFFF
|
||||
local hi = (b >> 4) & 0x0F
|
||||
local lo = b & 0x0F
|
||||
if not seen[hi] then seen[hi] = true; distinct = distinct + 1 end
|
||||
if not seen[lo] then seen[lo] = true; distinct = distinct + 1 end
|
||||
end
|
||||
local scb0 = mem:read_u8(0xE19D00)
|
||||
io.write(string.format("VERIFY-IIGS name=$NAME frame=%d distinctNibbles=%d nonZeroBytes=%d checksum=%06X scb0=%02X\n",
|
||||
frame, distinct, nonzero, sum, scb0))
|
||||
io.flush()
|
||||
end
|
||||
|
||||
local steps = {
|
||||
{3000, function() nat:post("J") end},
|
||||
{3120, function() press(key_cmd) end},
|
||||
{3126, function() nat:post("o") end},
|
||||
{3180, function() release(key_cmd) end},
|
||||
{3540, function() nat:post("$NAME") end},
|
||||
{3660, function() press(key_cmd) end},
|
||||
{3666, function() nat:post("o") end},
|
||||
{3720, function() release(key_cmd) end},
|
||||
{$readFrame, function() report(); manager.machine:exit() end},
|
||||
}
|
||||
|
||||
emu.register_frame_done(function()
|
||||
frame = frame + 1
|
||||
while idx <= #steps and frame >= steps[idx][1] do
|
||||
steps[idx][2]()
|
||||
idx = idx + 1
|
||||
end
|
||||
end)
|
||||
LUA
|
||||
|
||||
cd "$work"
|
||||
out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
|
||||
timeout 300 mame apple2gs \
|
||||
-rompath "$rompath" \
|
||||
-flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \
|
||||
-video none -sound none -nothrottle \
|
||||
-autoboot_script "$work/verify.lua" </dev/null 2>&1) || true
|
||||
|
||||
line=$(echo "$out" | grep -E '^VERIFY-IIGS ' | tail -1)
|
||||
echo "$line"
|
||||
if [ -z "$line" ]; then
|
||||
echo "verify-iigs: FAIL - no framebuffer report (boot/launch failed)" >&2
|
||||
echo "$out" | tail -15 >&2
|
||||
exit 1
|
||||
fi
|
||||
distinct=$(echo "$line" | sed -E 's/.*distinctNibbles=([0-9]+).*/\1/')
|
||||
if [ "$distinct" -ge "$minDistinct" ]; then
|
||||
echo "verify-iigs: PASS ($NAME rendered, distinctNibbles=$distinct >= $minDistinct)"
|
||||
else
|
||||
echo "verify-iigs: FAIL ($NAME distinctNibbles=$distinct < $minDistinct - screen looks blank)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
#include <proto/exec.h>
|
||||
|
||||
#include "ptplayer.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "joey/audio.h"
|
||||
|
||||
extern struct Custom custom;
|
||||
|
|
@ -48,7 +48,7 @@ extern UBYTE mt_E8Trigger;
|
|||
// unless the author placed an `E8FF` effect at song end. mt_E8Trigger
|
||||
// reflects the most-recently-seen E8 value; mt_init resets it to 0.
|
||||
// When loop=false on PlayMod, we poll for this sentinel in
|
||||
// halAudioFrameTick and clear mt_Enable when seen.
|
||||
// jlpAudioFrameTick and clear mt_Enable when seen.
|
||||
#define PTPLAYER_LOOP_END_MARKER 0xFF
|
||||
|
||||
// ----- Module state -----
|
||||
|
|
@ -86,7 +86,7 @@ static UWORD periodForRate(uint16_t rateHz) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halAudioInit(void) {
|
||||
bool jlpAudioInit(void) {
|
||||
int i;
|
||||
|
||||
if (gInstalled) {
|
||||
|
|
@ -109,7 +109,7 @@ bool halAudioInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioShutdown(void) {
|
||||
void jlpAudioShutdown(void) {
|
||||
int i;
|
||||
|
||||
if (!gInstalled) {
|
||||
|
|
@ -136,12 +136,12 @@ void halAudioShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
bool halAudioIsPlayingMod(void) {
|
||||
bool jlpAudioIsPlayingMod(void) {
|
||||
return gModPlaying && mt_Enable != 0;
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
UBYTE *chip;
|
||||
|
||||
if (!gInstalled) {
|
||||
|
|
@ -167,7 +167,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
|
||||
// PTPlayer takes the module pointer in a0, sample-data pointer in
|
||||
// a1 (NULL = samples follow the module header), song position in
|
||||
// d0. mt_init resets mt_E8Trigger to 0 so subsequent halAudioFrame
|
||||
// d0. mt_init resets mt_E8Trigger to 0 so subsequent jlpAudioFrame
|
||||
// Tick polling sees a clean state.
|
||||
mt_init((void *)&custom, chip, NULL, 0);
|
||||
mt_Enable = 1;
|
||||
|
|
@ -176,7 +176,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
SfxStructure sfx;
|
||||
SfxSlotT *s;
|
||||
UBYTE *chip;
|
||||
|
|
@ -221,7 +221,7 @@ void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1
|
|||
// need a per-slot Paula buffer with mid-playback refill from the
|
||||
// audio IRQ. No-op until then; callers that depend on streaming
|
||||
// will hear silence on Amiga.
|
||||
void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
(void)slot;
|
||||
(void)fill;
|
||||
(void)ctx;
|
||||
|
|
@ -233,7 +233,7 @@ void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uin
|
|||
// keep a single-period square sample in chip RAM and re-program
|
||||
// Paula's period register per note. Not wired yet -- AGI-on-Amiga
|
||||
// will be silent until this is filled in.
|
||||
void halAudioTone(uint16_t freqHz) {
|
||||
void jlpAudioTone(uint16_t freqHz) {
|
||||
(void)freqHz;
|
||||
}
|
||||
|
||||
|
|
@ -246,14 +246,14 @@ void halAudioTone(uint16_t freqHz) {
|
|||
// AGI 0..15 attenuation. Stubbed for now -- AGI-on-Amiga is silent
|
||||
// until the chip buffer + AllocAudio + direct Paula programming
|
||||
// lands. See feedback_amiga_audio_voice memory when it does.
|
||||
void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
(void)voice;
|
||||
(void)freqHz;
|
||||
(void)atten;
|
||||
}
|
||||
|
||||
|
||||
bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
// CIA-B Timer B at hz could drive this; not wired until the
|
||||
// Paula voice path lands.
|
||||
(void)fn;
|
||||
|
|
@ -262,15 +262,15 @@ bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioCriticalEnter(void) {
|
||||
void jlpAudioCriticalEnter(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioCriticalExit(void) {
|
||||
void jlpAudioCriticalExit(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioStopMod(void) {
|
||||
void jlpAudioStopMod(void) {
|
||||
if (!gInstalled) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -281,7 +281,7 @@ void halAudioStopMod(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopSfx(uint8_t slot) {
|
||||
void jlpAudioStopSfx(uint8_t slot) {
|
||||
UBYTE i;
|
||||
|
||||
if (!gInstalled || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
|
|
@ -298,7 +298,7 @@ void halAudioStopSfx(uint8_t slot) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioFrameTick(void) {
|
||||
void jlpAudioFrameTick(void) {
|
||||
// PTPlayer drives itself off CIA-B; the only host-loop work is the
|
||||
// play-once watchdog. When the song author placed an `E8FF` at
|
||||
// song end, mt_E8Trigger latches to 0xFF -- if the caller passed
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
#include <proto/intuition.h>
|
||||
#include <proto/graphics.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "spriteInternal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ extern struct Custom custom;
|
|||
|
||||
|
||||
// Frame-counter VBL server lives at end of file; forward-declare so
|
||||
// halInit / halShutdown can install / remove it without C inferring
|
||||
// jlpInit / jlpShutdown can install / remove it without C inferring
|
||||
// implicit non-static linkage at the call sites.
|
||||
static void installVblServer(void);
|
||||
static void removeVblServer(void);
|
||||
|
|
@ -103,7 +103,7 @@ static struct BitMap *gBitMap = NULL;
|
|||
// Phase 10 page-flip: two CHIP plane sets. gPlanes is set A (kept
|
||||
// under the original name to keep diffs minimal); gPlanesB is set B.
|
||||
// gFrontIdx selects which is currently displayed via BPL DMA. The
|
||||
// off-screen set is the destination for halPresent's shadow->CHIP
|
||||
// off-screen set is the destination for jlpPresent's shadow->CHIP
|
||||
// copy, so the beam never catches the writes.
|
||||
static UBYTE *gPlanes [AMIGA_BITPLANES];
|
||||
static UBYTE *gPlanesB[AMIGA_BITPLANES];
|
||||
|
|
@ -114,7 +114,7 @@ static struct UCopList *gNewUCL = NULL; // built but not yet installed
|
|||
// each BPLnPTH/PTL move instruction. scanBplCopperPokes finds them
|
||||
// after every installCopperList; patchBplCopperPokes writes new plane
|
||||
// addresses into them per flip. NULL until the first copper list is
|
||||
// installed (i.e. before the first halPresent).
|
||||
// installed (i.e. before the first jlpPresent).
|
||||
static volatile uint16_t *gBplPokes[AMIGA_BITPLANES][2]; // [plane][0=PTH,1=PTL]
|
||||
|
||||
// Snapshot of the previous frame's dirty bands. With N=2 buffers,
|
||||
|
|
@ -127,7 +127,7 @@ static uint8_t gPrevStageMinWord[SURFACE_HEIGHT];
|
|||
static uint8_t gPrevStageMaxWord[SURFACE_HEIGHT];
|
||||
|
||||
// "The previous frame's snapshot recorded at least one dirty row."
|
||||
// Lets halPresent early-out of the whole union scan + 400-byte
|
||||
// Lets jlpPresent early-out of the whole union scan + 400-byte
|
||||
// snapshot copy on consecutive idle frames: if neither the previous
|
||||
// frame nor the current frame touched any row, union(prev,current) is
|
||||
// empty AND the snapshot would copy clean->clean (a no-op), so there
|
||||
|
|
@ -136,7 +136,7 @@ static uint8_t gPrevStageMaxWord[SURFACE_HEIGHT];
|
|||
// presents still run.
|
||||
static bool gPrevAnyDirty = true;
|
||||
|
||||
// Cached SCB + palettes from the last present. halPresent* only needs
|
||||
// Cached SCB + palettes from the last present. jlpPresent* only needs
|
||||
// to rebuild/install the copper list when SCB assignments or palette
|
||||
// RGB values differ from what is already on screen; pure pixel updates
|
||||
// (which dominate a typical game loop and every frame of the keys
|
||||
|
|
@ -335,7 +335,7 @@ static void installCopperList(void) {
|
|||
|
||||
// MrgCop rebuilt the hardware copper list from scratch, including
|
||||
// fresh BPL pointer pokes derived from gBitMap->Planes[]. Re-cache
|
||||
// pointers to those pokes so the per-frame flip in halPresent can
|
||||
// pointers to those pokes so the per-frame flip in jlpPresent can
|
||||
// patch them in place without another MrgCop pass.
|
||||
scanBplCopperPokes();
|
||||
}
|
||||
|
|
@ -402,7 +402,7 @@ static void scanBplCopperPokes(void) {
|
|||
// of CHIP plane buffers. Takes effect at the next vblank when the
|
||||
// copper next runs through these pokes. Disable() guards against an
|
||||
// interrupt landing between word writes; the copper itself is past
|
||||
// the BPL section by the time halPresent runs (the app calls
|
||||
// the BPL section by the time jlpPresent runs (the app calls
|
||||
// jlWaitVBL before jlStagePresent, so we're already in the visible
|
||||
// scan region by here), so we can't actually tear a poke in flight.
|
||||
static void patchBplCopperPokes(UBYTE *planes[AMIGA_BITPLANES]) {
|
||||
|
|
@ -518,7 +518,7 @@ static void uploadFirstBandPalette(const jlSurfaceT *src) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halInit(const jlConfigT *config) {
|
||||
bool jlpInit(const jlConfigT *config) {
|
||||
uint16_t i;
|
||||
uint16_t j;
|
||||
|
||||
|
|
@ -545,7 +545,7 @@ bool halInit(const jlConfigT *config) {
|
|||
return false;
|
||||
}
|
||||
InitBitMap(gBitMap, (LONG)AMIGA_BITPLANES, (LONG)SURFACE_WIDTH, (LONG)SURFACE_HEIGHT);
|
||||
// Phase 10 page-flip needs TWO CHIP plane sets so halPresent can
|
||||
// Phase 10 page-flip needs TWO CHIP plane sets so jlpPresent can
|
||||
// write into the off-screen one and an atomic copper-list patch
|
||||
// swaps which one BPL DMA fetches from. Allocate both up front;
|
||||
// bail with full rollback if either runs out of chip RAM.
|
||||
|
|
@ -574,7 +574,7 @@ bool halInit(const jlConfigT *config) {
|
|||
return false;
|
||||
}
|
||||
// Set A is the initial front buffer; BPL DMA fetches from it
|
||||
// until the first halPresent flips to B.
|
||||
// until the first jlpPresent flips to B.
|
||||
gBitMap->Planes[i] = gPlanes[i];
|
||||
}
|
||||
gFrontIdx = 0;
|
||||
|
|
@ -590,7 +590,7 @@ bool halInit(const jlConfigT *config) {
|
|||
// SA_DisplayID pins us to OCS PAL low-res so Intuition opens a
|
||||
// real planar screen rather than an RTG substitute. SA_BitMap
|
||||
// makes Intuition use OUR pre-allocated planes; CloseScreen will
|
||||
// not free them -- our halShutdown does.
|
||||
// not free them -- our jlpShutdown does.
|
||||
gScreen = OpenScreenTags(NULL,
|
||||
(ULONG)SA_Width, (ULONG)SURFACE_WIDTH,
|
||||
(ULONG)SA_Height, (ULONG)SURFACE_HEIGHT,
|
||||
|
|
@ -630,16 +630,11 @@ bool halInit(const jlConfigT *config) {
|
|||
}
|
||||
|
||||
|
||||
const char *halLastError(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Phase 9 switch flip: present is now always a per-row planar memcpy
|
||||
// from the off-screen shadow planes (where every drawing primitive
|
||||
// dual-writes today) into gPlanes[] (the displayed BitMap). c2p is
|
||||
// gone; the chunky `s->pixels` shadow is still maintained by the
|
||||
// halFast* primitives but no longer drives display. Phase 10 will
|
||||
// jlp<Op> primitives but no longer drives display. Phase 10 will
|
||||
// either (a) BPLPTR-swap shadow <-> display planes (zero-copy) or
|
||||
// (b) stop writing chunky in the fast paths to recover the dual-
|
||||
// write cost. Per-row dirty tracking is reused: only dirty bands
|
||||
|
|
@ -710,7 +705,7 @@ static void amigaPresentRectInner(AmigaPlanarT *pd, UBYTE *destPlanes[AMIGA_BITP
|
|||
}
|
||||
|
||||
|
||||
void halPresent(const jlSurfaceT *src) {
|
||||
void jlpPresent(const jlSurfaceT *src) {
|
||||
AmigaPlanarT *pd;
|
||||
int16_t y;
|
||||
int16_t firstRow;
|
||||
|
|
@ -836,7 +831,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
// Snapshot current dirty bands as "previous" for next present.
|
||||
// jlStagePresent clears the live bands after halPresent returns,
|
||||
// jlStagePresent clears the live bands after jlpPresent returns,
|
||||
// so we have to grab them now or they're gone. Record whether any
|
||||
// row was dirty so the next present can take the idle-frame early-
|
||||
// out above. A row is dirty iff its min word stayed below the clean
|
||||
|
|
@ -859,7 +854,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
// WaitTOF() blocks the calling task until the next "top of frame"
|
||||
// VBlank interrupt -- 50 Hz on PAL, 60 Hz on NTSC. graphics.library
|
||||
// is auto-opened by libnix so no extra plumbing is needed.
|
||||
void halWaitVBL(void) {
|
||||
void jlpWaitVBL(void) {
|
||||
WaitTOF();
|
||||
}
|
||||
|
||||
|
|
@ -871,7 +866,7 @@ void halWaitVBL(void) {
|
|||
// when the caller's loop body is long. The interrupt server fires
|
||||
// exactly once per VBlank regardless of caller cadence.
|
||||
//
|
||||
// halFrameCount just reads the volatile counter -- no edge detection
|
||||
// jlpFrameCount just reads the volatile counter -- no edge detection
|
||||
// needed in the polling path.
|
||||
|
||||
static volatile uint16_t gFrameCount = 0;
|
||||
|
|
@ -915,12 +910,12 @@ static void removeVblServer(void) {
|
|||
}
|
||||
|
||||
|
||||
uint16_t halFrameCount(void) {
|
||||
uint16_t jlpFrameCount(void) {
|
||||
return gFrameCount;
|
||||
}
|
||||
|
||||
|
||||
uint16_t halFrameHz(void) {
|
||||
uint16_t jlpFrameHz(void) {
|
||||
/* PAL by default. The toolchain doesn't currently switch modes
|
||||
* at runtime; if we ever expose NTSC this returns 60. */
|
||||
return 50u;
|
||||
|
|
@ -931,17 +926,14 @@ uint16_t halFrameHz(void) {
|
|||
// IIgs. A timer.device-based ReadEClock impl would give microsecond
|
||||
// resolution and decouple from VBL load, but adds a 7-line library
|
||||
// open / close dance for what AGI doesn't notice yet (audio is
|
||||
// stubbed on Amiga -- see halAudioVoice). Revisit when 3-voice
|
||||
// stubbed on Amiga -- see jlpAudioVoice). Revisit when 3-voice
|
||||
// audio actually lands on Amiga.
|
||||
uint32_t halMillisElapsed(void) {
|
||||
return (uint32_t)((uint32_t)halFrameCount() * 1000u / 50u);
|
||||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
void jlpShutdown(void) {
|
||||
uint16_t i;
|
||||
|
||||
amigaDumpPresentCounters("halShutdown");
|
||||
amigaDumpPresentCounters("jlpShutdown");
|
||||
// Tear down the VBL server before closing the screen so the
|
||||
// interrupt chain is clean if anything else is watching.
|
||||
removeVblServer();
|
||||
|
|
@ -956,7 +948,7 @@ void halShutdown(void) {
|
|||
CloseScreen(gScreen);
|
||||
gScreen = NULL;
|
||||
}
|
||||
// We allocated the BitMap and its planes manually (see halInit)
|
||||
// We allocated the BitMap and its planes manually (see jlpInit)
|
||||
// so SA_BitMap could pin Intuition to non-interleaved layout.
|
||||
// CloseScreen with an SA_BitMap'd screen does NOT free our
|
||||
// BitMap or planes -- we own them and must clean up here.
|
||||
|
|
@ -994,27 +986,20 @@ extern void surface68kAmigaCircleOutline(uint8_t *p0, uint8_t *p1, uint8_t *p2,
|
|||
uint16_t cx, uint16_t cy, uint16_t r, uint8_t color);
|
||||
|
||||
|
||||
// Phase 3 planar dual-write: write the bitplanes alongside the
|
||||
// Phase 9: Amiga is pure planar. Every halFast* below returns true
|
||||
// for any surface that has planar storage (s->portData != NULL) to
|
||||
// suppress the cross-platform chunky fallback path -- there is no
|
||||
// chunky shadow on Amiga (s->pixels is NULL post-Phase 9). The gate
|
||||
// is portData presence, NOT stage identity, so non-stage surfaces
|
||||
// created by jlSurfaceCreate use their OWN planes instead of falling
|
||||
// into the chunky C fallback (which would deref s->pixels == NULL).
|
||||
// The actual planar work happens either inline here or in dedicated
|
||||
// halXxxPlanes hooks called by cross-platform code AFTER each halFast*.
|
||||
//
|
||||
// halFastSurfaceClear inlines the planar fill (no separate hook).
|
||||
// All other halFast* are short-circuit stubs that return true.
|
||||
bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
// Amiga is pure planar (s->pixels is NULL). Each jlp<Op> below does the planar
|
||||
// work directly on the bitplanes in s->portData (AmigaPlanarT); the degenerate
|
||||
// portData == NULL case defers to the chunky-C generic (a no-op there since
|
||||
// s->pixels is NULL). The gate is portData presence, not stage identity, so
|
||||
// non-stage surfaces created by jlSurfaceCreate use their own planes.
|
||||
void jlpSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t colorIndex;
|
||||
uint16_t i;
|
||||
bool setBits;
|
||||
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
jlpGenericSurfaceClear(s, doubled);
|
||||
return;
|
||||
}
|
||||
colorIndex = (uint8_t)(doubled & 0x0Fu);
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
|
|
@ -1022,7 +1007,7 @@ bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
|||
// Blitter DEST-only block fill of every whole plane to $00 (clear)
|
||||
// or $FF (set). Each plane is 200 rows x 20 words, contiguous, so
|
||||
// BLTSIZE = (200 << 6) | 20 and BLTDMOD = 0. The planes are now
|
||||
// CHIP RAM (see halSurfaceAllocPortData) so the blitter can reach
|
||||
// CHIP RAM (see jlpSurfaceAllocPortData) so the blitter can reach
|
||||
// them. Own the blitter once and fill all four planes; the final
|
||||
// WaitBlit before DisownBlitter guarantees the planes are fully
|
||||
// written before any CPU read (a subsequent draw or the present
|
||||
|
|
@ -1034,27 +1019,13 @@ bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
|||
}
|
||||
WaitBlit();
|
||||
DisownBlitter();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
/* Pure short-circuit: halFillRectPlanes (called by cross-platform
|
||||
* jlFillRect after this) does the actual planar fill with full
|
||||
* partial-byte mask handling. We just claim ownership so the C
|
||||
* chunky fallback never runs. */
|
||||
(void)x; (void)y; (void)w; (void)h; (void)colorIndex;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 3 planar dual-write for jlFillRect: writes the four off-screen
|
||||
// shadow plane buffers alongside the chunky shadow. Caller (cross-
|
||||
// platform jlFillRect) has already done the chunky write via
|
||||
// halFastFillRect or fillRectClipped. The shadow planes are off-
|
||||
// the chunky generic. The shadow planes are off-
|
||||
// screen so this is invisible until jlStagePresent.
|
||||
//
|
||||
// Layout reminder (see docs/amiga_planar.md): each plane byte covers
|
||||
|
|
@ -1070,7 +1041,7 @@ bool halFastFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h
|
|||
// For each plane, the bit value at every pixel in the rect is
|
||||
// constant: (colorIndex >> plane) & 1. Set bit -> OR with mask;
|
||||
// clear bit -> AND with ~mask.
|
||||
void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
void jlpFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t byteFirst;
|
||||
uint16_t byteLast;
|
||||
|
|
@ -1090,13 +1061,14 @@ void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t
|
|||
}
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
if (pd == NULL) {
|
||||
jlpGenericFillRect(s, x, y, w, h, colorIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Variable shifts on 68000 cost 8 cyc per bit shifted -- for shifts
|
||||
* by up to 7 that's ~30 cyc per mask, ~60 cyc both. LUTs compile
|
||||
* to a single byte load. jlFillCircle r=40 calls halFillRectPlanes
|
||||
* 160 times, so this saves ~160*60=9600 cyc per jlFillCircle. */
|
||||
* to a single byte load. jlFillCircle r=40 fills 160 spans, so
|
||||
* this saves ~160*60=9600 cyc per jlFillCircle. */
|
||||
static const uint8_t kLeftMaskLut[8] = {
|
||||
0xFFu, 0x7Fu, 0x3Fu, 0x1Fu, 0x0Fu, 0x07u, 0x03u, 0x01u
|
||||
};
|
||||
|
|
@ -1339,7 +1311,7 @@ void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t
|
|||
// byte per row, 8 rows per tile, no edge masks. Stride between rows
|
||||
// in a plane is AMIGA_BYTES_PER_ROW (40).
|
||||
|
||||
void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
void jlpTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t plane;
|
||||
uint8_t fillByte;
|
||||
|
|
@ -1348,6 +1320,7 @@ void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex
|
|||
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
if (pd == NULL) {
|
||||
jlpGenericTileFill(s, bx, by, colorIndex);
|
||||
return;
|
||||
}
|
||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
||||
|
|
@ -1361,7 +1334,7 @@ void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex
|
|||
}
|
||||
|
||||
|
||||
void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
void jlpTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
AmigaPlanarT *dstPd;
|
||||
AmigaPlanarT *srcPd;
|
||||
uint16_t plane;
|
||||
|
|
@ -1372,6 +1345,7 @@ void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSu
|
|||
dstPd = (AmigaPlanarT *)dst->portData;
|
||||
srcPd = (AmigaPlanarT *)src->portData;
|
||||
if (dstPd == NULL || srcPd == NULL) {
|
||||
jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
||||
return;
|
||||
}
|
||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
||||
|
|
@ -1393,10 +1367,9 @@ void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSu
|
|||
// bits = OR of 4 XOR'd bytes is 0 in that bit). Then for each plane,
|
||||
// dst = (dst & ~mask) | (src & mask) -- copy src bits at mask-set
|
||||
// positions, preserve dst bits elsewhere.
|
||||
void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
||||
void jlpTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
||||
AmigaPlanarT *dstPd;
|
||||
AmigaPlanarT *srcPd;
|
||||
uint8_t transparent;
|
||||
uint8_t transBitByte[AMIGA_BITPLANES];
|
||||
uint16_t i;
|
||||
uint8_t row;
|
||||
|
|
@ -1408,9 +1381,10 @@ void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, cons
|
|||
dstPd = (AmigaPlanarT *)dst->portData;
|
||||
srcPd = (AmigaPlanarT *)src->portData;
|
||||
if (dstPd == NULL || srcPd == NULL) {
|
||||
jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
|
||||
return;
|
||||
}
|
||||
transparent = (uint8_t)(transparentIndex & 0x0Fu);
|
||||
transparent = (uint8_t)(transparent & 0x0Fu);
|
||||
/* Per-plane "all bits set if transparent's bit at this plane is 1
|
||||
* else all 0" -- so XOR gives bit set where pixel differs from
|
||||
* transparent in that plane. */
|
||||
|
|
@ -1457,7 +1431,7 @@ void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, cons
|
|||
// pixel's 4bpp color is assembled from / written to the 4 plane bits
|
||||
// at byte x>>3, bit 0x80 >> (x & 7).
|
||||
|
||||
void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
void jlpSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
const AmigaPlanarT *pd;
|
||||
uint16_t pairs;
|
||||
uint16_t row;
|
||||
|
|
@ -1468,6 +1442,49 @@ void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w,
|
|||
if (pd == NULL || dstPlaneBytes == NULL || w == 0u || h == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Byte-aligned fast path: raw plane-byte copy, no per-pixel c2p.
|
||||
* When x and w are both 8-px aligned and the rect is fully on
|
||||
* surface, each sprite tile column maps to whole plane bytes, so we
|
||||
* save the raw bytes -- ~8x cheaper than the chunky walker below and
|
||||
* an exact round-trip on restore (the sprite fills its bytes, so
|
||||
* restoring whole bytes touches no neighbor pixels). The backup row
|
||||
* holds plane0..plane3, each `wb` bytes, contiguous. The chunky
|
||||
* walker below stays as the arbitrary-x fallback. */
|
||||
if ((x & 7) == 0 && (w & 7) == 0
|
||||
&& x >= 0 && (x + (int16_t)w) <= SURFACE_WIDTH
|
||||
&& y >= 0 && (y + (int16_t)h) <= SURFACE_HEIGHT) {
|
||||
uint16_t wb;
|
||||
uint16_t wb2;
|
||||
uint16_t wb3;
|
||||
uint16_t startByte;
|
||||
uint16_t rowOff;
|
||||
uint8_t *bdst;
|
||||
uint16_t col;
|
||||
|
||||
wb = (uint16_t)(w >> 3);
|
||||
wb2 = (uint16_t)(wb << 1);
|
||||
wb3 = (uint16_t)(wb2 + wb);
|
||||
startByte = (uint16_t)((uint16_t)x >> 3);
|
||||
rowOff = (uint16_t)((uint16_t)y * AMIGA_BYTES_PER_ROW + startByte);
|
||||
bdst = dstPlaneBytes;
|
||||
for (row = 0; row < (uint16_t)h; row++) {
|
||||
const uint8_t *s0 = pd->planes[0] + rowOff;
|
||||
const uint8_t *s1 = pd->planes[1] + rowOff;
|
||||
const uint8_t *s2 = pd->planes[2] + rowOff;
|
||||
const uint8_t *s3 = pd->planes[3] + rowOff;
|
||||
for (col = 0; col < wb; col++) {
|
||||
bdst[col] = s0[col];
|
||||
bdst[wb + col] = s1[col];
|
||||
bdst[wb2 + col] = s2[col];
|
||||
bdst[wb3 + col] = s3[col];
|
||||
}
|
||||
bdst += (uint16_t)(wb << 2);
|
||||
rowOff += AMIGA_BYTES_PER_ROW;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pairs = (uint16_t)(w >> 1);
|
||||
for (row = 0; row < (uint16_t)h; row++) {
|
||||
const uint8_t *p0 = pd->planes[0] + ((uint16_t)y + row) * AMIGA_BYTES_PER_ROW;
|
||||
|
|
@ -1497,7 +1514,7 @@ void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w,
|
|||
}
|
||||
|
||||
|
||||
void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
void jlpSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t pairs;
|
||||
uint16_t row;
|
||||
|
|
@ -1508,6 +1525,44 @@ void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uin
|
|||
if (pd == NULL || srcPlaneBytes == NULL || w == 0u || h == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Byte-aligned fast path mirroring jlpSpriteSavePlanes: writes the
|
||||
* raw plane bytes straight back (whole-byte round-trip), skipping
|
||||
* the per-pixel chunky->planar masking of the walker below. */
|
||||
if ((x & 7) == 0 && (w & 7) == 0
|
||||
&& x >= 0 && (x + (int16_t)w) <= SURFACE_WIDTH
|
||||
&& y >= 0 && (y + (int16_t)h) <= SURFACE_HEIGHT) {
|
||||
uint16_t wb;
|
||||
uint16_t wb2;
|
||||
uint16_t wb3;
|
||||
uint16_t startByte;
|
||||
uint16_t rowOff;
|
||||
const uint8_t *bsrc;
|
||||
uint16_t col;
|
||||
|
||||
wb = (uint16_t)(w >> 3);
|
||||
wb2 = (uint16_t)(wb << 1);
|
||||
wb3 = (uint16_t)(wb2 + wb);
|
||||
startByte = (uint16_t)((uint16_t)x >> 3);
|
||||
rowOff = (uint16_t)((uint16_t)y * AMIGA_BYTES_PER_ROW + startByte);
|
||||
bsrc = srcPlaneBytes;
|
||||
for (row = 0; row < (uint16_t)h; row++) {
|
||||
uint8_t *d0 = pd->planes[0] + rowOff;
|
||||
uint8_t *d1 = pd->planes[1] + rowOff;
|
||||
uint8_t *d2 = pd->planes[2] + rowOff;
|
||||
uint8_t *d3 = pd->planes[3] + rowOff;
|
||||
for (col = 0; col < wb; col++) {
|
||||
d0[col] = bsrc[col];
|
||||
d1[col] = bsrc[wb + col];
|
||||
d2[col] = bsrc[wb2 + col];
|
||||
d3[col] = bsrc[wb3 + col];
|
||||
}
|
||||
bsrc += (uint16_t)(wb << 2);
|
||||
rowOff += AMIGA_BYTES_PER_ROW;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pairs = (uint16_t)(w >> 1);
|
||||
for (row = 0; row < (uint16_t)h; row++) {
|
||||
uint8_t *p0 = pd->planes[0] + ((uint16_t)y + row) * AMIGA_BYTES_PER_ROW;
|
||||
|
|
@ -1578,7 +1633,7 @@ void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uin
|
|||
* one static routine in lib code handles every sprite cel, so the
|
||||
* codegen arena no longer needs the ~17-23 KB of unrolled per-shift
|
||||
* code per cel that the old emitter generated. */
|
||||
void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
void jlpSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
AmigaPlanarT *pd;
|
||||
int16_t dy;
|
||||
int16_t sy;
|
||||
|
|
@ -1703,7 +1758,7 @@ void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t
|
|||
// <-> planar conversion at all. The previous c2p-based path paid
|
||||
// 4 KB LUT lookups + bit shuffling per pixel; this is ~50x cheaper.
|
||||
#define AMIGA_TILE_PLANE_STRIDE 8
|
||||
void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *tileOut) {
|
||||
void jlpTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t row;
|
||||
uint16_t rowBase;
|
||||
|
|
@ -1711,12 +1766,13 @@ void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *t
|
|||
|
||||
pd = (AmigaPlanarT *)src->portData;
|
||||
if (pd == NULL) {
|
||||
jlpGenericTileSnap(src, bx, by, chunkyOut);
|
||||
return;
|
||||
}
|
||||
rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
||||
const uint8_t *p = pd->planes[plane] + rowBase;
|
||||
uint8_t *q = tileOut + plane * AMIGA_TILE_PLANE_STRIDE;
|
||||
uint8_t *q = chunkyOut + plane * AMIGA_TILE_PLANE_STRIDE;
|
||||
for (row = 0; row < 8u; row++) {
|
||||
q[row] = p[row * AMIGA_BYTES_PER_ROW];
|
||||
}
|
||||
|
|
@ -1724,7 +1780,7 @@ void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *t
|
|||
}
|
||||
|
||||
|
||||
void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *tileBytes) {
|
||||
void jlpTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t row;
|
||||
uint8_t plane;
|
||||
|
|
@ -1732,14 +1788,15 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
|
||||
pd = (AmigaPlanarT *)dst->portData;
|
||||
if (pd == NULL) {
|
||||
jlpGenericTilePaste(dst, bx, by, chunkyTile);
|
||||
return;
|
||||
}
|
||||
/* jlTileT.pixels[] holds plane-major planar bytes (see
|
||||
* halTileSnapPlanes header for layout). Paste = 32 byte stores
|
||||
* jlpTileSnap header for layout). Paste = 32 byte stores
|
||||
* with no chunky -> planar conversion. */
|
||||
rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
||||
for (plane = 0; plane < AMIGA_BITPLANES; plane++) {
|
||||
const uint8_t *q = tileBytes + plane * AMIGA_TILE_PLANE_STRIDE;
|
||||
const uint8_t *q = chunkyTile + plane * AMIGA_TILE_PLANE_STRIDE;
|
||||
uint8_t *p = pd->planes[plane] + rowBase;
|
||||
for (row = 0; row < 8u; row++) {
|
||||
p[row * AMIGA_BYTES_PER_ROW] = q[row];
|
||||
|
|
@ -1749,14 +1806,14 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
|
||||
|
||||
/* Planar monochrome paste. monoTile is plane-major (matches the
|
||||
* Amiga halTileSnapPlanes output): bytes [0..7] = plane 0 = shape
|
||||
* Amiga jlpTileSnap output): bytes [0..7] = plane 0 = shape
|
||||
* bits, bytes [8..15] = plane 1 (zero for monochrome tiles), etc.
|
||||
* Each pixel's output color: fgColor where shape bit is 1, bgColor
|
||||
* where 0. Output is written directly to the dst planar buffers via
|
||||
* the per-plane formula
|
||||
* out_plane_k = (shape & mask_fg_k) | (~shape & mask_bg_k)
|
||||
* where mask_X_k = $FF if (X & (1<<k)) else $00. */
|
||||
void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
void jlpTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t plane;
|
||||
uint8_t row;
|
||||
|
|
@ -1764,6 +1821,7 @@ void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8
|
|||
|
||||
pd = (AmigaPlanarT *)dst->portData;
|
||||
if (pd == NULL) {
|
||||
jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
|
||||
return;
|
||||
}
|
||||
rowBase = (uint16_t)((uint16_t)by * 8u) * AMIGA_BYTES_PER_ROW + bx;
|
||||
|
|
@ -1786,7 +1844,7 @@ void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8
|
|||
// the cross-platform chunky pixel memcpy. Both src and dst planes
|
||||
// are off-screen shadow buffers; the displayed gPlanes[] is updated
|
||||
// only at jlStagePresent.
|
||||
void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
AmigaPlanarT *dstPd;
|
||||
AmigaPlanarT *srcPd;
|
||||
|
||||
|
|
@ -1797,7 +1855,7 @@ void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
// Straight blitter A->D copy of every whole plane (200 rows x 20
|
||||
// words, contiguous). Both source and dest planes are CHIP RAM now
|
||||
// (halSurfaceAllocPortData) so the blitter can reach both. The
|
||||
// (jlpSurfaceAllocPortData) so the blitter can reach both. The
|
||||
// helper owns the blitter, copies all four planes, and WaitBlit's
|
||||
// before DisownBlitter so the dest planes are complete before any
|
||||
// CPU read.
|
||||
|
|
@ -1806,43 +1864,6 @@ void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
/* Phase 9: tile halFast hooks return true to suppress chunky fallback.
|
||||
* The actual planar work happens in halTileFillPlanes / halTileCopyPlanes
|
||||
* / etc. (called by cross-platform tile.c after each halFast). jlTileSnap
|
||||
* outputs a chunky jlTileT -- see halTileSnapPlanes-style derivation
|
||||
* inside the snap planar work added below if needed. For now jlTileSnap
|
||||
* skips its output (jlTileT will be all-zeros) when called on Amiga;
|
||||
* apps that depend on jlTileSnap on Amiga need a planar-to-chunky
|
||||
* derivation hook (TODO if any UBER/demo path actually exercises it). */
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
(void)transparent;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0;
|
||||
(void)srcTilePixels;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels;
|
||||
(void)srcRow0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 4 planar dual-write helper: set one pixel's bit in each of
|
||||
// the four shadow planes. Caller (the per-primitive walker below) has
|
||||
// already validated (x, y) is on-surface. byteOff is computed once
|
||||
|
|
@ -1903,16 +1924,16 @@ static void amigaPlanarSetPixel(AmigaPlanarT *pd, int16_t x, int16_t y, uint8_t
|
|||
}
|
||||
|
||||
|
||||
bool halFastDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
void jlpDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t nibLo;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
jlpGenericDrawPixel(s, x, y, colorIndex);
|
||||
return;
|
||||
}
|
||||
nibLo = (uint8_t)(colorIndex & 0x0Fu);
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
amigaPlanarSetPixel(pd, (int16_t)x, (int16_t)y, nibLo);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1959,28 +1980,26 @@ static void amigaPlanarLine(AmigaPlanarT *pd, int16_t x0, int16_t y0, int16_t x1
|
|||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
void jlpDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
amigaPlanarLine(pd, x0, y0, x1, y1, (uint8_t)(colorIndex & 0x0Fu));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
void jlpDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
surface68kAmigaCircleOutline(pd->planes[0], pd->planes[1],
|
||||
pd->planes[2], pd->planes[3],
|
||||
(uint16_t)cx, (uint16_t)cy, r,
|
||||
(uint8_t)(colorIndex & 0x0Fu));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2038,7 +2057,7 @@ void amigaFillSpanInline(AmigaPlanarT *pd, int16_t spanX, int16_t spanY,
|
|||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
bool jlpFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
AmigaPlanarT *pd;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
|
|
@ -2050,7 +2069,7 @@ bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_
|
|||
return false;
|
||||
}
|
||||
pd = (AmigaPlanarT *)s->portData;
|
||||
/* Inline per-span plane fill -- avoids 4 halFillRectPlanes
|
||||
/* Inline per-span plane fill -- avoids 4 jlpFillRect
|
||||
* dispatches per midpoint iter (~320 dispatches for r=40). */
|
||||
x = (int16_t)r;
|
||||
y = 0;
|
||||
|
|
@ -2076,65 +2095,6 @@ bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_
|
|||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* Plane-aware nibble at (x, y) given the 4 plane row bases. Reads one
|
||||
* byte per plane and assembles the 4-bit color from a single bit
|
||||
* position. Caller is responsible for valid (x, y). */
|
||||
|
|
@ -2168,7 +2128,7 @@ static void amigaPlaneRowPtrs(const jlSurfaceT *s, int16_t y, uint8_t **out /* [
|
|||
}
|
||||
|
||||
|
||||
bool halFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
bool jlpFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t *rowPlanes[AMIGA_BITPLANES];
|
||||
int16_t leftX;
|
||||
|
|
@ -2218,7 +2178,7 @@ bool halFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t
|
|||
}
|
||||
|
||||
|
||||
bool halFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
bool jlpFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t *rowPlanes[AMIGA_BITPLANES];
|
||||
int16_t byteCol;
|
||||
|
|
@ -2269,17 +2229,6 @@ bool halFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, i
|
|||
}
|
||||
|
||||
|
||||
bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
/* Phase 9: chunky write skipped; halTileFillPlanes (called by
|
||||
* cross-platform tile.c after this) does the planar fill. */
|
||||
(void)s;
|
||||
(void)bx;
|
||||
(void)by;
|
||||
(void)fillWord;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* ===== Phase 9 reader hooks: pure-planar Amiga implementations =====
|
||||
*
|
||||
* Cross-platform code that USED to read s->pixels (chunky shadow) now
|
||||
|
|
@ -2290,7 +2239,7 @@ bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
|||
* byte at row*40 + x/8 in each of the 4 planes. Color index = sum
|
||||
* of (bit_p << p). */
|
||||
|
||||
uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
uint8_t jlpSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t byteOff;
|
||||
uint8_t bitMask;
|
||||
|
|
@ -2314,7 +2263,7 @@ uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
|||
|
||||
|
||||
/* Reverse-c2p: per row, derive 160 chunky bytes from 40 plane bytes
|
||||
* (per plane, 4 planes). Used by halSurfaceHash to fold the planar
|
||||
* (per plane, 4 planes). Used by jlpSurfaceHash to fold the planar
|
||||
* surface into the same byte-stream the chunky ports hash, so cross-
|
||||
* port hash comparisons stay valid.
|
||||
* Walks 8 pixels per planar-byte column; per pixel assembles nibble
|
||||
|
|
@ -2354,7 +2303,7 @@ static void amigaPlanesToChunkyRow(const AmigaPlanarT *pd, int16_t y, uint8_t *d
|
|||
}
|
||||
|
||||
|
||||
uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
||||
uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t lo = 0xACE1u, hi = 0x1357u;
|
||||
uint16_t n, v;
|
||||
|
|
@ -2394,17 +2343,9 @@ uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
|||
}
|
||||
|
||||
|
||||
void halSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
/* Amiga has no chunky shadow. Plane copy happens in
|
||||
* halSurfaceCopyPlanes (called separately by jlSurfaceCopy). */
|
||||
(void)dst;
|
||||
(void)src;
|
||||
}
|
||||
|
||||
|
||||
// On-disk format is the Amiga's native plane-major buffer: planes
|
||||
// 0..3 written sequentially, AMIGA_PLANE_SIZE bytes each.
|
||||
bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t i;
|
||||
|
||||
|
|
@ -2421,7 +2362,7 @@ bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
|||
}
|
||||
|
||||
|
||||
bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
AmigaPlanarT *pd;
|
||||
uint8_t i;
|
||||
|
||||
|
|
@ -2438,36 +2379,23 @@ bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
|||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
uint8_t *jlpStageAllocPixels(void) {
|
||||
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
||||
* stays NULL; cross-platform code reads pixels via halSamplePixel
|
||||
* (or other halXxxChunky hooks) which read from planes. NULL is
|
||||
* stays NULL; cross-platform code reads pixels via jlpSamplePixel
|
||||
* (or the other chunky reader ops) which read from planes. NULL is
|
||||
* a valid return -- cross-platform stageAlloc treats NULL as
|
||||
* "port has no chunky storage" and skips the chunky memset. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
/* halStageAllocPixels returned NULL on Amiga, so this is always
|
||||
* NULL (free(NULL) is well-defined no-op). Symmetric for any
|
||||
* future port that does allocate stage pixels. */
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfaceAllocPixels(void) {
|
||||
/* Same rationale as halStageAllocPixels: no chunky on Amiga. */
|
||||
uint8_t *jlpSurfaceAllocPixels(void) {
|
||||
/* Same rationale as jlpStageAllocPixels: no chunky on Amiga. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
uint8_t *jlpSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
AmigaPlanarT *pd;
|
||||
if (planeIdx >= AMIGA_BITPLANES) {
|
||||
return NULL;
|
||||
|
|
@ -2495,7 +2423,7 @@ uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
|||
// Returns NULL on allocation failure. Cross-platform code stores the
|
||||
// result in s->portData; primitives access via (AmigaPlanarT *)
|
||||
// s->portData.
|
||||
void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
void *jlpSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t i;
|
||||
|
||||
|
|
@ -2512,7 +2440,7 @@ void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
|||
/* Both stage and non-stage: AllocMem fresh planes, MEMF_CLEAR
|
||||
* for the JoeyLib contract that color 0 = black at surface
|
||||
* allocation. Stage uses these as off-screen back planes (display
|
||||
* is gPlanes[], CHIP, updated only by halPresent). Non-stage uses
|
||||
* is gPlanes[], CHIP, updated only by jlpPresent). Non-stage uses
|
||||
* these as the surface's only planes.
|
||||
*
|
||||
* MEMF_CHIP because the bulk plane ops (clear, full-row fill,
|
||||
|
|
@ -2544,7 +2472,7 @@ void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
|||
}
|
||||
|
||||
|
||||
void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
void jlpSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
AmigaPlanarT *pd;
|
||||
uint16_t i;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
#include <proto/intuition.h>
|
||||
#include <proto/lowlevel.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
|
@ -242,13 +242,13 @@ static void pollJoysticks(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halJoystickReset(jlJoystickE js) {
|
||||
void jlpJoystickReset(jlJoystickE js) {
|
||||
// Amiga sticks are digital -- no calibration to do.
|
||||
(void)js;
|
||||
}
|
||||
|
||||
|
||||
void halInputInit(void) {
|
||||
void jlpInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
|
||||
|
|
@ -291,13 +291,13 @@ void halInputInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
void jlpInputPoll(void) {
|
||||
drainMessages();
|
||||
pollJoysticks();
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
void jlpInputShutdown(void) {
|
||||
if (gLowLevelBase != NULL) {
|
||||
CloseLibrary(gLowLevelBase);
|
||||
gLowLevelBase = NULL;
|
||||
|
|
@ -2,16 +2,16 @@
|
|||
// running PWM through YM2149 channel-A volume register.
|
||||
//
|
||||
// Pipeline:
|
||||
// * halAudioInit creates an xmp_context, allocates a 2 KB mix buffer
|
||||
// * jlpAudioInit creates an xmp_context, allocates a 2 KB mix buffer
|
||||
// (in BSS), parks the Timer A vector at our 68k ISR (audio_isr.s),
|
||||
// and starts Timer A at ~12.3 kHz (MFP prescaler 200, data 1).
|
||||
// * halAudioPlayMod loads a .MOD via xmp_load_module_from_memory and
|
||||
// * jlpAudioPlayMod loads a .MOD via xmp_load_module_from_memory and
|
||||
// calls xmp_start_player at the same 12288 Hz mono unsigned-8-bit
|
||||
// output that the ISR consumes one byte at a time.
|
||||
// * Each Timer A tick the ISR pops one byte off the buffer, writes
|
||||
// its high 4 bits to YM register 8 (channel A volume), advances
|
||||
// the play pointer, raises a refill flag at half/end. The refill
|
||||
// itself happens in halAudioFrameTick on the main thread because
|
||||
// itself happens in jlpAudioFrameTick on the main thread because
|
||||
// libxmp-lite is not interrupt-safe.
|
||||
//
|
||||
// Quality budget: 4-bit unsigned PWM at 12.3 kHz on the YM is grim by
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include <libxmp-lite/xmp.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "audioSfxMixInternal.h"
|
||||
#include "joey/audio.h"
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ static int gXmpLoopCount = 0;
|
|||
static void (*gOldTimerAVec)(void) = NULL;
|
||||
|
||||
// SFX overlay shared with the DOS HAL (see src/core/audioSfxMix.c).
|
||||
// Mixed in over libxmp's MOD output during halAudioFrameTick.
|
||||
// Mixed in over libxmp's MOD output during jlpAudioFrameTick.
|
||||
static AudioSfxSlotT gSfxSlots[JOEY_AUDIO_SFX_SLOTS];
|
||||
|
||||
// Single shadow of YM mixer reg 7's low 6 bits (tone+noise disables).
|
||||
|
|
@ -180,7 +180,7 @@ static void silenceMixBuffer(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halAudioInit(void) {
|
||||
bool jlpAudioInit(void) {
|
||||
if (gReady) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -194,7 +194,7 @@ bool halAudioInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioShutdown(void) {
|
||||
void jlpAudioShutdown(void) {
|
||||
if (!gReady) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -217,12 +217,12 @@ void halAudioShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
bool halAudioIsPlayingMod(void) {
|
||||
bool jlpAudioIsPlayingMod(void) {
|
||||
return gReady && gXmpStarted;
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
if (!gReady || gXmpCtx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -249,7 +249,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
if (!gReady || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
if (!gReady || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@ void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uin
|
|||
// YM2149 has three PSG voices; a proper tone-mode driver would
|
||||
// program voice A here. Not wired yet -- AGI-on-ST will be silent
|
||||
// until this is filled in.
|
||||
void halAudioTone(uint16_t freqHz) {
|
||||
void jlpAudioTone(uint16_t freqHz) {
|
||||
(void)freqHz;
|
||||
}
|
||||
|
||||
|
|
@ -342,30 +342,30 @@ static long applyYmVoice(void) {
|
|||
// driving 12 kHz PWM for MOD output (see installTimerA). A future
|
||||
// AGI-on-ST sound path could lift the YM2149 tone driver onto
|
||||
// Timer B or C and register it here.
|
||||
bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
(void)fn;
|
||||
(void)hz;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void halAudioCriticalEnter(void) {
|
||||
// No tick-callback ISR is registered (halAudioTickRegister returns
|
||||
void jlpAudioCriticalEnter(void) {
|
||||
// No tick-callback ISR is registered (jlpAudioTickRegister returns
|
||||
// false). The Timer A MOD ISR synchronizes with the producer via
|
||||
// the gNeedRefill flags, not this critical section, so masking
|
||||
// here would only starve that ~12 kHz audio ISR for no benefit.
|
||||
}
|
||||
|
||||
|
||||
void halAudioCriticalExit(void) {
|
||||
void jlpAudioCriticalExit(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
if (voice >= JOEY_AUDIO_VOICES) {
|
||||
return;
|
||||
}
|
||||
// The PWM-via-volume engine in halAudioInit hammers YM reg 8
|
||||
// The PWM-via-volume engine in jlpAudioInit hammers YM reg 8
|
||||
// (channel A volume) at 12 kHz for MOD output. If that IRQ is
|
||||
// running, it will fight the per-voice writes here. AGI does not
|
||||
// call jlAudioInit, so Timer A is not installed and the YM is
|
||||
|
|
@ -378,7 +378,7 @@ void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopMod(void) {
|
||||
void jlpAudioStopMod(void) {
|
||||
if (!gReady || gXmpCtx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -394,7 +394,7 @@ void halAudioStopMod(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopSfx(uint8_t slot) {
|
||||
void jlpAudioStopSfx(uint8_t slot) {
|
||||
if (slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -412,7 +412,7 @@ void halAudioStopSfx(uint8_t slot) {
|
|||
// being rewritten (audible tearing). A full MOD-frame mix plus
|
||||
// audioSfxOverlayMix can approach that budget under load, so callers
|
||||
// should run the game loop comfortably faster than ~12 Hz.
|
||||
void halAudioFrameTick(void) {
|
||||
void jlpAudioFrameTick(void) {
|
||||
if (!gReady) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
| Atari ST word-interleaved planar circle outline -- 68000 hand-rolled.
|
||||
|
|
||||
| Mirrors src/port/amiga/circle.s in spirit but for ST's single
|
||||
| Mirrors src/amiga/circle.s in spirit but for ST's single
|
||||
| word-interleaved planar buffer:
|
||||
| * Per scanline: 20 groups of 8 bytes; each group is 4 plane
|
||||
| words back-to-back (p0_word, p1_word, p2_word, p3_word).
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
// * Word-interleaved ST planar buffer copied to the screen at present.
|
||||
//
|
||||
// M2.5 scope (per-band palette / SCB emulation):
|
||||
// * halPresent scans the jlSurfaceT's SCB array and builds a compact
|
||||
// * jlpPresent scans the jlSurfaceT'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).
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
// ~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
|
||||
// table built in jlpPresent by flattenScbPalettes; the ISR uses
|
||||
// its first row per band as the source of 16 shifter writes.
|
||||
//
|
||||
// Deferred:
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
#include <mint/osbind.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
#include "spriteInternal.h"
|
||||
#include "joey/tile.h"
|
||||
|
|
@ -105,7 +105,7 @@ typedef struct {
|
|||
|
||||
// Phase 10: planar primitive helpers must be visible everywhere they
|
||||
// could inline. Defined up here (between StPlanarT and the rest of
|
||||
// the prototype block) so every halFast* / fillSpan / circle walker
|
||||
// the prototype block) so every jlp<Op> / fillSpan / circle walker
|
||||
// can fold the 4-plane RMW directly into its body. always_inline
|
||||
// hammers the point home for gcc-mint's conservative inliner.
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ static void refreshPaletteStateIfNeeded(const
|
|||
// Screen buffer: TWO 320x200x4bpp planar pages for page-flipping, plus
|
||||
// padding for runtime 256-byte alignment. TOS .PRG format only supports
|
||||
// 2-byte object-file alignment, so we overallocate and align the first
|
||||
// page pointer manually in halInit; the second page sits exactly one
|
||||
// page pointer manually in jlpInit; the second page sits exactly one
|
||||
// page (SURFACE_PIXELS_SIZE = 32000, itself a multiple of 256) after
|
||||
// the first, so it stays 256-byte aligned as the shifter requires.
|
||||
static uint8_t gScreenBuffer[(2 * SURFACE_PIXELS_SIZE) + ST_SCREEN_ALIGN];
|
||||
|
|
@ -184,6 +184,15 @@ static int16_t gPrevRez = 0;
|
|||
static uint16_t gPrevPalette[SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gModeSet = false;
|
||||
|
||||
// Supervisor-stack token returned by Super(0L) in jlpInit, used to drop
|
||||
// back to user mode in jlpShutdown. The ST maps the entire $FF8000..
|
||||
// $FFFFFF I/O region (shifter video-base, palette, MFP) as supervisor-
|
||||
// only; a GEMDOS .PRG starts in user mode, so the direct register pokes
|
||||
// in jlpPresent (setShifterVideoBase) bus-error until we enter
|
||||
// supervisor for the program's lifetime.
|
||||
static void *gSavedSsp = NULL;
|
||||
static bool gSuperEntered = false;
|
||||
|
||||
// Snapshot of the previous frame's dirty bands. With two physical
|
||||
// pages the off-screen page holds content from TWO frames ago, so each
|
||||
// present must refresh the union(prev, current) of dirty rows from the
|
||||
|
|
@ -196,7 +205,7 @@ static uint8_t gPrevStageMinWord[SURFACE_HEIGHT];
|
|||
static uint8_t gPrevStageMaxWord[SURFACE_HEIGHT];
|
||||
|
||||
// "The previous frame's snapshot recorded at least one dirty row."
|
||||
// Lets halPresent skip the whole 200-row union scan + 200-row snapshot
|
||||
// Lets jlpPresent skip the whole 200-row union scan + 200-row snapshot
|
||||
// copy on consecutive idle frames: if neither the previous frame nor
|
||||
// the current frame touched any row, union(prev,current) is empty, the
|
||||
// off-screen page already matches the displayed page (the settling
|
||||
|
|
@ -213,7 +222,7 @@ 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 jlSurfaceT's SCB array.
|
||||
// Built once per jlpPresent from the jlSurfaceT's SCB array.
|
||||
#define MAX_BANDS SURFACE_HEIGHT
|
||||
static uint16_t gBandStart [MAX_BANDS];
|
||||
static uint8_t gBandPalIdx[MAX_BANDS];
|
||||
|
|
@ -277,7 +286,7 @@ static void buildTransitions(const jlSurfaceT *src) {
|
|||
|
||||
// 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.
|
||||
// without any surface-level lookups. Called once per jlpPresent.
|
||||
static void flattenScbPalettes(const jlSurfaceT *src) {
|
||||
uint16_t line;
|
||||
uint16_t col;
|
||||
|
|
@ -490,7 +499,7 @@ static void vblIsr(void) {
|
|||
// bytes. The low byte does not exist on STF (base is 256-aligned), so
|
||||
// two byte writes fully specify the address. The shifter latches the
|
||||
// base at the next frame start, so calling this mid-visible-frame (as
|
||||
// halPresent does, after the app's jlWaitVBL) makes the flip take
|
||||
// jlpPresent does, after the app's jlWaitVBL) makes the flip take
|
||||
// effect on the following frame with no tearing. No TOS / Setscreen
|
||||
// involvement, which matters because our VBL ISR replaced TOS's and
|
||||
// does not run TOS's Setscreen-latch path.
|
||||
|
|
@ -532,12 +541,26 @@ static void writeDiagnostics(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halInit(const jlConfigT *config) {
|
||||
bool jlpInit(const jlConfigT *config) {
|
||||
uintptr_t addr;
|
||||
uint16_t i;
|
||||
|
||||
(void)config;
|
||||
|
||||
// Enter supervisor mode for the program's lifetime. The shifter
|
||||
// video-base, palette, and MFP registers live in the supervisor-only
|
||||
// $FF8000..$FFFFFF I/O region; jlpPresent pokes the video-base bytes
|
||||
// directly every flip, and a .PRG launched by GEMDOS starts in user
|
||||
// mode, so without this the first present bus-errors. Staying super
|
||||
// for the whole run (vs a per-present Supexec trap) also keeps the
|
||||
// present hot path free of trap overhead. jlpShutdown restores user
|
||||
// mode via the saved stack token. GEMDOS/XBIOS/BIOS traps (including
|
||||
// the stdio used by joeylog) work fine from supervisor.
|
||||
if (!gSuperEntered) {
|
||||
gSavedSsp = (void *)Super(0L);
|
||||
gSuperEntered = true;
|
||||
}
|
||||
|
||||
// Align page 0 to 256 bytes inside the static storage; page 1
|
||||
// follows exactly one 32000-byte (256-aligned) page later, so it
|
||||
// is 256-aligned too. The shifter only displays 256-byte-aligned
|
||||
|
|
@ -570,7 +593,7 @@ bool halInit(const jlConfigT *config) {
|
|||
}
|
||||
|
||||
// Switch to ST low-res: 320x200x16, mode 0. Display page 0 first;
|
||||
// halPresent flips to the off-screen page via the shifter base
|
||||
// jlpPresent flips to the off-screen page via the shifter base
|
||||
// registers from here on.
|
||||
Setscreen((long)gScreenPage[0], (long)gScreenPage[0], 0);
|
||||
gModeSet = true;
|
||||
|
|
@ -596,12 +619,7 @@ bool halInit(const jlConfigT *config) {
|
|||
}
|
||||
|
||||
|
||||
const char *halLastError(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halPresent(const jlSurfaceT *src) {
|
||||
void jlpPresent(const jlSurfaceT *src) {
|
||||
StPlanarT *pd;
|
||||
uint8_t *backPage;
|
||||
uint8_t backIdx;
|
||||
|
|
@ -778,7 +796,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
|
||||
// Snapshot this frame's dirty bands as "previous" for the next
|
||||
// present. jlStagePresent clears the live bands right after
|
||||
// halPresent returns, so we must grab them now. Next present unions
|
||||
// jlpPresent returns, so we must grab them now. Next present unions
|
||||
// these with that frame's dirty rows to refresh whatever the other
|
||||
// page is stale on. Record whether any row was dirty so the next
|
||||
// present can take the idle-frame early-out above.
|
||||
|
|
@ -798,7 +816,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
|
||||
// Vsync() is XBIOS opcode 37; mintlib exposes it directly. It blocks
|
||||
// until the next 50 Hz (PAL) or 60 Hz (NTSC) vertical blank.
|
||||
void halWaitVBL(void) {
|
||||
void jlpWaitVBL(void) {
|
||||
int16_t before;
|
||||
|
||||
// Can't use Vsync(): TOS's Vsync increments _vblsem inside its
|
||||
|
|
@ -815,12 +833,12 @@ void halWaitVBL(void) {
|
|||
|
||||
// gFrameCount is already maintained by our VBL ISR; just narrow to
|
||||
// uint16_t for the cross-port HAL contract.
|
||||
uint16_t halFrameCount(void) {
|
||||
uint16_t jlpFrameCount(void) {
|
||||
return (uint16_t)gFrameCount;
|
||||
}
|
||||
|
||||
|
||||
uint16_t halFrameHz(void) {
|
||||
uint16_t jlpFrameHz(void) {
|
||||
/* PAL ST is 50 Hz; NTSC ST and SM124 mono are ~60 / ~70. We
|
||||
* report 50 as the baseline -- close enough for ops/sec scaling,
|
||||
* and the actual frame rate is still observable via iter counts. */
|
||||
|
|
@ -839,7 +857,7 @@ static long readHz200(void) {
|
|||
return (long)*((volatile uint32_t *)0x4BAUL);
|
||||
}
|
||||
|
||||
uint32_t halMillisElapsed(void) {
|
||||
uint32_t jlpMillisElapsed(void) {
|
||||
uint32_t ticks;
|
||||
ticks = (uint32_t)Supexec(readHz200);
|
||||
// 0 is a legal counter value (power-on tick 0 and every ~248-day
|
||||
|
|
@ -855,19 +873,19 @@ uint32_t halMillisElapsed(void) {
|
|||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
void jlpShutdown(void) {
|
||||
if (!gModeSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the audio Timer A first. The audio HAL has its own
|
||||
// halAudioShutdown that disables Timer A and restores the vector,
|
||||
// jlpAudioShutdown that disables Timer A and restores the vector,
|
||||
// but cross-platform jlShutdown doesn't call it -- if a sketch
|
||||
// forgets jlAudioShutdown(), Timer A keeps firing after our
|
||||
// code unloads and TOS panics on the first dangling vector hit.
|
||||
// Calling halAudioShutdown here is idempotent (gReady guard),
|
||||
// Calling jlpAudioShutdown here is idempotent (gReady guard),
|
||||
// so explicit-shutdown sketches still work.
|
||||
halAudioShutdown();
|
||||
jlpAudioShutdown();
|
||||
|
||||
// Disable MFP Timer B and restore the exception vectors before
|
||||
// changing the screen -- a late ISR firing mid-Setscreen would
|
||||
|
|
@ -884,6 +902,13 @@ void halShutdown(void) {
|
|||
Supexec(writePrevPaletteRegs);
|
||||
writeDiagnostics();
|
||||
gModeSet = false;
|
||||
|
||||
// Drop back to user mode so control returns to GEMDOS the way it
|
||||
// launched us (mirrors the Super(0L) in jlpInit).
|
||||
if (gSuperEntered) {
|
||||
(void)Super(gSavedSsp);
|
||||
gSuperEntered = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -907,14 +932,15 @@ extern void surface68kStSpriteRestoreByteAligned(uint8_t *base, uint16_t x, uint
|
|||
// Phase 9: clear the entire planar buffer to a 4-bit color. Build an
|
||||
// 8-byte group template (4 plane words: 0xFFFF or 0x0000 each by
|
||||
// color bit) then stream it across all 4000 groups via long stores.
|
||||
bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
void jlpSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
StPlanarT *pd;
|
||||
uint8_t color;
|
||||
uint32_t loLong;
|
||||
uint32_t hiLong;
|
||||
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
jlpGenericSurfaceClear(s, doubled);
|
||||
return;
|
||||
}
|
||||
pd = (StPlanarT *)s->portData;
|
||||
color = (uint8_t)(doubled & 0x0Fu);
|
||||
|
|
@ -927,62 +953,20 @@ bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
|||
surface68kStLongFill(pd->base,
|
||||
(uint16_t)(ST_PLANAR_SIZE / ST_BYTES_PER_GROUP),
|
||||
loLong, hiLong);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 9: pure short-circuit. halFillRectPlanes (called by cross-
|
||||
// platform jlFillRect right after this) does the actual planar fill;
|
||||
// we just claim ownership so the chunky fillRectClipped fallback
|
||||
// never runs.
|
||||
bool halFastFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)x; (void)y; (void)w; (void)h; (void)colorIndex;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 9: claim every halFastTile* call so the cross-platform chunky
|
||||
// fallback (which would dereference NULL s->pixels) never fires. The
|
||||
// halTileXxxPlanes hook called separately by tile.c does the planar
|
||||
// work.
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0; (void)srcRow0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0; (void)srcRow0; (void)transparent;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0; (void)srcTilePixels;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels; (void)srcRow0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 9: planar-only. Chunky shadow is gone; only the planar buffer
|
||||
// gets the pixel.
|
||||
bool halFastDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
void jlpDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
jlpGenericDrawPixel(s, x, y, colorIndex);
|
||||
return;
|
||||
}
|
||||
pd = (StPlanarT *)s->portData;
|
||||
stPlanarSetPixel(pd, (int16_t)x, (int16_t)y, (uint8_t)(colorIndex & 0x0Fu));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1050,7 +1034,7 @@ static void stPlanarCircleOutline(StPlanarT *pd, int16_t cx, int16_t cy, uint16_
|
|||
|
||||
|
||||
// Phase 10: group-aware span fill -- the same leading-mask /
|
||||
// full-group / trailing-mask decomposition halFillRectPlanes uses,
|
||||
// full-group / trailing-mask decomposition jlpFillRect uses,
|
||||
// but for one row. Replaces the per-pixel walk that gave jlFillCircle
|
||||
// r=40 ~1 ops/sec.
|
||||
static void stPlanarFillSpan(StPlanarT *pd, int16_t x0, int16_t x1, int16_t y, uint8_t color) {
|
||||
|
|
@ -1092,10 +1076,10 @@ static void stPlanarCircleFill(StPlanarT *pd, int16_t cx, int16_t cy, uint16_t r
|
|||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
void jlpDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
pd = (StPlanarT *)s->portData;
|
||||
// Asm walker assumes fully on-surface; partial-clip lines fall
|
||||
|
|
@ -1106,14 +1090,13 @@ bool halFastDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t
|
|||
} else {
|
||||
stPlanarLine(pd, x0, y0, x1, y1, (uint8_t)(colorIndex & 0x0Fu));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
void jlpDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
pd = (StPlanarT *)s->portData;
|
||||
/* Off-surface circles fall back to the per-pixel C walker which
|
||||
|
|
@ -1128,11 +1111,10 @@ bool halFastDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_
|
|||
surface68kStCircleOutline(pd->base, (uint16_t)cx, (uint16_t)cy, r,
|
||||
(uint8_t)(colorIndex & 0x0Fu));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
bool jlpFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
|
|
@ -1153,91 +1135,6 @@ bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_
|
|||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)s; (void)startX; (void)y; (void)matchColor; (void)newColor; (void)matchEqual;
|
||||
(void)seedMatched; (void)leftXOut; (void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)s; (void)leftX; (void)rightX; (void)scanY; (void)matchColor; (void)newColor; (void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Phase 9: short-circuit. halTileFillPlanes does the planar work
|
||||
// after this returns true; the chunky fallback that would run on
|
||||
// false would dereference NULL s->pixels.
|
||||
bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
(void)bx; (void)by; (void)fillWord;
|
||||
if (s->portData == NULL) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Phase 2: allocate a shadow word-interleaved planar buffer per
|
||||
// surface. Both stage and non-stage get their own buffer; the two
|
||||
// displayed pages (gScreenPage[0/1]) are the flip targets, kept
|
||||
|
|
@ -1252,7 +1149,7 @@ bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
|||
// Symptom of getting this wrong: intermittent return-to-desktop
|
||||
// after the red startup paint as the first long write hits an
|
||||
// odd-by-2 base.
|
||||
void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
void *jlpSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
StPlanarT *pd;
|
||||
uint8_t *raw;
|
||||
uintptr_t addr;
|
||||
|
|
@ -1280,7 +1177,7 @@ void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
|||
}
|
||||
|
||||
|
||||
void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
void jlpSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
StPlanarT *pd;
|
||||
|
||||
(void)s;
|
||||
|
|
@ -1312,12 +1209,16 @@ void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
|||
// (stApplyMaskToGroup is defined inline near the top of the file.)
|
||||
|
||||
|
||||
void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
void jlpFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
uint16_t groupFirst;
|
||||
uint16_t groupLast;
|
||||
uint8_t *rowBase;
|
||||
|
||||
if (s->portData == NULL) {
|
||||
jlpGenericFillRect(s, x, y, w, h, colorIndex);
|
||||
return;
|
||||
}
|
||||
if (s == NULL || w == 0u || h == 0u) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1363,7 +1264,7 @@ void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t
|
|||
}
|
||||
|
||||
|
||||
void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
StPlanarT *dstPd;
|
||||
StPlanarT *srcPd;
|
||||
|
||||
|
|
@ -1399,12 +1300,16 @@ void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
// pixel (bx*8, by*8). 8 pixels wide always covers exactly half a
|
||||
// 16-pixel group: high half (bits 15..8) when bx is even, low half
|
||||
// (bits 7..0) when bx is odd. Per-row work is 4 plane half-word RMWs.
|
||||
void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
void jlpTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
StPlanarT *pd;
|
||||
uint16_t group;
|
||||
uint16_t halfMask;
|
||||
uint8_t *gp;
|
||||
|
||||
if (s->portData == NULL) {
|
||||
jlpGenericTileFill(s, bx, by, colorIndex);
|
||||
return;
|
||||
}
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1426,13 +1331,17 @@ void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex
|
|||
// positions. Width 8 = single tile column = single half-group
|
||||
// write per plane. The asm walker handles 8 rows just as well
|
||||
// as a sprite's variable height.
|
||||
void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *tileBytes) {
|
||||
void jlpTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
||||
StPlanarT *pd;
|
||||
uint16_t group;
|
||||
uint8_t *dstAddr;
|
||||
int16_t row;
|
||||
|
||||
if (dst == NULL || tileBytes == NULL) {
|
||||
if (dst->portData == NULL) {
|
||||
jlpGenericTilePaste(dst, bx, by, chunkyTile);
|
||||
return;
|
||||
}
|
||||
if (dst == NULL || chunkyTile == NULL) {
|
||||
return;
|
||||
}
|
||||
pd = (StPlanarT *)dst->portData;
|
||||
|
|
@ -1442,7 +1351,7 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
/* Phase 10.5: jlTileT.pixels holds plane-major bytes (4 plane bytes
|
||||
* per row * 8 rows = 32 bytes). Direct byte copy to the planar
|
||||
* buffer; no chunky <-> planar conversion. Mirrors the sibling
|
||||
* halTileCopyPlanes pattern but reads from the contiguous tile
|
||||
* jlpTileCopy pattern but reads from the contiguous tile
|
||||
* buffer. Drops the asm-walker entry/exit overhead. */
|
||||
group = (uint16_t)((uint16_t)bx >> 1);
|
||||
dstAddr = pd->base
|
||||
|
|
@ -1452,12 +1361,12 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
(void)row;
|
||||
#define ST_TILE_PASTE_ROW \
|
||||
do { \
|
||||
dstAddr[0] = tileBytes[0]; \
|
||||
dstAddr[2] = tileBytes[1]; \
|
||||
dstAddr[4] = tileBytes[2]; \
|
||||
dstAddr[6] = tileBytes[3]; \
|
||||
dstAddr[0] = chunkyTile[0]; \
|
||||
dstAddr[2] = chunkyTile[1]; \
|
||||
dstAddr[4] = chunkyTile[2]; \
|
||||
dstAddr[6] = chunkyTile[3]; \
|
||||
dstAddr += ST_BYTES_PER_ROW; \
|
||||
tileBytes += TILE_BYTES_PER_ROW; \
|
||||
chunkyTile += TILE_BYTES_PER_ROW; \
|
||||
} while (0)
|
||||
ST_TILE_PASTE_ROW;
|
||||
ST_TILE_PASTE_ROW;
|
||||
|
|
@ -1472,7 +1381,7 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
|
||||
|
||||
/* Planar monochrome paste. monoTile follows the ST tile layout from
|
||||
* halTileSnapPlanes: row-major-with-planes-interleaved-per-row, i.e.
|
||||
* jlpTileSnap: row-major-with-planes-interleaved-per-row, i.e.
|
||||
* row 0 plane 0 at byte 0
|
||||
* row 0 plane 1 at byte 1
|
||||
* row 0 plane 2 at byte 2
|
||||
|
|
@ -1489,7 +1398,7 @@ void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *
|
|||
* inside the 16-pixel group starting at (bx/2)*8 bytes from the
|
||||
* row's base. bx odd selects the second byte of the plane word
|
||||
* (low 8 pixels of the 16-pixel group). */
|
||||
void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
void jlpTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
StPlanarT *pd;
|
||||
uint16_t group;
|
||||
uint8_t *dstAddr;
|
||||
|
|
@ -1498,6 +1407,10 @@ void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8
|
|||
uint8_t masks_fg[4];
|
||||
uint8_t masks_bg[4];
|
||||
|
||||
if (dst->portData == NULL) {
|
||||
jlpGenericTilePasteMono(dst, bx, by, monoTile, fgColor, bgColor);
|
||||
return;
|
||||
}
|
||||
if (dst == NULL || monoTile == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1532,13 +1445,17 @@ void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8
|
|||
}
|
||||
|
||||
|
||||
void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *tileOut) {
|
||||
void jlpTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
||||
const StPlanarT *pd;
|
||||
uint16_t group;
|
||||
const uint8_t *srcAddr;
|
||||
int16_t row;
|
||||
|
||||
if (src == NULL || tileOut == NULL) {
|
||||
if (src->portData == NULL) {
|
||||
jlpGenericTileSnap(src, bx, by, chunkyOut);
|
||||
return;
|
||||
}
|
||||
if (src == NULL || chunkyOut == NULL) {
|
||||
return;
|
||||
}
|
||||
pd = (const StPlanarT *)src->portData;
|
||||
|
|
@ -1554,12 +1471,12 @@ void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *t
|
|||
(void)row;
|
||||
#define ST_TILE_SNAP_ROW \
|
||||
do { \
|
||||
tileOut[0] = srcAddr[0]; \
|
||||
tileOut[1] = srcAddr[2]; \
|
||||
tileOut[2] = srcAddr[4]; \
|
||||
tileOut[3] = srcAddr[6]; \
|
||||
chunkyOut[0] = srcAddr[0]; \
|
||||
chunkyOut[1] = srcAddr[2]; \
|
||||
chunkyOut[2] = srcAddr[4]; \
|
||||
chunkyOut[3] = srcAddr[6]; \
|
||||
srcAddr += ST_BYTES_PER_ROW; \
|
||||
tileOut += TILE_BYTES_PER_ROW; \
|
||||
chunkyOut += TILE_BYTES_PER_ROW; \
|
||||
} while (0)
|
||||
ST_TILE_SNAP_ROW;
|
||||
ST_TILE_SNAP_ROW;
|
||||
|
|
@ -1578,7 +1495,7 @@ void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *t
|
|||
// We just byte-copy 4 plane bytes per row -- no chunky scratch, no
|
||||
// bit transpose, no LUT. ~640 cyc per tile vs ~5000 cyc for the
|
||||
// snap+paste path.
|
||||
void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
void jlpTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
StPlanarT *dstPd;
|
||||
const StPlanarT *srcPd;
|
||||
uint8_t *dstAddr;
|
||||
|
|
@ -1587,6 +1504,10 @@ void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSu
|
|||
uint16_t dstGroup;
|
||||
int16_t row;
|
||||
|
||||
if (dst->portData == NULL || src->portData == NULL) {
|
||||
jlpGenericTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
||||
return;
|
||||
}
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1630,7 +1551,7 @@ void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSu
|
|||
}
|
||||
|
||||
|
||||
void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
||||
void jlpTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
||||
StPlanarT *dstPd;
|
||||
uint8_t scratch[TILE_BYTES];
|
||||
int16_t row;
|
||||
|
|
@ -1650,6 +1571,10 @@ void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, cons
|
|||
uint8_t mask;
|
||||
uint8_t notMask;
|
||||
|
||||
if (dst->portData == NULL) {
|
||||
jlpGenericTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparent);
|
||||
return;
|
||||
}
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1665,7 +1590,7 @@ void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, cons
|
|||
*
|
||||
* For transparent=0 this collapses to mask = p0|p1|p2|p3.
|
||||
* Replaces the prior 64-iteration per-pixel SetPixel walker. */
|
||||
halTileSnapPlanes(src, srcBx, srcBy, scratch);
|
||||
jlpTileSnap(src, srcBx, srcBy, scratch);
|
||||
dstX0 = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
|
||||
dstY0 = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
|
||||
group = (uint16_t)(dstX0 >> 4);
|
||||
|
|
@ -1673,10 +1598,10 @@ void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, cons
|
|||
dstByte = dstPd->base + dstY0 * ST_BYTES_PER_ROW
|
||||
+ group * ST_BYTES_PER_GROUP + halfOff;
|
||||
|
||||
xK0 = (transparentIndex & 1u) ? 0xFFu : 0u;
|
||||
xK1 = (transparentIndex & 2u) ? 0xFFu : 0u;
|
||||
xK2 = (transparentIndex & 4u) ? 0xFFu : 0u;
|
||||
xK3 = (transparentIndex & 8u) ? 0xFFu : 0u;
|
||||
xK0 = (transparent & 1u) ? 0xFFu : 0u;
|
||||
xK1 = (transparent & 2u) ? 0xFFu : 0u;
|
||||
xK2 = (transparent & 4u) ? 0xFFu : 0u;
|
||||
xK3 = (transparent & 8u) ? 0xFFu : 0u;
|
||||
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
p0 = scratch[row * 4 + 0];
|
||||
|
|
@ -1791,7 +1716,7 @@ static void stSpriteDrawByteAligned(StPlanarT *pd, const jlSpriteT *sp, int16_t
|
|||
// stPlanarSetPixel body so there's no nested function entry / y*160
|
||||
// re-derivation. Major rewrite of the dispatcher path that drove
|
||||
// the 0.06x sprite gap before this commit.
|
||||
void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
void jlpSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
StPlanarT *pd;
|
||||
int16_t spritePxStart;
|
||||
int16_t spritePyStart;
|
||||
|
|
@ -1943,7 +1868,7 @@ void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t
|
|||
// can compute base+row*160 once per row and just do per-pixel
|
||||
// (group, bitMask, 4 plane RMW). 2x speedup over the per-pixel
|
||||
// stPlanarSetPixel form.
|
||||
void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
void jlpSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
const StPlanarT *pd;
|
||||
int16_t row;
|
||||
int16_t pair;
|
||||
|
|
@ -2011,7 +1936,7 @@ void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w,
|
|||
}
|
||||
|
||||
|
||||
void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
void jlpSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
StPlanarT *pd;
|
||||
int16_t row;
|
||||
int16_t pair;
|
||||
|
|
@ -2085,7 +2010,7 @@ void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uin
|
|||
// (s->pixels) since that's the source-of-truth during transition.
|
||||
// Once Phase 9 sets s->pixels = NULL the planar shadow becomes
|
||||
// authoritative and we walk the 4 plane bits at (x, y).
|
||||
uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
uint8_t jlpSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
if (s->pixels != NULL) {
|
||||
uint8_t byte = s->pixels[(uint16_t)y * SURFACE_BYTES_PER_ROW + ((uint16_t)x >> 1)];
|
||||
if (x & 1) return (uint8_t)(byte & 0x0Fu);
|
||||
|
|
@ -2104,7 +2029,7 @@ uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
|||
// Derive 160 chunky bytes per row from the word-interleaved planar
|
||||
// buffer (20 groups x 4 plane words). Same shape as the Amiga's
|
||||
// amigaPlanesToChunkyRow but per-group instead of per-byte. Used by
|
||||
// halSurfaceHash to fold the planar surface into the same byte stream
|
||||
// jlpSurfaceHash to fold the planar surface into the same byte stream
|
||||
// the chunky ports hash, so cross-port hash comparisons stay valid.
|
||||
static void stPlanarToChunkyRow(const StPlanarT *pd, int16_t y, uint8_t *dstChunkyRow) {
|
||||
uint16_t group;
|
||||
|
|
@ -2134,7 +2059,7 @@ static void stPlanarToChunkyRow(const StPlanarT *pd, int16_t y, uint8_t *dstChun
|
|||
}
|
||||
|
||||
|
||||
uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
||||
uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||
StPlanarT *pd;
|
||||
uint16_t lo = 0xACE1u;
|
||||
uint16_t hi = 0x1357u;
|
||||
|
|
@ -2177,19 +2102,9 @@ uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
|||
}
|
||||
|
||||
|
||||
// Phase 9: planar-only. The chunky shadow is gone; surface copy is
|
||||
// 32000 bytes of planar data. halSurfaceCopyPlanes already handles
|
||||
// the planar copy via memcpy of pd->base. This stub only guards the
|
||||
// pre-Phase-9 contract; cross-platform jlSurfaceCopy still calls both
|
||||
// halSurfaceCopyChunky and halSurfaceCopyPlanes.
|
||||
void halSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
(void)dst; (void)src;
|
||||
}
|
||||
|
||||
|
||||
// On-disk format is the ST's native interleaved planar buffer; one
|
||||
// fread fills it directly, no chunky scratch or c2p step.
|
||||
bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
StPlanarT *pd;
|
||||
|
||||
pd = (StPlanarT *)dst->portData;
|
||||
|
|
@ -2200,7 +2115,7 @@ bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
|||
}
|
||||
|
||||
|
||||
bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
StPlanarT *pd;
|
||||
|
||||
pd = (StPlanarT *)src->portData;
|
||||
|
|
@ -2213,16 +2128,11 @@ bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
|||
|
||||
// Phase 9: no chunky storage on the ST. Cross-platform code treats
|
||||
// NULL as "port has no chunky shadow" (same contract Amiga uses).
|
||||
uint8_t *halSurfaceAllocPixels(void) {
|
||||
uint8_t *jlpSurfaceAllocPixels(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePixels(uint8_t *pixels) {
|
||||
free(pixels); /* free(NULL) is a no-op; symmetric for non-planar ports. */
|
||||
}
|
||||
|
||||
|
||||
// ST is word-interleaved: one buffer holds all 4 planes per group
|
||||
// back-to-back. There's no per-plane base, but we overload planeIdx
|
||||
// 0 to return the single buffer base so the cross-platform sprite
|
||||
|
|
@ -2230,7 +2140,7 @@ void halSurfaceFreePixels(uint8_t *pixels) {
|
|||
// routine, which computes plane offsets internally via d16(a0)
|
||||
// chains. planeIdx >= 1 returns NULL since they don't make sense
|
||||
// in interleaved layout.
|
||||
uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
uint8_t *jlpSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
StPlanarT *pd;
|
||||
|
||||
if (s == NULL || planeIdx != 0u) {
|
||||
|
|
@ -2246,11 +2156,7 @@ uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
|||
|
||||
// Phase 9: stage has no chunky shadow either. Cross-platform stageAlloc
|
||||
// stores NULL in s->pixels and skips the chunky memset.
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
uint8_t *jlpStageAllocPixels(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
|
@ -14,17 +14,17 @@
|
|||
// consume the documented byte count to stay in sync.
|
||||
//
|
||||
// The ISR writes into private gIsrState (keyboard) and gIsrMouse*
|
||||
// buffers; halInputPoll copies them into the public globals.
|
||||
// buffers; jlpInputPoll copies them into the public globals.
|
||||
// jlKeyPressed edge detection requires that public gKeyState only
|
||||
// advance during halInputPoll, never at interrupt time -- jlInputPoll
|
||||
// snapshots gKeyState into gKeyPrev before halInputPoll runs.
|
||||
// advance during jlpInputPoll, never at interrupt time -- jlInputPoll
|
||||
// snapshots gKeyState into gKeyPrev before jlpInputPoll runs.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <mint/osbind.h>
|
||||
#include <mint/ostruct.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "inputInternal.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ static int16_t gMouseAbsY = SURFACE_HEIGHT / 2;
|
|||
|
||||
// Latched joystick state, written by the IKBD ISR. The byte stays
|
||||
// unchanged between packets while a direction or fire is held, so
|
||||
// halInputPoll can simply read the latest value.
|
||||
// jlpInputPoll can simply read the latest value.
|
||||
static volatile uint8_t gIsrJoyByte[JOYSTICK_COUNT];
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
|
@ -283,13 +283,13 @@ static long restoreIkbdVector(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halJoystickReset(jlJoystickE js) {
|
||||
void jlpJoystickReset(jlJoystickE js) {
|
||||
// Atari ST sticks are digital -- no calibration to do.
|
||||
(void)js;
|
||||
}
|
||||
|
||||
|
||||
void halInputInit(void) {
|
||||
void jlpInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
|
|
@ -320,7 +320,7 @@ void halInputInit(void) {
|
|||
// gIsrJoyByte[] at ~100 Hz max; the ~60-byte memcpy is essentially
|
||||
// never racing a write. Worst case is a single key or one packet
|
||||
// lagging one frame -- well under perceptible.
|
||||
void halInputPoll(void) {
|
||||
void jlpInputPoll(void) {
|
||||
int32_t dx;
|
||||
int32_t dy;
|
||||
int32_t newX;
|
||||
|
|
@ -370,7 +370,7 @@ void halInputPoll(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
void jlpInputShutdown(void) {
|
||||
if (!gHooked) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -537,7 +537,7 @@ _surface68kStFillRectSingleGroup:
|
|||
|
|
||||
| Specialized 8x8 single-group fill: 16-way color dispatch + 8 rows
|
||||
| fully unrolled. Drops the per-row subq+bne overhead that the
|
||||
| generic FRG_LOOP pays. Used by halTileFillPlanes.
|
||||
| generic FRG_LOOP pays. Used by jlpTileFill.
|
||||
|
|
||||
| void surface68kStTileFill8x8(uint8_t *firstGroupPtr,
|
||||
| uint16_t mask,
|
||||
135
src/blank/blank.c
Normal file
135
src/blank/blank.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// JoeyLib new-port template ("jBlank").
|
||||
//
|
||||
// Copy this directory to src/<yourplatform>/, set JOEYLIB_PLATFORM_<YOURS> in
|
||||
// include/joey/platform.h (mirror the JOEYLIB_PLATFORM_BLANK blocks there), and
|
||||
// fill in the TODOs below. Build with -DJOEYLIB_PLATFORM_<YOURS>.
|
||||
//
|
||||
// HOW THE PORT MODEL WORKS
|
||||
// -----------------------
|
||||
// JoeyLib's public API (jlDrawPixel, jlFillRect, jlSpriteDraw, ...) is
|
||||
// implemented ONCE in src/core/*.c. Each overridable operation is a jlp<Op>()
|
||||
// resolved at COMPILE TIME: a portable-C default lives in src/generic/ and your
|
||||
// port supplies its own version ONLY when it defines JL_HAS_<OP> in platform.h.
|
||||
// There is no runtime dispatch.
|
||||
//
|
||||
// The payoff for a new port: you get ALL of the graphics for free. The generic
|
||||
// backend (src/generic/genericDraw|Tile|Sprite|Surface.c) is a complete chunky
|
||||
// 4bpp software renderer -- draw primitives, tiles, sprites, surface readers,
|
||||
// allocation. A new CHUNKY port renders correctly before you write a single
|
||||
// line of graphics code. You only have to implement the platform SERVICES below
|
||||
// (whose generics are no-op stubs): bring up the display, blit the stage to it,
|
||||
// read input, and report timing. Wire up audio whenever you like.
|
||||
//
|
||||
// To ACCELERATE a specific op later (e.g. a hardware blit for fillRect), add
|
||||
// JL_HAS_FILL_RECT to your platform.h block and a jlpFillRect() here; the core
|
||||
// will call yours instead of the generic. Nothing else changes.
|
||||
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h" // jlSurfaceT: ->pixels (chunky), ->scb, ->palette
|
||||
#include "inputInternal.h" // gKeyState[] etc. for jlpInputPoll
|
||||
|
||||
|
||||
// ----- Lifecycle -----------------------------------------------------------
|
||||
|
||||
// Called once from jlInit after config is stored, before any surfaces exist.
|
||||
// Bring up your video mode and allocate any hardware-adjacent buffers. Return
|
||||
// true on success (false aborts jlInit). jlpLastError may be set on failure.
|
||||
bool jlpInit(const jlConfigT *config) {
|
||||
(void)config;
|
||||
// TODO: program your display into a 320x200, 16-colors-per-scanline mode
|
||||
// (or the closest your hardware offers) and prepare to present.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Called from jlShutdown. Undo jlpInit: restore the prior video mode and free
|
||||
// whatever you allocated.
|
||||
void jlpShutdown(void) {
|
||||
// TODO: restore the display.
|
||||
}
|
||||
|
||||
|
||||
// ----- Present -------------------------------------------------------------
|
||||
|
||||
// Copy the library-owned stage surface to the visible display. This is the one
|
||||
// service every port MUST get right. The stage is chunky 4bpp:
|
||||
// src->pixels : SURFACE_PIXELS_SIZE bytes, 2 pixels per byte (high nibble is
|
||||
// the LEFT pixel), SURFACE_BYTES_PER_ROW per scanline.
|
||||
// src->scb[y] : palette index 0..15 selecting which of the 16 palettes row
|
||||
// y uses (SCB = scanline control byte).
|
||||
// src->palette[p][c] : 16 palettes x 16 colors, each a $0RGB 4-bit-channel
|
||||
// value. Expand to your hardware's color depth.
|
||||
// jlStagePresent has already walked the per-row dirty bands; for a first cut
|
||||
// just blit the whole frame, then optimize to dirty rows later (see the dirty
|
||||
// arrays in surfaceInternal.h).
|
||||
void jlpPresent(const jlSurfaceT *src) {
|
||||
(void)src;
|
||||
// TODO: for each scanline y, look up src->scb[y] to pick the palette, then
|
||||
// expand src->pixels nibbles -> your framebuffer pixels.
|
||||
}
|
||||
|
||||
|
||||
// ----- Input ---------------------------------------------------------------
|
||||
|
||||
// Set up your keyboard/joystick at the end of jlInit.
|
||||
void jlpInputInit(void) {
|
||||
// TODO: install / enable your input hardware.
|
||||
}
|
||||
|
||||
|
||||
// Tear input down (from jlShutdown, before jlpShutdown).
|
||||
void jlpInputShutdown(void) {
|
||||
// TODO: restore input hardware.
|
||||
}
|
||||
|
||||
|
||||
// Refresh the core-owned key-state array each frame. Write true into
|
||||
// gKeyState[key] for every currently-held key (see joey/input.h for the jlKeyE
|
||||
// codes); keys you can't map simply stay false. The cross-platform jlInputPoll
|
||||
// and joystick code build on this.
|
||||
void jlpInputPoll(void) {
|
||||
// TODO: read your keyboard and set gKeyState[...] = true for held keys.
|
||||
}
|
||||
|
||||
|
||||
// Clear any auto-disconnect / calibration state for a joystick and arm a fresh
|
||||
// center capture on the next poll. Digital-stick ports can leave this empty.
|
||||
void jlpJoystickReset(jlJoystickE js) {
|
||||
(void)js;
|
||||
// TODO (analog sticks only): reset calibration for js.
|
||||
}
|
||||
|
||||
|
||||
// ----- Timing --------------------------------------------------------------
|
||||
|
||||
// Block until the next display vertical blank (frame pacing).
|
||||
void jlpWaitVBL(void) {
|
||||
// TODO: spin/sleep until the next vblank.
|
||||
}
|
||||
|
||||
|
||||
// A monotonic 16-bit frame/refresh counter. Either edge-detect a vblank flag
|
||||
// inside this call or return a counter maintained by a vblank interrupt. Used
|
||||
// for animation cadence and the generic millisElapsed (frameCount*1000/frameHz).
|
||||
uint16_t jlpFrameCount(void) {
|
||||
// TODO: return your running frame count.
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
// Nominal display refresh rate in Hz (50 PAL, 60 NTSC, 70 VGA, ...). Reported
|
||||
// only; the generic millisElapsed divides by it.
|
||||
uint16_t jlpFrameHz(void) {
|
||||
// TODO: return your real refresh rate.
|
||||
return 60u;
|
||||
}
|
||||
|
||||
|
||||
// ----- Audio (optional) ----------------------------------------------------
|
||||
//
|
||||
// Left entirely to the no-op generics: your port links and runs SILENT until
|
||||
// you wire up sound. When you do, add JL_HAS_AUDIO_<OP> to your platform.h
|
||||
// block and implement the matching jlpAudio<Op>() here (see src/<port>/audio.c
|
||||
// in the shipping ports for the full op set: Init/Shutdown, PlayMod/StopMod/
|
||||
// IsPlayingMod, PlaySfx/PlaySfxStream/StopSfx, Tone/Voice, TickRegister,
|
||||
// CriticalEnter/Exit, FrameTick).
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
#include "codegenArenaInternal.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "spriteEmitter.h"
|
||||
#include "spriteInternal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
|
@ -30,16 +30,16 @@
|
|||
// `cap` argument and returns SPRITE_EMIT_OVERFLOW when a routine would
|
||||
// not fit. jlSpriteCompile then bails cleanly (no arena alloc,
|
||||
// sp->slot stays NULL, the dispatcher falls back to the interpreted
|
||||
// halSpriteXxxPlanes path) instead of corrupting the heap. The
|
||||
// the jlpSprite*Planes ops path) instead of corrupting the heap. The
|
||||
// constant is a long literal so it never wraps the 16-bit int the
|
||||
// IIgs uses for plain int arithmetic.
|
||||
//
|
||||
// The scratch is taken from halBigAlloc (jlAlloc's backing), NOT plain
|
||||
// The scratch is taken from jlpBigAlloc (jlAlloc's backing), NOT plain
|
||||
// malloc: 16 KB exceeds the per-allocation / total ceiling of the stock
|
||||
// C heap on some ports (the IIgs C heap lives in bank 0 and is only a
|
||||
// few KB), where a malloc that large either fails outright or -- with a
|
||||
// bump allocator that does not bound-check -- hands back an out-of-heap
|
||||
// pointer that corrupts memory. halBigAlloc routes to the platform's
|
||||
// pointer that corrupts memory. jlpBigAlloc routes to the platform's
|
||||
// native allocator (IIgs Memory Manager; plain malloc elsewhere).
|
||||
#define SPRITE_EMIT_SCRATCH_BYTES (16384ul)
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ bool jlSpriteCompile(jlSpriteT *sp) {
|
|||
* bytes per row). The post-emit pass below aliases slots 2..7
|
||||
* for save/restore to slot 1's bytes. */
|
||||
|
||||
scratch = (uint8_t *)halBigAlloc(SPRITE_EMIT_SCRATCH_BYTES);
|
||||
scratch = (uint8_t *)jlpBigAlloc(SPRITE_EMIT_SCRATCH_BYTES);
|
||||
if (scratch == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -186,7 +186,7 @@ bool jlSpriteCompile(jlSpriteT *sp) {
|
|||
/* SPRITE_EMIT_OVERFLOW (0xFFFFFFFF) is > 0xFFFF too, so a single
|
||||
* routine that did not fit the scratch is rejected here as well. */
|
||||
if (totalSize > 0xFFFFu) {
|
||||
halBigFree(scratch);
|
||||
jlpBigFree(scratch);
|
||||
return false;
|
||||
}
|
||||
if (totalSize == 0) {
|
||||
|
|
@ -194,14 +194,14 @@ bool jlSpriteCompile(jlSpriteT *sp) {
|
|||
* no compiled bytes -- spriteCompiledDraw / SaveUnder /
|
||||
* RestoreUnder would dereference a degenerate slot or chunky
|
||||
* shadow. Bail so sp->slot stays NULL and the dispatcher
|
||||
* routes through the interpreted halSpriteXxxPlanes path. */
|
||||
halBigFree(scratch);
|
||||
* routes through the interpreted the jlpSprite*Planes ops path. */
|
||||
jlpBigFree(scratch);
|
||||
return false;
|
||||
}
|
||||
|
||||
slot = codegenArenaAlloc(totalSize);
|
||||
if (slot == NULL) {
|
||||
halBigFree(scratch);
|
||||
jlpBigFree(scratch);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ bool jlSpriteCompile(jlSpriteT *sp) {
|
|||
/* The second pass disagreed with the sizing pass (must
|
||||
* not happen, but never write past the slot). Roll back. */
|
||||
codegenArenaFree(slot);
|
||||
halBigFree(scratch);
|
||||
jlpBigFree(scratch);
|
||||
return false;
|
||||
}
|
||||
/* routineOffsets is uint16_t with 0xFFFF (SPRITE_NOT_COMPILED)
|
||||
|
|
@ -249,7 +249,7 @@ bool jlSpriteCompile(jlSpriteT *sp) {
|
|||
}
|
||||
#endif
|
||||
sp->slot = slot;
|
||||
halBigFree(scratch);
|
||||
jlpBigFree(scratch);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -516,8 +516,8 @@ void spriteCompiledRestoreUnder(jlSurfaceT *dst, const jlSpriteBackupT *backup)
|
|||
* For non-zero shifts (x not 8-px-aligned), the dispatcher in
|
||||
* src/core/sprite.c (jlSpriteDraw / jlSpriteSaveUnder / jlSpriteRestoreUnder)
|
||||
* sees SPRITE_NOT_COMPILED for the shift and falls back to the
|
||||
* interpreter, which handles arbitrary x via halSpriteDrawPlanes /
|
||||
* halSpriteSavePlanes / halSpriteRestorePlanes. */
|
||||
* interpreter, which handles arbitrary x via jlpSpriteDrawPlanes /
|
||||
* jlpSpriteSavePlanes / jlpSpriteRestorePlanes. */
|
||||
|
||||
// Amiga per-plane row stride: 320 px / 8 px-per-plane-byte = 40.
|
||||
// Derived from surface geometry so it cannot drift if the fixed
|
||||
|
|
@ -535,11 +535,20 @@ void spriteCompiledDraw(jlSurfaceT *dst, const jlSpriteT *sp, int16_t x, int16_t
|
|||
DrawFn fn;
|
||||
|
||||
shift = (uint8_t)(x & 7);
|
||||
/* Only shift 0 (byte-aligned x) is compiled; shifts 1..7 stay
|
||||
* SPRITE_NOT_COMPILED. jlSpriteDraw gates only on sp->slot != NULL,
|
||||
* so guard the per-shift offset here and delegate a non-aligned draw
|
||||
* to the interpreter (COMPILED_SPRITE_WRITES_PLANES==1 means
|
||||
* jlSpriteDraw suppresses its own planes hook, so we must run it). */
|
||||
if (sp->routineOffsets[shift][SPRITE_OP_DRAW] == SPRITE_NOT_COMPILED) {
|
||||
jlpSpriteDrawPlanes(dst, sp, x, y);
|
||||
return;
|
||||
}
|
||||
byteOff = (uint16_t)((uint16_t)y * AMIGA_PLANE_BYTES_PER_ROW + ((uint16_t)x >> 3));
|
||||
p0 = halSurfacePlanePtr(dst, 0); if (p0 == NULL) return;
|
||||
p1 = halSurfacePlanePtr(dst, 1);
|
||||
p2 = halSurfacePlanePtr(dst, 2);
|
||||
p3 = halSurfacePlanePtr(dst, 3);
|
||||
p0 = jlpSurfacePlanePtr(dst, 0); if (p0 == NULL) return;
|
||||
p1 = jlpSurfacePlanePtr(dst, 1);
|
||||
p2 = jlpSurfacePlanePtr(dst, 2);
|
||||
p3 = jlpSurfacePlanePtr(dst, 3);
|
||||
fn = (DrawFn)(codegenArenaBase() + sp->slot->offset + sp->routineOffsets[shift][SPRITE_OP_DRAW]);
|
||||
fn(p0 + byteOff, p1 + byteOff, p2 + byteOff, p3 + byteOff);
|
||||
}
|
||||
|
|
@ -576,10 +585,10 @@ void spriteCompiledSaveUnder(const jlSurfaceT *src, jlSpriteT *sp, int16_t x, in
|
|||
/* 4 planes * h * (widthPx/8) bytes = h * widthPx/2. */
|
||||
backup->sizeBytes = (uint16_t)((uint16_t)heightPx * (widthPx >> 1));
|
||||
|
||||
p0 = halSurfacePlanePtr(src, 0); if (p0 == NULL) return;
|
||||
p1 = halSurfacePlanePtr(src, 1);
|
||||
p2 = halSurfacePlanePtr(src, 2);
|
||||
p3 = halSurfacePlanePtr(src, 3);
|
||||
p0 = jlpSurfacePlanePtr(src, 0); if (p0 == NULL) return;
|
||||
p1 = jlpSurfacePlanePtr(src, 1);
|
||||
p2 = jlpSurfacePlanePtr(src, 2);
|
||||
p3 = jlpSurfacePlanePtr(src, 3);
|
||||
fn = (SaveFn)(codegenArenaBase() + sp->slot->offset + sp->routineOffsets[shift][SPRITE_OP_SAVE]);
|
||||
fn(p0 + byteOff, p1 + byteOff, p2 + byteOff, p3 + byteOff, backup->bytes);
|
||||
}
|
||||
|
|
@ -605,10 +614,10 @@ void spriteCompiledRestoreUnder(jlSurfaceT *dst, const jlSpriteBackupT *backup)
|
|||
shift = (uint8_t)(backup->width > (uint16_t)(sp->widthTiles * 8) ? 1u : 0u);
|
||||
byteOff = (uint16_t)((uint16_t)backup->y * AMIGA_PLANE_BYTES_PER_ROW + ((uint16_t)backup->x >> 3));
|
||||
|
||||
p0 = halSurfacePlanePtr(dst, 0); if (p0 == NULL) return;
|
||||
p1 = halSurfacePlanePtr(dst, 1);
|
||||
p2 = halSurfacePlanePtr(dst, 2);
|
||||
p3 = halSurfacePlanePtr(dst, 3);
|
||||
p0 = jlpSurfacePlanePtr(dst, 0); if (p0 == NULL) return;
|
||||
p1 = jlpSurfacePlanePtr(dst, 1);
|
||||
p2 = jlpSurfacePlanePtr(dst, 2);
|
||||
p3 = jlpSurfacePlanePtr(dst, 3);
|
||||
fn = (RestoreFn)(codegenArenaBase() + sp->slot->offset + sp->routineOffsets[shift][SPRITE_OP_RESTORE]);
|
||||
fn(p0 + byteOff, p1 + byteOff, p2 + byteOff, p3 + byteOff, backup->bytes);
|
||||
}
|
||||
|
|
@ -627,7 +636,7 @@ void spriteCompiledRestoreUnder(jlSurfaceT *dst, const jlSpriteBackupT *backup)
|
|||
* 1 : byte-aligned x with x mod 16 == 8 (first tile col low half)
|
||||
* 2+ : non-byte-aligned x, never compiled (emitter returns 0); the
|
||||
* per-shift offset is SPRITE_NOT_COMPILED so the dispatcher
|
||||
* falls back to halSpriteDrawPlanes. */
|
||||
* falls back to jlpSpriteDrawPlanes. */
|
||||
|
||||
// ST word-interleaved row stride: 320 px * 4 planes / 8 bits = 160
|
||||
// bytes/row. Numerically equal to SURFACE_BYTES_PER_ROW but a distinct
|
||||
|
|
@ -648,16 +657,16 @@ void spriteCompiledDraw(jlSurfaceT *dst, const jlSpriteT *sp, int16_t x, int16_t
|
|||
routeOffset = sp->routineOffsets[shift][SPRITE_OP_DRAW];
|
||||
if (routeOffset == SPRITE_NOT_COMPILED) {
|
||||
/* Non-byte-aligned x: cross-platform jlSpriteDraw will call
|
||||
* halSpriteDrawPlanes after this returns (since the dispatcher
|
||||
* jlpSpriteDrawPlanes after this returns (since the dispatcher
|
||||
* already chose the compiled path based on sp->slot != NULL,
|
||||
* but COMPILED_SPRITE_WRITES_PLANES is 1 on ST so it normally
|
||||
* suppresses the planes hook). For non-aligned shifts we
|
||||
* deliberately want the interpreted planes hook to run, so
|
||||
* delegate via halSpriteDrawPlanes here. */
|
||||
halSpriteDrawPlanes(dst, sp, x, y);
|
||||
* delegate via jlpSpriteDrawPlanes here. */
|
||||
jlpSpriteDrawPlanes(dst, sp, x, y);
|
||||
return;
|
||||
}
|
||||
base = halSurfacePlanePtr(dst, 0);
|
||||
base = jlpSurfacePlanePtr(dst, 0);
|
||||
if (base == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -672,7 +681,7 @@ void spriteCompiledDraw(jlSurfaceT *dst, const jlSpriteT *sp, int16_t x, int16_t
|
|||
/* Save/Restore aren't compiled on ST yet (emitter returns 0). The
|
||||
* dispatcher's check on sp->routineOffsets[shift][SPRITE_OP_SAVE/_RESTORE]
|
||||
* == SPRITE_NOT_COMPILED already routes those through the
|
||||
* interpreted halSpriteSavePlanes / halSpriteRestorePlanes. These
|
||||
* interpreted jlpSpriteSavePlanes / jlpSpriteRestorePlanes. These
|
||||
* stubs exist only to satisfy the linker. */
|
||||
void spriteCompiledSaveUnder(const jlSurfaceT *src, jlSpriteT *sp, int16_t x, int16_t y, jlSpriteBackupT *backup) {
|
||||
(void)src; (void)sp; (void)x; (void)y; (void)backup;
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
// 68k sprite codegen for ST word-interleaved planar layout.
|
||||
//
|
||||
// Phase 11 (shared-walker): the per-shift unrolled emitter was
|
||||
// retired. halSpriteDrawPlanes in src/port/atarist/hal.c now handles
|
||||
// all shifts byte-wide at runtime, including the formerly fast shifts
|
||||
// 0 and 1 -- per-cel arena cost drops to zero. The byte-aligned fast
|
||||
// path stSpriteDrawByteAligned is still called from halSpriteDrawPlanes
|
||||
// for shift==0 sprites that are fully on surface, so byte-aligned UBER
|
||||
// perf does not regress. The entry points below are kept so the
|
||||
// dispatcher API stays uniform across ports; they emit zero bytes, so
|
||||
// jlSpriteCompile takes the "totalSize == 0 -> bail" path and sp->slot
|
||||
// stays NULL.
|
||||
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
#include "spriteEmitter.h"
|
||||
#include "spriteInternal.h"
|
||||
|
||||
|
||||
// ----- Emit API -----
|
||||
|
||||
uint32_t spriteEmitDrawInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitSaveInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitRestoreInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// Planar 68k sprite codegen for Amiga.
|
||||
//
|
||||
// Phase 11 (shared-walker): the Amiga port no longer emits per-cel
|
||||
// unrolled draw/save/restore code. Each cel was ~17-23 KB compiled --
|
||||
// 21 cels burned ~340 KB of arena. The cross-platform dispatcher in
|
||||
// src/core/sprite.c routes through halSpriteXxxPlanes in
|
||||
// src/port/amiga/hal.c when sp->slot is NULL, and those HAL hooks are
|
||||
// byte-wide walkers that read sp->tileData on the fly and synthesize
|
||||
// plane bytes at draw time. Same per-byte work, shared as one static
|
||||
// lib routine instead of 21 inlined copies; the pre-shift plane-byte
|
||||
// decomposition (and its shift/transparency math) now lives ONLY in
|
||||
// halSpriteDrawPlanes, so there is one source of truth.
|
||||
//
|
||||
// The entry points below are kept so the dispatcher API stays uniform
|
||||
// across ports. Returning 0 tells emitTotalSize() the emitter has no
|
||||
// bytes for this op, so jlSpriteCompile takes the "totalSize == 0 ->
|
||||
// bail" path and sp->slot stays NULL.
|
||||
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
#include "spriteEmitter.h"
|
||||
#include "spriteInternal.h"
|
||||
|
||||
|
||||
// ----- Public API -----
|
||||
|
||||
uint32_t spriteEmitDrawPlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitSavePlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitRestorePlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
// function. jlSpriteCompile.c picks the right one at compile time
|
||||
// (via #ifdef on JOEYLIB_PLATFORM_*) for the runtime build. The
|
||||
// planar 68k emitters now return 0 bytes after Phase 11 -- the
|
||||
// halSpriteDrawPlanes walker reads chunky tile data and synthesizes
|
||||
// jlpSpriteDrawPlanes walker reads chunky tile data and synthesizes
|
||||
// plane bytes at draw time -- but the entry points are kept so the
|
||||
// dispatcher API stays uniform across ports.
|
||||
//
|
||||
|
|
@ -104,7 +104,7 @@ uint32_t spriteEmitRestorePlanar68k (uint8_t *out, uint32_t cap, const jlSpriteT
|
|||
// where groupBase = pd->base + y*160 + (x>>4)*8. Shifts 0 and 1 emit
|
||||
// real bytes (x mod 16 == 0 for shift 0, x mod 16 == 8 for shift 1);
|
||||
// other shifts return 0 so the cross-platform dispatcher falls back
|
||||
// to halSpriteDrawPlanes.
|
||||
// to jlpSpriteDrawPlanes.
|
||||
uint32_t spriteEmitDrawInterleaved68k (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
|
||||
uint32_t spriteEmitSaveInterleaved68k (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
|
||||
uint32_t spriteEmitRestoreInterleaved68k (uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@
|
|||
#define EXPECTED_TILE_TARGET 3u
|
||||
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
||||
#define EXPECTED_TILE_TARGET 4u
|
||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||
#define EXPECTED_TILE_TARGET 3u // chunky 4bpp, same as DOS (uses the generic tile ops)
|
||||
#else
|
||||
#error "Unknown platform for asset loader"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#include <stddef.h>
|
||||
|
||||
#include "joey/audio.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
|
||||
|
||||
static bool gAudioReady = false;
|
||||
|
|
@ -16,7 +16,7 @@ bool jlAudioInit(void) {
|
|||
if (gAudioReady) {
|
||||
return true;
|
||||
}
|
||||
gAudioReady = halAudioInit();
|
||||
gAudioReady = jlpAudioInit();
|
||||
return gAudioReady;
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ void jlAudioShutdown(void) {
|
|||
if (!gAudioReady) {
|
||||
return;
|
||||
}
|
||||
halAudioShutdown();
|
||||
jlpAudioShutdown();
|
||||
gAudioReady = false;
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ void jlAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
if (!gAudioReady || data == NULL || length == 0) {
|
||||
return;
|
||||
}
|
||||
halAudioPlayMod(data, length, loop);
|
||||
jlpAudioPlayMod(data, length, loop);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ void jlAudioStopMod(void) {
|
|||
if (!gAudioReady) {
|
||||
return;
|
||||
}
|
||||
halAudioStopMod();
|
||||
jlpAudioStopMod();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ bool jlAudioIsPlayingMod(void) {
|
|||
if (!gAudioReady) {
|
||||
return false;
|
||||
}
|
||||
return halAudioIsPlayingMod();
|
||||
return jlpAudioIsPlayingMod();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ void jlAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16
|
|||
if (slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
halAudioPlaySfx(slot, sample, length, rateHz);
|
||||
jlpAudioPlaySfx(slot, sample, length, rateHz);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ void jlAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint
|
|||
if (slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
halAudioPlaySfxStream(slot, fill, ctx, rateHz);
|
||||
jlpAudioPlaySfxStream(slot, fill, ctx, rateHz);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ void jlAudioTone(uint16_t freqHz) {
|
|||
// PCM/SFX paths that need DMA + libxmp wiring. The PC speaker
|
||||
// (and other simple hardware tone generators) is direct port
|
||||
// I/O and works whether the DSP came up or not.
|
||||
halAudioTone(freqHz);
|
||||
jlpAudioTone(freqHz);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
|||
if (voice >= JOEY_AUDIO_VOICES) {
|
||||
return;
|
||||
}
|
||||
halAudioVoice(voice, freqHz, atten);
|
||||
jlpAudioVoice(voice, freqHz, atten);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -100,17 +100,17 @@ bool jlAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
|||
if (fn != NULL && hz == 0u) {
|
||||
return false;
|
||||
}
|
||||
return halAudioTickRegister(fn, hz);
|
||||
return jlpAudioTickRegister(fn, hz);
|
||||
}
|
||||
|
||||
|
||||
void jlAudioCriticalEnter(void) {
|
||||
halAudioCriticalEnter();
|
||||
jlpAudioCriticalEnter();
|
||||
}
|
||||
|
||||
|
||||
void jlAudioCriticalExit(void) {
|
||||
halAudioCriticalExit();
|
||||
jlpAudioCriticalExit();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ void jlAudioStopSfx(uint8_t slot) {
|
|||
if (!gAudioReady || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
halAudioStopSfx(slot);
|
||||
jlpAudioStopSfx(slot);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -127,9 +127,9 @@ void jlAudioFrameTick(void) {
|
|||
return;
|
||||
}
|
||||
#ifndef JOEYLIB_PLATFORM_IIGS
|
||||
// IIgs: NTPstreamsound is fully DOC-IRQ-driven, halAudioFrameTick
|
||||
// IIgs: NTPstreamsound is fully DOC-IRQ-driven, jlpAudioFrameTick
|
||||
// is an empty no-op there. Skip the wrapper JSL entirely on IIgs
|
||||
// so per-frame audio cost stays at the gAudioReady branch above.
|
||||
halAudioFrameTick();
|
||||
jlpAudioFrameTick();
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
199
src/core/draw.c
199
src/core/draw.c
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
#include "joey/draw.h"
|
||||
#include "joey/debug.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
|
||||
|
|
@ -30,54 +30,14 @@
|
|||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void fillRectClipped(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
|
||||
static void fillRectOnSurface(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
|
||||
static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uint8_t newColor, uint8_t matchColor, bool matchEqual);
|
||||
static void plotPixelNoMark(jlSurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex);
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
static void fillRectClipped(jlSurfaceT *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;
|
||||
uint16_t pxStart;
|
||||
uint16_t pxEnd;
|
||||
uint16_t midBytes;
|
||||
uint8_t *line;
|
||||
|
||||
// Planar ports have NULL s->pixels; the planar dual-write hook
|
||||
// handles the actual plane fill. Guarding here keeps the chunky
|
||||
// fallback from dereferencing NULL when a port has no chunky store.
|
||||
if (s->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* px* and midBytes are uint16_t (clipped values are non-negative)
|
||||
* so `>>1` lowers to a single LSR instead of a signed-shift
|
||||
* helper. Same with `<<1` for midBytes. */
|
||||
for (row = 0; row < h; row++) {
|
||||
line = &s->pixels[SURFACE_ROW_OFFSET(y + row)];
|
||||
pxStart = (uint16_t)x;
|
||||
pxEnd = (uint16_t)(x + w);
|
||||
|
||||
if (pxStart & 1u) {
|
||||
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0xF0) | nibble);
|
||||
pxStart++;
|
||||
}
|
||||
|
||||
midBytes = (uint16_t)((pxEnd - pxStart) >> 1);
|
||||
if (midBytes > 0u) {
|
||||
memset(&line[pxStart >> 1], doubled, (size_t)midBytes);
|
||||
pxStart = (uint16_t)(pxStart + (midBytes << 1));
|
||||
}
|
||||
|
||||
if (pxStart < pxEnd) {
|
||||
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (fillRectClipped moved to src/generic/genericDraw.c as jlpGenericFillRect,
|
||||
// the chunky default for the migrated jlpFillRect op.)
|
||||
|
||||
// Fill an axis-aligned span that the CALLER has already proven to be
|
||||
// fully on-surface (x >= 0, y >= 0, x + w <= SURFACE_WIDTH, y + h <=
|
||||
|
|
@ -88,10 +48,9 @@ static void fillRectClipped(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int1
|
|||
// it per span. Pixel output and dirty-mark word bands are bit-for-bit
|
||||
// identical to routing the same span through jlFillRect.
|
||||
static void fillRectOnSurface(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex) {
|
||||
if (!halFastFillRect(s, x, y, (uint16_t)w, (uint16_t)h, colorIndex)) {
|
||||
fillRectClipped(s, x, y, w, h, colorIndex);
|
||||
}
|
||||
halFillRectPlanes(s, x, y, (uint16_t)w, (uint16_t)h, colorIndex);
|
||||
// Compile-time-selected op: machine fill (IIgs asm stage / Amiga+ST planar)
|
||||
// or the generic chunky fill.
|
||||
jlpFillRect(s, x, y, w, h, colorIndex);
|
||||
surfaceMarkDirtyRect(s, x, y, w, h);
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +87,6 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
int16_t y;
|
||||
int16_t leftX;
|
||||
int16_t rightX;
|
||||
uint8_t *row;
|
||||
uint8_t pix;
|
||||
bool pixMatch;
|
||||
uint8_t newNibble;
|
||||
|
|
@ -150,19 +108,17 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
continue;
|
||||
}
|
||||
|
||||
/* Phase 9: planar ports have NULL s->pixels and the asm fast
|
||||
* paths take a chunky-row pointer. Skip them on planar; the C
|
||||
* fallback below uses halSamplePixel which works on both
|
||||
* storage layouts. */
|
||||
if (s->pixels != NULL) {
|
||||
// Highest-tier asm fast path: seed-test + walk-left + walk-right
|
||||
#if defined(JL_HAS_FLOOD_WALK_AND_SCANS)
|
||||
// Highest-tier asm fast path (IIgs): seed-test + walk-left + walk-right
|
||||
// + 1-row fill + scan-above + scan-below + push, all in one
|
||||
// cross-segment call. The asm caches row addr / match decoder
|
||||
// across every sub-operation. C just pops and dispatches; this
|
||||
// path completes the entire per-seed work and computes the row
|
||||
// address itself, so we don't pay y*160 in C unless we fall back.
|
||||
// address itself, so we don't pay y*160 in C. On this platform it
|
||||
// always handles the pop, so the C fallback below is never reached.
|
||||
if (s->pixels != NULL) {
|
||||
bool seedMatched;
|
||||
if (halFastFloodWalkAndScans(s->pixels, x, y,
|
||||
if (jlpFloodWalkAndScans(s->pixels, x, y,
|
||||
matchColor, newNibble, matchEqual,
|
||||
stackX, stackY,
|
||||
&sp, FLOOD_STACK_SIZE,
|
||||
|
|
@ -176,28 +132,23 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Fallback path: compute row only if chunky; halFastFloodWalk
|
||||
* needs it but isn't implemented on Amiga. */
|
||||
row = (s->pixels != NULL) ? &s->pixels[SURFACE_ROW_OFFSET(y)] : NULL;
|
||||
|
||||
// Tier-2 asm fast path: combined seed test + walk-left +
|
||||
// walk-right in one cross-segment call. Falls back to the
|
||||
// pure-C walks below on ports without an asm implementation.
|
||||
// Walk-out: find the matching run containing the seed. Planar ports
|
||||
// (Amiga) override with a plane-aware walk; everyone else uses the
|
||||
// portable jlpSamplePixel walk, which works on chunky and planar.
|
||||
{
|
||||
#if defined(JL_HAS_FLOOD_WALK_PLANES)
|
||||
bool seedMatched;
|
||||
if (row != NULL && halFastFloodWalk(row, x, matchColor, newNibble, matchEqual,
|
||||
if (jlpFloodWalkPlanes(s, x, y, matchColor, newNibble, matchEqual,
|
||||
&seedMatched, &leftX, &rightX)) {
|
||||
if (!seedMatched) {
|
||||
continue;
|
||||
}
|
||||
} else if (halFloodWalkPlanes(s, x, y, matchColor, newNibble, matchEqual,
|
||||
&seedMatched, &leftX, &rightX)) {
|
||||
if (!seedMatched) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
pix = halSamplePixel(s, x, y);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
pix = jlpSamplePixel(s, x, y);
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual) {
|
||||
if (!pixMatch) {
|
||||
|
|
@ -212,7 +163,7 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
// Walk left to find the start of the matching run.
|
||||
leftX = x;
|
||||
while (leftX > 0) {
|
||||
pix = halSamplePixel(s, (int16_t)(leftX - 1), y);
|
||||
pix = jlpSamplePixel(s, (int16_t)(leftX - 1), y);
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual ? !pixMatch : (pixMatch || pix == newNibble)) {
|
||||
break;
|
||||
|
|
@ -223,7 +174,7 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
// Walk right to find the end.
|
||||
rightX = x;
|
||||
while (rightX < SURFACE_WIDTH - 1) {
|
||||
pix = halSamplePixel(s, (int16_t)(rightX + 1), y);
|
||||
pix = jlpSamplePixel(s, (int16_t)(rightX + 1), y);
|
||||
pixMatch = (pix == matchColor);
|
||||
if (matchEqual ? !pixMatch : (pixMatch || pix == newNibble)) {
|
||||
break;
|
||||
|
|
@ -246,15 +197,14 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
fillRectOnSurface(s, leftX, y, spanW, 1, newNibble);
|
||||
}
|
||||
|
||||
// Scan rows above and below for run boundaries. The hot
|
||||
// per-pixel match check goes through halFastFloodScanRow on
|
||||
// ports that have it (IIgs); fills markBuf[] with 1/0 per
|
||||
// pixel so the run-edge walk below is array-only -- no
|
||||
// function call, no nibble extract.
|
||||
// Scan rows above and below for run boundaries: fill markBuf[] with
|
||||
// 1/0 per pixel, then walk it for run-edge transitions and push a
|
||||
// seed at each run's right edge. Planar ports (Amiga) override the
|
||||
// markBuf fill with a plane-aware scan; everyone else fills it via
|
||||
// the portable jlpSamplePixel walk.
|
||||
{
|
||||
int16_t i;
|
||||
int16_t spanLen;
|
||||
uint8_t *scanRow;
|
||||
int16_t scanY;
|
||||
int16_t side;
|
||||
bool curHit;
|
||||
|
|
@ -273,26 +223,18 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
}
|
||||
scanY = (int16_t)(y + 1);
|
||||
}
|
||||
scanRow = (s->pixels != NULL) ? &s->pixels[SURFACE_ROW_OFFSET(scanY)] : NULL;
|
||||
// Prefer the combined scan+push asm path (one call per
|
||||
// scan, no markBuf and no per-pixel C edge walk). Skip
|
||||
// the asm tiers if we don't have a chunky row pointer
|
||||
// (Phase 9 planar ports).
|
||||
if (scanRow == NULL ||
|
||||
!halFastFloodScanAndPush(scanRow, leftX, rightX,
|
||||
if (
|
||||
#if defined(JL_HAS_FLOOD_SCAN_ROW_PLANES)
|
||||
!jlpFloodScanRowPlanes(s, leftX, rightX, scanY,
|
||||
matchColor, newNibble, matchEqual,
|
||||
scanY, stackX, stackY,
|
||||
&sp, FLOOD_STACK_SIZE)) {
|
||||
if ((scanRow == NULL ||
|
||||
!halFastFloodScanRow(scanRow, leftX, rightX,
|
||||
matchColor, newNibble, matchEqual,
|
||||
floodMarkBuf)) &&
|
||||
!halFloodScanRowPlanes(s, leftX, rightX, scanY,
|
||||
matchColor, newNibble, matchEqual,
|
||||
floodMarkBuf)) {
|
||||
floodMarkBuf)
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
) {
|
||||
// C fallback: fill markBuf the slow way.
|
||||
for (i = 0; i < spanLen; i++) {
|
||||
pix = halSamplePixel(s, (int16_t)(leftX + i), scanY);
|
||||
pix = jlpSamplePixel(s, (int16_t)(leftX + i), scanY);
|
||||
pixMatch = (pix == matchColor);
|
||||
floodMarkBuf[i] = (uint8_t)(matchEqual
|
||||
? (pixMatch ? 1 : 0)
|
||||
|
|
@ -323,36 +265,23 @@ static void floodFillInternal(jlSurfaceT *s, int16_t startX, int16_t startY, uin
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Plot a single clipped pixel WITHOUT marking the dirty rect. Shared by
|
||||
// the public jlDrawPixel (which marks per call afterward) and the
|
||||
// Bresenham line/circle fallbacks (which accumulate one bounding box and
|
||||
// mark it once after their loop). Mirrors jlDrawPixel's NULL + bounds
|
||||
// check, halFastDrawPixel dispatch, and chunky nibble RMW fallback.
|
||||
// check, jlpDrawPixel dispatch, and chunky nibble RMW fallback.
|
||||
static void plotPixelNoMark(jlSurfaceT *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;
|
||||
}
|
||||
|
||||
if (!halFastDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex) && s->pixels != NULL) {
|
||||
/* Cast to uint16_t before shift -- already validated x >= 0,
|
||||
* so unsigned semantics match. Avoids ~SSHIFTRIGHT helper. */
|
||||
byte = &s->pixels[SURFACE_ROW_OFFSET(y) + ((uint16_t)x >> 1)];
|
||||
nibble = colorIndex & 0x0F;
|
||||
if (x & 1) {
|
||||
*byte = (uint8_t)((*byte & 0xF0) | nibble);
|
||||
} else {
|
||||
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
// Compile-time-selected op: machine plot (asm/planar) or the generic chunky
|
||||
// nibble RMW (src/generic/genericDraw.c). No runtime dispatch.
|
||||
jlpDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -362,7 +291,6 @@ void jlDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t col
|
|||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t err;
|
||||
int16_t ir;
|
||||
int16_t minX;
|
||||
int16_t minY;
|
||||
int16_t maxX;
|
||||
|
|
@ -382,16 +310,20 @@ void jlDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t col
|
|||
// with r >= SURFACE_HEIGHT can never fit on-surface, and casting
|
||||
// such an r to int16_t can wrap negative (r > 32767), so reject it
|
||||
// here before the cast feeds the fast path / dirty-rect extents.
|
||||
#if defined(JL_HAS_DRAW_CIRCLE)
|
||||
if (r < SURFACE_HEIGHT) {
|
||||
ir = (int16_t)r;
|
||||
int16_t ir = (int16_t)r;
|
||||
// On-surface fast path: machine asm / planar. No runtime platform check
|
||||
// -- the whole block is compiled out on targets without JL_HAS_DRAW_CIRCLE.
|
||||
if (cx - ir >= 0 && cx + ir < SURFACE_WIDTH &&
|
||||
cy - ir >= 0 && cy + ir < SURFACE_HEIGHT &&
|
||||
halFastDrawCircle(s, cx, cy, r, colorIndex)) {
|
||||
cy - ir >= 0 && cy + ir < SURFACE_HEIGHT) {
|
||||
jlpDrawCircle(s, cx, cy, r, colorIndex);
|
||||
surfaceMarkDirtyRect(s, (int16_t)(cx - ir), (int16_t)(cy - ir),
|
||||
(int16_t)(2 * ir + 1), (int16_t)(2 * ir + 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Bresenham midpoint: maintain (x, y) on the perimeter, eight-
|
||||
// octant symmetry plots all 8 reflections each iteration. Plots
|
||||
|
|
@ -521,16 +453,19 @@ void jlDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, u
|
|||
// can run without per-pixel bound checks. Hand off to the port
|
||||
// fast path; bounding-box dirty marking happens here in C either
|
||||
// way.
|
||||
#if defined(JL_HAS_DRAW_LINE)
|
||||
if (x0 >= 0 && x0 < SURFACE_WIDTH && x1 >= 0 && x1 < SURFACE_WIDTH &&
|
||||
y0 >= 0 && y0 < SURFACE_HEIGHT && y1 >= 0 && y1 < SURFACE_HEIGHT &&
|
||||
halFastDrawLine(s, x0, y0, x1, y1, colorIndex)) {
|
||||
y0 >= 0 && y0 < SURFACE_HEIGHT && y1 >= 0 && y1 < SURFACE_HEIGHT) {
|
||||
int16_t bbx = (x0 < x1) ? x0 : x1;
|
||||
int16_t bby = (y0 < y1) ? y0 : y1;
|
||||
int16_t bbw = (int16_t)(((x0 > x1) ? x0 : x1) - bbx + 1);
|
||||
int16_t bbh = (int16_t)(((y0 > y1) ? y0 : y1) - bby + 1);
|
||||
// On-surface fast path: machine asm / planar, compiled out where unsupported.
|
||||
jlpDrawLine(s, x0, y0, x1, y1, colorIndex);
|
||||
surfaceMarkDirtyRect(s, bbx, bby, bbw, bbh);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Diagonal fallback: plot through plotPixelNoMark so each pixel still
|
||||
// clips off-surface individually (matching jlDrawPixel), but skip the
|
||||
|
|
@ -584,7 +519,7 @@ void jlDrawPixel(jlSurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
|||
return;
|
||||
}
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// Hot per-pixel path: the asm plot (halFastDrawPixel -> iigsDrawPixelInner)
|
||||
// Hot per-pixel path: the asm plot (jlpDrawPixel -> iigsDrawPixelInner)
|
||||
// plus an inline dirty mark, with NO plotPixelNoMark / surfaceMarkDirtyRect
|
||||
// call layers. NB: an all-C inline RMW here (using the gRowOffsetLut table,
|
||||
// no multiply) was measured SLOWER on hardware (1875 -> 1672 ops/sec) --
|
||||
|
|
@ -594,7 +529,7 @@ void jlDrawPixel(jlSurfaceT *s, int16_t x, int16_t y, uint8_t colorIndex) {
|
|||
if ((uint16_t)x >= SURFACE_WIDTH || (uint16_t)y >= SURFACE_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
(void)halFastDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex);
|
||||
jlpDrawPixel(s, (uint16_t)x, (uint16_t)y, colorIndex);
|
||||
if (s == jlStageGet()) {
|
||||
uint8_t word = SURFACE_WORD_INDEX(x);
|
||||
|
||||
|
|
@ -692,11 +627,15 @@ void jlFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t col
|
|||
// route spans through the lighter on-surface fill instead of
|
||||
// re-clipping each one through jlFillRect.
|
||||
onSurface = true;
|
||||
if (halFastFillCircle(s, cx, cy, r, colorIndex)) {
|
||||
#if defined(JL_HAS_FILL_CIRCLE)
|
||||
// Stage/portData-gated fast path (returns bool). Compiled out where
|
||||
// unsupported; the span fill below is the universal fallback.
|
||||
if (jlpFillCircle(s, cx, cy, r, colorIndex)) {
|
||||
surfaceMarkDirtyRect(s, (int16_t)(cx - ir), (int16_t)(cy - ir),
|
||||
(int16_t)(2 * ir + 1), (int16_t)(2 * ir + 1));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -792,9 +731,9 @@ void jlFloodFill(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor) {
|
|||
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
/* halSamplePixel reads from whichever storage the port uses --
|
||||
/* jlpSamplePixel reads from whichever storage the port uses --
|
||||
* works on both chunky (s->pixels) and planar (s->portData) ports. */
|
||||
seedColor = halSamplePixel(s, x, y);
|
||||
seedColor = jlpSamplePixel(s, x, y);
|
||||
if ((seedColor & 0x0F) == (newColor & 0x0F)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -811,7 +750,7 @@ void jlFloodFillBounded(jlSurfaceT *s, int16_t x, int16_t y, uint8_t newColor, u
|
|||
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
pix = halSamplePixel(s, x, y);
|
||||
pix = jlpSamplePixel(s, x, y);
|
||||
// Starting on a boundary pixel or already-filled pixel: nothing
|
||||
// to do.
|
||||
if ((pix & 0x0F) == (boundaryColor & 0x0F)) {
|
||||
|
|
@ -831,10 +770,10 @@ uint8_t jlSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
|||
if (x < 0 || x >= SURFACE_WIDTH || y < 0 || y >= SURFACE_HEIGHT) {
|
||||
return 0;
|
||||
}
|
||||
/* halSamplePixel reads from whichever storage the port uses --
|
||||
/* jlpSamplePixel reads from whichever storage the port uses --
|
||||
* chunky ports return a nibble extracted from s->pixels; planar
|
||||
* ports read 4 plane bits and assemble the nibble. */
|
||||
return halSamplePixel(s, x, y);
|
||||
return jlpSamplePixel(s, x, y);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -849,8 +788,8 @@ void jlSurfaceClear(jlSurfaceT *s, uint8_t colorIndex) {
|
|||
}
|
||||
nibble = colorIndex & 0x0F;
|
||||
doubled = (uint8_t)((nibble << 4) | nibble);
|
||||
if (!halFastSurfaceClear(s, doubled) && s->pixels != NULL) {
|
||||
memset(s->pixels, doubled, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
// Compile-time-selected op: a machine override (asm / planar) or the
|
||||
// portable-C generic default -- no runtime dispatch.
|
||||
jlpSurfaceClear(s, doubled);
|
||||
surfaceMarkDirtyAll(s);
|
||||
}
|
||||
|
|
|
|||
572
src/core/hal.h
572
src/core/hal.h
|
|
@ -1,572 +0,0 @@
|
|||
// 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 <stdio.h>
|
||||
|
||||
#include "joey/audio.h"
|
||||
#include "joey/core.h"
|
||||
#include "joey/input.h"
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
// Per-port one-shot initialization. Called from jlInit 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 jlConfigT *config);
|
||||
|
||||
// Per-port teardown. Restores display mode, frees HW-adjacent buffers.
|
||||
void halShutdown(void);
|
||||
|
||||
// Large / persistent allocation backing for the public jlAlloc / jlFree.
|
||||
// Ports with a tiny or per-allocation-capped C heap (IIgs: bank-0 heap,
|
||||
// ~32 KB malloc cap) implement these with their native allocator (IIgs:
|
||||
// Memory Manager NewHandle / DisposeHandle). Ports with a normal heap
|
||||
// use the shared malloc / free default defined in core/init.c, so only
|
||||
// the constrained ports need a per-port override. halBigAlloc returns
|
||||
// NULL on failure; the returned memory is not zero-filled. halBigFree
|
||||
// accepts NULL.
|
||||
void *halBigAlloc(uint32_t bytes);
|
||||
void halBigFree(void *p);
|
||||
|
||||
// Allocate / release the SURFACE_PIXELS_SIZE-byte pixel buffer that
|
||||
// backs the library-owned stage surface. Ports that have a
|
||||
// hardware-friendly pin location for the back buffer (IIgs $01/2000
|
||||
// with SHR shadow inhibited) return that address here; ports with no
|
||||
// such constraint just malloc/free. Planar 68k ports may return NULL
|
||||
// if the surface is planar-only and has no chunky shadow.
|
||||
uint8_t *halStageAllocPixels(void);
|
||||
void halStageFreePixels(uint8_t *pixels);
|
||||
|
||||
// Allocate / release the per-surface portData blob (see jlSurfaceT in
|
||||
// surfaceInternal.h). Chunky ports return NULL from Init -- they keep
|
||||
// portData unused and operate on the chunky `pixels` buffer. Planar
|
||||
// 68k ports allocate a per-surface struct here describing the
|
||||
// bitplane storage (Amiga: 4 separate plane buffers + stride; ST: one
|
||||
// interleaved buffer + stride). Called by jlSurfaceCreate / stageAlloc
|
||||
// after pixels is allocated; freed by jlSurfaceDestroy / stageFree
|
||||
// before pixels is freed. `isStage` lets the port short-circuit for
|
||||
// the stage if its planes are display-owned (e.g. Amiga's BitMap
|
||||
// planes from OpenScreen) rather than allocated per surface.
|
||||
void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage);
|
||||
void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData);
|
||||
|
||||
// Phase 3 planar dual-write: called from cross-platform jlFillRect AFTER
|
||||
// the chunky shadow has been written, with the same already-clipped
|
||||
// (x, y, w, h) and the raw color index 0..15. Planar ports update
|
||||
// the bitplanes with the rect's bit pattern (per-plane bit value =
|
||||
// (color >> plane) & 1). Chunky ports (DOS, IIgs) provide a no-op
|
||||
// stub. Called unconditionally so cross-platform code doesn't have
|
||||
// to know the port is planar; the per-port stub is the cheapest
|
||||
// possible thing on chunky ports.
|
||||
void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex);
|
||||
|
||||
// Phase 3 planar dual-write for jlSurfaceCopy: called from cross-platform
|
||||
// jlSurfaceCopy AFTER the chunky pixel buffer is memcpy'd. Planar ports
|
||||
// also memcpy the bitplanes from src to dst so JOEYLIB_PLANAR_PRESENT
|
||||
// builds see correct planes. dst and src are non-NULL and distinct
|
||||
// (caller's no-op guards already passed).
|
||||
void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
|
||||
// Phase 5 planar dual-write for tile ops. Called from cross-platform
|
||||
// tile.c AFTER the chunky path completes. (bx, by) are tile-grid
|
||||
// coords (0..39 horiz, 0..24 vert; surface is 40x25 tiles).
|
||||
// transparentIndex for jlTileCopyMasked: pixel value to skip. jlTilePaste
|
||||
// reads from a packed 32-byte chunky jlTileT (4 bytes/row x 8 rows).
|
||||
// All Amiga impls operate on the off-screen shadow planes via
|
||||
// AmigaPlanarT; chunky-port stubs are no-ops. jlTileSnap is read-only
|
||||
// so has no planar dual-write hook.
|
||||
void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
||||
void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||
void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex);
|
||||
void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile);
|
||||
|
||||
// halTilePasteMonoPlanes: planar-port jlTilePasteMono. Input tile holds
|
||||
// a 1bpp monochrome shape (typically loaded from a font / charset
|
||||
// asset) in the port-specific layout produced by halTileSnapPlanes;
|
||||
// plane 0 carries the shape bytes, planes 1-3 are zero. Writes a
|
||||
// colorized copy to the dst surface at (bx, by): fgColor where each
|
||||
// shape bit is set, bgColor where clear, in the dst's native plane
|
||||
// layout. Chunky ports stub this out -- the cross-platform code in
|
||||
// jlTilePasteMono handles them via its chunky-shadow path.
|
||||
void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor);
|
||||
|
||||
// jlTileSnap: cross-platform code reads s->pixels chunky bytes into a
|
||||
// 32-byte jlTileT. On planar ports (s->pixels NULL) the chunky read
|
||||
// crashes -- this hook is the planar derivation: reads bitplane bits
|
||||
// for the tile rect and assembles 32 chunky bytes (4 bytes/row x 8
|
||||
// rows) into chunkyTileOut. Chunky ports (s->pixels valid) implement
|
||||
// this as a no-op since the cross-platform fallback already filled
|
||||
// chunkyTileOut from s->pixels.
|
||||
void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyTileOut);
|
||||
|
||||
// Phase 6 planar dual-write for jlSpriteDraw. Called from cross-platform
|
||||
// sprite.c AFTER spriteCompiledDraw or spriteDrawInterpreted has
|
||||
// updated the chunky shadow. (x, y) is the destination top-left in
|
||||
// surface pixels (may be partially off-surface; the hook does its own
|
||||
// clipping). Walks the sprite's chunky tile data and updates dst
|
||||
// surface planes for every non-transparent pixel (nibble != 0).
|
||||
// Save/restore have NO planar dual-write yet -- after jlSpriteSaveUnder
|
||||
// + jlSpriteDraw + jlSpriteRestoreUnder under JOEYLIB_PLANAR_PRESENT, the
|
||||
// planes still show the sprite (chunky restored, planes unchanged).
|
||||
// Workable approach for that needs a parallel plane backup buffer;
|
||||
// deferred until apps actually depend on PLANAR_PRESENT save/restore.
|
||||
void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y);
|
||||
|
||||
// Phase 9 sprite save/restore plane data. Chunky ports already hold
|
||||
// pixel data in backup->bytes via the cross-platform memcpy. Planar
|
||||
// ports (Amiga) DO have chunky NULL, so backup->bytes is unused by
|
||||
// the chunky path -- we repurpose it to hold per-plane bytes. Layout:
|
||||
// 4 plane stripes of (h * bytesPerPlaneRow) bytes each, where
|
||||
// bytesPerPlaneRow = w/8 (sprite x and w are guaranteed 2-pixel
|
||||
// aligned by jlSpriteSaveUnder; planar requires further 8-pixel
|
||||
// rounding -- see Amiga impl notes). Total bytes:
|
||||
// 4 * h * w/8 = h * w/2 = same as chunky. backup->sizeBytes capacity
|
||||
// works on both ports. Chunky-port impls are no-ops; Amiga writes /
|
||||
// reads plane bytes via AmigaPlanarT.
|
||||
void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes);
|
||||
void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes);
|
||||
|
||||
// Phase 9 reader hooks. Cross-platform code calls these instead of
|
||||
// reading from s->pixels directly so it works regardless of whether
|
||||
// the port stores chunky or planar as the source of truth. Chunky
|
||||
// ports (DOS, IIgs) implement these reading from s->pixels (cheap);
|
||||
// Amiga reads from the bitplanes in AmigaPlanarT. (x, y) bounds are
|
||||
// already validated by the caller.
|
||||
//
|
||||
// halSamplePixel: returns the 0..15 nibble at (x, y).
|
||||
// halSurfaceHash: returns the FNV-style hash of pixel + scb + palette
|
||||
// that jlSurfaceHash currently computes by walking s->pixels. Allows
|
||||
// ports to use their native pixel storage instead.
|
||||
// halSurfaceCopyChunky: cross-platform jlSurfaceCopy used to memcpy
|
||||
// s->pixels src->dst; on planar ports there is no chunky to copy
|
||||
// (planes already covered by halSurfaceCopyPlanes). Chunky ports
|
||||
// do the memcpy here; Amiga is a no-op.
|
||||
// halSurfaceLoadFile / halSurfaceSaveFile wrap fread / fwrite of the
|
||||
// pixel data using each port's native pixel format (chunky on
|
||||
// IIgs/DOS, interleaved planar on ST, plane-major on Amiga). Files
|
||||
// written by one port are NOT loadable by another -- conversion is
|
||||
// the asset pipeline's job.
|
||||
uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y);
|
||||
uint32_t halSurfaceHash(const jlSurfaceT *s);
|
||||
void halSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp);
|
||||
bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp);
|
||||
|
||||
// Present the dirty regions of the source surface to the display.
|
||||
// The cross-platform jlStagePresent walks the dirty arrays before
|
||||
// calling this; ports may use the dirty arrays themselves to skip
|
||||
// untouched rows.
|
||||
void halPresent(const jlSurfaceT *src);
|
||||
|
||||
// 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);
|
||||
|
||||
// Input: per-port keyboard setup, polling, and teardown.
|
||||
// halInputInit is called at the end of jlInit; halInputShutdown
|
||||
// from jlShutdown before halShutdown. halInputPoll refreshes the
|
||||
// core-owned key-state array (declared in core/inputInternal.h) --
|
||||
// the port writes true into gKeyState[key] for held keys. Keys the
|
||||
// port does not recognize simply stay zero.
|
||||
void halInputInit(void);
|
||||
void halInputShutdown(void);
|
||||
void halInputPoll(void);
|
||||
|
||||
// Block until the next display vertical blank. Each port implements
|
||||
// this with whatever native wait the hardware provides (VGA $3DA,
|
||||
// graphics.library WaitTOF, XBIOS Vsync, $C019 polling).
|
||||
void halWaitVBL(void);
|
||||
|
||||
// Monotonic 16-bit frame counter. Caller polls; ports either detect
|
||||
// the rising edge inside this call (IIgs $C019 / DOS $3DA / Amiga
|
||||
// VPOSR) or return a counter maintained by a VBL ISR (ST). Required
|
||||
// caller invariant: poll faster than 2 * halFrameHz() so no edge is
|
||||
// missed. Used by benchmarks; cheap enough for animation cadence too.
|
||||
uint16_t halFrameCount(void);
|
||||
|
||||
// Nominal display frame rate in Hz (50 PAL Amiga, 60 NTSC IIgs / ST,
|
||||
// ~70 VGA mode 13h). Reported only -- no API contract that VBLs
|
||||
// arrive at exactly this rate. Benchmarks divide by it to convert
|
||||
// iters-per-N-frames to ops/sec.
|
||||
uint16_t halFrameHz(void);
|
||||
|
||||
// Wall-clock millisecond counter since halInit. Source of truth
|
||||
// for time-based scheduling (sound event timing, animation cadence
|
||||
// on platforms where rendering can outpace VBL). Per-port impl uses
|
||||
// the cheapest sub-frame timer available on that hardware: DOS uses
|
||||
// uclock() (PIT counter 0, ~1 us resolution); ST reads the 200 Hz
|
||||
// Timer C counter at _hz_200; Amiga uses ReadEClock (~1.4 us PAL);
|
||||
// IIgs falls back to frame_count * 1000 / 60 since pulling in time()
|
||||
// bursts the stdio cluster bank.
|
||||
uint32_t halMillisElapsed(void);
|
||||
|
||||
// Audio: per-port engine setup, module + SFX playback, teardown.
|
||||
// halAudioInit returns true if the platform has a working engine.
|
||||
// Per-surface chunky pixel allocation. Chunky ports (DOS, IIgs, ST
|
||||
// while still chunky) allocate SURFACE_PIXELS_SIZE bytes (calloc-
|
||||
// style, zero-filled). Pure-planar Amiga returns NULL -- there's no
|
||||
// chunky shadow; cross-platform code that previously read s->pixels
|
||||
// goes through halSamplePixel / halSurfaceCopyChunky / etc. instead.
|
||||
// halSurfaceFreePixels mirrors free(); NULL is a valid input on
|
||||
// planar ports.
|
||||
uint8_t *halSurfaceAllocPixels(void);
|
||||
void halSurfaceFreePixels(uint8_t *pixels);
|
||||
|
||||
// Get a pointer to the start of bitplane `planeIdx` (0..3) for surface
|
||||
// `s`. Returns NULL on chunky ports (no planes). On Amiga returns
|
||||
// pd->planes[planeIdx] from the AmigaPlanarT struct in portData.
|
||||
// Used by the planar sprite codegen dispatcher to compute the 4
|
||||
// plane addresses to hand the emitted asm.
|
||||
uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx);
|
||||
|
||||
bool halAudioInit(void);
|
||||
void halAudioShutdown(void);
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
||||
void halAudioStopMod(void);
|
||||
bool halAudioIsPlayingMod(void);
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz);
|
||||
void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
|
||||
void halAudioStopSfx(uint8_t slot);
|
||||
void halAudioTone(uint16_t freqHz);
|
||||
void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||
bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
||||
void halAudioCriticalEnter(void);
|
||||
void halAudioCriticalExit(void);
|
||||
void halAudioFrameTick(void);
|
||||
|
||||
// Optional fast-path hooks. Each returns true if the port handled the
|
||||
// operation in a port-specific accelerated path; false means the
|
||||
// caller should fall back to the platform-agnostic C implementation.
|
||||
//
|
||||
// Funneling all asm dispatches through hal.c (one TU per port) avoids
|
||||
// the cumulative linker "Expression too complex" failure that
|
||||
// hits when multiple cross-platform TUs each call into a named load
|
||||
// segment full of asm primitives. Cross-platform code in src/core/
|
||||
// only ever calls into HAL, so the link-time expression cost is paid
|
||||
// once per binary -- not once per TU that wants speed.
|
||||
//
|
||||
// Each port must provide all of these; ports without an accelerated
|
||||
// path simply return false from every hook.
|
||||
bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled);
|
||||
bool halFastFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex);
|
||||
bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord);
|
||||
// Tile primitives operate on already-computed row-0 pointers from
|
||||
// the C wrapper. dstRow0 / srcRow0 point at the first byte of the
|
||||
// 8x8 region within their respective surfaces (stride 160). For
|
||||
// jlTilePaste / jlTileSnap the jlTileT side is a packed 32-byte buffer
|
||||
// (stride 4); the corresponding pointer points at byte 0 of that
|
||||
// buffer.
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0);
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent);
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
|
||||
// jlDrawPixel inner: caller has already done NULL + bounds checks.
|
||||
// (x, y) are guaranteed in [0..SURFACE_WIDTH-1] x [0..SURFACE_HEIGHT-1].
|
||||
// colorIndex is the 0..15 nibble. Surface dirty marking happens in
|
||||
// the C wrapper after this returns.
|
||||
bool halFastDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex);
|
||||
|
||||
// jlDrawLine inner for the diagonal case. Caller ensures both endpoints
|
||||
// are inside the surface bounds, so the inner loop runs without
|
||||
// per-pixel clip checks. The C wrapper still routes pure horizontal
|
||||
// and vertical lines through jlFillRect (which has its own fast path).
|
||||
bool halFastDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex);
|
||||
|
||||
// jlDrawCircle / jlFillCircle inner. Caller has already validated that
|
||||
// the entire bounding circle (cx-r .. cx+r, cy-r .. cy+r) fits inside
|
||||
// the surface bounds, so the inner loop plots every octant pixel
|
||||
// unconditionally. r is guaranteed > 0; the cx == 0 / r == 0 cases
|
||||
// stay in the C wrapper.
|
||||
bool halFastDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
|
||||
// jlFloodFill helper: combined seed test + walk-left + walk-right for
|
||||
// one row. Returns true if the port handled it (asm path taken). The
|
||||
// out-param seedMatched tells the caller whether the seed pixel
|
||||
// satisfied the match criterion -- if false, caller skips this pop;
|
||||
// if true, leftXOut/rightXOut hold the run boundaries.
|
||||
// Returns false if no asm path; caller falls back to C walks.
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
bool *seedMatched,
|
||||
int16_t *leftXOut, int16_t *rightXOut);
|
||||
|
||||
// jlFloodFill helper for the row-above / row-below run-detection scans.
|
||||
// Walks pixels [leftX..rightX] inclusive of `row`, writing 1 byte per
|
||||
// pixel into markBuf (1 = qualifies for flood, 0 = does not). The C
|
||||
// side then walks markBuf for run-edge transitions, replacing the
|
||||
// per-pixel srcPixel + match check inside the inner loop.
|
||||
// Returns true if the port handled it; false to fall back to C.
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
uint8_t *markBuf);
|
||||
|
||||
// Combined per-pixel scan + run-edge walk + seed push. Higher-level
|
||||
// than halFastFloodScanRow: replaces both the markBuf fill AND the C
|
||||
// loop that walks markBuf for falling edges. *spInOut is read on entry
|
||||
// and updated with the new top-of-stack on return. Returns true if
|
||||
// the port handled it (caller skips the C run-edge walk entirely);
|
||||
// false to fall back to halFastFloodScanRow + C walk.
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
int16_t scanY,
|
||||
int16_t *stackX, int16_t *stackY,
|
||||
int16_t *spInOut, int16_t maxSp);
|
||||
|
||||
// Highest-level flood helper: combined seed-test + walk-left + walk-right
|
||||
// + scan-above + scan-below + push for ONE popped seed. Replaces three
|
||||
// cross-segment HAL calls (halFastFloodWalk + 2x halFastFloodScanAndPush)
|
||||
// per dispatch loop iteration with one. The asm internally caches row
|
||||
// addr / matchByte / nibble decoder across all three sub-operations.
|
||||
//
|
||||
// pixels is the surface base (s->pixels). On return, leftXOut / rightXOut
|
||||
// hold the matching-run boundaries (only valid if seedMatched != 0); the
|
||||
// caller does the 1-row halFastFillRect using those bounds. *spInOut is
|
||||
// updated with any new seeds the asm pushed for the row above/below.
|
||||
//
|
||||
// Returns true if the port handled it; false to fall back to
|
||||
// halFastFloodWalk + the per-side halFastFloodScanAndPush calls.
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
int16_t *stackX, int16_t *stackY,
|
||||
int16_t *spInOut, int16_t maxSp,
|
||||
bool *seedMatched,
|
||||
int16_t *leftXOut, int16_t *rightXOut);
|
||||
|
||||
// Planar variants of halFastFloodWalk / halFastFloodScanRow. Take a
|
||||
// jlSurfaceT* instead of a chunky-row pointer so they work on planar
|
||||
// ports (Amiga post-Phase 9) where s->pixels is NULL. Same semantics;
|
||||
// chunky ports return false (the chunky variants above are faster
|
||||
// when a chunky row is available). Replace the per-pixel
|
||||
// halSamplePixel walk on planar ports.
|
||||
bool halFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
bool *seedMatched,
|
||||
int16_t *leftXOut, int16_t *rightXOut);
|
||||
|
||||
bool halFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY,
|
||||
uint8_t matchColor, uint8_t newColor, bool matchEqual,
|
||||
uint8_t *markBuf);
|
||||
|
||||
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// =====================================================================
|
||||
// IIgs direct-dispatch macros.
|
||||
//
|
||||
// The halFast* function declarations above are the cross-platform API.
|
||||
// On IIgs, those wrappers were ~60-80 cyc/call of pure plumbing on top
|
||||
// of the asm itself: wrapper prologue (PHB/PHD/TCD), redundant arg
|
||||
// re-push for the inner JSL, then wrapper epilogue. The macros below
|
||||
// take effect at preprocess time and inline the asm call at the call
|
||||
// site, eliminating the wrapper layer entirely.
|
||||
//
|
||||
// Cross-platform code in src/core/*.c is unchanged -- it still calls
|
||||
// halFastDrawPixel(...) etc. On IIgs the preprocessor swaps that for
|
||||
// the macro expansion before the file is compiled. The matching
|
||||
// halFast* C definitions in src/port/iigs/hal.c are deleted, since
|
||||
// nothing references them once the macros take effect.
|
||||
//
|
||||
// Macros use comma-expression form so they evaluate to a `bool` value
|
||||
// (most halFast* return true on IIgs since the asm always succeeds).
|
||||
// =====================================================================
|
||||
|
||||
extern void iigsDrawPixelInner (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t nibble);
|
||||
extern void iigsDrawLineInner (uint8_t *pixels, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t nibble);
|
||||
extern void iigsDrawCircleInner (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
|
||||
extern void iigsFillCircleInner (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t fillWord);
|
||||
extern void iigsSurfaceClearInner(uint8_t *pixels, uint16_t fillWord);
|
||||
extern void iigsSurfaceClearFastInner(uint8_t *pixels, uint16_t fillWord);
|
||||
extern void iigsTileFillInner (uint8_t *dstRow0, uint16_t fillWord);
|
||||
extern void iigsTileCopyInner (uint8_t *dstRow0, const uint8_t *srcRow0);
|
||||
extern void iigsTileCopyMaskedInner(uint8_t *dstRow0, const uint8_t *srcRow0, uint16_t transparent);
|
||||
extern void iigsTilePasteInner (uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
extern void iigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
extern void iigsFillRectInner (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||
extern uint16_t gFloodSeedMatch;
|
||||
extern uint16_t gFloodLeftX;
|
||||
extern uint16_t gFloodRightX;
|
||||
|
||||
#undef halFastDrawPixel
|
||||
#define halFastDrawPixel(_s, _x, _y, _c) \
|
||||
(iigsDrawPixelInner((_s)->pixels, (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)((_c) & 0x0F)), \
|
||||
true)
|
||||
|
||||
#undef halFastDrawLine
|
||||
#define halFastDrawLine(_s, _x0, _y0, _x1, _y1, _c) \
|
||||
(iigsDrawLineInner((_s)->pixels, (uint16_t)(_x0), (uint16_t)(_y0), \
|
||||
(uint16_t)(_x1), (uint16_t)(_y1), \
|
||||
(uint16_t)((_c) & 0x0F)), \
|
||||
true)
|
||||
|
||||
#undef halFastDrawCircle
|
||||
#define halFastDrawCircle(_s, _cx, _cy, _r, _c) \
|
||||
(iigsDrawCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
|
||||
(_r), (uint16_t)((_c) & 0x0F)), \
|
||||
true)
|
||||
|
||||
// fillWord = doubled byte * $0101 = (nib*$11) * $101 = nib * $1111.
|
||||
// Compile-time arithmetic when caller passes a constant; at most a
|
||||
// single multiply when the nibble is variable (still cheaper than
|
||||
// the wrapper's three sequential ORs / shifts).
|
||||
#undef halFastFillCircle
|
||||
#define halFastFillCircle(_s, _cx, _cy, _r, _c) \
|
||||
((_s) == jlStageGet() \
|
||||
? (iigsFillCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
|
||||
(_r), (uint16_t)(((_c) & 0x0F) * 0x1111)), \
|
||||
true) \
|
||||
: false)
|
||||
|
||||
#undef halFastSurfaceClear
|
||||
#define halFastSurfaceClear(_s, _d) \
|
||||
((_s) == jlStageGet() \
|
||||
? (iigsSurfaceClearFastInner((_s)->pixels, \
|
||||
(uint16_t)((uint16_t)(_d) | ((uint16_t)(_d) << 8))), \
|
||||
true) \
|
||||
: false)
|
||||
|
||||
// halFastFillRect: macro form, same shape as the others. Builds
|
||||
// clean now that _ROOT has been thinned out via the CORESYS load
|
||||
// segment migration -- previous attempts shrank _ROOT enough to
|
||||
// retrip the bank-packing fragility, but with most core .c files
|
||||
// out of _ROOT that's no longer reactive. Saves ~80 cyc/call.
|
||||
#undef halFastFillRect
|
||||
#define halFastFillRect(_s, _x, _y, _w, _h, _c) \
|
||||
((_s) == jlStageGet() \
|
||||
? (iigsFillRectInner((_s)->pixels, (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)(_w), (uint16_t)(_h), \
|
||||
(uint16_t)((_c) & 0x0F)), \
|
||||
true) \
|
||||
: false)
|
||||
|
||||
// Tile primitives operate on caller-computed row pointers; just
|
||||
// forward the args. by/bx are tile coords -> bx*4 + by*8*160 byte
|
||||
// offset within the surface. Use SURFACE_ROW_OFFSET (LUT lookup) to
|
||||
// dodge a software multiply helper for the *160 multiply.
|
||||
#undef halFastTileFill
|
||||
#define halFastTileFill(_s, _bx, _by, _fw) \
|
||||
(iigsTileFillInner(&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) \
|
||||
+ ((uint16_t)(_bx) << 2)], \
|
||||
(_fw)), \
|
||||
true)
|
||||
|
||||
#undef halFastTileCopy
|
||||
#define halFastTileCopy(_d, _s) (iigsTileCopyInner((_d), (_s)), true)
|
||||
|
||||
#undef halFastTileCopyMasked
|
||||
#define halFastTileCopyMasked(_d, _s, _t) \
|
||||
(iigsTileCopyMaskedInner((_d), (_s), (uint16_t)(_t)), true)
|
||||
|
||||
#undef halFastTilePaste
|
||||
#define halFastTilePaste(_d, _s) (iigsTilePasteInner((_d), (_s)), true)
|
||||
|
||||
#undef halFastTileSnap
|
||||
#define halFastTileSnap(_d, _s) (iigsTileSnapInner((_d), (_s)), true)
|
||||
|
||||
// Tier 2/3 flood fallbacks always returned false on IIgs (the asm
|
||||
// impls were deleted as unreachable). Macros to constant false so
|
||||
// the compiler dead-code-eliminates the never-taken fallback branches
|
||||
// in floodFillInternal.
|
||||
#undef halFastFloodWalk
|
||||
#define halFastFloodWalk(_row, _sx, _mc, _nc, _me, _sm, _lx, _rx) (false)
|
||||
|
||||
#undef halFastFloodScanRow
|
||||
#define halFastFloodScanRow(_row, _lx, _rx, _mc, _nc, _me, _mb) (false)
|
||||
|
||||
#undef halFastFloodScanAndPush
|
||||
#define halFastFloodScanAndPush(_row, _lx, _rx, _mc, _nc, _me, _sy, _sx, _syA, _sp, _ms) (false)
|
||||
|
||||
// IIgs is chunky; the planar flood hooks are never reachable.
|
||||
#undef halFloodWalkPlanes
|
||||
#define halFloodWalkPlanes(_s, _sx, _y, _mc, _nc, _me, _sm, _lx, _rx) (false)
|
||||
#undef halFloodScanRowPlanes
|
||||
#define halFloodScanRowPlanes(_s, _lx, _rx, _sy, _mc, _nc, _me, _mb) (false)
|
||||
|
||||
// Tier-1 flood: multi-output. Asm sets gFloodSeedMatch / gFloodLeftX /
|
||||
// gFloodRightX; macro reads those into the caller's out-ptrs.
|
||||
#undef halFastFloodWalkAndScans
|
||||
#define halFastFloodWalkAndScans(_pix, _x, _y, _mc, _nc, _me, _sx, _sy, _sp, _ms, _smOut, _lxOut, _rxOut) \
|
||||
(iigsFloodWalkAndScansInner((_pix), (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)((_mc) & 0x0F), \
|
||||
(uint16_t)((_nc) & 0x0F), \
|
||||
(uint16_t)((_me) ? 1 : 0), \
|
||||
(_sx), (_sy), \
|
||||
(uint16_t *)(_sp), \
|
||||
(uint16_t)(_ms)), \
|
||||
*(_smOut) = (gFloodSeedMatch != 0), \
|
||||
*(_lxOut) = (int16_t)gFloodLeftX, \
|
||||
*(_rxOut) = (int16_t)gFloodRightX, \
|
||||
true)
|
||||
|
||||
// =====================================================================
|
||||
// IIgs planar dual-write elision.
|
||||
//
|
||||
// Every cross-platform primitive calls a halXxxPlanes() hook
|
||||
// unconditionally so planar ports (Amiga, ST) can mirror each draw
|
||||
// into their bitplanes. IIgs is chunky-native, so those hooks are
|
||||
// pure no-op stubs -- but the call site still costs a cross-segment
|
||||
// JSL per primitive (PHB/PHD setup + RTL) for zero work. These macros
|
||||
// expand each call to ((void)0) at preprocess time so the call site is
|
||||
// deleted entirely, exactly as the halFast* macros above eliminate the
|
||||
// wrapper layer. The matching no-op stub definitions in
|
||||
// src/port/iigs/hal.c are deleted, since nothing references them once
|
||||
// the macros take effect.
|
||||
//
|
||||
// Gated on JOEYLIB_PLATFORM_IIGS specifically (NOT JOEYLIB_NATIVE_CHUNKY):
|
||||
// DOS is also chunky-native and keeps its real stub functions, because
|
||||
// its stubs are cheap near-calls and DOS is not the perf reference.
|
||||
//
|
||||
// Arg lists match each hook's arity so the preprocessor parses and
|
||||
// discards the exact call expression; arguments with side effects are
|
||||
// not present at any halXxxPlanes call site (all args are plain lvalues
|
||||
// / field reads), so dropping them changes no behavior.
|
||||
// =====================================================================
|
||||
|
||||
#undef halFillRectPlanes
|
||||
#define halFillRectPlanes(_s, _x, _y, _w, _h, _c) ((void)0)
|
||||
|
||||
#undef halSurfaceCopyPlanes
|
||||
#define halSurfaceCopyPlanes(_dst, _src) ((void)0)
|
||||
|
||||
#undef halTileFillPlanes
|
||||
#define halTileFillPlanes(_s, _bx, _by, _c) ((void)0)
|
||||
|
||||
#undef halTileCopyPlanes
|
||||
#define halTileCopyPlanes(_dst, _dbx, _dby, _src, _sbx, _sby) ((void)0)
|
||||
|
||||
#undef halTileCopyMaskedPlanes
|
||||
#define halTileCopyMaskedPlanes(_dst, _dbx, _dby, _src, _sbx, _sby, _t) ((void)0)
|
||||
|
||||
#undef halTilePastePlanes
|
||||
#define halTilePastePlanes(_dst, _bx, _by, _ct) ((void)0)
|
||||
|
||||
#undef halTilePasteMonoPlanes
|
||||
#define halTilePasteMonoPlanes(_dst, _bx, _by, _mt, _fg, _bg) ((void)0)
|
||||
|
||||
#undef halTileSnapPlanes
|
||||
#define halTileSnapPlanes(_src, _bx, _by, _out) ((void)0)
|
||||
|
||||
#undef halSpriteDrawPlanes
|
||||
#define halSpriteDrawPlanes(_s, _sp, _x, _y) ((void)0)
|
||||
|
||||
#undef halSpriteSavePlanes
|
||||
#define halSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) ((void)0)
|
||||
|
||||
#undef halSpriteRestorePlanes
|
||||
#define halSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) ((void)0)
|
||||
|
||||
#endif /* JOEYLIB_PLATFORM_IIGS */
|
||||
|
||||
#endif
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include "joey/core.h"
|
||||
#include "codegenArenaInternal.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
|
||||
|
|
@ -47,35 +47,22 @@ void coreSetError(const char *message) {
|
|||
|
||||
// ----- Default large-allocation backing -----
|
||||
//
|
||||
// jlAlloc / jlFree route through halBigAlloc / halBigFree. Most ports
|
||||
// have a normal heap, so the default below (plain malloc / free) serves
|
||||
// them and they do NOT need to implement the hook themselves. Only ports
|
||||
// whose C heap is too small or per-allocation-capped to satisfy a large
|
||||
// request -- currently just the IIgs -- provide their own halBigAlloc /
|
||||
// halBigFree (src/port/iigs/hal.c) and compile out this default.
|
||||
#ifndef JOEYLIB_PLATFORM_IIGS
|
||||
|
||||
void *halBigAlloc(uint32_t bytes) {
|
||||
return malloc((size_t)bytes);
|
||||
}
|
||||
|
||||
|
||||
void halBigFree(void *p) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
#endif
|
||||
// jlAlloc / jlFree route through jlpBigAlloc / jlpBigFree. Most ports have a
|
||||
// normal heap, so the malloc/free generic default (src/generic/genericPort.c)
|
||||
// serves them; only ports whose C heap is too small or per-allocation-capped
|
||||
// (IIgs: bank-0 heap) define JL_HAS_BIG_ALLOC and override with their native
|
||||
// allocator (IIgs Memory Manager NewHandle / DisposeHandle).
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
||||
void *jlAlloc(uint32_t bytes) {
|
||||
return halBigAlloc(bytes);
|
||||
return jlpBigAlloc(bytes);
|
||||
}
|
||||
|
||||
|
||||
void jlFree(void *p) {
|
||||
halBigFree(p);
|
||||
jlpBigFree(p);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,18 +81,18 @@ bool jlInit(const jlConfigT *config) {
|
|||
|
||||
memcpy(&gConfig, config, sizeof(gConfig));
|
||||
|
||||
// halInit must run before stageAlloc: on IIgs the stage's pixel
|
||||
// buffer comes from halStageAllocPixels, which depends on shadow /
|
||||
// SHR setup that halInit performs.
|
||||
if (!halInit(&gConfig)) {
|
||||
const char *halMsg = halLastError();
|
||||
coreSetError(halMsg != NULL ? halMsg : "halInit failed");
|
||||
// jlpInit must run before stageAlloc: on IIgs the stage's pixel
|
||||
// buffer comes from jlpStageAllocPixels, which depends on shadow /
|
||||
// SHR setup that jlpInit performs.
|
||||
if (!jlpInit(&gConfig)) {
|
||||
const char *halMsg = jlpLastError();
|
||||
coreSetError(halMsg != NULL ? halMsg : "jlpInit failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stageAlloc()) {
|
||||
coreSetError("failed to allocate stage surface");
|
||||
halShutdown();
|
||||
jlpShutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -113,11 +100,11 @@ bool jlInit(const jlConfigT *config) {
|
|||
: DEFAULT_CODEGEN_BYTES)) {
|
||||
coreSetError("failed to allocate codegen arena");
|
||||
stageFree();
|
||||
halShutdown();
|
||||
jlpShutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
halInputInit();
|
||||
jlpInputInit();
|
||||
|
||||
gInitialized = true;
|
||||
return true;
|
||||
|
|
@ -138,10 +125,10 @@ void jlShutdown(void) {
|
|||
if (!gInitialized) {
|
||||
return;
|
||||
}
|
||||
halInputShutdown();
|
||||
jlpInputShutdown();
|
||||
codegenArenaShutdown();
|
||||
stageFree();
|
||||
halShutdown();
|
||||
jlpShutdown();
|
||||
gInitialized = false;
|
||||
clearError();
|
||||
}
|
||||
|
|
@ -153,20 +140,20 @@ const char *jlVersionString(void) {
|
|||
|
||||
|
||||
void jlWaitVBL(void) {
|
||||
halWaitVBL();
|
||||
jlpWaitVBL();
|
||||
}
|
||||
|
||||
|
||||
uint16_t jlFrameCount(void) {
|
||||
return halFrameCount();
|
||||
return jlpFrameCount();
|
||||
}
|
||||
|
||||
|
||||
uint16_t jlFrameHz(void) {
|
||||
return halFrameHz();
|
||||
return jlpFrameHz();
|
||||
}
|
||||
|
||||
|
||||
uint32_t jlMillisElapsed(void) {
|
||||
return halMillisElapsed();
|
||||
return jlpMillisElapsed();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "joey/input.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
|
||||
|
|
@ -54,14 +54,14 @@ void jlInputPoll(void) {
|
|||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// One asm pass for: TTL decrement + key snapshot + mouse/joy
|
||||
// button snapshots. Replaces 3 memcpys + the C TTL loop
|
||||
// that used to live in halInputPoll. ~0.6 ms saved per frame.
|
||||
// that used to live in jlpInputPoll. ~0.6 ms saved per frame.
|
||||
iigsInputSnapshot();
|
||||
#else
|
||||
memcpy(gKeyPrev, gKeyState, sizeof(gKeyState));
|
||||
memcpy(gMouseButtonPrev, gMouseButtonState, sizeof(gMouseButtonState));
|
||||
memcpy(gJoyButtonPrev, gJoyButtonState, sizeof(gJoyButtonState));
|
||||
#endif
|
||||
halInputPoll();
|
||||
jlpInputPoll();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -220,5 +220,5 @@ void jlJoystickReset(jlJoystickE js, uint8_t deadZone) {
|
|||
return;
|
||||
}
|
||||
gJoyDeadZone[js] = deadZone;
|
||||
halJoystickReset(js);
|
||||
jlpJoystickReset(js);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Internal input state shared between core and per-port HAL.
|
||||
//
|
||||
// Per-port halInputPoll() writes directly into the public state
|
||||
// Per-port jlpInputPoll() writes directly into the public state
|
||||
// globals: gKeyState[] for keyboard, gMouse* for mouse, gJoy* for
|
||||
// joysticks. The core compares against the matching *Prev shadow
|
||||
// each jlInputPoll to derive edge events for keys/buttons. Mouse
|
||||
|
|
@ -37,8 +37,8 @@ extern uint8_t gJoyButtonPrev [JOYSTICK_COUNT][JOY_BUTTON_COUNT];
|
|||
// with analog paddles (IIgs); ignored on digital-stick platforms.
|
||||
extern uint8_t gJoyDeadZone [JOYSTICK_COUNT];
|
||||
|
||||
// Per-port hook: called from jlJoystickReset to clear any auto-
|
||||
// disconnect state and arm a fresh center capture on the next poll.
|
||||
void halJoystickReset(jlJoystickE js);
|
||||
// (jlpJoystickReset hook -- called from jlJoystickReset to clear any auto-
|
||||
// disconnect state and arm a fresh center capture -- is declared with the
|
||||
// other platform-only services in src/core/port.h.)
|
||||
|
||||
#endif
|
||||
|
|
|
|||
708
src/core/port.h
Normal file
708
src/core/port.h
Normal file
|
|
@ -0,0 +1,708 @@
|
|||
// Internal platform-override registry (the new joeylib's joey.h equivalent).
|
||||
//
|
||||
// Every overridable operation is a jlp<Op>() resolved at COMPILE TIME: a
|
||||
// portable-C default lives in src/generic/ and a machine supplies its own
|
||||
// version when it defines JL_HAS_<OP> in include/joey/platform.h. There is no
|
||||
// runtime dispatch. This header wires the two together (the dispatch tail at
|
||||
// the bottom) and holds the IIgs asm-macro overrides.
|
||||
//
|
||||
// Two flavors of op (both use the same jlp + JL_HAS mechanism):
|
||||
// (A) ops with a genuine portable generic -- draw/tile/sprite primitives,
|
||||
// surface readers, allocation, millisElapsed. A machine overrides only
|
||||
// where it needs native speed/behavior; otherwise it links the generic.
|
||||
// (B) platform SERVICES with no portable behavior -- init/shutdown/present,
|
||||
// input, vbl/frame timing, the audio engine. The "generic" is a no-op/
|
||||
// zero stub so a blank/template port still links; every real port
|
||||
// overrides via JL_HAS_<OP>.
|
||||
//
|
||||
// Included by src/core/*.c, src/<machine>/*.c, and src/generic/*.c. NOT public.
|
||||
|
||||
#ifndef JOEYLIB_PORT_H
|
||||
#define JOEYLIB_PORT_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "joey/audio.h"
|
||||
#include "joey/core.h"
|
||||
#include "joey/input.h"
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
// Every op's declaration + its generic/override dispatch lives in the "Op
|
||||
// dispatch" section at the bottom of this header. The block immediately below
|
||||
// holds the IIgs's asm-macro overrides: it #define-s jlp<Op> directly to the
|
||||
// iigs*Inner asm entry points, so IIgs primitives never pay a function call.
|
||||
|
||||
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// =====================================================================
|
||||
// IIgs direct-dispatch macros.
|
||||
//
|
||||
// IIgs overrides the draw/tile/flood primitives by #define-ing jlp<Op>
|
||||
// directly to its asm entry points (iigs*Inner), so each primitive inlines
|
||||
// the asm call at the call site with no function-call plumbing. A C wrapper
|
||||
// would cost ~60-80 cyc/call of pure overhead here (prologue PHB/PHD/TCD,
|
||||
// redundant arg re-push for the inner JSL, then epilogue); the macro form
|
||||
// eliminates it. Cross-platform code in src/core/*.c calls jlp<Op>(...)
|
||||
// unchanged and the preprocessor swaps in the asm before compile.
|
||||
//
|
||||
// Ops the IIgs does not accelerate (or only accelerates for the stage)
|
||||
// fall through to the portable-C generic jlpGeneric<Op>, like every other
|
||||
// machine. Some macros use comma-expression form to evaluate to a `bool`
|
||||
// (e.g. fillCircle: stage-only, returns false off-stage so the core falls
|
||||
// back to its C span fill).
|
||||
// =====================================================================
|
||||
|
||||
extern void iigsDrawPixelInner (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t nibble);
|
||||
extern void iigsDrawLineInner (uint8_t *pixels, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t nibble);
|
||||
extern void iigsDrawCircleInner (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t nibble);
|
||||
extern void iigsFillCircleInner (uint8_t *pixels, uint16_t cx, uint16_t cy, uint16_t r, uint16_t fillWord);
|
||||
extern void iigsSurfaceClearInner(uint8_t *pixels, uint16_t fillWord);
|
||||
extern void iigsSurfaceClearFastInner(uint8_t *pixels, uint16_t fillWord);
|
||||
extern void iigsTileFillInner (uint8_t *dstRow0, uint16_t fillWord);
|
||||
extern void iigsTileCopyInner (uint8_t *dstRow0, const uint8_t *srcRow0);
|
||||
extern void iigsTileCopyMaskedInner(uint8_t *dstRow0, const uint8_t *srcRow0, uint16_t transparent);
|
||||
extern void iigsTilePasteInner (uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
extern void iigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
extern void iigsFillRectInner (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||
extern uint16_t gFloodSeedMatch;
|
||||
extern uint16_t gFloodLeftX;
|
||||
extern uint16_t gFloodRightX;
|
||||
|
||||
// drawPixel: migrated to jlpDrawPixel. The asm plots into s->pixels for any
|
||||
// IIgs (chunky) surface; no generic defer needed.
|
||||
#define jlpDrawPixel(_s, _x, _y, _c) \
|
||||
iigsDrawPixelInner((_s)->pixels, (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)((_c) & 0x0F))
|
||||
|
||||
// drawLine / drawCircle migrated to jlp<Op>: asm on any IIgs (chunky) surface,
|
||||
// callers gate the on-surface fast path on JL_HAS_DRAW_LINE / _CIRCLE.
|
||||
#define jlpDrawLine(_s, _x0, _y0, _x1, _y1, _c) \
|
||||
iigsDrawLineInner((_s)->pixels, (uint16_t)(_x0), (uint16_t)(_y0), \
|
||||
(uint16_t)(_x1), (uint16_t)(_y1), \
|
||||
(uint16_t)((_c) & 0x0F))
|
||||
|
||||
#define jlpDrawCircle(_s, _cx, _cy, _r, _c) \
|
||||
iigsDrawCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
|
||||
(_r), (uint16_t)((_c) & 0x0F))
|
||||
|
||||
// fillWord = doubled byte * $0101 = (nib*$11) * $101 = nib * $1111.
|
||||
// Compile-time arithmetic when caller passes a constant; at most a
|
||||
// single multiply when the nibble is variable (still cheaper than
|
||||
// the wrapper's three sequential ORs / shifts).
|
||||
// fillCircle migrated to jlpFillCircle: stage-only asm, returns bool so the core
|
||||
// falls to the C span fill for non-stage / off-surface. JL_HAS_FILL_CIRCLE-gated.
|
||||
#define jlpFillCircle(_s, _cx, _cy, _r, _c) \
|
||||
((_s) == jlStageGet() \
|
||||
? (iigsFillCircleInner((_s)->pixels, (uint16_t)(_cx), (uint16_t)(_cy), \
|
||||
(_r), (uint16_t)(((_c) & 0x0F) * 0x1111)), \
|
||||
true) \
|
||||
: false)
|
||||
|
||||
// surfaceClear: migrated to the jlp<Op> override model. The asm clears the
|
||||
// stage surface; non-stage surfaces defer to the portable-C generic default.
|
||||
#define jlpSurfaceClear(_s, _d) \
|
||||
((_s) == jlStageGet() \
|
||||
? iigsSurfaceClearFastInner((_s)->pixels, \
|
||||
(uint16_t)((uint16_t)(_d) | ((uint16_t)(_d) << 8))) \
|
||||
: jlpGenericSurfaceClear((_s), (_d)))
|
||||
|
||||
// fillRect: macro form, same shape as the others. Stage surfaces use the asm;
|
||||
// non-stage surfaces defer to the chunky generic. (Builds clean now that the
|
||||
// CORESYS load-segment migration drained _ROOT; earlier the macro form retripped
|
||||
// the bank-packing fragility.) Saves ~80 cyc/call.
|
||||
#define jlpFillRect(_s, _x, _y, _w, _h, _c) \
|
||||
((_s) == jlStageGet() \
|
||||
? iigsFillRectInner((_s)->pixels, (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)(_w), (uint16_t)(_h), \
|
||||
(uint16_t)((_c) & 0x0F)) \
|
||||
: jlpGenericFillRect((_s), (_x), (_y), (_w), (_h), (_c)))
|
||||
|
||||
// Tile primitives operate on caller-computed row pointers; just
|
||||
// forward the args. by/bx are tile coords -> bx*4 + by*8*160 byte
|
||||
// offset within the surface. Use SURFACE_ROW_OFFSET (LUT lookup) to
|
||||
// dodge a software multiply helper for the *160 multiply.
|
||||
// tileFill: takes colorIndex; the asm derives the fill word ((c&0x0F) * 0x1111).
|
||||
#define jlpTileFill(_s, _bx, _by, _c) \
|
||||
iigsTileFillInner(&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) \
|
||||
+ ((uint16_t)(_bx) << 2)], \
|
||||
(uint16_t)(((_c) & 0x0F) * 0x1111))
|
||||
|
||||
// tileCopy / tileCopyMasked: take surface+tile-coords; the asm macro derives
|
||||
// row-0 pointers (row = by*8 -> SURFACE_ROW_OFFSET(by<<3); col byte = bx*4 -> bx<<2).
|
||||
#define jlpTileCopy(_d, _dbx, _dby, _s, _sbx, _sby) \
|
||||
iigsTileCopyInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_dby) << 3) + ((uint16_t)(_dbx) << 2)], \
|
||||
&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_sby) << 3) + ((uint16_t)(_sbx) << 2)])
|
||||
|
||||
#define jlpTileCopyMasked(_d, _dbx, _dby, _s, _sbx, _sby, _t) \
|
||||
iigsTileCopyMaskedInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_dby) << 3) + ((uint16_t)(_dbx) << 2)], \
|
||||
&(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_sby) << 3) + ((uint16_t)(_sbx) << 2)], \
|
||||
(uint16_t)(_t))
|
||||
|
||||
// tilePaste / tileSnap: take surface+coords + the chunky tile buffer; the asm
|
||||
// macros derive row-0.
|
||||
#define jlpTilePaste(_d, _bx, _by, _ct) \
|
||||
iigsTilePasteInner(&(_d)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)], (_ct))
|
||||
|
||||
#define jlpTileSnap(_s, _bx, _by, _out) \
|
||||
iigsTileSnapInner((_out), &(_s)->pixels[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)])
|
||||
|
||||
// Flood: IIgs overrides only the all-in-one tier (JL_HAS_FLOOD_WALK_AND_SCANS).
|
||||
// The dispatch tail leaves jlpFloodWalkAndScans defined here; the planar flood
|
||||
// hooks (jlpFloodWalkPlanes / jlpFloodScanRowPlanes) are Amiga-only and IIgs
|
||||
// never declares them (their call sites are #if'd out for non-Amiga).
|
||||
//
|
||||
// Tier-1 flood: multi-output. Asm sets gFloodSeedMatch / gFloodLeftX /
|
||||
// gFloodRightX; macro reads those into the caller's out-ptrs.
|
||||
#define jlpFloodWalkAndScans(_pix, _x, _y, _mc, _nc, _me, _sx, _sy, _sp, _ms, _smOut, _lxOut, _rxOut) \
|
||||
(iigsFloodWalkAndScansInner((_pix), (uint16_t)(_x), (uint16_t)(_y), \
|
||||
(uint16_t)((_mc) & 0x0F), \
|
||||
(uint16_t)((_nc) & 0x0F), \
|
||||
(uint16_t)((_me) ? 1 : 0), \
|
||||
(_sx), (_sy), \
|
||||
(uint16_t *)(_sp), \
|
||||
(uint16_t)(_ms)), \
|
||||
*(_smOut) = (gFloodSeedMatch != 0), \
|
||||
*(_lxOut) = (int16_t)gFloodLeftX, \
|
||||
*(_rxOut) = (int16_t)gFloodRightX, \
|
||||
true)
|
||||
|
||||
// =====================================================================
|
||||
// IIgs planar-hook elision.
|
||||
//
|
||||
// The planar dual-write hooks (surfaceCopyPlanes, the sprite *Planes ops)
|
||||
// let Amiga/ST mirror draws into their bitplanes. IIgs is chunky-native, so
|
||||
// it #define-s them to ((void)0): the call site is deleted at preprocess time
|
||||
// rather than paying a cross-segment JSL/RTL for a no-op. Arg lists match each
|
||||
// op's arity so the preprocessor parses and discards the exact call; all args
|
||||
// are plain lvalues / field reads, so dropping them changes no behavior. (DOS
|
||||
// is also chunky but links the real no-op generic -- it is not the perf
|
||||
// reference. The tile/fillRect planar work is folded into each jlp<Op> op, so
|
||||
// there are no separate planar hooks left to elide for those.)
|
||||
// =====================================================================
|
||||
|
||||
#define jlpSurfaceCopyPlanes(_dst, _src) ((void)0)
|
||||
|
||||
// sprite planes mirror: no-op on IIgs (chunky -> codegen does the work).
|
||||
#define jlpSpriteDrawPlanes(_s, _sp, _x, _y) ((void)0)
|
||||
#define jlpSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) ((void)0)
|
||||
#define jlpSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) ((void)0)
|
||||
|
||||
#endif /* JOEYLIB_PLATFORM_IIGS */
|
||||
|
||||
// =====================================================================
|
||||
// Migrated-op dispatch (generic default unless a machine overrides).
|
||||
//
|
||||
// jlpGenericSurfaceClear is the portable-C reference (src/generic/genericDraw.c),
|
||||
// always available. Machine overrides are selected at compile time via
|
||||
// JL_HAS_<OP> (include/joey/platform.h): a macro override (IIgs, above) already
|
||||
// #define-d jlp<Op>; a function override (Amiga/ST) just declares its prototype
|
||||
// here; otherwise jlp<Op> aliases the generic default. No runtime dispatch.
|
||||
// =====================================================================
|
||||
|
||||
void jlpGenericSurfaceClear(jlSurfaceT *s, uint8_t doubled);
|
||||
#if !defined(jlpSurfaceClear)
|
||||
#if defined(JL_HAS_SURFACE_CLEAR)
|
||||
void jlpSurfaceClear(jlSurfaceT *s, uint8_t doubled); /* function override */
|
||||
#else
|
||||
#define jlpSurfaceClear(_s, _d) jlpGenericSurfaceClear((_s), (_d))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex);
|
||||
#if !defined(jlpDrawPixel)
|
||||
#if defined(JL_HAS_DRAW_PIXEL)
|
||||
void jlpDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex); /* function override */
|
||||
#else
|
||||
#define jlpDrawPixel(_s, _x, _y, _c) jlpGenericDrawPixel((_s), (_x), (_y), (_c))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// drawLine / drawCircle have no generic default: the portable fallback is the
|
||||
// clipping Bresenham in core/draw.c, and the on-surface fast path is gated by
|
||||
// `#if defined(JL_HAS_DRAW_LINE/_CIRCLE)`. So here we only declare the function
|
||||
// override prototype for non-macro overriders (Amiga/ST).
|
||||
#if defined(JL_HAS_DRAW_LINE) && !defined(jlpDrawLine)
|
||||
void jlpDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex);
|
||||
#endif
|
||||
#if defined(JL_HAS_DRAW_CIRCLE) && !defined(jlpDrawCircle)
|
||||
void jlpDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
#endif
|
||||
// fillCircle returns bool (stage/portData check is inherent); the platform
|
||||
// dispatch is compile-time via JL_HAS_FILL_CIRCLE.
|
||||
#if defined(JL_HAS_FILL_CIRCLE) && !defined(jlpFillCircle)
|
||||
bool jlpFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex);
|
||||
#endif
|
||||
|
||||
void jlpGenericFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex);
|
||||
#if !defined(jlpFillRect)
|
||||
#if defined(JL_HAS_FILL_RECT)
|
||||
void jlpFillRect(jlSurfaceT *s, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t colorIndex); /* function override (planar) */
|
||||
#else
|
||||
#define jlpFillRect(_s, _x, _y, _w, _h, _c) jlpGenericFillRect((_s), (_x), (_y), (_w), (_h), (_c))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex);
|
||||
#if !defined(jlpTileFill)
|
||||
#if defined(JL_HAS_TILE_FILL)
|
||||
void jlpTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex); /* function override (planar) */
|
||||
#else
|
||||
#define jlpTileFill(_s, _bx, _by, _c) jlpGenericTileFill((_s), (_bx), (_by), (_c))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||
#if !defined(jlpTileCopy)
|
||||
#if defined(JL_HAS_TILE_COPY)
|
||||
void jlpTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy);
|
||||
#else
|
||||
#define jlpTileCopy(_d, _dbx, _dby, _s, _sbx, _sby) jlpGenericTileCopy((_d), (_dbx), (_dby), (_s), (_sbx), (_sby))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent);
|
||||
#if !defined(jlpTileCopyMasked)
|
||||
#if defined(JL_HAS_TILE_COPY_MASKED)
|
||||
void jlpTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent);
|
||||
#else
|
||||
#define jlpTileCopyMasked(_d, _dbx, _dby, _s, _sbx, _sby, _t) jlpGenericTileCopyMasked((_d), (_dbx), (_dby), (_s), (_sbx), (_sby), (_t))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile);
|
||||
#if !defined(jlpTilePaste)
|
||||
#if defined(JL_HAS_TILE_PASTE)
|
||||
void jlpTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile);
|
||||
#else
|
||||
#define jlpTilePaste(_d, _bx, _by, _ct) jlpGenericTilePaste((_d), (_bx), (_by), (_ct))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut);
|
||||
#if !defined(jlpTileSnap)
|
||||
#if defined(JL_HAS_TILE_SNAP)
|
||||
void jlpTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut);
|
||||
#else
|
||||
#define jlpTileSnap(_s, _bx, _by, _out) jlpGenericTileSnap((_s), (_bx), (_by), (_out))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor);
|
||||
#if !defined(jlpTilePasteMono)
|
||||
#if defined(JL_HAS_TILE_PASTE_MONO)
|
||||
void jlpTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor);
|
||||
#else
|
||||
#define jlpTilePasteMono(_d, _bx, _by, _mt, _fg, _bg) jlpGenericTilePasteMono((_d), (_bx), (_by), (_mt), (_fg), (_bg))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y);
|
||||
#if !defined(jlpSpriteDrawPlanes)
|
||||
#if defined(JL_HAS_SPRITE_DRAW)
|
||||
void jlpSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y);
|
||||
#else
|
||||
#define jlpSpriteDrawPlanes(_s, _sp, _x, _y) jlpGenericSpriteDrawPlanes((_s), (_sp), (_x), (_y))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes);
|
||||
#if !defined(jlpSpriteSavePlanes)
|
||||
#if defined(JL_HAS_SPRITE_SAVE)
|
||||
void jlpSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes);
|
||||
#else
|
||||
#define jlpSpriteSavePlanes(_s, _x, _y, _w, _h, _dst) jlpGenericSpriteSavePlanes((_s), (_x), (_y), (_w), (_h), (_dst))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes);
|
||||
#if !defined(jlpSpriteRestorePlanes)
|
||||
#if defined(JL_HAS_SPRITE_RESTORE)
|
||||
void jlpSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes);
|
||||
#else
|
||||
#define jlpSpriteRestorePlanes(_s, _x, _y, _w, _h, _src) jlpGenericSpriteRestorePlanes((_s), (_x), (_y), (_w), (_h), (_src))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Flood ops (PATTERN B: no extractable generic -- the portable default is the
|
||||
// inline C scanline fallback in floodFillInternal, gated per-tier by
|
||||
// `#if defined(JL_HAS_FLOOD_*)` at the call site). Each real override is just a
|
||||
// prototype here for the claiming machine. jlpFloodWalkAndScans is a macro on
|
||||
// IIgs (defined above), so its `!defined` guard skips the proto there.
|
||||
#if defined(JL_HAS_FLOOD_WALK_AND_SCANS) && !defined(jlpFloodWalkAndScans)
|
||||
bool jlpFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut);
|
||||
#endif
|
||||
#if defined(JL_HAS_FLOOD_WALK_PLANES) && !defined(jlpFloodWalkPlanes)
|
||||
bool jlpFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut);
|
||||
#endif
|
||||
#if defined(JL_HAS_FLOOD_SCAN_ROW_PLANES) && !defined(jlpFloodScanRowPlanes)
|
||||
bool jlpFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf);
|
||||
#endif
|
||||
|
||||
// =====================================================================
|
||||
// Category B: structural port services (generic default + JL_HAS override).
|
||||
//
|
||||
// Unlike Category A accelerators these are resolved purely at link/compile
|
||||
// time (no runtime dispatch ever existed). Each op has a portable-C default in
|
||||
// src/generic/ -- a real implementation where one exists, or a no-op/zero stub
|
||||
// for inherently platform-specific services so a blank/template port still
|
||||
// links. A port that supplies its own version defines JL_HAS_<OP> in
|
||||
// include/joey/platform.h. Same jlp<Op> stanza as Category A.
|
||||
// =====================================================================
|
||||
|
||||
// --- Generic-only surface services (no port overrides them today) ---
|
||||
void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
#if !defined(jlpSurfaceCopyChunky)
|
||||
#if defined(JL_HAS_SURFACE_COPY_CHUNKY)
|
||||
void jlpSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
#else
|
||||
#define jlpSurfaceCopyChunky(_d, _s) jlpGenericSurfaceCopyChunky((_d), (_s))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSurfaceFreePixels(uint8_t *pixels);
|
||||
#if !defined(jlpSurfaceFreePixels)
|
||||
#if defined(JL_HAS_SURFACE_FREE_PIXELS)
|
||||
void jlpSurfaceFreePixels(uint8_t *pixels);
|
||||
#else
|
||||
#define jlpSurfaceFreePixels(_p) jlpGenericSurfaceFreePixels((_p))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const char *jlpGenericLastError(void);
|
||||
#if !defined(jlpLastError)
|
||||
#if defined(JL_HAS_LAST_ERROR)
|
||||
const char *jlpLastError(void);
|
||||
#else
|
||||
#define jlpLastError() jlpGenericLastError()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- Planar dual-write hook (no-op generic default; planar ports override) ---
|
||||
void jlpGenericSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
#if !defined(jlpSurfaceCopyPlanes)
|
||||
#if defined(JL_HAS_SURFACE_COPY_PLANES)
|
||||
void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
#else
|
||||
#define jlpSurfaceCopyPlanes(_d, _s) jlpGenericSurfaceCopyPlanes((_d), (_s))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- Surface readers (chunky generic default; planar ports decode portData) ---
|
||||
uint8_t jlpGenericSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y);
|
||||
#if !defined(jlpSamplePixel)
|
||||
#if defined(JL_HAS_SAMPLE_PIXEL)
|
||||
uint8_t jlpSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y);
|
||||
#else
|
||||
#define jlpSamplePixel(_s, _x, _y) jlpGenericSamplePixel((_s), (_x), (_y))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint32_t jlpGenericSurfaceHash(const jlSurfaceT *s);
|
||||
#if !defined(jlpSurfaceHash)
|
||||
#if defined(JL_HAS_SURFACE_HASH)
|
||||
uint32_t jlpSurfaceHash(const jlSurfaceT *s);
|
||||
#else
|
||||
#define jlpSurfaceHash(_s) jlpGenericSurfaceHash((_s))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool jlpGenericSurfaceLoadFile(jlSurfaceT *dst, FILE *fp);
|
||||
#if !defined(jlpSurfaceLoadFile)
|
||||
#if defined(JL_HAS_SURFACE_LOAD_FILE)
|
||||
bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp);
|
||||
#else
|
||||
#define jlpSurfaceLoadFile(_d, _fp) jlpGenericSurfaceLoadFile((_d), (_fp))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool jlpGenericSurfaceSaveFile(const jlSurfaceT *src, FILE *fp);
|
||||
#if !defined(jlpSurfaceSaveFile)
|
||||
#if defined(JL_HAS_SURFACE_SAVE_FILE)
|
||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp);
|
||||
#else
|
||||
#define jlpSurfaceSaveFile(_s, _fp) jlpGenericSurfaceSaveFile((_s), (_fp))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- Allocation ops (chunky generic defaults; planar/pinned overrides) ---
|
||||
uint8_t *jlpGenericStageAllocPixels(void);
|
||||
#if !defined(jlpStageAllocPixels)
|
||||
#if defined(JL_HAS_STAGE_ALLOC_PIXELS)
|
||||
uint8_t *jlpStageAllocPixels(void);
|
||||
#else
|
||||
#define jlpStageAllocPixels() jlpGenericStageAllocPixels()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericStageFreePixels(uint8_t *pixels);
|
||||
#if !defined(jlpStageFreePixels)
|
||||
#if defined(JL_HAS_STAGE_FREE_PIXELS)
|
||||
void jlpStageFreePixels(uint8_t *pixels);
|
||||
#else
|
||||
#define jlpStageFreePixels(_p) jlpGenericStageFreePixels((_p))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint8_t *jlpGenericSurfaceAllocPixels(void);
|
||||
#if !defined(jlpSurfaceAllocPixels)
|
||||
#if defined(JL_HAS_SURFACE_ALLOC_PIXELS)
|
||||
uint8_t *jlpSurfaceAllocPixels(void);
|
||||
#else
|
||||
#define jlpSurfaceAllocPixels() jlpGenericSurfaceAllocPixels()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void *jlpGenericSurfaceAllocPortData(jlSurfaceT *s, bool isStage);
|
||||
#if !defined(jlpSurfaceAllocPortData)
|
||||
#if defined(JL_HAS_SURFACE_ALLOC_PORT_DATA)
|
||||
void *jlpSurfaceAllocPortData(jlSurfaceT *s, bool isStage);
|
||||
#else
|
||||
#define jlpSurfaceAllocPortData(_s, _st) jlpGenericSurfaceAllocPortData((_s), (_st))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData);
|
||||
#if !defined(jlpSurfaceFreePortData)
|
||||
#if defined(JL_HAS_SURFACE_FREE_PORT_DATA)
|
||||
void jlpSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData);
|
||||
#else
|
||||
#define jlpSurfaceFreePortData(_s, _st, _pd) jlpGenericSurfaceFreePortData((_s), (_st), (_pd))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint8_t *jlpGenericSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx);
|
||||
#if !defined(jlpSurfacePlanePtr)
|
||||
#if defined(JL_HAS_SURFACE_PLANE_PTR)
|
||||
uint8_t *jlpSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx);
|
||||
#else
|
||||
#define jlpSurfacePlanePtr(_s, _i) jlpGenericSurfacePlanePtr((_s), (_i))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// --- Timing services (platform-only stub generics; millisElapsed has a real default) ---
|
||||
uint16_t jlpGenericFrameHz(void);
|
||||
#if !defined(jlpFrameHz)
|
||||
#if defined(JL_HAS_FRAME_HZ)
|
||||
uint16_t jlpFrameHz(void);
|
||||
#else
|
||||
#define jlpFrameHz() jlpGenericFrameHz()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint16_t jlpGenericFrameCount(void);
|
||||
#if !defined(jlpFrameCount)
|
||||
#if defined(JL_HAS_FRAME_COUNT)
|
||||
uint16_t jlpFrameCount(void);
|
||||
#else
|
||||
#define jlpFrameCount() jlpGenericFrameCount()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericWaitVBL(void);
|
||||
#if !defined(jlpWaitVBL)
|
||||
#if defined(JL_HAS_WAIT_VBL)
|
||||
void jlpWaitVBL(void);
|
||||
#else
|
||||
#define jlpWaitVBL() jlpGenericWaitVBL()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint32_t jlpGenericMillisElapsed(void);
|
||||
#if !defined(jlpMillisElapsed)
|
||||
#if defined(JL_HAS_MILLIS_ELAPSED)
|
||||
uint32_t jlpMillisElapsed(void);
|
||||
#else
|
||||
#define jlpMillisElapsed() jlpGenericMillisElapsed()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// --- Audio services (platform-only; blank-port stub generic + JL_HAS override) ---
|
||||
bool jlpGenericAudioInit(void);
|
||||
#if !defined(jlpAudioInit)
|
||||
#if defined(JL_HAS_AUDIO_INIT)
|
||||
bool jlpAudioInit(void);
|
||||
#else
|
||||
#define jlpAudioInit() jlpGenericAudioInit()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioShutdown(void);
|
||||
#if !defined(jlpAudioShutdown)
|
||||
#if defined(JL_HAS_AUDIO_SHUTDOWN)
|
||||
void jlpAudioShutdown(void);
|
||||
#else
|
||||
#define jlpAudioShutdown() jlpGenericAudioShutdown()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
||||
#if !defined(jlpAudioPlayMod)
|
||||
#if defined(JL_HAS_AUDIO_PLAY_MOD)
|
||||
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop);
|
||||
#else
|
||||
#define jlpAudioPlayMod(_a, _b, _c) jlpGenericAudioPlayMod((_a), (_b), (_c))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioStopMod(void);
|
||||
#if !defined(jlpAudioStopMod)
|
||||
#if defined(JL_HAS_AUDIO_STOP_MOD)
|
||||
void jlpAudioStopMod(void);
|
||||
#else
|
||||
#define jlpAudioStopMod() jlpGenericAudioStopMod()
|
||||
#endif
|
||||
#endif
|
||||
bool jlpGenericAudioIsPlayingMod(void);
|
||||
#if !defined(jlpAudioIsPlayingMod)
|
||||
#if defined(JL_HAS_AUDIO_IS_PLAYING_MOD)
|
||||
bool jlpAudioIsPlayingMod(void);
|
||||
#else
|
||||
#define jlpAudioIsPlayingMod() jlpGenericAudioIsPlayingMod()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz);
|
||||
#if !defined(jlpAudioPlaySfx)
|
||||
#if defined(JL_HAS_AUDIO_PLAY_SFX)
|
||||
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz);
|
||||
#else
|
||||
#define jlpAudioPlaySfx(_a, _b, _c, _d) jlpGenericAudioPlaySfx((_a), (_b), (_c), (_d))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
|
||||
#if !defined(jlpAudioPlaySfxStream)
|
||||
#if defined(JL_HAS_AUDIO_PLAY_SFX_STREAM)
|
||||
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz);
|
||||
#else
|
||||
#define jlpAudioPlaySfxStream(_a, _b, _c, _d) jlpGenericAudioPlaySfxStream((_a), (_b), (_c), (_d))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioStopSfx(uint8_t slot);
|
||||
#if !defined(jlpAudioStopSfx)
|
||||
#if defined(JL_HAS_AUDIO_STOP_SFX)
|
||||
void jlpAudioStopSfx(uint8_t slot);
|
||||
#else
|
||||
#define jlpAudioStopSfx(_a) jlpGenericAudioStopSfx((_a))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioTone(uint16_t freqHz);
|
||||
#if !defined(jlpAudioTone)
|
||||
#if defined(JL_HAS_AUDIO_TONE)
|
||||
void jlpAudioTone(uint16_t freqHz);
|
||||
#else
|
||||
#define jlpAudioTone(_a) jlpGenericAudioTone((_a))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||
#if !defined(jlpAudioVoice)
|
||||
#if defined(JL_HAS_AUDIO_VOICE)
|
||||
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||
#else
|
||||
#define jlpAudioVoice(_a, _b, _c) jlpGenericAudioVoice((_a), (_b), (_c))
|
||||
#endif
|
||||
#endif
|
||||
bool jlpGenericAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
||||
#if !defined(jlpAudioTickRegister)
|
||||
#if defined(JL_HAS_AUDIO_TICK_REGISTER)
|
||||
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz);
|
||||
#else
|
||||
#define jlpAudioTickRegister(_a, _b) jlpGenericAudioTickRegister((_a), (_b))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioCriticalEnter(void);
|
||||
#if !defined(jlpAudioCriticalEnter)
|
||||
#if defined(JL_HAS_AUDIO_CRITICAL_ENTER)
|
||||
void jlpAudioCriticalEnter(void);
|
||||
#else
|
||||
#define jlpAudioCriticalEnter() jlpGenericAudioCriticalEnter()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioCriticalExit(void);
|
||||
#if !defined(jlpAudioCriticalExit)
|
||||
#if defined(JL_HAS_AUDIO_CRITICAL_EXIT)
|
||||
void jlpAudioCriticalExit(void);
|
||||
#else
|
||||
#define jlpAudioCriticalExit() jlpGenericAudioCriticalExit()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericAudioFrameTick(void);
|
||||
#if !defined(jlpAudioFrameTick)
|
||||
#if defined(JL_HAS_AUDIO_FRAME_TICK)
|
||||
void jlpAudioFrameTick(void);
|
||||
#else
|
||||
#define jlpAudioFrameTick() jlpGenericAudioFrameTick()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// --- Lifecycle / present / input (platform-only; blank stub generic + JL_HAS) ---
|
||||
bool jlpGenericInit(const jlConfigT *config);
|
||||
#if !defined(jlpInit)
|
||||
#if defined(JL_HAS_INIT)
|
||||
bool jlpInit(const jlConfigT *config);
|
||||
#else
|
||||
#define jlpInit(_a) jlpGenericInit((_a))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericShutdown(void);
|
||||
#if !defined(jlpShutdown)
|
||||
#if defined(JL_HAS_SHUTDOWN)
|
||||
void jlpShutdown(void);
|
||||
#else
|
||||
#define jlpShutdown() jlpGenericShutdown()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericPresent(const jlSurfaceT *src);
|
||||
#if !defined(jlpPresent)
|
||||
#if defined(JL_HAS_PRESENT)
|
||||
void jlpPresent(const jlSurfaceT *src);
|
||||
#else
|
||||
#define jlpPresent(_a) jlpGenericPresent((_a))
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericInputInit(void);
|
||||
#if !defined(jlpInputInit)
|
||||
#if defined(JL_HAS_INPUT_INIT)
|
||||
void jlpInputInit(void);
|
||||
#else
|
||||
#define jlpInputInit() jlpGenericInputInit()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericInputShutdown(void);
|
||||
#if !defined(jlpInputShutdown)
|
||||
#if defined(JL_HAS_INPUT_SHUTDOWN)
|
||||
void jlpInputShutdown(void);
|
||||
#else
|
||||
#define jlpInputShutdown() jlpGenericInputShutdown()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericInputPoll(void);
|
||||
#if !defined(jlpInputPoll)
|
||||
#if defined(JL_HAS_INPUT_POLL)
|
||||
void jlpInputPoll(void);
|
||||
#else
|
||||
#define jlpInputPoll() jlpGenericInputPoll()
|
||||
#endif
|
||||
#endif
|
||||
void jlpGenericJoystickReset(jlJoystickE js);
|
||||
#if !defined(jlpJoystickReset)
|
||||
#if defined(JL_HAS_JOYSTICK_RESET)
|
||||
void jlpJoystickReset(jlJoystickE js);
|
||||
#else
|
||||
#define jlpJoystickReset(_a) jlpGenericJoystickReset((_a))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// --- Large allocation (malloc/free generic default; IIgs Memory Manager override) ---
|
||||
void *jlpGenericBigAlloc(uint32_t bytes);
|
||||
void jlpGenericBigFree(void *p);
|
||||
#if !defined(jlpBigAlloc)
|
||||
#if defined(JL_HAS_BIG_ALLOC)
|
||||
void *jlpBigAlloc(uint32_t bytes);
|
||||
void jlpBigFree(void *p);
|
||||
#else
|
||||
#define jlpBigAlloc(_b) jlpGenericBigAlloc((_b))
|
||||
#define jlpBigFree(_p) jlpGenericBigFree((_p))
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "joey/debug.h"
|
||||
#include "joey/present.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
|
||||
|
|
@ -21,6 +21,6 @@ void jlStagePresent(void) {
|
|||
if (stage == NULL) {
|
||||
return;
|
||||
}
|
||||
halPresent(stage);
|
||||
jlpPresent(stage);
|
||||
stageDirtyClearAll();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
#include "joey/sprite.h"
|
||||
#include "joey/tile.h"
|
||||
#include "codegenArenaInternal.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "spriteInternal.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
#define TRANSPARENT_NIBBLE 0
|
||||
|
||||
// On Amiga (post-Phase 9 / Phase 6 redux) the compiled sprite emitter
|
||||
// writes directly to the bitplanes, so the halSpritePlanes hooks are
|
||||
// writes directly to the bitplanes, so the jlpSprite*Planes hooks are
|
||||
// pure duplicate work after a compiled call. On other ports the
|
||||
// hooks are either no-op stubs (chunky-only IIgs/DOS) or the only
|
||||
// thing writing planes (ST: chunky-shadow + planes). Slow / interpreter
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
// is the only draw.
|
||||
/* ST also runs pure planar post-Phase-9 (s->pixels NULL); the JIT
|
||||
* routine writes plane bytes directly, so the chunky interpreter
|
||||
* is a no-op and the halSpriteDrawPlanes hook would be a redundant
|
||||
* is a no-op and the jlpSpriteDrawPlanes hook would be a redundant
|
||||
* second draw. Same rationale as Amiga. */
|
||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST)
|
||||
#define COMPILED_SPRITE_WRITES_PLANES 1
|
||||
|
|
@ -158,7 +158,7 @@ static void spriteDrawInterpreted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16
|
|||
wTiles = sp->widthTiles;
|
||||
|
||||
/* Skip the chunky write loop on planar ports (s->pixels == NULL).
|
||||
* halSpriteDrawPlanes is called by the jlSpriteDraw caller and does
|
||||
* jlpSpriteDrawPlanes is called by the jlSpriteDraw caller and does
|
||||
* its own clip + plane write, so the dirty mark + planar update
|
||||
* happen there. Phase 9 dropped the chunky shadow on Amiga. */
|
||||
if (s->pixels != NULL) {
|
||||
|
|
@ -275,8 +275,8 @@ jlSpriteT *jlSpriteCreateFromSurface(const jlSurfaceT *src, int16_t x, int16_t y
|
|||
}
|
||||
} else {
|
||||
// Planar port (Phase 9: no chunky shadow). Derive chunky 4bpp
|
||||
// pixels from plane bits via halSamplePixel and pack into the
|
||||
// tile-major sprite layout. Slow (per-pixel halSamplePixel) but
|
||||
// pixels from plane bits via jlpSamplePixel and pack into the
|
||||
// tile-major sprite layout. Slow (per-pixel jlpSamplePixel) but
|
||||
// only runs at sprite-capture time, not per frame. Without
|
||||
// this branch sprites loaded from a work surface on Amiga/ST
|
||||
// all came back NULL -- the symptom was simply no sprites on
|
||||
|
|
@ -293,8 +293,8 @@ jlSpriteT *jlSpriteCreateFromSurface(const jlSurfaceT *src, int16_t x, int16_t y
|
|||
py = (uint16_t)((uint16_t)y + ty * TILE_PIXELS_PER_SIDE + row);
|
||||
for (col = 0; col < TILE_PIXELS_PER_SIDE; col += 2) {
|
||||
px = (uint16_t)((uint16_t)x + tx * TILE_PIXELS_PER_SIDE + col);
|
||||
hiNibble = (uint8_t)(halSamplePixel(src, (int16_t)px, (int16_t)py) & 0x0Fu);
|
||||
loNibble = (uint8_t)(halSamplePixel(src, (int16_t)(px + 1), (int16_t)py) & 0x0Fu);
|
||||
hiNibble = (uint8_t)(jlpSamplePixel(src, (int16_t)px, (int16_t)py) & 0x0Fu);
|
||||
loNibble = (uint8_t)(jlpSamplePixel(src, (int16_t)(px + 1), (int16_t)py) & 0x0Fu);
|
||||
dstTile[row * TILE_BYTES_PER_ROW + (col >> 1)] =
|
||||
(uint8_t)((hiNibble << 4) | loNibble);
|
||||
}
|
||||
|
|
@ -346,13 +346,13 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y) {
|
|||
if (sp->slot != NULL && isFullyOnSurface(x, y, widthPx, heightPx)) {
|
||||
spriteCompiledDraw(s, sp, x, y);
|
||||
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
||||
halSpriteDrawPlanes(s, sp, x, y);
|
||||
jlpSpriteDrawPlanes(s, sp, x, y);
|
||||
}
|
||||
surfaceMarkDirtyRect(s, x, y, (int16_t)widthPx, (int16_t)heightPx);
|
||||
return;
|
||||
}
|
||||
spriteDrawInterpreted(s, sp, x, y);
|
||||
halSpriteDrawPlanes(s, sp, x, y);
|
||||
jlpSpriteDrawPlanes(s, sp, x, y);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -414,8 +414,8 @@ void jlSpriteSaveAndDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlS
|
|||
spriteCompiledSaveUnder(s, sp, x, y, backup);
|
||||
spriteCompiledDraw (s, sp, x, y);
|
||||
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
||||
halSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
||||
halSpriteDrawPlanes(s, sp, x, y);
|
||||
jlpSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
||||
jlpSpriteDrawPlanes(s, sp, x, y);
|
||||
}
|
||||
surfaceMarkDirtyRect (s, x, y, (int16_t)widthPx, (int16_t)heightPx);
|
||||
return;
|
||||
|
|
@ -502,7 +502,7 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
|||
if (routeOffset != SPRITE_NOT_COMPILED) {
|
||||
spriteCompiledRestoreUnder(s, backup);
|
||||
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
||||
halSpriteRestorePlanes(s, bx, by, bw, bh, backup->bytes);
|
||||
jlpSpriteRestorePlanes(s, bx, by, bw, bh, backup->bytes);
|
||||
}
|
||||
surfaceMarkDirtyRect(s, bx, by, (int16_t)bw, (int16_t)bh);
|
||||
return;
|
||||
|
|
@ -512,7 +512,7 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
|||
|
||||
/* Slow / interpreted memcpy fallback. Skip the chunky memcpy if
|
||||
* the port has no chunky shadow (Phase 9 Amiga: s->pixels NULL);
|
||||
* halSpriteRestorePlanes below does the planar restore. */
|
||||
* jlpSpriteRestorePlanes below does the planar restore. */
|
||||
if (s->pixels != NULL) {
|
||||
int16_t row;
|
||||
int16_t byteStart;
|
||||
|
|
@ -527,7 +527,7 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
|||
(size_t)copyBytes);
|
||||
}
|
||||
}
|
||||
halSpriteRestorePlanes(s, bx, by, bw, bh, backup->bytes);
|
||||
jlpSpriteRestorePlanes(s, bx, by, bw, bh, backup->bytes);
|
||||
surfaceMarkDirtyRect(s, bx, by, (int16_t)bw, (int16_t)bh);
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +568,7 @@ void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y,
|
|||
if (routeOffset != SPRITE_NOT_COMPILED) {
|
||||
spriteCompiledSaveUnder(s, sp, x, y, backup);
|
||||
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
||||
halSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
||||
jlpSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -626,7 +626,7 @@ void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y,
|
|||
return;
|
||||
}
|
||||
/* Chunky save path: skip on planar ports (s->pixels NULL).
|
||||
* halSpriteSavePlanes below covers the planar case. */
|
||||
* jlpSpriteSavePlanes below covers the planar case. */
|
||||
if (s->pixels != NULL) {
|
||||
for (row = 0; row < h; row++) {
|
||||
srcRow = &s->pixels[(dy + row) * SURFACE_BYTES_PER_ROW];
|
||||
|
|
@ -635,6 +635,6 @@ void jlSpriteSaveUnder(const jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y,
|
|||
(size_t)copyBytes);
|
||||
}
|
||||
}
|
||||
halSpriteSavePlanes(s, clippedX, dy, (uint16_t)clippedW, (uint16_t)h, backup->bytes);
|
||||
jlpSpriteSavePlanes(s, clippedX, dy, (uint16_t)clippedW, (uint16_t)h, backup->bytes);
|
||||
} /* end slow path */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "joey/surface.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
|
|
@ -64,10 +64,10 @@ void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
if (dst == NULL || src == NULL || dst == src) {
|
||||
return;
|
||||
}
|
||||
halSurfaceCopyChunky(dst, src); /* memcpy on chunky ports; no-op on planar */
|
||||
jlpSurfaceCopyChunky(dst, src); /* memcpy on chunky ports; no-op on planar */
|
||||
memcpy(dst->scb, src->scb, sizeof(src->scb));
|
||||
memcpy(dst->palette, src->palette, sizeof(src->palette));
|
||||
halSurfaceCopyPlanes(dst, src); /* 4 plane memcpys on planar ports; no-op on chunky */
|
||||
jlpSurfaceCopyPlanes(dst, src); /* 4 plane memcpys on planar ports; no-op on chunky */
|
||||
surfaceMarkDirtyAll(dst);
|
||||
// Bulk-replacing the stage's SCB/palette must re-mark them dirty so
|
||||
// the per-port present re-uploads them; surfaceMarkDirtyAll only
|
||||
|
|
@ -87,23 +87,23 @@ jlSurfaceT *jlSurfaceCreate(void) {
|
|||
coreSetError("out of memory allocating surface");
|
||||
return NULL;
|
||||
}
|
||||
// halSurfaceAllocPixels returns NULL on planar ports (Amiga); the
|
||||
// jlpSurfaceAllocPixels returns NULL on planar ports (Amiga); the
|
||||
// primary storage is the port-allocated planes via portData below.
|
||||
// Classify NULL by the build's storage model: chunky ports need
|
||||
// pixels, planar ports need portData. A NULL in the required slot
|
||||
// is an out-of-memory failure (header contract: return NULL).
|
||||
s->pixels = halSurfaceAllocPixels();
|
||||
s->portData = halSurfaceAllocPortData(s, false);
|
||||
s->pixels = jlpSurfaceAllocPixels();
|
||||
s->portData = jlpSurfaceAllocPortData(s, false);
|
||||
#ifdef JOEYLIB_NATIVE_CHUNKY
|
||||
if (s->pixels == NULL) {
|
||||
halSurfaceFreePortData(s, false, s->portData);
|
||||
jlpSurfaceFreePortData(s, false, s->portData);
|
||||
free(s);
|
||||
coreSetError("out of memory allocating surface pixels");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (s->portData == NULL) {
|
||||
halSurfaceFreePixels(s->pixels);
|
||||
jlpSurfaceFreePixels(s->pixels);
|
||||
free(s);
|
||||
coreSetError("out of memory allocating surface planes");
|
||||
return NULL;
|
||||
|
|
@ -121,8 +121,8 @@ void jlSurfaceDestroy(jlSurfaceT *s) {
|
|||
if (s == gStage) {
|
||||
return;
|
||||
}
|
||||
halSurfaceFreePortData(s, false, s->portData);
|
||||
halSurfaceFreePixels(s->pixels);
|
||||
jlpSurfaceFreePortData(s, false, s->portData);
|
||||
jlpSurfaceFreePixels(s->pixels);
|
||||
free(s);
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ uint32_t jlSurfaceHash(const jlSurfaceT *s) {
|
|||
if (s == NULL) {
|
||||
return 0u;
|
||||
}
|
||||
return halSurfaceHash(s);
|
||||
return jlpSurfaceHash(s);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path) {
|
|||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
if (!halSurfaceLoadFile(dst, fp)) {
|
||||
if (!jlpSurfaceLoadFile(dst, fp)) {
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path) {
|
|||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!halSurfaceSaveFile(src, fp)) {
|
||||
if (!jlpSurfaceSaveFile(src, fp)) {
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -280,26 +280,26 @@ bool stageAlloc(void) {
|
|||
if (gStage == NULL) {
|
||||
return false;
|
||||
}
|
||||
// halStageAllocPixels returns NULL on planar ports (Amiga) where
|
||||
// jlpStageAllocPixels returns NULL on planar ports (Amiga) where
|
||||
// the chunky shadow doesn't exist; the planes from portData are
|
||||
// the source of truth. On chunky ports NULL pixels means the
|
||||
// back-buffer alloc failed (IIgs pins a fixed address and never
|
||||
// returns NULL, so this is a real OOM only on DOS).
|
||||
gStage->pixels = halStageAllocPixels();
|
||||
gStage->pixels = jlpStageAllocPixels();
|
||||
if (gStage->pixels != NULL) {
|
||||
memset(gStage->pixels, 0, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
gStage->portData = halSurfaceAllocPortData(gStage, true);
|
||||
gStage->portData = jlpSurfaceAllocPortData(gStage, true);
|
||||
#ifdef JOEYLIB_NATIVE_CHUNKY
|
||||
if (gStage->pixels == NULL) {
|
||||
halSurfaceFreePortData(gStage, true, gStage->portData);
|
||||
jlpSurfaceFreePortData(gStage, true, gStage->portData);
|
||||
free(gStage);
|
||||
gStage = NULL;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (gStage->portData == NULL) {
|
||||
halStageFreePixels(gStage->pixels);
|
||||
jlpStageFreePixels(gStage->pixels);
|
||||
free(gStage);
|
||||
gStage = NULL;
|
||||
return false;
|
||||
|
|
@ -325,8 +325,8 @@ void stageFree(void) {
|
|||
if (gStage == NULL) {
|
||||
return;
|
||||
}
|
||||
halSurfaceFreePortData(gStage, true, gStage->portData);
|
||||
halStageFreePixels(gStage->pixels);
|
||||
jlpSurfaceFreePortData(gStage, true, gStage->portData);
|
||||
jlpStageFreePixels(gStage->pixels);
|
||||
free(gStage);
|
||||
gStage = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
// (Amiga, Atari ST) it points to a port-private struct describing the
|
||||
// 4 bitplanes (Amiga: 4 separate plane buffers + stride; ST: single
|
||||
// interleaved buffer + stride). Cross-platform code never touches it
|
||||
// directly -- all primitive access goes through halFast* on planar
|
||||
// directly -- all primitive access goes through the jlp ops on planar
|
||||
// ports. See project_planar_68k_plan.md for the full architecture.
|
||||
struct jlSurfaceT {
|
||||
uint8_t *pixels;
|
||||
|
|
@ -112,7 +112,7 @@ void stageDirtyClearAll(void);
|
|||
|
||||
// y -> byte offset of row y in a SURFACE_BYTES_PER_ROW-strided buffer.
|
||||
// On IIgs this expands to a single indexed long-mode read against
|
||||
// gRowOffsetLut (built once at halInit).
|
||||
// gRowOffsetLut (built once at jlpInit).
|
||||
//
|
||||
// The explicit (y << 1) byte-pointer arithmetic dodges the multiply
|
||||
// helper that gets emitted for `uint16_t arr[N]` indexing
|
||||
|
|
@ -137,7 +137,7 @@ void coreSetError(const char *message);
|
|||
// Allocate and free the library-owned stage (the back-buffer surface
|
||||
// that jlStagePresent flips to the display). Called from init.c during
|
||||
// jlInit / jlShutdown. The stage's pixel storage is supplied by
|
||||
// the port HAL via halStageAllocPixels.
|
||||
// the port HAL via jlpStageAllocPixels.
|
||||
bool stageAlloc(void);
|
||||
void stageFree(void);
|
||||
|
||||
|
|
|
|||
207
src/core/tile.c
207
src/core/tile.c
|
|
@ -11,7 +11,7 @@
|
|||
#include <stddef.h>
|
||||
|
||||
#include "joey/tile.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// (No <string.h> -- the 4-byte-per-row inner copies are spelled out
|
||||
|
|
@ -21,66 +21,13 @@
|
|||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void copyTileOpaque(uint8_t *dst, const uint8_t *src);
|
||||
static void copyTileMasked(uint8_t *dst, const uint8_t *src, uint8_t transparent);
|
||||
// (copyTileOpaque / copyTileMasked moved to src/generic/genericTile.c as
|
||||
// jlpGenericTileCopy / jlpGenericTileCopyMasked.)
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
// 32-byte block copy with the surface row stride between rows. dst
|
||||
// and src already point at the row-0 first byte of their respective
|
||||
// 8x8 regions.
|
||||
static void copyTileOpaque(uint8_t *dst, const uint8_t *src) {
|
||||
uint8_t row;
|
||||
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
dst[3] = src[3];
|
||||
dst += SURFACE_BYTES_PER_ROW;
|
||||
src += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Same as copyTileOpaque but treats source nibbles equal to
|
||||
// `transparent` as skip-through. The src bytes are inspected nibble-
|
||||
// by-nibble; only non-transparent nibbles overwrite the destination.
|
||||
static void copyTileMasked(uint8_t *dst, const uint8_t *src, uint8_t transparent) {
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
uint8_t srcByte;
|
||||
uint8_t dstByte;
|
||||
uint8_t transHi;
|
||||
uint8_t transLo;
|
||||
uint8_t srcHi;
|
||||
uint8_t srcLo;
|
||||
|
||||
transHi = (uint8_t)((transparent & 0x0F) << 4);
|
||||
transLo = (uint8_t)(transparent & 0x0F);
|
||||
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
||||
srcByte = src[col];
|
||||
srcHi = (uint8_t)(srcByte & 0xF0);
|
||||
srcLo = (uint8_t)(srcByte & 0x0F);
|
||||
// Both nibbles transparent: skip the byte entirely.
|
||||
if (srcHi == transHi && srcLo == transLo) {
|
||||
continue;
|
||||
}
|
||||
dstByte = dst[col];
|
||||
if (srcHi != transHi) {
|
||||
dstByte = (uint8_t)((dstByte & 0x0F) | srcHi);
|
||||
}
|
||||
if (srcLo != transLo) {
|
||||
dstByte = (uint8_t)((dstByte & 0xF0) | srcLo);
|
||||
}
|
||||
dst[col] = dstByte;
|
||||
}
|
||||
dst += SURFACE_BYTES_PER_ROW;
|
||||
src += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
// (the chunky tile copy/masked-copy helpers now live in
|
||||
// src/generic/genericTile.c as jlpGenericTileCopy / jlpGenericTileCopyMasked.)
|
||||
|
||||
|
||||
// ----- Public API (alphabetical) -----
|
||||
|
|
@ -119,12 +66,8 @@ void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlSurfaceT *fontS
|
|||
|
||||
|
||||
void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
uint8_t *dstRow0;
|
||||
const uint8_t *srcRow0;
|
||||
uint16_t dstPixelX;
|
||||
uint16_t dstPixelY;
|
||||
uint16_t srcPixelX;
|
||||
uint16_t srcPixelY;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
|
|
@ -135,30 +78,16 @@ void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT
|
|||
}
|
||||
dstPixelX = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
|
||||
dstPixelY = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelX = (uint16_t)((uint16_t)srcBx * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelY = (uint16_t)((uint16_t)srcBy * TILE_PIXELS_PER_SIDE);
|
||||
|
||||
/* Skip the chunky path on planar ports (pixels NULL). */
|
||||
if (dst->pixels != NULL && src->pixels != NULL) {
|
||||
dstRow0 = &dst->pixels[SURFACE_ROW_OFFSET(dstPixelY) + (dstPixelX >> 1)];
|
||||
srcRow0 = &src->pixels[SURFACE_ROW_OFFSET(srcPixelY) + (srcPixelX >> 1)];
|
||||
if (!halFastTileCopy(dstRow0, srcRow0)) {
|
||||
copyTileOpaque(dstRow0, srcRow0);
|
||||
}
|
||||
}
|
||||
halTileCopyPlanes(dst, dstBx, dstBy, src, srcBx, srcBy);
|
||||
// Single op: machine asm/planar override or the generic chunky default.
|
||||
jlpTileCopy(dst, dstBx, dstBy, src, srcBx, srcBy);
|
||||
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
||||
uint8_t *dstRow0;
|
||||
const uint8_t *srcRow0;
|
||||
uint16_t dstPixelX;
|
||||
uint16_t dstPixelY;
|
||||
uint16_t srcPixelX;
|
||||
uint16_t srcPixelY;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return;
|
||||
|
|
@ -169,25 +98,13 @@ void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSur
|
|||
}
|
||||
dstPixelX = (uint16_t)((uint16_t)dstBx * TILE_PIXELS_PER_SIDE);
|
||||
dstPixelY = (uint16_t)((uint16_t)dstBy * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelX = (uint16_t)((uint16_t)srcBx * TILE_PIXELS_PER_SIDE);
|
||||
srcPixelY = (uint16_t)((uint16_t)srcBy * TILE_PIXELS_PER_SIDE);
|
||||
|
||||
/* Skip the chunky path on planar ports (pixels NULL). */
|
||||
if (dst->pixels != NULL && src->pixels != NULL) {
|
||||
dstRow0 = &dst->pixels[SURFACE_ROW_OFFSET(dstPixelY) + (dstPixelX >> 1)];
|
||||
srcRow0 = &src->pixels[SURFACE_ROW_OFFSET(srcPixelY) + (srcPixelX >> 1)];
|
||||
if (!halFastTileCopyMasked(dstRow0, srcRow0, transparentIndex)) {
|
||||
copyTileMasked(dstRow0, srcRow0, transparentIndex);
|
||||
}
|
||||
}
|
||||
halTileCopyMaskedPlanes(dst, dstBx, dstBy, src, srcBx, srcBy, transparentIndex);
|
||||
jlpTileCopyMasked(dst, dstBx, dstBy, src, srcBx, srcBy, transparentIndex);
|
||||
surfaceMarkDirtyRect(dst, (int16_t)dstPixelX, (int16_t)dstPixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
uint8_t doubled;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
|
||||
|
|
@ -199,21 +116,9 @@ void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
doubled = (uint8_t)(((colorIndex & 0x0F) << 4) | (colorIndex & 0x0F));
|
||||
if (s->pixels != NULL
|
||||
&& !halFastTileFill(s, bx, by,
|
||||
(uint16_t)((uint16_t)doubled | ((uint16_t)doubled << 8)))) {
|
||||
uint8_t *row = &s->pixels[SURFACE_ROW_OFFSET(pixelY) + (pixelX >> 1)];
|
||||
uint8_t i;
|
||||
for (i = 0; i < TILE_PIXELS_PER_SIDE; i++) {
|
||||
row[0] = doubled;
|
||||
row[1] = doubled;
|
||||
row[2] = doubled;
|
||||
row[3] = doubled;
|
||||
row += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
halTileFillPlanes(s, bx, by, colorIndex);
|
||||
// Single compile-time-selected op: machine asm/planar override or the
|
||||
// generic chunky default.
|
||||
jlpTileFill(s, bx, by, colorIndex);
|
||||
surfaceMarkDirtyRect(s, (int16_t)pixelX, (int16_t)pixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
|
@ -221,55 +126,27 @@ void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|||
|
||||
void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
||||
const jlTileT *in, uint8_t fgColor, uint8_t bgColor) {
|
||||
jlTileT colored;
|
||||
uint8_t i;
|
||||
uint8_t srcByte;
|
||||
uint8_t hi;
|
||||
uint8_t lo;
|
||||
|
||||
if (dst == NULL || in == NULL) {
|
||||
return;
|
||||
}
|
||||
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
fgColor &= 0x0Fu;
|
||||
bgColor &= 0x0Fu;
|
||||
|
||||
if (dst->pixels == NULL) {
|
||||
/* Planar port: in->pixels is in the port-specific layout
|
||||
* produced by halTileSnapPlanes (plane-major on Amiga,
|
||||
* row-major-with-planes-interleaved-per-row on ST). The
|
||||
* colorization formula -- write fgColor bits where the shape
|
||||
* bit is 1, bgColor bits where it's 0 -- has to be applied in
|
||||
* the port-specific format, so delegate to the port hook.
|
||||
* Each port reads the shape bytes from its own layout and
|
||||
* writes colorized planes back to its own destination format
|
||||
* directly, without a chunky intermediate. */
|
||||
halTilePasteMonoPlanes(dst, bx, by, &in->pixels[0], fgColor, bgColor);
|
||||
// Single compile-time op: planar ports colorize in their own tile format
|
||||
// (Amiga/ST); chunky ports colorize into a chunky tile then jlpTilePaste.
|
||||
// Replaces the old runtime pixels==NULL planar/chunky branch.
|
||||
jlpTilePasteMono(dst, bx, by, &in->pixels[0], fgColor, bgColor);
|
||||
surfaceMarkDirtyRect(dst, (int16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE),
|
||||
(int16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE),
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk the 32 bytes of 4bpp-packed source and emit a colorized
|
||||
// copy: each non-zero source nibble becomes fgColor, each zero
|
||||
// becomes bgColor. Then delegate to jlTilePaste so the chunky
|
||||
// and planar write paths stay shared.
|
||||
for (i = 0u; i < TILE_BYTES; i++) {
|
||||
srcByte = in->pixels[i];
|
||||
hi = (uint8_t)((srcByte >> 4) ? fgColor : bgColor);
|
||||
lo = (uint8_t)((srcByte & 0x0Fu) ? fgColor : bgColor);
|
||||
colored.pixels[i] = (uint8_t)((hi << 4) | lo);
|
||||
}
|
||||
jlTilePaste(dst, bx, by, &colored);
|
||||
}
|
||||
|
||||
|
||||
void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in) {
|
||||
uint8_t *dstRow;
|
||||
const uint8_t *src;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
uint8_t row;
|
||||
|
||||
if (dst == NULL || in == NULL) {
|
||||
return;
|
||||
|
|
@ -279,59 +156,21 @@ void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in) {
|
|||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
src = &in->pixels[0];
|
||||
/* Skip the chunky write path on planar ports (dst->pixels NULL) --
|
||||
* mirrors jlTileSnap's pixels-NULL short-circuit. Saves the dstRow
|
||||
* SURFACE_ROW_OFFSET multiply + halFastTilePaste jsr/rts per call
|
||||
* on ST/Amiga where the planar path below does the real work. */
|
||||
if (dst->pixels != NULL) {
|
||||
dstRow = &dst->pixels[SURFACE_ROW_OFFSET(pixelY) + (pixelX >> 1)];
|
||||
if (!halFastTilePaste(dstRow, src)) {
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dstRow[0] = src[0];
|
||||
dstRow[1] = src[1];
|
||||
dstRow[2] = src[2];
|
||||
dstRow[3] = src[3];
|
||||
dstRow += SURFACE_BYTES_PER_ROW;
|
||||
src += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
halTilePastePlanes(dst, bx, by, &in->pixels[0]);
|
||||
// Single op: machine asm/planar override or the generic chunky default.
|
||||
jlpTilePaste(dst, bx, by, &in->pixels[0]);
|
||||
surfaceMarkDirtyRect(dst, (int16_t)pixelX, (int16_t)pixelY,
|
||||
TILE_PIXELS_PER_SIDE, TILE_PIXELS_PER_SIDE);
|
||||
}
|
||||
|
||||
|
||||
void jlTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, jlTileT *out) {
|
||||
const uint8_t *srcRow;
|
||||
uint8_t *dst;
|
||||
uint16_t pixelX;
|
||||
uint16_t pixelY;
|
||||
uint8_t row;
|
||||
|
||||
if (src == NULL || out == NULL) {
|
||||
return;
|
||||
}
|
||||
if (bx >= TILE_BLOCKS_PER_ROW || by >= TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
dst = &out->pixels[0];
|
||||
/* On planar ports (s->pixels NULL) the chunky read path is
|
||||
* skipped; halTileSnapPlanes below derives the tile bytes from
|
||||
* the bitplanes. */
|
||||
if (src->pixels != NULL && !halFastTileSnap(dst, &src->pixels[SURFACE_ROW_OFFSET(pixelY) + (pixelX >> 1)])) {
|
||||
srcRow = &src->pixels[SURFACE_ROW_OFFSET(pixelY) + (pixelX >> 1)];
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
dst[0] = srcRow[0];
|
||||
dst[1] = srcRow[1];
|
||||
dst[2] = srcRow[2];
|
||||
dst[3] = srcRow[3];
|
||||
srcRow += SURFACE_BYTES_PER_ROW;
|
||||
dst += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
halTileSnapPlanes(src, bx, by, &out->pixels[0]);
|
||||
// Read-only single op: machine asm/planar override or the generic chunky
|
||||
// default. No dirty mark.
|
||||
jlpTileSnap(src, bx, by, &out->pixels[0]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
// by 8237 auto-init DMA, fed by libxmp-lite's mixer.
|
||||
//
|
||||
// Pipeline:
|
||||
// * halAudioInit detects the card via DSP reset + version, allocates
|
||||
// * jlpAudioInit detects the card via DSP reset + version, allocates
|
||||
// a 4 KB conventional-memory DMA buffer (aligned to not cross a
|
||||
// 64 KB boundary), installs an IRQ handler on the SB's IRQ line,
|
||||
// creates an xmp_context, and arms auto-init DMA at 22050 Hz mono
|
||||
// 8-bit. The DMA buffer is split into two halves; the SB IRQ fires
|
||||
// at the end of each half and our handler refills the *other* half
|
||||
// by calling xmp_play_buffer with libxmp-lite's mixer output.
|
||||
// * halAudioPlayMod loads a .MOD blob through xmp_load_module and
|
||||
// * jlpAudioPlayMod loads a .MOD blob through xmp_load_module and
|
||||
// starts the player; subsequent IRQs auto-pump the audio.
|
||||
// * halAudioPlaySfx routes through libxmp-lite's SMIX overlay so SFX
|
||||
// * jlpAudioPlaySfx routes through libxmp-lite's SMIX overlay so SFX
|
||||
// mix on top of the music using xmp_smix_play_sample.
|
||||
//
|
||||
// Defaults match DOSBox-Staging's emulated SB Pro: base $220, IRQ 7,
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
#include <libxmp-lite/xmp.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "audioSfxMixInternal.h"
|
||||
#include "joey/audio.h"
|
||||
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
// MIX_RATE is the nominal target; the SB's 8-bit time-constant DAC can
|
||||
// only clock at 1000000/(256-tc) Hz, which is not exactly 22050. The
|
||||
// SB hardware rate is the fixed one, so the effective rate derived from
|
||||
// the same time constant (see gEffRate, set in halAudioInit) is the
|
||||
// the same time constant (see gEffRate, set in jlpAudioInit) is the
|
||||
// single source of truth handed to libxmp and the SFX resampler.
|
||||
#define MIX_RATE 22050
|
||||
#define MIX_DMA_BUFFER 4096 // total; two 2048-byte halves
|
||||
|
|
@ -116,7 +116,7 @@ static uint8_t gDspMinor = 0;
|
|||
static bool gDspPresent = false;
|
||||
|
||||
// Effective SB playback rate derived from the DSP time constant. Set
|
||||
// once in halAudioInit and used as the single rate source of truth for
|
||||
// once in jlpAudioInit and used as the single rate source of truth for
|
||||
// libxmp's mixer and the SFX resampler, so they match what the DAC
|
||||
// actually clocks (~22222 Hz for a 22050 Hz target) instead of drifting.
|
||||
static uint16_t gEffRate = MIX_RATE;
|
||||
|
|
@ -145,7 +145,7 @@ static AudioSfxSlotT gSfxSlots[JOEY_AUDIO_SFX_SLOTS];
|
|||
|
||||
// ISR-side state. volatile so the main loop sees IRQ updates promptly.
|
||||
// gNextHalf flips between 0 and 1 each IRQ; gNeedRefill[i] is raised
|
||||
// in the IRQ handler and lowered in halAudioFrameTick after the
|
||||
// in the IRQ handler and lowered in jlpAudioFrameTick after the
|
||||
// half has been refilled. We deliberately do NOT call libxmp-lite
|
||||
// from interrupt context -- the mixer allocates internally and is
|
||||
// not reentrancy-safe -- so all work that touches the xmp context
|
||||
|
|
@ -426,7 +426,7 @@ static void picMaskIrq(uint8_t irq) {
|
|||
|
||||
|
||||
// Refill one half of the DMA buffer from libxmp-lite's mixer.
|
||||
// Called from halAudioFrameTick on the main thread (NOT in IRQ
|
||||
// Called from jlpAudioFrameTick on the main thread (NOT in IRQ
|
||||
// context); the IRQ only flags which halves need refilling.
|
||||
// xmp_play_buffer fills our bytes regardless of whether a module is
|
||||
// loaded -- it returns silence (centered 0x80) when idle.
|
||||
|
|
@ -448,7 +448,7 @@ static void refillHalf(uint8_t halfIdx) {
|
|||
|
||||
// SB IRQ. Acks the SB (read $0E), marks the half that just played
|
||||
// for refill, EOIs the PIC. The actual mixer work happens in
|
||||
// halAudioFrameTick on the main thread.
|
||||
// jlpAudioFrameTick on the main thread.
|
||||
static void sbIsr(void) {
|
||||
uint8_t fillHalf;
|
||||
|
||||
|
|
@ -467,7 +467,7 @@ static void sbIsr(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halAudioInit(void) {
|
||||
bool jlpAudioInit(void) {
|
||||
int major;
|
||||
int minor;
|
||||
int vector;
|
||||
|
|
@ -539,7 +539,7 @@ bool halAudioInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioShutdown(void) {
|
||||
void jlpAudioShutdown(void) {
|
||||
int vector;
|
||||
|
||||
if (!gDspPresent) {
|
||||
|
|
@ -571,12 +571,12 @@ void halAudioShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
bool halAudioIsPlayingMod(void) {
|
||||
bool jlpAudioIsPlayingMod(void) {
|
||||
return gXmpStarted;
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
if (!gDspPresent || gXmpCtx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -607,7 +607,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
if (!gDspPresent || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -615,7 +615,7 @@ void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
if (!gDspPresent || slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -645,7 +645,7 @@ void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uin
|
|||
#define PC_SPEAKER_PORT_GATE 0x61u
|
||||
#define PC_SPEAKER_CTRL_BYTE 0xB6u
|
||||
|
||||
void halAudioTone(uint16_t freqHz) {
|
||||
void jlpAudioTone(uint16_t freqHz) {
|
||||
uint32_t divisor;
|
||||
uint8_t gate;
|
||||
|
||||
|
|
@ -802,11 +802,11 @@ static void adlibInit(void) {
|
|||
// and subtract -- the math comes out to exactly 18.2065 Hz
|
||||
// regardless of our own IRQ rate.
|
||||
//
|
||||
// 2. halMillisElapsed must report wall-clock ms even though
|
||||
// 2. jlpMillisElapsed must report wall-clock ms even though
|
||||
// uclock()'s sub-tick PIT read is now wrong (PIT cycles
|
||||
// 0..gPitDivisor, not 0..65535 as uclock assumes). We expose
|
||||
// an own-ISR-counter-driven ms via the gAudioIsr* globals
|
||||
// below; src/port/dos/hal.c::halMillisElapsed prefers it
|
||||
// below; src/dos/hal.c::jlpMillisElapsed prefers it
|
||||
// whenever gAudioIsrActive is true.
|
||||
//
|
||||
// Literal "chain to old INT $08" doesn't work cleanly under DPMI
|
||||
|
|
@ -822,7 +822,7 @@ static void adlibInit(void) {
|
|||
#define BIOS_TICK_ADDR 0x46Cu
|
||||
#define IRQ0_INT_VECTOR 0x08
|
||||
|
||||
// Exposed (extern in hal.c) so halMillisElapsed can read wall-clock
|
||||
// Exposed (extern in hal.c) so jlpMillisElapsed can read wall-clock
|
||||
// ms straight from the ISR counter and bypass the broken uclock.
|
||||
volatile uint32_t gAudioIsrFires = 0;
|
||||
uint16_t gAudioIsrHz = 0;
|
||||
|
|
@ -863,7 +863,7 @@ static void programPit0(uint16_t divisor) {
|
|||
}
|
||||
|
||||
|
||||
bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
uint32_t divisor;
|
||||
|
||||
if (fn == NULL) {
|
||||
|
|
@ -915,7 +915,7 @@ bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioCriticalEnter(void) {
|
||||
void jlpAudioCriticalEnter(void) {
|
||||
// "memory" clobber forces GCC to commit pending register-cached
|
||||
// writes to memory before the cli (and reload after sti), so
|
||||
// the timer ISR can't observe a half-written critical section.
|
||||
|
|
@ -923,12 +923,12 @@ void halAudioCriticalEnter(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioCriticalExit(void) {
|
||||
void jlpAudioCriticalExit(void) {
|
||||
__asm__ __volatile__("sti" ::: "memory");
|
||||
}
|
||||
|
||||
|
||||
void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
uint8_t car;
|
||||
uint8_t block;
|
||||
uint32_t fnum;
|
||||
|
|
@ -989,7 +989,7 @@ void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopMod(void) {
|
||||
void jlpAudioStopMod(void) {
|
||||
if (!gDspPresent || gXmpCtx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1004,7 +1004,7 @@ void halAudioStopMod(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopSfx(uint8_t slot) {
|
||||
void jlpAudioStopSfx(uint8_t slot) {
|
||||
if (slot >= JOEY_AUDIO_SFX_SLOTS) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1018,7 +1018,7 @@ void halAudioStopSfx(uint8_t slot) {
|
|||
// 22050 Hz half-buffer of 2048 samples drains ~143000 samples/sec
|
||||
// of headroom, well above the playback rate, so a missed frame or
|
||||
// two is OK without underflow.
|
||||
void halAudioFrameTick(void) {
|
||||
void jlpAudioFrameTick(void) {
|
||||
int i;
|
||||
|
||||
if (!gDspPresent) {
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
#include <sys/exceptn.h>
|
||||
#include <sys/nearptr.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
|
@ -62,7 +62,7 @@ static uint8_t gPrevInVret;
|
|||
static uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gCacheValid = false;
|
||||
|
||||
// Expand LUT for halPresent. Maps a 4bpp-packed source byte to the
|
||||
// Expand LUT for jlpPresent. Maps a 4bpp-packed source byte to the
|
||||
// 2-byte little-endian mode-13h output pair for the current scanline's
|
||||
// SCB. The low byte (= dst[x], lower VGA address = LEFT pixel) holds
|
||||
// the packed byte's HIGH nibble; the high byte (= dst[x+1] = RIGHT
|
||||
|
|
@ -88,7 +88,7 @@ static void expandAndWriteLine(const jlSurfaceT *src, int16_t y, int16_t x, uint
|
|||
if (!gExpandLutValid || palBase != gExpandLutBase) {
|
||||
rebuildExpandLut(palBase);
|
||||
}
|
||||
// Dirty bands are chunky-word aligned (halPresent computes
|
||||
// Dirty bands are chunky-word aligned (jlpPresent computes
|
||||
// pixelX = minWord*4, pixelW = ...*4), so x is even and 1 source
|
||||
// byte (= 2 nibbles) maps to 2 destination bytes. The even x keeps
|
||||
// &dst[x] 16-bit aligned, so each pair is one aligned 16-bit store
|
||||
|
|
@ -237,7 +237,7 @@ static void uploadPaletteIfNeeded(const jlSurfaceT *src) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halInit(const jlConfigT *config) {
|
||||
bool jlpInit(const jlConfigT *config) {
|
||||
__dpmi_regs regs;
|
||||
|
||||
(void)config;
|
||||
|
|
@ -267,12 +267,7 @@ bool halInit(const jlConfigT *config) {
|
|||
}
|
||||
|
||||
|
||||
const char *halLastError(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halPresent(const jlSurfaceT *src) {
|
||||
void jlpPresent(const jlSurfaceT *src) {
|
||||
int16_t y;
|
||||
uint8_t minWord;
|
||||
uint8_t maxWord;
|
||||
|
|
@ -313,7 +308,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
// rising-edge wait (so back-to-back calls each yield one frame),
|
||||
// first wait while we're already in retrace, then wait until we
|
||||
// re-enter retrace.
|
||||
void halWaitVBL(void) {
|
||||
void jlpWaitVBL(void) {
|
||||
while (inportb(VGA_INPUT_STAT_1) & VGA_VRETRACE_BIT) {
|
||||
/* spin while still in current retrace */;
|
||||
}
|
||||
|
|
@ -323,7 +318,7 @@ void halWaitVBL(void) {
|
|||
}
|
||||
|
||||
|
||||
uint16_t halFrameCount(void) {
|
||||
uint16_t jlpFrameCount(void) {
|
||||
uint8_t now;
|
||||
|
||||
now = (uint8_t)(inportb(VGA_INPUT_STAT_1) & VGA_VRETRACE_BIT);
|
||||
|
|
@ -335,7 +330,7 @@ uint16_t halFrameCount(void) {
|
|||
}
|
||||
|
||||
|
||||
uint16_t halFrameHz(void) {
|
||||
uint16_t jlpFrameHz(void) {
|
||||
/* VGA mode 13h vertical refresh on a real CRT runs at ~70 Hz
|
||||
* (70.086 to be exact). Reporting 70 keeps ops/sec scaling
|
||||
* accurate within ~0.1%. */
|
||||
|
|
@ -355,7 +350,7 @@ uint16_t halFrameHz(void) {
|
|||
// because PIT 0 is at the BIOS default and DJGPP's sub-tick
|
||||
// math holds.
|
||||
//
|
||||
// gAudioIsr* are owned by src/port/dos/audio.c.
|
||||
// gAudioIsr* are owned by src/dos/audio.c.
|
||||
extern volatile uint32_t gAudioIsrFires;
|
||||
extern uint16_t gAudioIsrHz;
|
||||
extern volatile bool gAudioIsrActive;
|
||||
|
|
@ -363,7 +358,7 @@ extern volatile bool gAudioIsrActive;
|
|||
static uclock_t gMillisBase = 0;
|
||||
static bool gMillisBaseSet = false;
|
||||
|
||||
uint32_t halMillisElapsed(void) {
|
||||
uint32_t jlpMillisElapsed(void) {
|
||||
uclock_t delta;
|
||||
|
||||
if (gAudioIsrActive && gAudioIsrHz > 0u) {
|
||||
|
|
@ -391,7 +386,7 @@ uint32_t halMillisElapsed(void) {
|
|||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
void jlpShutdown(void) {
|
||||
__dpmi_regs regs;
|
||||
|
||||
if (gNearEnabled) {
|
||||
|
|
@ -411,321 +406,14 @@ void halShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
// DOS has no asm fast paths yet; cross-platform code falls back to
|
||||
// its C implementations when these return false.
|
||||
bool halFastSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
(void)s;
|
||||
(void)doubled;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopy(uint8_t *dstRow0, const uint8_t *srcRow0) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileCopyMasked(uint8_t *dstRow0, const uint8_t *srcRow0, uint8_t transparent) {
|
||||
(void)dstRow0;
|
||||
(void)srcRow0;
|
||||
(void)transparent;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTilePaste(uint8_t *dstRow0, const uint8_t *srcTilePixels) {
|
||||
(void)dstRow0;
|
||||
(void)srcTilePixels;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileSnap(uint8_t *dstTilePixels, const uint8_t *srcRow0) {
|
||||
(void)dstTilePixels;
|
||||
(void)srcRow0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawLine(jlSurfaceT *s, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x0;
|
||||
(void)y0;
|
||||
(void)x1;
|
||||
(void)y1;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastDrawCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFillCircle(jlSurfaceT *s, int16_t cx, int16_t cy, uint16_t r, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)cx;
|
||||
(void)cy;
|
||||
(void)r;
|
||||
(void)colorIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalk(uint8_t *row, int16_t startX, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)row;
|
||||
(void)startX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFloodWalkPlanes(const jlSurfaceT *s, int16_t startX, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)s; (void)startX; (void)y; (void)matchColor; (void)newColor; (void)matchEqual;
|
||||
(void)seedMatched; (void)leftXOut; (void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, int16_t scanY, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)s; (void)leftX; (void)rightX; (void)scanY; (void)matchColor; (void)newColor; (void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanRow(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, uint8_t *markBuf) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)markBuf;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodScanAndPush(uint8_t *row, int16_t leftX, int16_t rightX, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t scanY, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp) {
|
||||
(void)row;
|
||||
(void)leftX;
|
||||
(void)rightX;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)scanY;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastFloodWalkAndScans(uint8_t *pixels, int16_t x, int16_t y, uint8_t matchColor, uint8_t newColor, bool matchEqual, int16_t *stackX, int16_t *stackY, int16_t *spInOut, int16_t maxSp, bool *seedMatched, int16_t *leftXOut, int16_t *rightXOut) {
|
||||
(void)pixels;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)matchColor;
|
||||
(void)newColor;
|
||||
(void)matchEqual;
|
||||
(void)stackX;
|
||||
(void)stackY;
|
||||
(void)spInOut;
|
||||
(void)maxSp;
|
||||
(void)seedMatched;
|
||||
(void)leftXOut;
|
||||
(void)rightXOut;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool halFastTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint16_t fillWord) {
|
||||
(void)s;
|
||||
(void)bx;
|
||||
(void)by;
|
||||
(void)fillWord;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
// DOS / VGA mode 13h is chunky-native (8bpp linear). portData is
|
||||
// unused; the chunky `pixels` buffer feeds the present-time
|
||||
// nearest-neighbor copy to VGA RAM.
|
||||
void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
(void)portData;
|
||||
}
|
||||
|
||||
|
||||
// DOS has no bitplanes -- chunky pixels are the source of truth and
|
||||
// expandAndWriteLine derives the VGA DAC indices straight from them.
|
||||
// This hook is a stub here; the cross-platform jlFillRect calls it
|
||||
// unconditionally.
|
||||
void halFillRectPlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t colorIndex) {
|
||||
(void)s;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)w;
|
||||
(void)h;
|
||||
(void)colorIndex;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
(void)dst;
|
||||
(void)src;
|
||||
}
|
||||
|
||||
|
||||
void halTileFillPlanes(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
(void)s; (void)bx; (void)by; (void)colorIndex;
|
||||
}
|
||||
void halTileCopyPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
(void)dst; (void)dstBx; (void)dstBy; (void)src; (void)srcBx; (void)srcBy;
|
||||
}
|
||||
void halTileCopyMaskedPlanes(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex) {
|
||||
(void)dst; (void)dstBx; (void)dstBy; (void)src; (void)srcBx; (void)srcBy; (void)transparentIndex;
|
||||
}
|
||||
void halTilePastePlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
||||
(void)dst; (void)bx; (void)by; (void)chunkyTile;
|
||||
}
|
||||
void halTilePasteMonoPlanes(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
(void)dst; (void)bx; (void)by; (void)monoTile; (void)fgColor; (void)bgColor;
|
||||
}
|
||||
void halTileSnapPlanes(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyTileOut) {
|
||||
(void)src; (void)bx; (void)by; (void)chunkyTileOut;
|
||||
}
|
||||
void halSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
(void)s; (void)sp; (void)x; (void)y;
|
||||
}
|
||||
void halSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
(void)s; (void)x; (void)y; (void)w; (void)h; (void)dstPlaneBytes;
|
||||
}
|
||||
void halSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
(void)s; (void)x; (void)y; (void)w; (void)h; (void)srcPlaneBytes;
|
||||
}
|
||||
|
||||
// surfaceCopyPlanes migrated: DOS defines no JL_HAS_SURFACE_COPY_PLANES, so it
|
||||
// links the no-op generic jlpGenericSurfaceCopyPlanes.
|
||||
|
||||
/* Phase 9 reader hooks: chunky ports use the original s->pixels-based
|
||||
* paths. */
|
||||
|
||||
uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
uint8_t byte = s->pixels[(uint16_t)y * SURFACE_BYTES_PER_ROW + ((uint16_t)x >> 1)];
|
||||
if (x & 1) return (uint8_t)(byte & 0x0Fu);
|
||||
return (uint8_t)((byte & 0xF0u) >> 4);
|
||||
}
|
||||
|
||||
|
||||
uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
||||
uint16_t lo = 0xACE1u, hi = 0x1357u, blocks, n, v;
|
||||
const uint8_t *p;
|
||||
const uint16_t *w;
|
||||
uint8_t b;
|
||||
p = s->pixels;
|
||||
blocks = (uint16_t)(SURFACE_PIXELS_SIZE / 8);
|
||||
do {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
blocks--;
|
||||
} while (blocks > 0u);
|
||||
p = s->scb;
|
||||
for (n = 0; n < (uint16_t)SURFACE_HEIGHT; n++) {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
w = &s->palette[0][0];
|
||||
for (n = 0; n < (uint16_t)SURFACE_PALETTE_ENTRIES; n++) {
|
||||
v = *w++;
|
||||
b = (uint8_t)((v >> 8) & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = (uint8_t)(v & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
return ((uint32_t)hi << 16) | (uint32_t)lo;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
memcpy(dst->pixels, src->pixels, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
return fread(dst->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
return fwrite(src->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfaceAllocPixels(void) {
|
||||
return (uint8_t *)calloc(1, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
(void)s; (void)planeIdx;
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@
|
|||
//
|
||||
// Keyboard: hooks INT 9 to capture AT set-1 scan codes from port 0x60.
|
||||
// The ISR reads the scan code, maps it to a jlKeyE, updates a private
|
||||
// gIsrState buffer, and sends EOI to the PIC. halInputPoll snapshots
|
||||
// gIsrState buffer, and sends EOI to the PIC. jlpInputPoll snapshots
|
||||
// gIsrState into gKeyState with interrupts disabled.
|
||||
//
|
||||
// The two-buffer split is required for jlKeyPressed edge detection.
|
||||
// jlInputPoll does memcpy(gKeyPrev, gKeyState) *before* halInputPoll
|
||||
// jlInputPoll does memcpy(gKeyPrev, gKeyState) *before* jlpInputPoll
|
||||
// runs, so whatever gKeyState holds at that moment becomes gKeyPrev.
|
||||
// If the ISR wrote directly to gKeyState, press/release events that
|
||||
// happened between polls would already have landed there -- the memcpy
|
||||
|
|
@ -18,8 +18,8 @@
|
|||
// will hang the machine.
|
||||
//
|
||||
// Mouse: uses INT 33h, the standard MS DOS mouse driver interface.
|
||||
// halInputInit calls function 0 (reset/detect), then 7/8 to clamp the
|
||||
// driver's coordinate range to the surface dimensions. halInputPoll
|
||||
// jlpInputInit calls function 0 (reset/detect), then 7/8 to clamp the
|
||||
// driver's coordinate range to the surface dimensions. jlpInputPoll
|
||||
// calls function 3 once per frame to read absolute X/Y and the button
|
||||
// bitmask. INT 33h is provided by every common DOS mouse driver and by
|
||||
// DOSBox / DOSBox-X.
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
#include <pc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "inputInternal.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
|
|
@ -323,13 +323,13 @@ static void mousePoll(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halJoystickReset(jlJoystickE js) {
|
||||
void jlpJoystickReset(jlJoystickE js) {
|
||||
// DOS sticks are digital -- no calibration to do.
|
||||
(void)js;
|
||||
}
|
||||
|
||||
|
||||
void halInputInit(void) {
|
||||
void jlpInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
|
|
@ -353,7 +353,7 @@ void halInputInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
void jlpInputPoll(void) {
|
||||
disable();
|
||||
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
||||
enable();
|
||||
|
|
@ -362,7 +362,7 @@ void halInputPoll(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
void jlpInputShutdown(void) {
|
||||
if (!gHooked) {
|
||||
return;
|
||||
}
|
||||
76
src/generic/genericDraw.c
Normal file
76
src/generic/genericDraw.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Generic portable-C op defaults -- the new joeylib's "generic backend" (the
|
||||
// joey.c-equivalent reference). A machine that does not override an op (no
|
||||
// JL_HAS_<OP> defined for it in include/joey/platform.h) links these. An
|
||||
// override may also defer edge cases here, e.g. the IIgs stage-only fast clear
|
||||
// falls back to jlpGenericSurfaceClear for non-stage surfaces.
|
||||
//
|
||||
// These functions are always compiled into every target; the jlp<Op> dispatch
|
||||
// in src/core/port.h routes to either a machine override or, by default, the
|
||||
// matching generic here.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "surfaceInternal.h" // full jlSurfaceT (s->pixels) + SURFACE_PIXELS_SIZE
|
||||
#include "port.h" // jlpGenericSurfaceClear prototype
|
||||
|
||||
|
||||
void jlpGenericSurfaceClear(jlSurfaceT *s, uint8_t doubled) {
|
||||
if (s->pixels != NULL) {
|
||||
memset(s->pixels, doubled, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Chunky 4bpp on-surface rect fill. Caller has proven the rect is fully on
|
||||
// surface (x,y >= 0; x+w <= WIDTH; y+h <= HEIGHT; w,h > 0). Planar ports have
|
||||
// NULL s->pixels and override jlpFillRect with their plane fill, so this is the
|
||||
// chunky default (IIgs non-stage / DOS).
|
||||
void jlpGenericFillRect(jlSurfaceT *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;
|
||||
uint16_t pxStart;
|
||||
uint16_t pxEnd;
|
||||
uint16_t midBytes;
|
||||
uint8_t *line;
|
||||
|
||||
if (s->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
for (row = 0; row < h; row++) {
|
||||
line = &s->pixels[SURFACE_ROW_OFFSET(y + row)];
|
||||
pxStart = (uint16_t)x;
|
||||
pxEnd = (uint16_t)(x + w);
|
||||
if (pxStart & 1u) {
|
||||
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0xF0) | nibble);
|
||||
pxStart++;
|
||||
}
|
||||
midBytes = (uint16_t)((pxEnd - pxStart) >> 1);
|
||||
if (midBytes > 0u) {
|
||||
memset(&line[pxStart >> 1], doubled, (size_t)midBytes);
|
||||
pxStart = (uint16_t)(pxStart + (midBytes << 1));
|
||||
}
|
||||
if (pxStart < pxEnd) {
|
||||
line[pxStart >> 1] = (uint8_t)((line[pxStart >> 1] & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Chunky 4bpp nibble read-modify-write. Caller (plotPixelNoMark / jlDrawPixel)
|
||||
// has already done the NULL + bounds check.
|
||||
void jlpGenericDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorIndex) {
|
||||
uint8_t *byte;
|
||||
uint8_t nibble;
|
||||
|
||||
if (s->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
byte = &s->pixels[SURFACE_ROW_OFFSET(y) + (x >> 1)];
|
||||
nibble = colorIndex & 0x0F;
|
||||
if (x & 1) {
|
||||
*byte = (uint8_t)((*byte & 0xF0) | nibble);
|
||||
} else {
|
||||
*byte = (uint8_t)((*byte & 0x0F) | (nibble << 4));
|
||||
}
|
||||
}
|
||||
146
src/generic/genericPort.c
Normal file
146
src/generic/genericPort.c
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// Generic portable-C defaults for Category B platform SERVICES (timing, and --
|
||||
// in later steps -- lifecycle/present/input/audio). Most are inherently
|
||||
// platform-specific, so the "generic" is a no-op/zero stub that lets a blank/
|
||||
// template port link; every real port overrides via JL_HAS_<OP>. A few have a
|
||||
// genuine portable default (millisElapsed derives from the frame counter).
|
||||
//
|
||||
// See genericDraw.c for the jlp<Op> dispatch model. Always compiled in via the
|
||||
// src/generic/*.c wildcard.
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "surfaceInternal.h"
|
||||
#include "port.h"
|
||||
|
||||
|
||||
// Display refresh rate in Hz. No universal value (IIgs 50/60, Amiga/ST 50,
|
||||
// DOS 70); every real port overrides. The stub keeps a blank port linkable.
|
||||
uint16_t jlpGenericFrameHz(void) {
|
||||
return 60u;
|
||||
}
|
||||
|
||||
|
||||
// Monotonic frame/refresh counter. Each port reads its own hardware (VBL bar,
|
||||
// tick counter, retrace edge); the stub returns 0 for a blank port.
|
||||
uint16_t jlpGenericFrameCount(void) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
// Block until the next vertical blank. No portable implementation; a blank port
|
||||
// gets a no-op (it simply won't pace to the display).
|
||||
void jlpGenericWaitVBL(void) {
|
||||
}
|
||||
|
||||
|
||||
// Approximate elapsed milliseconds from the frame counter and refresh rate.
|
||||
// This is the IIgs/Amiga path; ports with a real hardware timer (DOS uclock,
|
||||
// ST 200 Hz tick) override via JL_HAS_MILLIS_ELAPSED.
|
||||
uint32_t jlpGenericMillisElapsed(void) {
|
||||
return (uint32_t)((uint32_t)jlpFrameCount() * 1000u / (uint32_t)jlpFrameHz());
|
||||
}
|
||||
|
||||
|
||||
// --- Audio services (platform-only stub generics; every real port overrides) ---
|
||||
|
||||
bool jlpGenericAudioInit(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void jlpGenericAudioShutdown(void) {
|
||||
}
|
||||
|
||||
void jlpGenericAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
(void)data;
|
||||
(void)length;
|
||||
(void)loop;
|
||||
}
|
||||
|
||||
void jlpGenericAudioStopMod(void) {
|
||||
}
|
||||
|
||||
bool jlpGenericAudioIsPlayingMod(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void jlpGenericAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
(void)slot;
|
||||
(void)sample;
|
||||
(void)length;
|
||||
(void)rateHz;
|
||||
}
|
||||
|
||||
void jlpGenericAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
(void)slot;
|
||||
(void)fill;
|
||||
(void)ctx;
|
||||
(void)rateHz;
|
||||
}
|
||||
|
||||
void jlpGenericAudioStopSfx(uint8_t slot) {
|
||||
(void)slot;
|
||||
}
|
||||
|
||||
void jlpGenericAudioTone(uint16_t freqHz) {
|
||||
(void)freqHz;
|
||||
}
|
||||
|
||||
void jlpGenericAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
(void)voice;
|
||||
(void)freqHz;
|
||||
(void)atten;
|
||||
}
|
||||
|
||||
bool jlpGenericAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
(void)fn;
|
||||
(void)hz;
|
||||
return false;
|
||||
}
|
||||
|
||||
void jlpGenericAudioCriticalEnter(void) {
|
||||
}
|
||||
|
||||
void jlpGenericAudioCriticalExit(void) {
|
||||
}
|
||||
|
||||
void jlpGenericAudioFrameTick(void) {
|
||||
}
|
||||
|
||||
|
||||
// --- Lifecycle / present / input services (platform-only stub generics) ---
|
||||
|
||||
bool jlpGenericInit(const jlConfigT *config) {
|
||||
(void)config;
|
||||
return true;
|
||||
}
|
||||
|
||||
void jlpGenericShutdown(void) {
|
||||
}
|
||||
|
||||
void jlpGenericPresent(const jlSurfaceT *src) {
|
||||
(void)src;
|
||||
}
|
||||
|
||||
void jlpGenericInputInit(void) {
|
||||
}
|
||||
|
||||
void jlpGenericInputShutdown(void) {
|
||||
}
|
||||
|
||||
void jlpGenericInputPoll(void) {
|
||||
}
|
||||
|
||||
void jlpGenericJoystickReset(jlJoystickE js) {
|
||||
(void)js;
|
||||
}
|
||||
|
||||
|
||||
// --- Large allocation (malloc/free default; IIgs overrides via Memory Manager) ---
|
||||
void *jlpGenericBigAlloc(uint32_t bytes) {
|
||||
return malloc((size_t)bytes);
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericBigFree(void *p) {
|
||||
free(p);
|
||||
}
|
||||
22
src/generic/genericSprite.c
Normal file
22
src/generic/genericSprite.c
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Generic sprite "planes" op defaults. The chunky software sprite blit lives in
|
||||
// core/sprite.c (spriteDrawInterpreted) and the per-platform sprite codegen does
|
||||
// the fast path; jlpSprite*Planes is the PLANAR mirror/interpreter, which has no
|
||||
// work to do on a chunky target -- so the generic default is a no-op. Planar
|
||||
// machines (Amiga/ST) override it; IIgs overrides it with a no-op macro (perf).
|
||||
|
||||
#include "port.h"
|
||||
|
||||
|
||||
void jlpGenericSpriteDrawPlanes(jlSurfaceT *s, const jlSpriteT *sp, int16_t x, int16_t y) {
|
||||
(void)s; (void)sp; (void)x; (void)y;
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericSpriteSavePlanes(const jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dstPlaneBytes) {
|
||||
(void)s; (void)x; (void)y; (void)w; (void)h; (void)dstPlaneBytes;
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericSpriteRestorePlanes(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *srcPlaneBytes) {
|
||||
(void)s; (void)x; (void)y; (void)w; (void)h; (void)srcPlaneBytes;
|
||||
}
|
||||
145
src/generic/genericSurface.c
Normal file
145
src/generic/genericSurface.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// Generic portable-C surface-service defaults -- the joey.c-equivalent reference
|
||||
// for Category B structural ops (see genericDraw.c for the jlp<Op> dispatch
|
||||
// model). A machine that does not override an op (no JL_HAS_<OP> in
|
||||
// include/joey/platform.h) links these; the dispatch stanzas live in
|
||||
// src/core/port.h. Always compiled into every target via the src/generic/*.c
|
||||
// wildcard.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "surfaceInternal.h" // full jlSurfaceT (s->pixels) + SURFACE_PIXELS_SIZE
|
||||
#include "port.h"
|
||||
|
||||
|
||||
// jlSurfaceCopy's chunky payload copy. Guarded so it is a no-op on planar
|
||||
// ports (pixels == NULL, plane copy is jlpSurfaceCopyPlanes' job); chunky ports
|
||||
// memcpy the whole buffer.
|
||||
void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
if (dst->pixels != NULL && src->pixels != NULL) {
|
||||
memcpy(dst->pixels, src->pixels, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Release a per-surface chunky pixel buffer. free(NULL) is a no-op, so this is
|
||||
// safe on planar ports whose surfaces carry no chunky shadow.
|
||||
void jlpGenericSurfaceFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
// jlSurfaceCopy's planar dual-write hook. No-op default: chunky ports carry no
|
||||
// bitplanes (the chunky payload is jlpSurfaceCopyChunky's job), and a blank/
|
||||
// template port links this until it provides its own. Planar ports (Amiga, ST)
|
||||
// override via JL_HAS_SURFACE_COPY_PLANES.
|
||||
void jlpGenericSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
(void)dst;
|
||||
(void)src;
|
||||
}
|
||||
|
||||
|
||||
// No port currently surfaces a human-readable last-error string.
|
||||
const char *jlpGenericLastError(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// --- Chunky surface readers (generic default; planar ports override) ---
|
||||
// Bodies are the IIgs/DOS chunky implementations verbatim; amiga/ST decode
|
||||
// their portData planes in their own overrides (JL_HAS_*).
|
||||
|
||||
|
||||
uint8_t jlpGenericSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
uint8_t byte = s->pixels[(uint16_t)y * SURFACE_BYTES_PER_ROW + ((uint16_t)x >> 1)];
|
||||
if (x & 1) return (uint8_t)(byte & 0x0Fu);
|
||||
return (uint8_t)((byte & 0xF0u) >> 4);
|
||||
}
|
||||
|
||||
|
||||
uint32_t jlpGenericSurfaceHash(const jlSurfaceT *s) {
|
||||
uint16_t lo = 0xACE1u, hi = 0x1357u, blocks, n, v;
|
||||
const uint8_t *p;
|
||||
const uint16_t *w;
|
||||
uint8_t b;
|
||||
p = s->pixels;
|
||||
blocks = (uint16_t)(SURFACE_PIXELS_SIZE / 8);
|
||||
do {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
blocks--;
|
||||
} while (blocks > 0u);
|
||||
p = s->scb;
|
||||
for (n = 0; n < (uint16_t)SURFACE_HEIGHT; n++) {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
w = &s->palette[0][0];
|
||||
for (n = 0; n < (uint16_t)SURFACE_PALETTE_ENTRIES; n++) {
|
||||
v = *w++;
|
||||
b = (uint8_t)((v >> 8) & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = (uint8_t)(v & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
return ((uint32_t)hi << 16) | (uint32_t)lo;
|
||||
}
|
||||
|
||||
|
||||
bool jlpGenericSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
return fread(dst->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
bool jlpGenericSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
return fwrite(src->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
// --- Allocation split by storage model (chunky generic defaults) ---
|
||||
// Planar ports (Amiga/ST) override the per-surface allocators to manage their
|
||||
// plane structs; IIgs overrides the stage allocators (pinned display memory).
|
||||
|
||||
// Stage back-buffer pixels: chunky default is a heap alloc.
|
||||
uint8_t *jlpGenericStageAllocPixels(void) {
|
||||
return (uint8_t *)malloc(SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericStageFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
// Per-surface chunky pixels: chunky ports calloc (zero-filled); planar ports
|
||||
// override to return NULL (storage is the portData planes).
|
||||
uint8_t *jlpGenericSurfaceAllocPixels(void) {
|
||||
return (uint8_t *)calloc(1, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
// Per-surface portData: NULL on chunky ports (unused); planar ports override.
|
||||
void *jlpGenericSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
(void)portData;
|
||||
}
|
||||
|
||||
|
||||
// Bitplane base pointer: NULL on chunky ports (no planes); planar ports override.
|
||||
uint8_t *jlpGenericSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
(void)s;
|
||||
(void)planeIdx;
|
||||
return NULL;
|
||||
}
|
||||
150
src/generic/genericTile.c
Normal file
150
src/generic/genericTile.c
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Generic portable-C tile op defaults (chunky 4bpp). The compile-time jlp<Op>
|
||||
// dispatch in src/core/port.h routes to these when a machine declares no
|
||||
// JL_HAS_<OP> for the tile op; machine overrides (IIgs asm macros, Amiga/ST
|
||||
// planar functions) live in the machine folders.
|
||||
|
||||
#include "joey/tile.h"
|
||||
#include "surfaceInternal.h"
|
||||
#include "port.h"
|
||||
|
||||
|
||||
// Derive the chunky row-0 pointer for tile block (bx,by) within a surface.
|
||||
#define GENERIC_TILE_ROW0(_pix, _bx, _by) \
|
||||
(&(_pix)[SURFACE_ROW_OFFSET((uint16_t)(_by) << 3) + ((uint16_t)(_bx) << 2)])
|
||||
|
||||
|
||||
void jlpGenericTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy) {
|
||||
uint8_t *d;
|
||||
const uint8_t *s;
|
||||
uint8_t row;
|
||||
|
||||
if (dst->pixels == NULL || src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
d = GENERIC_TILE_ROW0(dst->pixels, dstBx, dstBy);
|
||||
s = GENERIC_TILE_ROW0(src->pixels, srcBx, srcBy);
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3];
|
||||
d += SURFACE_BYTES_PER_ROW;
|
||||
s += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparent) {
|
||||
uint8_t *d;
|
||||
const uint8_t *s;
|
||||
uint8_t row;
|
||||
uint8_t col;
|
||||
uint8_t srcByte;
|
||||
uint8_t dstByte;
|
||||
uint8_t transHi = (uint8_t)((transparent & 0x0F) << 4);
|
||||
uint8_t transLo = (uint8_t)(transparent & 0x0F);
|
||||
uint8_t srcHi;
|
||||
uint8_t srcLo;
|
||||
|
||||
if (dst->pixels == NULL || src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
d = GENERIC_TILE_ROW0(dst->pixels, dstBx, dstBy);
|
||||
s = GENERIC_TILE_ROW0(src->pixels, srcBx, srcBy);
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
for (col = 0; col < TILE_BYTES_PER_ROW; col++) {
|
||||
srcByte = s[col];
|
||||
srcHi = (uint8_t)(srcByte & 0xF0);
|
||||
srcLo = (uint8_t)(srcByte & 0x0F);
|
||||
if (srcHi == transHi && srcLo == transLo) {
|
||||
continue;
|
||||
}
|
||||
dstByte = d[col];
|
||||
if (srcHi != transHi) {
|
||||
dstByte = (uint8_t)((dstByte & 0x0F) | srcHi);
|
||||
}
|
||||
if (srcLo != transLo) {
|
||||
dstByte = (uint8_t)((dstByte & 0xF0) | srcLo);
|
||||
}
|
||||
d[col] = dstByte;
|
||||
}
|
||||
d += SURFACE_BYTES_PER_ROW;
|
||||
s += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Colorize a 1bpp-ish mono shape (non-zero nibble -> fg, zero -> bg) into a
|
||||
// chunky tile, then paste via the jlpTilePaste op (IIgs asm / DOS generic). The
|
||||
// caller (jlTilePasteMono) marks the rect dirty, so this does not.
|
||||
void jlpGenericTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *monoTile, uint8_t fgColor, uint8_t bgColor) {
|
||||
jlTileT colored;
|
||||
uint8_t i;
|
||||
uint8_t srcByte;
|
||||
uint8_t hi;
|
||||
uint8_t lo;
|
||||
|
||||
fgColor &= 0x0Fu;
|
||||
bgColor &= 0x0Fu;
|
||||
for (i = 0u; i < TILE_BYTES; i++) {
|
||||
srcByte = monoTile[i];
|
||||
hi = (uint8_t)((srcByte >> 4) ? fgColor : bgColor);
|
||||
lo = (uint8_t)((srcByte & 0x0Fu) ? fgColor : bgColor);
|
||||
colored.pixels[i] = (uint8_t)((hi << 4) | lo);
|
||||
}
|
||||
jlpTilePaste(dst, bx, by, &colored.pixels[0]);
|
||||
}
|
||||
|
||||
|
||||
// Paste a chunky 8x8 tile (32 bytes, 4-bytes/row) into surface block (bx,by).
|
||||
void jlpGenericTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *chunkyTile) {
|
||||
uint8_t *d;
|
||||
const uint8_t *s = chunkyTile;
|
||||
uint8_t row;
|
||||
|
||||
if (dst->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
d = GENERIC_TILE_ROW0(dst->pixels, bx, by);
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3];
|
||||
d += SURFACE_BYTES_PER_ROW;
|
||||
s += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read surface block (bx,by) into a chunky 8x8 tile buffer (32 bytes).
|
||||
void jlpGenericTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, uint8_t *chunkyOut) {
|
||||
const uint8_t *s;
|
||||
uint8_t *d = chunkyOut;
|
||||
uint8_t row;
|
||||
|
||||
if (src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
s = GENERIC_TILE_ROW0(src->pixels, bx, by);
|
||||
for (row = 0; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||
d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3];
|
||||
s += SURFACE_BYTES_PER_ROW;
|
||||
d += TILE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void jlpGenericTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
||||
uint8_t doubled = (uint8_t)(((colorIndex & 0x0F) << 4) | (colorIndex & 0x0F));
|
||||
uint16_t pixelX = (uint16_t)((uint16_t)bx * TILE_PIXELS_PER_SIDE);
|
||||
uint16_t pixelY = (uint16_t)((uint16_t)by * TILE_PIXELS_PER_SIDE);
|
||||
uint8_t *row;
|
||||
uint8_t i;
|
||||
|
||||
if (s->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
row = &s->pixels[SURFACE_ROW_OFFSET(pixelY) + (pixelX >> 1)];
|
||||
for (i = 0; i < TILE_PIXELS_PER_SIDE; i++) {
|
||||
row[0] = doubled;
|
||||
row[1] = doubled;
|
||||
row[2] = doubled;
|
||||
row[3] = doubled;
|
||||
row += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
// references they actually use.
|
||||
//
|
||||
// The NinjaTrackerPlus replayer is Merlin32-assembled at build time to
|
||||
// ntpplayer.bin and LOADED FROM DISK at runtime (NTP_PATH). halAudioInit
|
||||
// ntpplayer.bin and LOADED FROM DISK at runtime (NTP_PATH). jlpAudioInit
|
||||
// reads those bytes into a fixed-bank Memory Manager handle and JSLs into
|
||||
// them through the self-modifying call stub below; NTP is bank-internal /
|
||||
// position-independent so it runs at whatever address Memory Manager picked.
|
||||
|
|
@ -28,7 +28,7 @@ typedef void *Pointer;
|
|||
#define attrFixed 0x4000
|
||||
#define attrLocked 0x8000
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "joey/audio.h"
|
||||
|
||||
// The ~34 KB NTP replayer is LOADED FROM DISK at runtime, not baked into
|
||||
|
|
@ -95,7 +95,7 @@ static uint32_t gSfxBase = 0;
|
|||
static bool gNTPReady = false;
|
||||
static bool gNTPPlaying = false;
|
||||
|
||||
// Per-slot config cache. halAudioPlaySfx's biggest cost is the
|
||||
// Per-slot config cache. jlpAudioPlaySfx's biggest cost is the
|
||||
// per-byte XOR-with-$80 loop over the entire sample (signed -> DOC's
|
||||
// unsigned format), which on a 4 KB sample is ~120 k cyc / ~43 ms.
|
||||
// Most callers play the same SFX repeatedly into the same
|
||||
|
|
@ -211,7 +211,7 @@ static bool loadNTP(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halAudioInit(void) {
|
||||
bool jlpAudioInit(void) {
|
||||
Handle modHandle;
|
||||
Handle sfxHandle;
|
||||
|
||||
|
|
@ -258,12 +258,12 @@ bool halAudioInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioShutdown(void) {
|
||||
void jlpAudioShutdown(void) {
|
||||
if (!gNTPReady) {
|
||||
return;
|
||||
}
|
||||
if (gNTPPlaying) {
|
||||
halAudioStopMod();
|
||||
jlpAudioStopMod();
|
||||
}
|
||||
// Silence every SFX slot before disposing the handles. NTP's DOC
|
||||
// IRQ vector points into the buffer we are about to free; if any
|
||||
|
|
@ -274,7 +274,7 @@ void halAudioShutdown(void) {
|
|||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < JOEY_AUDIO_SFX_SLOTS; i++) {
|
||||
halAudioStopSfx(i);
|
||||
jlpAudioStopSfx(i);
|
||||
gSfxSlotSample[i] = (const uint8_t *)0;
|
||||
gSfxSlotLength[i] = 0;
|
||||
gSfxSlotRateHz[i] = 0;
|
||||
|
|
@ -299,12 +299,12 @@ void halAudioShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
bool halAudioIsPlayingMod(void) {
|
||||
bool jlpAudioIsPlayingMod(void) {
|
||||
return gNTPReady && gNTPPlaying;
|
||||
}
|
||||
|
||||
|
||||
void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||
if (!gNTPReady || gModuleHandle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -313,7 +313,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
}
|
||||
// Drop any previous song before loading a new one.
|
||||
if (gNTPPlaying) {
|
||||
halAudioStopMod();
|
||||
jlpAudioStopMod();
|
||||
}
|
||||
BlockMove((Pointer)data, (Pointer)gModuleBase, length);
|
||||
|
||||
|
|
@ -341,7 +341,7 @@ void halAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||
unsigned char *sfx;
|
||||
uint32_t slotBase;
|
||||
uint32_t sampleAddr;
|
||||
|
|
@ -427,7 +427,7 @@ void halAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint1
|
|||
// fully-resident samples handed to NTPstreamsound. Live refill from
|
||||
// a callback would need a DOC-IRQ ring buffer, which is a separate
|
||||
// project. No-op until then.
|
||||
void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||
(void)slot;
|
||||
(void)fill;
|
||||
(void)ctx;
|
||||
|
|
@ -440,7 +440,7 @@ void halAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uin
|
|||
// already cooperate with the NTP MOD engine (SFX uses DOC oscillators
|
||||
// 14-21; NTP uses the lower ones). Loudness comes from the sample
|
||||
// amplitude, so no per-oscillator DOC volume plumbing is needed; the
|
||||
// host re-asserts active voices each frame via halAudioFrameTick (the
|
||||
// host re-asserts active voices each frame via jlpAudioFrameTick (the
|
||||
// registered tick fn), which sustains them between the one-shot triggers.
|
||||
#define TONE_RATE 26000U // DOC playback rate for synthesized PSG
|
||||
#define TONE_BUF_BYTES 2048 // ~2 frames of square wave @ TONE_RATE
|
||||
|
|
@ -476,7 +476,7 @@ static uint16_t toneFill(uint16_t halfPeriod, int8_t amp) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||
uint16_t halfPeriod;
|
||||
uint16_t len;
|
||||
int8_t amp;
|
||||
|
|
@ -485,7 +485,7 @@ void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
|||
return;
|
||||
}
|
||||
if (freqHz == 0u || atten >= AGI_ATTEN_OFF) {
|
||||
halAudioStopSfx(voice); // freq 0 / max atten = silence
|
||||
jlpAudioStopSfx(voice); // freq 0 / max atten = silence
|
||||
return;
|
||||
}
|
||||
// AGI attenuation 0..14 -> amplitude 127..8 (linear; loudness is in the
|
||||
|
|
@ -494,14 +494,14 @@ void halAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
|||
halfPeriod = (uint16_t)((TONE_RATE / 2u) / freqHz);
|
||||
len = toneFill(halfPeriod, amp);
|
||||
// gToneBuf is shared across voices and its contents change every call,
|
||||
// so bust halAudioPlaySfx's (ptr,len,rate) copy-cache to force a refresh.
|
||||
// so bust jlpAudioPlaySfx's (ptr,len,rate) copy-cache to force a refresh.
|
||||
gSfxSlotSample[voice] = (const uint8_t *)0;
|
||||
halAudioPlaySfx(voice, (const uint8_t *)gToneBuf, len, TONE_RATE);
|
||||
jlpAudioPlaySfx(voice, (const uint8_t *)gToneBuf, len, TONE_RATE);
|
||||
}
|
||||
|
||||
|
||||
void halAudioTone(uint16_t freqHz) {
|
||||
halAudioVoice(0u, freqHz, 0u); // single full-volume tone on voice 0
|
||||
void jlpAudioTone(uint16_t freqHz) {
|
||||
jlpAudioVoice(0u, freqHz, 0u); // single full-volume tone on voice 0
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -509,7 +509,7 @@ void halAudioTone(uint16_t freqHz) {
|
|||
// ride the host's per-frame jlAudioFrameTick pump rather than install a
|
||||
// raw VBL ISR (which would fight GS/OS heartbeat tasks). hz > 60 clamps to
|
||||
// per-frame; lower rates divide down.
|
||||
bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||
if (fn == (jlAudioTickFnT)0 || hz == 0u) {
|
||||
gTickFn = (jlAudioTickFnT)0;
|
||||
return false;
|
||||
|
|
@ -521,15 +521,15 @@ bool halAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioCriticalEnter(void) {
|
||||
void jlpAudioCriticalEnter(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioCriticalExit(void) {
|
||||
void jlpAudioCriticalExit(void) {
|
||||
}
|
||||
|
||||
|
||||
void halAudioStopMod(void) {
|
||||
void jlpAudioStopMod(void) {
|
||||
if (!gNTPReady || !gNTPPlaying) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -543,7 +543,7 @@ void halAudioStopMod(void) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioStopSfx(uint8_t slot) {
|
||||
void jlpAudioStopSfx(uint8_t slot) {
|
||||
uint8_t firstOsc;
|
||||
uint8_t i;
|
||||
|
||||
|
|
@ -566,9 +566,9 @@ void halAudioStopSfx(uint8_t slot) {
|
|||
}
|
||||
|
||||
|
||||
void halAudioFrameTick(void) {
|
||||
void jlpAudioFrameTick(void) {
|
||||
// NTP is DOC-IRQ driven; nothing to pump there. But a registered audio
|
||||
// tick (halAudioTickRegister) rides this per-frame hook to re-assert PSG
|
||||
// tick (jlpAudioTickRegister) rides this per-frame hook to re-assert PSG
|
||||
// voices and advance AGI-style sound at up to IIGS_FRAME_HZ.
|
||||
if (gTickFn != (jlAudioTickFnT)0) {
|
||||
gTickCount--;
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
//
|
||||
// DIRTY-WALK + PEI-SLAM PRESENT
|
||||
// -----------------------------
|
||||
// halPresent walks the per-row dirty bands maintained by drawing
|
||||
// jlpPresent walks the per-row dirty bands maintained by drawing
|
||||
// primitives in src/core/*.c. Fully-dirty rows go through the PEI
|
||||
// slam in src/port/iigs/peislam.s (~530 cyc/row, ~55% faster than
|
||||
// slam in src/iigs/peislam.s (~530 cyc/row, ~55% faster than
|
||||
// memcpy/MVN); partial-dirty rows use memcpy, which lowers
|
||||
// to MVN (7 cyc/byte) -- the fastest 65816 way to move bytes into
|
||||
// bank $E1 when the dirty band is too narrow to amortize the slam's
|
||||
|
|
@ -29,11 +29,11 @@
|
|||
#include <string.h>
|
||||
|
||||
#include "joey/debug.h"
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "surfaceInternal.h"
|
||||
|
||||
// Memory Manager surface for halBigAlloc / halBigFree (jlAlloc backing).
|
||||
// Same idiom as src/port/iigs/audio_full.c: Handles/pointers are void*,
|
||||
// Memory Manager surface for jlpBigAlloc / jlpBigFree (jlAlloc backing).
|
||||
// Same idiom as src/iigs/audio_full.c: Handles/pointers are void*,
|
||||
// the owner ID comes from MMStartUp(), and the attribute bit values are
|
||||
// Apple's Memory Manager constants. Only the bits we use are defined.
|
||||
#include <iigs/toolbox.h>
|
||||
|
|
@ -58,17 +58,16 @@ extern uint16_t iigsGetTickWord(void);
|
|||
extern uint16_t iigsReadHzParam(void);
|
||||
static uint16_t gFrameHz = 60u;
|
||||
|
||||
// hal.c is the single TU that calls into joeyDraw.asm. Cross-
|
||||
// platform draw.c / tile.c / etc. dispatch through halFast*
|
||||
// functions defined here; they never reference the asm symbols
|
||||
// directly. Keeping the asm externs in a single TU avoids the
|
||||
// link-time fragility seen when each cross-platform TU brought its
|
||||
// own asm extern.
|
||||
// hal.c is the single TU that calls into joeyDraw.asm. The IIgs jlp<Op>
|
||||
// macros in core/port.h expand to these asm entry points; cross-platform
|
||||
// draw.c / tile.c / etc. never reference the asm symbols directly. Keeping
|
||||
// the asm externs in a single TU avoids the link-time fragility seen when
|
||||
// each cross-platform TU brought its own asm extern.
|
||||
|
||||
// 32 KB stack-slam fill via AUXWRITE. ~25 ms full-screen.
|
||||
extern void iigsSurfaceClearInner(uint8_t *pixels, uint16_t fillWord);
|
||||
// Full-fill asm helper (partial leading byte + middle MVN + partial
|
||||
// trailing byte). Called by halFastFillRect below.
|
||||
// trailing byte). Called by the jlpFillRect macro (core/port.h).
|
||||
extern void iigsFillRectInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||
// 16 STA abs,X stores at fixed offsets along a 160-byte stride.
|
||||
// ~120 cyc per call.
|
||||
|
|
@ -94,11 +93,11 @@ extern void iigsDrawCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint1
|
|||
// Stage-to-SHR full upload: pixels (MVN $01->$E1), SCB, palette.
|
||||
// Asm uses post-MVN DBR=$E1 to do sta abs,Y for SCB/palette.
|
||||
// The asm performs the upload directly rather than a C-side memcpy,
|
||||
// which is unreliable when called from halPresent (DBR-state quirk
|
||||
// which is unreliable when called from jlpPresent (DBR-state quirk
|
||||
// after prior asm primitives).
|
||||
extern void iigsBlitStageToShr(const uint8_t *scbPtr, const uint16_t *palettePtr, uint16_t uploadFlags);
|
||||
// jlFloodFill walk results: written by iigsFloodWalkAndScansInner,
|
||||
// read back by halFastFloodWalkAndScans.
|
||||
// read back by the jlpFloodWalkAndScans macro (core/port.h).
|
||||
extern uint16_t gFloodSeedMatch;
|
||||
extern uint16_t gFloodLeftX;
|
||||
extern uint16_t gFloodRightX;
|
||||
|
|
@ -107,12 +106,12 @@ extern uint16_t gFloodRightX;
|
|||
// match decoders. Outputs to gFloodSeedMatch / gFloodLeftX / gFloodRightX.
|
||||
extern void iigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||
// One-shot init for the y*160 lookup table (gRowOffsetLut, 400 bytes
|
||||
// in DRAWPRIMS data). Called once from halInit. After this returns,
|
||||
// in DRAWPRIMS data). Called once from jlpInit. After this returns,
|
||||
// every asm primitive that needs row offset can do `lda >lut,x` instead
|
||||
// of the 7-instruction shift-add.
|
||||
extern void iigsInitRowLut(void);
|
||||
// Per-row MVN blit from $01:srcOffset to $E1:srcOffset for partial-
|
||||
// screen presents (halPresentRect). srcOffset is the byte offset
|
||||
// screen presents (the partial-rect present). srcOffset is the byte offset
|
||||
// within bank $01 of the FIRST byte to copy on the FIRST row;
|
||||
// subsequent rows are at srcOffset + 160, etc. ~9 cyc/byte vs
|
||||
// C memcpy's ~30 cyc/byte.
|
||||
|
|
@ -166,7 +165,7 @@ extern void iigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uint1
|
|||
// $C034 BORDER register: high nibble = beep/IRQ enables (preserve),
|
||||
// low nibble = border color index 0..15. Color 0 is the all-zero
|
||||
// palette entry by IIgs convention; we force the low nibble to 0
|
||||
// in halInit so the visible bezel matches the cleared SHR background.
|
||||
// in jlpInit so the visible bezel matches the cleared SHR background.
|
||||
#define BORDER_COLOR_MASK 0xF0
|
||||
|
||||
// ----- Module state -----
|
||||
|
|
@ -196,7 +195,6 @@ volatile uint16_t gPeiMvnCount; // narrow-extent present: MVN byte coun
|
|||
// iigsBlitStageToShr above (with dirty-row skip).
|
||||
|
||||
|
||||
|
||||
// Upload SCB / palette into bank-$E1 SHR memory only when the
|
||||
// matching dirty flag is set. Replaces a per-frame 712-byte memcmp
|
||||
// pair (~7 ms / frame) with a 2-cyc flag check.
|
||||
|
|
@ -226,7 +224,7 @@ static void uploadScbAndPaletteIfNeeded(const jlSurfaceT *src) {
|
|||
// pointer. The 65816 has no alignment fault, so the BIG_ALLOC_HDR-byte
|
||||
// offset applied to the master pointer is safe for any access pattern.
|
||||
// The returned memory is not zero-filled (matches malloc semantics).
|
||||
void *halBigAlloc(uint32_t bytes) {
|
||||
void *jlpBigAlloc(uint32_t bytes) {
|
||||
Handle h;
|
||||
uint8_t *base;
|
||||
|
||||
|
|
@ -241,13 +239,13 @@ void *halBigAlloc(uint32_t bytes) {
|
|||
DisposeHandle(h);
|
||||
return NULL;
|
||||
}
|
||||
// Stash the Handle in the leading slot so halBigFree can recover it.
|
||||
// Stash the Handle in the leading slot so jlpBigFree can recover it.
|
||||
*(Handle *)base = h;
|
||||
return base + BIG_ALLOC_HDR;
|
||||
}
|
||||
|
||||
|
||||
void halBigFree(void *p) {
|
||||
void jlpBigFree(void *p) {
|
||||
Handle h;
|
||||
|
||||
if (p == NULL) {
|
||||
|
|
@ -258,7 +256,7 @@ void halBigFree(void *p) {
|
|||
}
|
||||
|
||||
|
||||
bool halInit(const jlConfigT *config) {
|
||||
bool jlpInit(const jlConfigT *config) {
|
||||
(void)config;
|
||||
gPreviousNewVideo = *IIGS_NEWVIDEO_REG;
|
||||
gPreviousBorder = *IIGS_BORDER_REG;
|
||||
|
|
@ -269,9 +267,9 @@ bool halInit(const jlConfigT *config) {
|
|||
// write to $01/2000-9FFF mirrors to $E1 and the off-screen-buffer
|
||||
// illusion breaks (the user would see drawing in progress).
|
||||
*IIGS_SHADOW_REG = (uint8_t)(gPreviousShadow | SHADOW_INHIBIT_SHR_MASK);
|
||||
// SCB and palette are uploaded by halPresent's iigsBlitStageToShr
|
||||
// SCB and palette are uploaded by jlpPresent's iigsBlitStageToShr
|
||||
// (asm path, MVN to bank $E1). C-side memset/memcpy to bank $E1
|
||||
// is unreliable from halInit's calling context, so we don't try
|
||||
// is unreliable from jlpInit's calling context, so we don't try
|
||||
// it here -- the first present will set up SCB to 320 mode.
|
||||
iigsInitRowLut();
|
||||
gFrameHz = (iigsReadHzParam() == 1u) ? 50u : 60u;
|
||||
|
|
@ -280,12 +278,7 @@ bool halInit(const jlConfigT *config) {
|
|||
}
|
||||
|
||||
|
||||
const char *halLastError(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halPresent(const jlSurfaceT *src) {
|
||||
void jlpPresent(const jlSurfaceT *src) {
|
||||
if (src == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -306,7 +299,7 @@ void halPresent(const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
void halShutdown(void) {
|
||||
void jlpShutdown(void) {
|
||||
if (gModeSet) {
|
||||
*IIGS_NEWVIDEO_REG = gPreviousNewVideo;
|
||||
*IIGS_BORDER_REG = gPreviousBorder;
|
||||
|
|
@ -316,28 +309,19 @@ void halShutdown(void) {
|
|||
}
|
||||
|
||||
|
||||
// halFastSurfaceClear / halFastDrawLine / halFastDrawCircle /
|
||||
// halFastFillCircle / halFastTileCopy / halFastTileCopyMasked /
|
||||
// halFastTilePaste / halFastTileSnap / halFastTileFill /
|
||||
// halFastFloodWalk[AndScans] / halFastFloodScanRow /
|
||||
// halFastFloodScanAndPush all dispatch via macros in core/hal.h on
|
||||
// IIgs (#ifdef JOEYLIB_PLATFORM_IIGS block). Only halFastFillRect
|
||||
// remains a real function below because its partial-byte (nibble-
|
||||
// edge) handling is too gnarly for a macro.
|
||||
// The IIgs draw/tile/flood primitives (surfaceClear, drawLine, drawCircle,
|
||||
// fillCircle, fillRect, tileCopy / tileCopyMasked / tilePaste / tileSnap /
|
||||
// tileFill, floodWalkAndScans) are all dispatched via the jlp<Op> macros in
|
||||
// core/port.h (#ifdef JOEYLIB_PLATFORM_IIGS block); they expand to the
|
||||
// iigs*Inner asm entry points above, so no C wrapper functions live here.
|
||||
|
||||
|
||||
// halFastFillRect: macro-dispatched in core/hal.h, same as the other
|
||||
// halFast* primitives. The C wrapper that used to live here was kept
|
||||
// as load-bearing _ROOT mass to defeat linker bank fragility;
|
||||
// since the CORESYS migration drained _ROOT, the macro form is safe.
|
||||
|
||||
|
||||
uint8_t *halStageAllocPixels(void) {
|
||||
uint8_t *jlpStageAllocPixels(void) {
|
||||
return IIGS_STAGE_PIXELS;
|
||||
}
|
||||
|
||||
|
||||
void halStageFreePixels(uint8_t *pixels) {
|
||||
void jlpStageFreePixels(uint8_t *pixels) {
|
||||
(void)pixels;
|
||||
// Backing memory is hardware-pinned; nothing to free.
|
||||
}
|
||||
|
|
@ -346,102 +330,22 @@ void halStageFreePixels(uint8_t *pixels) {
|
|||
// IIgs is chunky-native: portData is unused. The chunky `pixels`
|
||||
// buffer at $01:2000 is the stage's pixel storage and the source for
|
||||
// jlStagePresent's PEI-slam to $E1.
|
||||
void *halSurfaceAllocPortData(jlSurfaceT *s, bool isStage) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePortData(jlSurfaceT *s, bool isStage, void *portData) {
|
||||
(void)s;
|
||||
(void)isStage;
|
||||
(void)portData;
|
||||
}
|
||||
|
||||
|
||||
// IIgs is chunky-native: the hal*Planes hooks are compiled out by
|
||||
// no-op macros in core/hal.h (gated on JOEYLIB_PLATFORM_IIGS), so no
|
||||
// no-op macros in core/port.h (gated on JOEYLIB_PLATFORM_IIGS), so no
|
||||
// stub functions are defined here.
|
||||
|
||||
|
||||
/* Phase 9 chunky reader hooks: IIgs reads from s->pixels just like
|
||||
* the legacy paths did. Same logic as the DOS port. */
|
||||
uint8_t halSamplePixel(const jlSurfaceT *s, int16_t x, int16_t y) {
|
||||
uint8_t byte = s->pixels[(uint16_t)y * SURFACE_BYTES_PER_ROW + ((uint16_t)x >> 1)];
|
||||
if (x & 1) return (uint8_t)(byte & 0x0Fu);
|
||||
return (uint8_t)((byte & 0xF0u) >> 4);
|
||||
}
|
||||
|
||||
|
||||
uint32_t halSurfaceHash(const jlSurfaceT *s) {
|
||||
uint16_t lo = 0xACE1u, hi = 0x1357u, blocks, n, v;
|
||||
const uint8_t *p;
|
||||
const uint16_t *w;
|
||||
uint8_t b;
|
||||
p = s->pixels;
|
||||
blocks = (uint16_t)(SURFACE_PIXELS_SIZE / 8);
|
||||
do {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
blocks--;
|
||||
} while (blocks > 0u);
|
||||
p = s->scb;
|
||||
for (n = 0; n < (uint16_t)SURFACE_HEIGHT; n++) {
|
||||
b = *p++; SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
w = &s->palette[0][0];
|
||||
for (n = 0; n < (uint16_t)SURFACE_PALETTE_ENTRIES; n++) {
|
||||
v = *w++;
|
||||
b = (uint8_t)((v >> 8) & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
b = (uint8_t)(v & 0xFFu); SURFACE_HASH_MIX_BYTE(lo, hi, b);
|
||||
}
|
||||
return ((uint32_t)hi << 16) | (uint32_t)lo;
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
||||
memcpy(dst->pixels, src->pixels, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
bool halSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||
return fread(dst->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
bool halSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||
return fwrite(src->pixels, 1, SURFACE_PIXELS_SIZE, fp) == SURFACE_PIXELS_SIZE;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfaceAllocPixels(void) {
|
||||
return (uint8_t *)calloc(1, SURFACE_PIXELS_SIZE);
|
||||
}
|
||||
|
||||
|
||||
void halSurfaceFreePixels(uint8_t *pixels) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *halSurfacePlanePtr(const jlSurfaceT *s, uint8_t planeIdx) {
|
||||
(void)s; (void)planeIdx;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// $C019 RDVBLBAR: bit 7 = 0 during vertical blank, 1 during active
|
||||
// scan. To produce a rising-edge wait (one VBL per call), first spin
|
||||
// while VBL is currently active (bit 7 = 0), then spin until VBL
|
||||
// fires again (bit 7 returns to 0). The IIgs SHR refresh is 60 Hz.
|
||||
void halWaitVBL(void) {
|
||||
void jlpWaitVBL(void) {
|
||||
while ((*IIGS_VBL_STATUS & VBL_BAR_BIT) == 0) {
|
||||
/* already in VBL: wait for active scan */;
|
||||
}
|
||||
|
|
@ -460,17 +364,17 @@ void halWaitVBL(void) {
|
|||
// than `gFrameCount++` because the post-increment can lower to
|
||||
// `inc |gFrameCount` (the only INC abs form on 65816 -- there is no
|
||||
// INC long-abs). With this file in the DRAWPRIMS load segment but
|
||||
// halFrameCount called from CORESYS via JSL, DBR isn't pointing at
|
||||
// jlpFrameCount called from CORESYS via JSL, DBR isn't pointing at
|
||||
// DRAWPRIMS's data bank, so the abs INC silently mutates the wrong
|
||||
// byte and the counter never advances. The explicit lda > / sta >
|
||||
// pattern uses long-mode addressing throughout, which is
|
||||
// DBR-independent.
|
||||
uint16_t halFrameCount(void) {
|
||||
uint16_t jlpFrameCount(void) {
|
||||
return iigsGetTickWord();
|
||||
}
|
||||
|
||||
|
||||
uint16_t halFrameHz(void) {
|
||||
uint16_t jlpFrameHz(void) {
|
||||
return gFrameHz;
|
||||
}
|
||||
|
||||
|
|
@ -480,6 +384,3 @@ uint16_t halFrameHz(void) {
|
|||
// gFrameHz (60 Hz). The 16-bit counter wraps every ~1090 sec, so
|
||||
// callers tracking deltas should use this for short intervals only
|
||||
// (sound event scheduling is fine).
|
||||
uint32_t halMillisElapsed(void) {
|
||||
return (uint32_t)((uint32_t)halFrameCount() * 1000u / (uint32_t)gFrameHz);
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
//
|
||||
// Release detection uses the IIe-inherited "any key currently down"
|
||||
// live flag at $C010 bit 7 (set by the keyboard scanner independently
|
||||
// of the strobe). Each halInputPoll drains pending strobe events to
|
||||
// of the strobe). Each jlpInputPoll drains pending strobe events to
|
||||
// pick up presses, then samples $C010: bit 7 == 0 means no
|
||||
// non-modifier key is physically held, and we wholesale-clear
|
||||
// gKeyState. readModifierKeys then re-asserts the modifiers from
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
// returns one signed 7-bit delta; $C027 bit 1 indicates whether the
|
||||
// next read will return X (0) or Y (1). On the Y read, $C024 bit 7
|
||||
// also encodes inverted button state (0 = pressed). We do exactly two
|
||||
// $C024 reads per halInputPoll, accumulating the deltas onto an
|
||||
// $C024 reads per jlpInputPoll, accumulating the deltas onto an
|
||||
// absolute position which is clamped to the surface rectangle. The
|
||||
// IIgs ADB MCU autopolls the mouse and queues fifos behind these
|
||||
// softswitches, so the per-frame two-read cadence keeps up with
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "port.h"
|
||||
#include "inputInternal.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
|
||||
// Cap on the per-poll keyboard-FIFO drain. The IIgs ADB queue is
|
||||
// small in practice; this is purely a defensive bound so a stuck
|
||||
// strobe can't spin halInputPoll forever.
|
||||
// strobe can't spin jlpInputPoll forever.
|
||||
#define KBD_DRAIN_GUARD 32u
|
||||
|
||||
// $C025 layout (IIgs Hardware Reference): bit 0 = shift, bit 1 = ctrl,
|
||||
|
|
@ -116,7 +116,7 @@ static int8_t thresholdPaddle(uint8_t v);
|
|||
|
||||
// ----- Module state -----
|
||||
|
||||
// ASCII -> jlKeyE, filled once at halInputInit. Runtime fill keeps
|
||||
// ASCII -> jlKeyE, filled once at jlpInputInit. Runtime fill keeps
|
||||
// lookup O(1) instead of a 40-plus-case switch.
|
||||
static uint8_t gAsciiToKey[ASCII_TABLE_SIZE];
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ static bool gJoyDisconnectLatched = false;
|
|||
// captured the last time the user called jlJoystickReset. Until
|
||||
// that's called, gJoyCenterValid is false and pollJoystick falls back
|
||||
// to the digital threshold mapping. gJoyRecalibrate is set by
|
||||
// halJoystickReset and cleared on the next successful poll, which
|
||||
// jlpJoystickReset and cleared on the next successful poll, which
|
||||
// captures the new center.
|
||||
// uint8_t (not bool) so the per-element stride is a known 1 byte.
|
||||
// Storage is still 0 or 1 either way.
|
||||
|
|
@ -254,7 +254,7 @@ static uint8_t gJoyCenterValid [JOYSTICK_COUNT];
|
|||
static uint8_t gJoyRecalibrate [JOYSTICK_COUNT];
|
||||
|
||||
|
||||
void halJoystickReset(jlJoystickE js) {
|
||||
void jlpJoystickReset(jlJoystickE js) {
|
||||
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -410,7 +410,7 @@ static void pollMouse(void) {
|
|||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
void jlpInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
buildAsciiTable();
|
||||
|
|
@ -425,7 +425,7 @@ void halInputInit(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
void jlpInputPoll(void) {
|
||||
uint8_t kbd;
|
||||
uint8_t ascii;
|
||||
uint8_t key;
|
||||
|
|
@ -479,6 +479,6 @@ void halInputPoll(void) {
|
|||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
void jlpInputShutdown(void) {
|
||||
(void)*IIGS_KBDSTRB;
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
; void iigsInitRowLut(void)
|
||||
;
|
||||
; One-shot init for the y*160 lookup table at gRowOffsetLut. C calls
|
||||
; this once from halInit; afterwards every primitive that needs y ->
|
||||
; this once from jlpInit; afterwards every primitive that needs y ->
|
||||
; row byte offset can do `lda gRowOffsetLut,x` (X = y*2) in a few cyc
|
||||
; instead of a shift-add chain. 200 entries x 2 bytes = 400 bytes.
|
||||
; ----------------------------------------------------------------
|
||||
|
|
@ -139,10 +139,10 @@ clrExit:
|
|||
; long,X path (6 cyc / 2 bytes). SP is relocated to the top of the $01
|
||||
; stage region ($2000..$9CFF); AUXWRITE ($C005) redirects the bank-$00
|
||||
; stack writes to the $01 stage bank, and SHR shadow is already
|
||||
; inhibited by halInit. ~1.5x faster.
|
||||
; inhibited by jlpInit. ~1.5x faster.
|
||||
;
|
||||
; SEI guards the AUXWRITE/SP-hijack window; SP is restored before CLI.
|
||||
; SAFE ONLY POST-halInit. Soft switches use LONG addressing to bank $E0
|
||||
; SAFE ONLY POST-jlpInit. Soft switches use LONG addressing to bank $E0
|
||||
; ($E0C005), NOT `sta $C005`: under the GS/OS Loader DBR=$08, a DBR-rel
|
||||
; `sta $C0xx` hits bank-$08 RAM and the switch silently never toggles, so
|
||||
; AUXWRITE stays off and the slam corrupts bank 0 instead of the $01
|
||||
|
|
@ -41,9 +41,9 @@ iigsGetTickWord:
|
|||
; and returns the raw value: 0 = NTSC (60 Hz), 1 = PAL (50 Hz).
|
||||
;
|
||||
; GetTick fires from the hardware VBL ISR, so its rate matches the
|
||||
; video field rate -- 60 Hz on NTSC, 50 Hz on PAL. halFrameHz must
|
||||
; video field rate -- 60 Hz on NTSC, 50 Hz on PAL. jlpFrameHz must
|
||||
; report whichever this machine actually runs so wall-clock math
|
||||
; (frames * 1000 / halFrameHz) is correct on both.
|
||||
; (frames * 1000 / jlpFrameHz) is correct on both.
|
||||
; ----------------------------------------------------------------
|
||||
.section .text.iigsReadHzParam,"ax"
|
||||
.globl iigsReadHzParam
|
||||
244
src/m68k/spriteEmitInterleaved68k.c
Normal file
244
src/m68k/spriteEmitInterleaved68k.c
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// 68k sprite codegen for the ST word-interleaved planar layout.
|
||||
//
|
||||
// Emits a position-independent, cdecl-callable DRAW routine that blits
|
||||
// one sprite into the ST planar buffer. The sprite's tileData is fixed
|
||||
// at compile time, so the chunky->planar (c2p) conversion and every
|
||||
// destination offset are constant-folded here; the emitted routine is a
|
||||
// flat unrolled chain of byte read-modify-writes with the plane bytes
|
||||
// and masks baked in as immediates.
|
||||
//
|
||||
// Calling convention (m68k cdecl, the routine is invoked through a
|
||||
// C function pointer by spriteCompiledDraw in spriteCompile.c):
|
||||
// void draw(uint8_t *groupBase);
|
||||
// groupBase = pd->base + y*160 + (x>>4)*8 (top-left 16-px group).
|
||||
// The single arg sits at 4(sp) on entry (0(sp) = return address). The
|
||||
// routine loads it into a0, advances a0 by 160 (ST_BYTES_PER_ROW) at
|
||||
// the start of each subsequent row, and RMWs plane bytes at d16(a0).
|
||||
// Only d0/a0 (cdecl scratch) are touched, so no callee-save is needed.
|
||||
//
|
||||
// ST planar layout: each 16-pixel group is 4 plane words (8 bytes) at
|
||||
// group_off + plane*2; the word's high byte (offset +0) holds the left
|
||||
// 8 pixels, the low byte (offset +1) the right 8. A byte-aligned sprite
|
||||
// tile column writes exactly one half (one plane byte) per plane, so the
|
||||
// per-plane work is a single byte RMW:
|
||||
// move.b disp(a0),d0
|
||||
// andi.b #~pop,d0 ; clear the opaque pixel bits
|
||||
// ori.b #planeByte,d0 ; set this plane's bits (omitted if zero)
|
||||
// move.b d0,disp(a0)
|
||||
// matching stSpriteDrawByteAligned in src/atarist/hal.c bit-for-bit.
|
||||
//
|
||||
// Only shifts 0 (x mod 16 == 0) and 1 (x mod 16 == 8) are byte-aligned
|
||||
// and compiled; other shifts return 0 so jlSpriteCompile records
|
||||
// SPRITE_NOT_COMPILED and the dispatcher routes non-aligned x through
|
||||
// the interpreted jlpSpriteDrawPlanes walker.
|
||||
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
#include "spriteEmitter.h"
|
||||
#include "spriteInternal.h"
|
||||
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
#define TILE_BYTES 32u // bytes per 8x8 4bpp tile
|
||||
#define TILE_BYTES_PER_ROW 4u // 8 pixels * 4 bits / 8
|
||||
#define TILE_PIXELS 8u
|
||||
#define ST_PLANAR_STRIDE 160u // 320 px * 4 planes / 8 bits
|
||||
#define ST_GROUP_BYTES 8u // 4 plane words per 16-px group
|
||||
|
||||
// 68k opcode words (big-endian), operands follow as separate words:
|
||||
// movea.l 4(sp),a0 : 206F 0004 (mode 101 = (d16,An); 205F would be (An)+)
|
||||
// adda.w #160,a0 : D0FC 00A0
|
||||
// move.b d16(a0),d0 : 1028 dddd
|
||||
// andi.b #imm,d0 : 0200 00ii
|
||||
// ori.b #imm,d0 : 0000 00ii
|
||||
// move.b d0,d16(a0) : 1140 dddd
|
||||
// move.b #imm,d16(a0): 117C 00ii dddd (fully-opaque column: direct store)
|
||||
// rts : 4E75
|
||||
#define OP_MOVEA_4SP_A0 0x206Fu
|
||||
#define OP_ADDA_W_A0 0xD0FCu
|
||||
#define OP_MOVE_B_A0_D0 0x1028u
|
||||
#define OP_ANDI_B_D0 0x0200u
|
||||
#define OP_ORI_B_D0 0x0000u
|
||||
#define OP_MOVE_B_D0_A0 0x1140u
|
||||
#define OP_MOVE_B_IMM_A0 0x117Cu
|
||||
#define OP_RTS 0x4E75u
|
||||
|
||||
// Emitted bytes for the trailing rts.
|
||||
#define EPILOGUE_BYTES 2u
|
||||
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void c2pColumn(const uint8_t *trp, uint8_t plane[4], uint8_t *opaqueOut);
|
||||
static uint32_t putWord(uint8_t *out, uint32_t cursor, uint16_t w);
|
||||
|
||||
|
||||
// ----- Emit helpers -----
|
||||
|
||||
// Convert one 8-pixel sprite tile column (4 chunky 4bpp bytes) into 4
|
||||
// plane bytes plus an opaque mask, bit 7 = leftmost pixel. Identical
|
||||
// pixel walk to stSpriteDrawByteAligned: hi(b0),lo(b0),hi(b1),lo(b1),
|
||||
// hi(b2),lo(b2),hi(b3),lo(b3) at bit positions 7..0.
|
||||
static void c2pColumn(const uint8_t *trp, uint8_t plane[4], uint8_t *opaqueOut) {
|
||||
uint8_t opaque;
|
||||
uint8_t i;
|
||||
|
||||
plane[0] = 0u;
|
||||
plane[1] = 0u;
|
||||
plane[2] = 0u;
|
||||
plane[3] = 0u;
|
||||
opaque = 0u;
|
||||
for (i = 0u; i < 8u; i++) {
|
||||
uint8_t nibble;
|
||||
uint8_t bit;
|
||||
|
||||
nibble = (i & 1u) ? (uint8_t)(trp[i >> 1] & 0x0Fu)
|
||||
: (uint8_t)(trp[i >> 1] >> 4);
|
||||
if (nibble != 0u) {
|
||||
bit = (uint8_t)(0x80u >> i);
|
||||
opaque = (uint8_t)(opaque | bit);
|
||||
if (nibble & 1u) { plane[0] = (uint8_t)(plane[0] | bit); }
|
||||
if (nibble & 2u) { plane[1] = (uint8_t)(plane[1] | bit); }
|
||||
if (nibble & 4u) { plane[2] = (uint8_t)(plane[2] | bit); }
|
||||
if (nibble & 8u) { plane[3] = (uint8_t)(plane[3] | bit); }
|
||||
}
|
||||
}
|
||||
*opaqueOut = opaque;
|
||||
}
|
||||
|
||||
|
||||
// Append a big-endian 16-bit word at cursor; returns the new cursor.
|
||||
static uint32_t putWord(uint8_t *out, uint32_t cursor, uint16_t w) {
|
||||
out[cursor] = (uint8_t)(w >> 8);
|
||||
out[cursor + 1u] = (uint8_t)(w & 0xFFu);
|
||||
return cursor + 2u;
|
||||
}
|
||||
|
||||
|
||||
// ----- Emit API -----
|
||||
|
||||
uint32_t spriteEmitDrawInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
uint32_t cursor;
|
||||
uint16_t wTiles;
|
||||
uint16_t srcH;
|
||||
uint16_t xMod16;
|
||||
int16_t row;
|
||||
int16_t tileCol;
|
||||
|
||||
if (shift > 1u) {
|
||||
return 0u;
|
||||
}
|
||||
wTiles = sp->widthTiles;
|
||||
srcH = (uint16_t)(sp->heightTiles * TILE_PIXELS);
|
||||
xMod16 = (uint16_t)(shift * 8u); // shift 0 -> 0, shift 1 -> 8
|
||||
|
||||
cursor = 0u;
|
||||
|
||||
// Prologue: movea.l 4(sp),a0
|
||||
if (cursor + 4u > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_MOVEA_4SP_A0);
|
||||
cursor = putWord(out, cursor, 0x0004u);
|
||||
|
||||
for (row = 0; row < (int16_t)srcH; row++) {
|
||||
uint16_t tileY = (uint16_t)((uint16_t)row >> 3);
|
||||
uint16_t inTileY = (uint16_t)((uint16_t)row & 7u);
|
||||
const uint8_t *tileRowBase = sp->tileData
|
||||
+ (uint32_t)tileY * wTiles * TILE_BYTES
|
||||
+ (uint32_t)inTileY * TILE_BYTES_PER_ROW;
|
||||
|
||||
if (row > 0) {
|
||||
// adda.w #160,a0 -- advance to this row's planar base.
|
||||
if (cursor + 4u > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_ADDA_W_A0);
|
||||
cursor = putWord(out, cursor, ST_PLANAR_STRIDE);
|
||||
}
|
||||
|
||||
for (tileCol = 0; tileCol < (int16_t)wTiles; tileCol++) {
|
||||
const uint8_t *trp = tileRowBase + (uint32_t)tileCol * TILE_BYTES;
|
||||
uint8_t plane[4];
|
||||
uint8_t opaque;
|
||||
uint16_t pixOff;
|
||||
uint16_t groupByteOff;
|
||||
uint16_t halfByte;
|
||||
uint8_t p;
|
||||
|
||||
c2pColumn(trp, plane, &opaque);
|
||||
if (opaque == 0u) {
|
||||
continue;
|
||||
}
|
||||
// Group + which byte half this 8-px column lands in, relative
|
||||
// to groupBase. All compile-time constant given the shift.
|
||||
pixOff = (uint16_t)(xMod16 + (uint16_t)tileCol * 8u);
|
||||
groupByteOff = (uint16_t)((uint16_t)(pixOff >> 4) * ST_GROUP_BYTES);
|
||||
halfByte = (uint16_t)((pixOff & 8u) ? 1u : 0u);
|
||||
|
||||
for (p = 0u; p < 4u; p++) {
|
||||
uint16_t disp = (uint16_t)(groupByteOff + (uint16_t)p * 2u + halfByte);
|
||||
|
||||
// Exact reservation: a conservative (worst-case) reserve would
|
||||
// make the emit pass -- which gets exactly the size the sizing
|
||||
// pass reported -- falsely overflow on the trailing shorter
|
||||
// writes. The final rts is checked separately below.
|
||||
if (opaque == 0xFFu) {
|
||||
// Fully opaque column: the whole byte becomes plane[p], so
|
||||
// store it directly -- no read / andi / ori needed.
|
||||
// move.b #plane[p],disp(a0) (6 bytes)
|
||||
if (cursor + 6u > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_IMM_A0);
|
||||
cursor = putWord(out, cursor, (uint16_t)plane[p]);
|
||||
cursor = putWord(out, cursor, disp);
|
||||
} else {
|
||||
uint32_t planeBytes = (plane[p] != 0u) ? 16u : 12u; // +ori.b only when bits set
|
||||
|
||||
if (cursor + planeBytes > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
// move.b disp(a0),d0
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_A0_D0);
|
||||
cursor = putWord(out, cursor, disp);
|
||||
// andi.b #~opaque,d0 (clear the opaque pixel bits)
|
||||
cursor = putWord(out, cursor, OP_ANDI_B_D0);
|
||||
cursor = putWord(out, cursor, (uint16_t)(uint8_t)(~opaque));
|
||||
// ori.b #plane[p],d0 (set this plane's bits; skip if none)
|
||||
if (plane[p] != 0u) {
|
||||
cursor = putWord(out, cursor, OP_ORI_B_D0);
|
||||
cursor = putWord(out, cursor, (uint16_t)plane[p]);
|
||||
}
|
||||
// move.b d0,disp(a0)
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_D0_A0);
|
||||
cursor = putWord(out, cursor, disp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rts
|
||||
if (cursor + EPILOGUE_BYTES > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_RTS);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
// Save/restore are not compiled on the ST yet -- the interpreted
|
||||
// jlpSpriteSavePlanes / jlpSpriteRestorePlanes path already meets the
|
||||
// IIgs perf floor (plane byte copies), so returning 0 routes them
|
||||
// there. The entry points exist only for the dispatcher's uniform API.
|
||||
uint32_t spriteEmitSaveInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitRestoreInterleaved68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
235
src/m68k/spriteEmitPlanar68k.c
Normal file
235
src/m68k/spriteEmitPlanar68k.c
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
// Planar 68k sprite codegen for the Amiga (4 separate bitplanes).
|
||||
//
|
||||
// Emits a position-independent, cdecl-callable DRAW routine that blits
|
||||
// one sprite into the Amiga's 4 bitplanes. The sprite's tileData is
|
||||
// fixed at compile time, so the chunky->planar (c2p) conversion and
|
||||
// every destination offset are constant-folded here; the emitted
|
||||
// routine is a flat unrolled chain of byte writes with the plane bytes
|
||||
// and masks baked in as immediates.
|
||||
//
|
||||
// Calling convention (m68k cdecl, invoked through a C function pointer
|
||||
// by spriteCompiledDraw in spriteCompile.c):
|
||||
// void draw(uint8_t *p0, uint8_t *p1, uint8_t *p2, uint8_t *p3);
|
||||
// each p_i = plane_base + y*40 + x/8 (top-left byte of plane i).
|
||||
// Args sit at 4/8/12/16(sp) on entry. The routine saves a2/a3, loads
|
||||
// the four plane pointers into a0..a3, then writes plane bytes at
|
||||
// d16(a_i) with the offset (row*40 + byteCol) baked in -- no per-row
|
||||
// pointer advance (offsets stay well inside the 8000-byte plane and the
|
||||
// signed 16-bit displacement range for any on-surface sprite). Only
|
||||
// d0/a0/a1 are cdecl scratch; a2/a3 are saved/restored via movem.
|
||||
//
|
||||
// ONLY shift 0 (x mod 8 == 0, byte-aligned) is compiled. For shift 0 a
|
||||
// sprite tile column maps to exactly one plane byte (no sub-byte spill),
|
||||
// so the per-plane work mirrors the ST byte path:
|
||||
// fully opaque (mask==0xFF): move.b #plane[p],off(a_i) (direct store)
|
||||
// mixed: move.b off(a_i),d0; andi.b #~mask,d0; ori.b #plane[p],d0;
|
||||
// move.b d0,off(a_i)
|
||||
// matching jlpSpriteDrawPlanes in src/amiga/hal.c for the shift-0 case.
|
||||
// Shifts 1..7 return 0 so jlSpriteCompile records SPRITE_NOT_COMPILED
|
||||
// and the dispatcher routes non-aligned x through the interpreted
|
||||
// jlpSpriteDrawPlanes walker (which handles the two-byte sub-byte spill).
|
||||
|
||||
#include "joey/sprite.h"
|
||||
#include "joey/surface.h"
|
||||
#include "spriteEmitter.h"
|
||||
#include "spriteInternal.h"
|
||||
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
#define TILE_BYTES 32u // bytes per 8x8 4bpp tile
|
||||
#define TILE_BYTES_PER_ROW 4u // 8 pixels * 4 bits / 8
|
||||
#define TILE_PIXELS 8u
|
||||
#define AMIGA_PLANE_STRIDE (SURFACE_WIDTH / 8) // 40 bytes/plane-row
|
||||
#define AMIGA_PLANES 4u
|
||||
|
||||
// 68k opcode words (big-endian). Per-plane move.b variants are indexed
|
||||
// by plane (a0..a3); andi.b/ori.b only ever target d0.
|
||||
// movem.l a2-a3,-(sp) : 48E7 0030 (predec mask is bit-reversed: a3=4,a2=5)
|
||||
// movem.l (sp)+,a2-a3 : 4CDF 0C00 (postinc mask: a2=10,a3=11)
|
||||
// movea.l d16(sp),aN : 206F/226F/246F/266F dddd
|
||||
// move.b d16(aN),d0 : 1028/1029/102A/102B dddd
|
||||
// andi.b #imm,d0 : 0200 00ii
|
||||
// ori.b #imm,d0 : 0000 00ii
|
||||
// move.b d0,d16(aN) : 1140/1340/1540/1740 dddd
|
||||
// move.b #imm,d16(aN): 117C/137C/157C/177C 00ii dddd
|
||||
// rts : 4E75
|
||||
#define OP_MOVEM_SAVE 0x48E7u
|
||||
#define OP_MOVEM_SAVE_M 0x0030u
|
||||
#define OP_MOVEM_REST 0x4CDFu
|
||||
#define OP_MOVEM_REST_M 0x0C00u
|
||||
#define OP_ANDI_B_D0 0x0200u
|
||||
#define OP_ORI_B_D0 0x0000u
|
||||
#define OP_RTS 0x4E75u
|
||||
|
||||
static const uint16_t OP_MOVEA_SP_AN[AMIGA_PLANES] = { 0x206Fu, 0x226Fu, 0x246Fu, 0x266Fu };
|
||||
static const uint16_t OP_MOVE_B_AN_D0[AMIGA_PLANES] = { 0x1028u, 0x1029u, 0x102Au, 0x102Bu };
|
||||
static const uint16_t OP_MOVE_B_D0_AN[AMIGA_PLANES] = { 0x1140u, 0x1340u, 0x1540u, 0x1740u };
|
||||
static const uint16_t OP_MOVE_B_IMM_AN[AMIGA_PLANES] = { 0x117Cu, 0x137Cu, 0x157Cu, 0x177Cu };
|
||||
|
||||
// Stack displacement of arg p_i AFTER the prologue pushes a2/a3 (8 bytes):
|
||||
// entry 4/8/12/16(sp) -> 12/16/20/24(sp).
|
||||
#define ARG_DISP(i) (uint16_t)(12u + (uint16_t)(i) * 4u)
|
||||
|
||||
#define PROLOGUE_BYTES 20u // movem(4) + 4 * movea.l(4)
|
||||
#define EPILOGUE_BYTES 6u // movem(4) + rts(2)
|
||||
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void c2pColumn(const uint8_t *trp, uint8_t plane[4], uint8_t *opaqueOut);
|
||||
static uint32_t putWord(uint8_t *out, uint32_t cursor, uint16_t w);
|
||||
|
||||
|
||||
// ----- Emit helpers -----
|
||||
|
||||
// Convert one 8-pixel sprite tile column (4 chunky 4bpp bytes) into 4
|
||||
// plane bytes plus an opaque mask, bit 7 = leftmost pixel. Identical
|
||||
// pixel walk to jlpSpriteDrawPlanes' c2p loop.
|
||||
static void c2pColumn(const uint8_t *trp, uint8_t plane[4], uint8_t *opaqueOut) {
|
||||
uint8_t opaque;
|
||||
uint8_t i;
|
||||
|
||||
plane[0] = 0u;
|
||||
plane[1] = 0u;
|
||||
plane[2] = 0u;
|
||||
plane[3] = 0u;
|
||||
opaque = 0u;
|
||||
for (i = 0u; i < 8u; i++) {
|
||||
uint8_t nibble;
|
||||
uint8_t bit;
|
||||
|
||||
nibble = (i & 1u) ? (uint8_t)(trp[i >> 1] & 0x0Fu)
|
||||
: (uint8_t)(trp[i >> 1] >> 4);
|
||||
if (nibble != 0u) {
|
||||
bit = (uint8_t)(0x80u >> i);
|
||||
opaque = (uint8_t)(opaque | bit);
|
||||
if (nibble & 1u) { plane[0] = (uint8_t)(plane[0] | bit); }
|
||||
if (nibble & 2u) { plane[1] = (uint8_t)(plane[1] | bit); }
|
||||
if (nibble & 4u) { plane[2] = (uint8_t)(plane[2] | bit); }
|
||||
if (nibble & 8u) { plane[3] = (uint8_t)(plane[3] | bit); }
|
||||
}
|
||||
}
|
||||
*opaqueOut = opaque;
|
||||
}
|
||||
|
||||
|
||||
// Append a big-endian 16-bit word at cursor; returns the new cursor.
|
||||
static uint32_t putWord(uint8_t *out, uint32_t cursor, uint16_t w) {
|
||||
out[cursor] = (uint8_t)(w >> 8);
|
||||
out[cursor + 1u] = (uint8_t)(w & 0xFFu);
|
||||
return cursor + 2u;
|
||||
}
|
||||
|
||||
|
||||
// ----- Emit API -----
|
||||
|
||||
uint32_t spriteEmitDrawPlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
uint32_t cursor;
|
||||
uint16_t wTiles;
|
||||
uint16_t srcH;
|
||||
uint8_t i;
|
||||
int16_t row;
|
||||
int16_t byteCol;
|
||||
|
||||
if (shift != 0u) {
|
||||
return 0u;
|
||||
}
|
||||
wTiles = sp->widthTiles;
|
||||
srcH = (uint16_t)(sp->heightTiles * TILE_PIXELS);
|
||||
|
||||
cursor = 0u;
|
||||
|
||||
// Prologue: save a2/a3, load p0..p3 into a0..a3.
|
||||
if (cursor + PROLOGUE_BYTES > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_MOVEM_SAVE);
|
||||
cursor = putWord(out, cursor, OP_MOVEM_SAVE_M);
|
||||
for (i = 0u; i < AMIGA_PLANES; i++) {
|
||||
cursor = putWord(out, cursor, OP_MOVEA_SP_AN[i]);
|
||||
cursor = putWord(out, cursor, ARG_DISP(i));
|
||||
}
|
||||
|
||||
for (row = 0; row < (int16_t)srcH; row++) {
|
||||
uint16_t tileY = (uint16_t)((uint16_t)row >> 3);
|
||||
uint16_t inTileY = (uint16_t)((uint16_t)row & 7u);
|
||||
const uint8_t *tileRowBase = sp->tileData
|
||||
+ (uint32_t)tileY * wTiles * TILE_BYTES
|
||||
+ (uint32_t)inTileY * TILE_BYTES_PER_ROW;
|
||||
uint16_t rowOff = (uint16_t)((uint16_t)row * AMIGA_PLANE_STRIDE);
|
||||
|
||||
for (byteCol = 0; byteCol < (int16_t)wTiles; byteCol++) {
|
||||
const uint8_t *trp = tileRowBase + (uint32_t)byteCol * TILE_BYTES;
|
||||
uint8_t plane[4];
|
||||
uint8_t opaque;
|
||||
uint16_t off;
|
||||
|
||||
c2pColumn(trp, plane, &opaque);
|
||||
if (opaque == 0u) {
|
||||
continue;
|
||||
}
|
||||
off = (uint16_t)(rowOff + (uint16_t)byteCol);
|
||||
|
||||
for (i = 0u; i < AMIGA_PLANES; i++) {
|
||||
if (opaque == 0xFFu) {
|
||||
// Fully opaque column: store the plane byte directly.
|
||||
// move.b #plane[i],off(a_i) (6 bytes)
|
||||
if (cursor + 6u > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_IMM_AN[i]);
|
||||
cursor = putWord(out, cursor, (uint16_t)plane[i]);
|
||||
cursor = putWord(out, cursor, off);
|
||||
} else {
|
||||
// Exact reservation (see ST emitter): a worst-case reserve
|
||||
// would falsely overflow the emit pass on trailing shorter
|
||||
// writes. +ori.b only when this plane sets bits.
|
||||
uint32_t planeBytes = (plane[i] != 0u) ? 16u : 12u;
|
||||
|
||||
if (cursor + planeBytes > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
// move.b off(a_i),d0
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_AN_D0[i]);
|
||||
cursor = putWord(out, cursor, off);
|
||||
// andi.b #~opaque,d0
|
||||
cursor = putWord(out, cursor, OP_ANDI_B_D0);
|
||||
cursor = putWord(out, cursor, (uint16_t)(uint8_t)(~opaque));
|
||||
// ori.b #plane[i],d0 (skip if no bits)
|
||||
if (plane[i] != 0u) {
|
||||
cursor = putWord(out, cursor, OP_ORI_B_D0);
|
||||
cursor = putWord(out, cursor, (uint16_t)plane[i]);
|
||||
}
|
||||
// move.b d0,off(a_i)
|
||||
cursor = putWord(out, cursor, OP_MOVE_B_D0_AN[i]);
|
||||
cursor = putWord(out, cursor, off);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Epilogue: restore a2/a3, return.
|
||||
if (cursor + EPILOGUE_BYTES > cap) {
|
||||
return SPRITE_EMIT_OVERFLOW;
|
||||
}
|
||||
cursor = putWord(out, cursor, OP_MOVEM_REST);
|
||||
cursor = putWord(out, cursor, OP_MOVEM_REST_M);
|
||||
cursor = putWord(out, cursor, OP_RTS);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
// Save/restore are not compiled on the Amiga yet -- the interpreted
|
||||
// jlpSpriteSavePlanes / jlpSpriteRestorePlanes path handles them. The
|
||||
// entry points exist only for the dispatcher's uniform API.
|
||||
uint32_t spriteEmitSavePlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
|
||||
uint32_t spriteEmitRestorePlanar68k(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||
(void)out; (void)cap; (void)sp; (void)shift;
|
||||
return 0u;
|
||||
}
|
||||
|
|
@ -26,15 +26,15 @@
|
|||
| uint8_t fb2, uint8_t fb3);
|
||||
|
|
||||
| Fill ONE planar row across 4 planes -- the per-row body of
|
||||
| halFillRectPlanes lifted into asm. Each pN points at the leading
|
||||
| jlpFillRect lifted into asm. Each pN points at the leading
|
||||
| byte (already advanced by planeBase + y*40 + byteFirst on the C
|
||||
| side). leftMask and rightMask are the partial-byte masks for the
|
||||
| left/right edges; numMid is the count of full bytes between them.
|
||||
| fbN is 0x00 or 0xFF, the per-plane fill byte (caller pre-classifies
|
||||
| (colorIndex >> N) & 1 -> 0xFF or 0x00).
|
||||
|
|
||||
| Used by Amiga halFastFillCircle (one call per scanline span) and
|
||||
| Amiga halFillRectPlanes (one call per row of the rect). Replaces
|
||||
| Used by Amiga jlpFillCircle (one call per scanline span) and
|
||||
| Amiga jlpFillRect (one call per row of the rect). Replaces
|
||||
| the C inner loop whose ~13 cyc/byte was the gating cost on
|
||||
| jlFillCircle r=40 even after C-side inlining.
|
||||
|
|
||||
Loading…
Add table
Reference in a new issue