Start of DVX target!
This commit is contained in:
parent
2c8d726ed7
commit
17343a0b4f
61 changed files with 3560 additions and 272 deletions
14
Makefile
14
Makefile
|
|
@ -7,6 +7,7 @@
|
||||||
# make atarist builds Atari ST only
|
# make atarist builds Atari ST only
|
||||||
# make dos builds MS-DOS only
|
# make dos builds MS-DOS only
|
||||||
# make x68000 builds Sharp X68000 only
|
# make x68000 builds Sharp X68000 only
|
||||||
|
# make dvx builds DVX (JoeyLib programs as DVX desktop programs)
|
||||||
# make docs renders the manual to build/docs (HTML + PDF)
|
# make docs renders the manual to build/docs (HTML + PDF)
|
||||||
# make verify builds every port, then runs every gate (needs emulators)
|
# make verify builds every port, then runs every gate (needs emulators)
|
||||||
# make verify-audio runs every port's audio gate (needs emulators)
|
# make verify-audio runs every port's audio gate (needs emulators)
|
||||||
|
|
@ -27,6 +28,8 @@ HAVE_AMIGA := $(if $(wildcard $(AMIGA_CC)),1)
|
||||||
HAVE_ATARIST := $(if $(wildcard $(ST_CC)),1)
|
HAVE_ATARIST := $(if $(wildcard $(ST_CC)),1)
|
||||||
HAVE_DOS := $(if $(wildcard $(DOS_CC)),1)
|
HAVE_DOS := $(if $(wildcard $(DOS_CC)),1)
|
||||||
HAVE_X68000 := $(if $(wildcard $(X68K_ROOT)/bin/m68k-xelf-gcc),1)
|
HAVE_X68000 := $(if $(wildcard $(X68K_ROOT)/bin/m68k-xelf-gcc),1)
|
||||||
|
# DVX needs the DOS compiler and the DVX SDK the installer copies.
|
||||||
|
HAVE_DVX := $(if $(and $(wildcard $(DOS_CC)),$(wildcard $(DVX_SDK)/sdk.mk)),1)
|
||||||
|
|
||||||
ALL_TARGETS :=
|
ALL_TARGETS :=
|
||||||
ifeq ($(HAVE_IIGS),1)
|
ifeq ($(HAVE_IIGS),1)
|
||||||
|
|
@ -44,8 +47,11 @@ endif
|
||||||
ifeq ($(HAVE_X68000),1)
|
ifeq ($(HAVE_X68000),1)
|
||||||
ALL_TARGETS += x68000
|
ALL_TARGETS += x68000
|
||||||
endif
|
endif
|
||||||
|
ifeq ($(HAVE_DVX),1)
|
||||||
|
ALL_TARGETS += dvx
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 blank-check tools clean help status verify verify-audio verify-serial docs
|
.PHONY: all iigs iigs-disk iigs-verify amiga atarist dos x68000 dvx blank-check tools clean help status verify verify-audio verify-serial docs
|
||||||
|
|
||||||
all: $(ALL_TARGETS) tools
|
all: $(ALL_TARGETS) tools
|
||||||
ifeq ($(ALL_TARGETS),)
|
ifeq ($(ALL_TARGETS),)
|
||||||
|
|
@ -77,6 +83,9 @@ dos:
|
||||||
x68000:
|
x68000:
|
||||||
@$(MAKE) -f $(REPO_DIR)/make/x68000.mk
|
@$(MAKE) -f $(REPO_DIR)/make/x68000.mk
|
||||||
|
|
||||||
|
dvx:
|
||||||
|
@$(MAKE) -f $(REPO_DIR)/make/dvx.mk
|
||||||
|
|
||||||
# Compile + link-check the new-port template (src/blank/blank.c) with the host
|
# 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.
|
# cc. No toolchain needed; keeps the template from bit-rotting as the lib grows.
|
||||||
blank-check:
|
blank-check:
|
||||||
|
|
@ -120,9 +129,10 @@ status:
|
||||||
@echo " atarist: $(if $(HAVE_ATARIST),yes,NO)"
|
@echo " atarist: $(if $(HAVE_ATARIST),yes,NO)"
|
||||||
@echo " dos: $(if $(HAVE_DOS),yes,NO)"
|
@echo " dos: $(if $(HAVE_DOS),yes,NO)"
|
||||||
@echo " x68000: $(if $(HAVE_X68000),yes,NO)"
|
@echo " x68000: $(if $(HAVE_X68000),yes,NO)"
|
||||||
|
@echo " dvx: $(if $(HAVE_DVX),yes,NO)"
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Targets: iigs amiga atarist dos x68000 all clean status help"
|
@echo "Targets: iigs amiga atarist dos x68000 dvx all clean status help"
|
||||||
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
@echo " iigs-disk iigs-verify (IIgs MAME visual check)"
|
||||||
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
@echo " blank-check (host-cc smoke test of the new-port template + gnu89 lint)"
|
||||||
@echo " verify (build every port, then run every gate; PORTS= subset)"
|
@echo " verify (build every port, then run every gate; PORTS= subset)"
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -1,13 +1,15 @@
|
||||||
# JoeyLib
|
# JoeyLib
|
||||||
|
|
||||||
A unified C game-development library targeting five early 16-bit
|
A unified C game-development library targeting five early 16-bit
|
||||||
platforms from a single codebase:
|
platforms, and one DOS desktop, from a single codebase:
|
||||||
|
|
||||||
- Apple IIgs (reference platform)
|
- Apple IIgs (reference platform)
|
||||||
- Commodore Amiga (A500 / 68000 baseline)
|
- Commodore Amiga (A500 / 68000 baseline)
|
||||||
- Atari ST (STF / 68000 baseline)
|
- Atari ST (STF / 68000 baseline)
|
||||||
- MS-DOS (386 / VGA, DJGPP)
|
- MS-DOS (386 / VGA, DJGPP)
|
||||||
- Sharp X68000 (68000 @ 10 MHz, Human68k, elf2x68k)
|
- Sharp X68000 (68000 @ 10 MHz, Human68k, elf2x68k)
|
||||||
|
- DVX (the DOS desktop GUI in `../dvxgui`: each program a windowed `.app`,
|
||||||
|
built with the DOS compiler against DVX's SDK)
|
||||||
|
|
||||||
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
|
The Apple IIgs defines the capability ceiling. Stronger platforms coast.
|
||||||
Hot paths are hand-written assembly per port; the public API is C.
|
Hot paths are hand-written assembly per port; the public API is C.
|
||||||
|
|
@ -46,7 +48,11 @@ the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
||||||
`sertest`, `save`, `tilebank`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
`sertest`, `save`, `tilebank`, `joy`, `sprite`, `audio`, `uber`, `adventure`,
|
||||||
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
`adventure2`, `agi`, `spacetaxi`) for each. The IIgs is the exception:
|
||||||
`make iigs` builds the library, and `make iigs-disk` builds its examples and
|
`make iigs` builds the library, and `make iigs-disk` builds its examples and
|
||||||
packages them onto a disk image.
|
packages them onto a disk image. DVX needs the `dvx` installer component,
|
||||||
|
which copies DVX and its SDK from a built DVX tree
|
||||||
|
(`./install.sh --dvx-dir PATH install dvx`; the default is `../dvxgui`); its
|
||||||
|
examples land in `build/dvx/bin/apps/joeylib/<name>/`, laid out as they
|
||||||
|
install in DVX.
|
||||||
|
|
||||||
Space Taxi's levels come from the original Commodore 64 game, which is not in
|
Space Taxi's levels come from the original Commodore 64 game, which is not in
|
||||||
the repository. Without `stuff/spacetaxi/raw.bin` the build compiles the
|
the repository. Without `stuff/spacetaxi/raw.bin` the build compiles the
|
||||||
|
|
@ -63,6 +69,7 @@ the emulators and their firmware:
|
||||||
./joey-run build/dos/bin/DRAW.EXE
|
./joey-run build/dos/bin/DRAW.EXE
|
||||||
./joey-run --port amiga draw
|
./joey-run --port amiga draw
|
||||||
./joey-run --headless --seconds 30 --snapshot draw.png --port atarist draw
|
./joey-run --headless --seconds 30 --snapshot draw.png --port atarist draw
|
||||||
|
./joey-run --port dvx staxi # DVX in DOSBox, booting into the program
|
||||||
```
|
```
|
||||||
|
|
||||||
Four files are never downloaded -- the Amiga Kickstart, the Amiga
|
Four files are never downloaded -- the Amiga Kickstart, the Amiga
|
||||||
|
|
@ -80,6 +87,7 @@ make amiga
|
||||||
make atarist
|
make atarist
|
||||||
make dos
|
make dos
|
||||||
make x68000
|
make x68000
|
||||||
|
make dvx
|
||||||
```
|
```
|
||||||
|
|
||||||
Every fragment also answers to a bare `clean`, and every fragment that builds
|
Every fragment also answers to a bare `clean`, and every fragment that builds
|
||||||
|
|
@ -99,7 +107,9 @@ logs a surface hash per op. `tools/diff-uber-hashes <reference-log> <test-log>`
|
||||||
compares two `joeylog.txt` captures and exits non-zero on any mismatch. The
|
compares two `joeylog.txt` captures and exits non-zero on any mismatch. The
|
||||||
frozen references live in `tests/goldens/uber/`. All five ports currently agree
|
frozen references live in `tests/goldens/uber/`. All five ports currently agree
|
||||||
on all 51 hashes; the X68000 has no golden of its own and is diffed against
|
on all 51 hashes; the X68000 has no golden of its own and is diffed against
|
||||||
`tests/goldens/uber/iigs.txt`, which it matches exactly:
|
`tests/goldens/uber/iigs.txt`, which it matches exactly, and DVX, which shows
|
||||||
|
the DOS stage, is diffed against `tests/goldens/uber/dos.txt`
|
||||||
|
(`scripts/bench-dvx.sh`, then `tools/diff-uber-hashes`):
|
||||||
|
|
||||||
```
|
```
|
||||||
make -f make/x68000.mk x68000-verify-golden # ~70 min under the patched MAME
|
make -f make/x68000.mk x68000-verify-golden # ~70 min under the patched MAME
|
||||||
|
|
@ -119,10 +129,12 @@ src/core/ portable library code
|
||||||
src/generic/ portable-C default for every overridable op
|
src/generic/ portable-C default for every overridable op
|
||||||
src/codegen/ shared sprite-codegen staging / compile machinery
|
src/codegen/ shared sprite-codegen staging / compile machinery
|
||||||
src/<machine>/ per-machine HAL + per-CPU sprite emitter
|
src/<machine>/ per-machine HAL + per-CPU sprite emitter
|
||||||
(iigs, amiga, atarist, dos, x68000)
|
(iigs, amiga, atarist, dos, x68000, dvx)
|
||||||
src/m68k/ 68k code shared across machines (planar and word-
|
src/m68k/ 68k code shared across machines (planar and word-
|
||||||
interleaved sprite emitters; surface68k.s, which is
|
interleaved sprite emitters; surface68k.s, which is
|
||||||
linked by Amiga and Atari ST only)
|
linked by Amiga and Atari ST only)
|
||||||
|
src/x86/ x86 code DOS and DVX share (the sprite emitter, the
|
||||||
|
whole-map tile walker, the set-1 scan code table)
|
||||||
src/blank/ copy-to-start template for a new port
|
src/blank/ copy-to-start template for a new port
|
||||||
tools/assetbake/ PNG -> native .tbk / .spr baker (Python)
|
tools/assetbake/ PNG -> native .tbk / .spr baker (Python)
|
||||||
tools/joeymod/ Protracker .MOD converter (passthrough or .NTP)
|
tools/joeymod/ Protracker .MOD converter (passthrough or .NTP)
|
||||||
|
|
@ -187,7 +199,7 @@ void jlRandomSeed (uint32_t seed);
|
||||||
|
|
||||||
All surfaces are 320x200 16-color images with a 200-entry SCB table
|
All surfaces are 320x200 16-color images with a 200-entry SCB table
|
||||||
and 16 palettes of 16 `$0RGB` colors. In-memory storage is
|
and 16 palettes of 16 `$0RGB` colors. In-memory storage is
|
||||||
target-native: chunky 4bpp packed on IIgs and DOS, native planar
|
target-native: chunky 4bpp packed on IIgs, DOS and DVX, native planar
|
||||||
(separate bitplanes on Amiga and Sharp X68000, word-interleaved planes
|
(separate bitplanes on Amiga and Sharp X68000, word-interleaved planes
|
||||||
on Atari ST) on the 68k ports. The public API speaks in color indices
|
on Atari ST) on the 68k ports. The public API speaks in color indices
|
||||||
(0..15) and hides the storage format.
|
(0..15) and hides the storage format.
|
||||||
|
|
@ -648,7 +660,7 @@ from compiler-predefined macros is a fallback. Game code can
|
||||||
conditionally compile on these:
|
conditionally compile on these:
|
||||||
|
|
||||||
```
|
```
|
||||||
JOEYLIB_PLATFORM_IIGS / _AMIGA / _ATARIST / _DOS // exactly one defined
|
JOEYLIB_PLATFORM_IIGS / _AMIGA / _ATARIST / _DOS / _X68000 / _DVX // exactly one defined
|
||||||
JOEYLIB_CPU_65816 / _68000 / _X86
|
JOEYLIB_CPU_65816 / _68000 / _X86
|
||||||
JOEYLIB_ENDIAN_LITTLE / _BIG
|
JOEYLIB_ENDIAN_LITTLE / _BIG
|
||||||
JOEYLIB_NATIVE_CHUNKY / _NATIVE_PLANAR
|
JOEYLIB_NATIVE_CHUNKY / _NATIVE_PLANAR
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,8 @@ deactivates.
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
This is called from the audio engine's refill context -- the main thread on DOS
|
This is called from the audio engine's refill context -- the main thread on DOS
|
||||||
and ST, but the *audio interrupt* on Amiga and IIgs. It must not block and must
|
and ST (on DVX, DVX's own sound pump, while the program is yielded), but the
|
||||||
not allocate.
|
*audio interrupt* on Amiga and IIgs. It must not block and must not allocate.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioPlaySfxStream
|
=== jlAudioPlaySfxStream
|
||||||
|
|
@ -274,6 +274,7 @@ What each port can actually do:
|
||||||
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move
|
|X68000 |Ignored. Everything mixes into one ADPCM stream, so panning would move
|
||||||
all audio at once.
|
all audio at once.
|
||||||
|DOS, ST |Ignored. Output is mono.
|
|DOS, ST |Ignored. Output is mono.
|
||||||
|
|DVX |All three, through DVX's stereo mixer (on a stereo card).
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== jlAudioStopSfx
|
=== jlAudioStopSfx
|
||||||
|
|
@ -302,7 +303,8 @@ Direct hardware tone generator: plays a square wave continuously until the next
|
||||||
call. Designed for AGI-era music where the engine programs a divisor and lets
|
call. Designed for AGI-era music where the engine programs a divisor and lets
|
||||||
the chip play, so CPU cost during playback is zero. Single voice; uses voice 0
|
the chip play, so CPU cost during playback is zero. Single voice; uses voice 0
|
||||||
on platforms with several oscillators. On DOS this programs PIT counter 2 and
|
on platforms with several oscillators. On DOS this programs PIT counter 2 and
|
||||||
the speaker gate.
|
the speaker gate; on DVX it is a square tone of DVX's sound library, on the FM
|
||||||
|
chip when there is one.
|
||||||
|
|
||||||
[cols="1,1,4", options="header"]
|
[cols="1,1,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -336,7 +338,8 @@ Three-voice PSG-style polyphonic tone generator.
|
||||||
Mapping to hardware: DOS uses AdLib OPL2 melodic channels 0..2; the ST uses
|
Mapping to hardware: DOS uses AdLib OPL2 melodic channels 0..2; the ST uses
|
||||||
YM2149 channels A, B and C; the Amiga uses Paula channels 0..2 looping a
|
YM2149 channels A, B and C; the Amiga uses Paula channels 0..2 looping a
|
||||||
two-byte square sample; the IIgs uses Ensoniq DOC oscillators playing a
|
two-byte square sample; the IIgs uses Ensoniq DOC oscillators playing a
|
||||||
resident square.
|
resident square; DVX plays square tones of its sound library, which puts them
|
||||||
|
on the FM chip when there is a free voice and mixes them otherwise.
|
||||||
|
|
||||||
Voice state is sticky -- a voice keeps its last frequency until you change it
|
Voice state is sticky -- a voice keeps its last frequency until you change it
|
||||||
-- and CPU cost during sustained playback is zero, because the hardware
|
-- and CPU cost during sustained playback is zero, because the hardware
|
||||||
|
|
@ -348,7 +351,7 @@ The IIgs is the exception to stickiness: each call is an approximately 80 ms
|
||||||
retriggered one-shot, so a note held longer must be re-asserted about once a
|
retriggered one-shot, so a note held longer must be re-asserted about once a
|
||||||
frame. It also requires `jlAudioInit`, because the DOC path rides the tracker
|
frame. It also requires `jlAudioInit`, because the DOC path rides the tracker
|
||||||
engine. On DOS, ST and Amiga this is direct hardware programming and works
|
engine. On DOS, ST and Amiga this is direct hardware programming and works
|
||||||
without `jlAudioInit`.
|
without `jlAudioInit`; on DVX it opens the program's sound on first use.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== jlAudioNoise
|
=== jlAudioNoise
|
||||||
|
|
@ -375,7 +378,7 @@ Mapping: the ST uses the YM2149 noise generator mixed onto channel C; DOS uses
|
||||||
the OPL2 rhythm-mode snare, leaving the three tone channels untouched; the
|
the OPL2 rhythm-mode snare, leaving the three tone channels untouched; the
|
||||||
Amiga uses Paula channel 3 with an LFSR noise sample; the IIgs uses a resident
|
Amiga uses Paula channel 3 with an LFSR noise sample; the IIgs uses a resident
|
||||||
noise sample on slot 3 with the same retriggered one-shot model as its tone
|
noise sample on slot 3 with the same retriggered one-shot model as its tone
|
||||||
voices.
|
voices; DVX uses a noise tone of its sound library.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
|
|
@ -392,7 +395,7 @@ bool jlMusicPlay(const uint8_t *data, uint32_t length, bool loop);
|
||||||
----
|
----
|
||||||
|
|
||||||
Plays a JYM1 event stream baked by `songbake` through the tone and noise layer.
|
Plays a JYM1 event stream baked by `songbake` through the tone and noise layer.
|
||||||
One authored song plays on all five ports at register-write cost. Because there
|
One authored song plays on every port at register-write cost. Because there
|
||||||
is no CPU mixing, this is fully viable on the ST and cheap on DOS, which
|
is no CPU mixing, this is fully viable on the ST and cheap on DOS, which
|
||||||
`.MOD` playback is not.
|
`.MOD` playback is not.
|
||||||
|
|
||||||
|
|
@ -516,6 +519,12 @@ On DOS this reprograms PIT counter 0 and hooks INT 8, keeping the BIOS tick
|
||||||
counter advancing at the canonical 18.2 Hz through an accumulator so `time`
|
counter advancing at the canonical 18.2 Hz through an accumulator so `time`
|
||||||
stays correct.
|
stays correct.
|
||||||
|
|
||||||
|
On DVX a timer of DVX's (at most 1000 Hz) counts the ticks, and `fn` runs in
|
||||||
|
program time, once per counted tick, at the next `jlAudioFrameTick`: DVX's
|
||||||
|
sound library, which `jlAudioVoice` and the rest go through there, may not be
|
||||||
|
called from an interrupt. `fn` may therefore do anything, but what it does
|
||||||
|
lands on the frame, not between frames.
|
||||||
|
|
||||||
=== jlAudioCriticalEnter / jlAudioCriticalExit
|
=== jlAudioCriticalEnter / jlAudioCriticalExit
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
@ -527,6 +536,7 @@ void jlAudioCriticalExit(void);
|
||||||
Critical section against the tick interrupt. If you mutate state that the
|
Critical section against the tick interrupt. If you mutate state that the
|
||||||
registered tick callback reads, wrap the mutation in these so the interrupt
|
registered tick callback reads, wrap the mutation in these so the interrupt
|
||||||
cannot observe a half-written value. On DOS this is `cli` and `sti`; on ports
|
cannot observe a half-written value. On DOS this is `cli` and `sti`; on ports
|
||||||
with no audio interrupt both are no-ops.
|
with no audio interrupt, and on DVX, where the callback runs in program time,
|
||||||
|
both are no-ops.
|
||||||
|
|
||||||
*Parameters:* none. *Returns:* nothing.
|
*Parameters:* none. *Returns:* nothing.
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ typedef struct {
|
||||||
|`codegenBytes`
|
|`codegenBytes`
|
||||||
|`uint32_t`
|
|`uint32_t`
|
||||||
|Size of the runtime compiled-sprite cache. `0` selects a per-platform
|
|Size of the runtime compiled-sprite cache. `0` selects a per-platform
|
||||||
default: 8 KB on IIgs, 32 KB on Amiga, ST and DOS. On the IIgs the cache is
|
default: 8 KB on IIgs, 32 KB on Amiga, ST, DOS and DVX. On the IIgs the cache is
|
||||||
one Memory Manager block that may not span a 64 KB bank boundary, so values
|
one Memory Manager block that may not span a 64 KB bank boundary, so values
|
||||||
above 65536 are clamped to 65536, and even a clamped request can fail if no
|
above 65536 are clamped to 65536, and even a clamped request can fail if no
|
||||||
free bank exists. Size it against `jlSpriteCompiledSize()`.
|
free bank exists. Size it against `jlSpriteCompiledSize()`.
|
||||||
|
|
@ -78,8 +78,8 @@ const char *jlPlatformName(void);
|
||||||
|
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* a human-readable platform identifier, for example `"Apple IIgs"` or
|
*Returns:* a human-readable platform identifier, for example `"Apple IIgs"`,
|
||||||
`"MS-DOS"`. Library-owned.
|
`"MS-DOS"` or `"DVX"`. Library-owned.
|
||||||
|
|
||||||
=== jlVersionString
|
=== jlVersionString
|
||||||
|
|
||||||
|
|
@ -193,8 +193,9 @@ uint16_t jlFrameHz(void);
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* the nominal display frame rate in Hz -- 50 on Amiga PAL, 60 on IIgs,
|
*Returns:* the nominal display frame rate in Hz -- 50 on Amiga PAL, 60 on IIgs,
|
||||||
ST NTSC and X68000, 70 on VGA mode 13h. The real cadence may drift slightly;
|
ST NTSC and X68000, 70 on VGA mode 13h and on DVX, whose window has a virtual
|
||||||
this is the figure to divide by when converting frames to seconds.
|
70 Hz. The real cadence may drift slightly; this is the figure to divide by
|
||||||
|
when converting frames to seconds.
|
||||||
|
|
||||||
=== jlMillisElapsed
|
=== jlMillisElapsed
|
||||||
|
|
||||||
|
|
@ -203,8 +204,8 @@ this is the figure to divide by when converting frames to seconds.
|
||||||
uint32_t jlMillisElapsed(void);
|
uint32_t jlMillisElapsed(void);
|
||||||
----
|
----
|
||||||
|
|
||||||
Monotonic millisecond counter since `jlInit`. DOS and ST read a real hardware
|
Monotonic millisecond counter since `jlInit`. DOS, ST and DVX read a real
|
||||||
timer; IIgs and Amiga derive it from the frame counter, so the value steps at
|
hardware timer; IIgs and Amiga derive it from the frame counter, so the value steps at
|
||||||
frame granularity there. Two calls separated by a long render report the true
|
frame granularity there. Two calls separated by a long render report the true
|
||||||
elapsed time, not the number of vertical blanks that fired.
|
elapsed time, not the number of vertical blanks that fired.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
Declared in `<joey/debug.h>`.
|
Declared in `<joey/debug.h>`.
|
||||||
|
|
||||||
A crash-tracing logger that writes to a file beside the executable. Debugging a
|
A crash-tracing logger that writes to a file beside the executable (on DVX,
|
||||||
program on an emulated 1988 machine is awkward, and this is usually the fastest
|
in the program's settings folder, `CONFIG\APPS\JOEYLIB\NAME\JOEYLOG.TXT`).
|
||||||
way to find out what it did before it stopped doing it. The headless run
|
Debugging a program on an emulated 1988 machine is awkward, and this is
|
||||||
scripts retrieve the log automatically.
|
usually the fastest way to find out what it did before it stopped doing it.
|
||||||
|
The headless run scripts retrieve the log automatically.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ packager:
|
||||||
* `DATA/` -- read-only game content: levels, sprites, audio.
|
* `DATA/` -- read-only game content: levels, sprites, audio.
|
||||||
* `SAVES/` -- writable saves and preferences.
|
* `SAVES/` -- writable saves and preferences.
|
||||||
|
|
||||||
|
DVX is the exception, because its programs share one current directory:
|
||||||
|
there `DATA/` is beside the program, in its folder under DVX's `APPS`, and
|
||||||
|
`SAVES/` is in the program's own settings folder, `CONFIG\APPS\JOEYLIB\NAME`,
|
||||||
|
which DVX keeps apart from the program files so a reinstall never overwrites
|
||||||
|
it. The calls below find both there by themselves.
|
||||||
|
|
||||||
Both are addressed by a *bare name relative to their directory*. The prefix is
|
Both are addressed by a *bare name relative to their directory*. The prefix is
|
||||||
added for you, so never hard-code `"DATA/"` or `"SAVES/"` yourself.
|
added for you, so never hard-code `"DATA/"` or `"SAVES/"` yourself.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,8 @@ int jlInputGetChar(void);
|
||||||
|
|
||||||
Pops the next typed character. Shift, caps lock and the keyboard layout have
|
Pops the next typed character. Shift, caps lock and the keyboard layout have
|
||||||
already been applied by the backend, so punctuation and shifted symbols arrive
|
already been applied by the backend, so punctuation and shifted symbols arrive
|
||||||
correctly. The layout is the machine's own on IIgs, Amiga, ST and X68000; the
|
correctly. The layout is the machine's own on IIgs, Amiga, ST and X68000, and
|
||||||
DOS port translates with a fixed US layout.
|
DVX's own on DVX; the DOS port translates with a fixed US layout.
|
||||||
|
|
||||||
Arrow keys are not characters and stay on the predicate API.
|
Arrow keys are not characters and stay on the predicate API.
|
||||||
|
|
||||||
|
|
@ -138,8 +138,10 @@ bool jlMousePresent(void);
|
||||||
*Parameters:* none.
|
*Parameters:* none.
|
||||||
|
|
||||||
*Returns:* `true` if the platform confirmed a mouse at init. On DOS that means
|
*Returns:* `true` if the platform confirmed a mouse at init. On DOS that means
|
||||||
the INT 33h driver reset successfully; on Amiga, ST, IIgs and X68000 the OS
|
the INT 33h driver reset successfully; on Amiga, ST, IIgs, X68000 and DVX the
|
||||||
mouse is always wired.
|
OS mouse is always wired. On DVX the position is the pointer's over the
|
||||||
|
program's window, divided by the window's scale; it keeps its last value
|
||||||
|
while the pointer is elsewhere.
|
||||||
|
|
||||||
=== jlMouseX / jlMouseY
|
=== jlMouseX / jlMouseY
|
||||||
|
|
||||||
|
|
@ -251,7 +253,9 @@ The next poll after this call captures the stick's *current* raw position as
|
||||||
the new centre, so the user must be holding the stick centred when you call it.
|
the new centre, so the user must be holding the stick centred when you call it.
|
||||||
|
|
||||||
On Amiga, ST and DOS the sticks are genuinely digital and already report -1, 0
|
On Amiga, ST and DOS the sticks are genuinely digital and already report -1, 0
|
||||||
or +1, so recalibration is a no-op and `deadZone` is ignored. The call still
|
or +1, so recalibration is a no-op and `deadZone` is ignored. DVX's sticks are
|
||||||
|
analog but calibrated, dead zone included, in DVX's own Control Panel, so the
|
||||||
|
call is a no-op there too. The call still
|
||||||
clears any auto-disconnect state, so it is safe and correct to make it
|
clears any auto-disconnect state, so it is safe and correct to make it
|
||||||
unconditionally.
|
unconditionally.
|
||||||
====
|
====
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,17 @@ One byte-stream API spans every port while the HAL hides very different
|
||||||
hardware: the IIgs drives a Zilog 8530 SCC directly or a 6551 card in a slot;
|
hardware: the IIgs drives a Zilog 8530 SCC directly or a 6551 card in a slot;
|
||||||
DOS uses an interrupt-driven 8250/16550 UART; the Amiga uses `serial.device`;
|
DOS uses an interrupt-driven 8250/16550 UART; the Amiga uses `serial.device`;
|
||||||
the ST goes through the BIOS Aux channel; the X68000 transmits through IOCS
|
the ST goes through the BIOS Aux channel; the X68000 transmits through IOCS
|
||||||
and receives on its own SCC interrupt.
|
and receives on its own SCC interrupt; DVX goes through DVX's serial library,
|
||||||
|
which owns the COM ports for every program on the desktop (a port another
|
||||||
|
program has open cannot be opened).
|
||||||
|
|
||||||
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
The model is *non-blocking and buffered*. Received bytes accumulate in a
|
||||||
background ring -- a hardware interrupt on DOS, the X68000 and the IIgs, the
|
background ring -- a hardware interrupt on DOS, the X68000 and the IIgs, the
|
||||||
OS on Amiga and ST -- and reads never block. Call `jlSerialPoll` once per
|
OS on Amiga, ST and DVX -- and reads never block. Call `jlSerialPoll` once per
|
||||||
frame: it is next to a no-op where the interrupt or OS already fills the ring,
|
frame: it is next to a no-op where the interrupt or OS already fills the ring,
|
||||||
and the hardware drain where nothing does, so calling it every frame is always
|
and the hardware drain where nothing does, so calling it every frame is always
|
||||||
correct.
|
correct. On DVX it also gives the other programs their turn
|
||||||
|
(<<_dvx_programs>>).
|
||||||
|
|
||||||
On the IIgs a Super Serial Card only interrupts with its interrupt switch
|
On the IIgs a Super Serial Card only interrupts with its interrupt switch
|
||||||
(SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but
|
(SW2-6) on; with it off the card still works, drained by `jlSerialPoll`, but
|
||||||
|
|
@ -57,6 +60,7 @@ at 9600 baud, so:
|
||||||
|IIgs Super Serial Card (6551) |1 byte |~1 ms
|
|IIgs Super Serial Card (6551) |1 byte |~1 ms
|
||||||
|DOS, 16550A UART |16 bytes, interrupt at 14 |~2 ms from the interrupt
|
|DOS, 16550A UART |16 bytes, interrupt at 14 |~2 ms from the interrupt
|
||||||
|DOS, 8250 or 16450 UART |1 byte |~1 ms
|
|DOS, 8250 or 16450 UART |1 byte |~1 ms
|
||||||
|
|DVX |as DOS: DVX's library drives the same UARTs |as DOS
|
||||||
|Amiga (Paula) |1 byte |~1 ms
|
|Amiga (Paula) |1 byte |~1 ms
|
||||||
|Atari ST (68901 MFP) |1 byte |~1 ms
|
|Atari ST (68901 MFP) |1 byte |~1 ms
|
||||||
|X68000 (Zilog 8530) |3 bytes |~3 ms
|
|X68000 (Zilog 8530) |3 bytes |~3 ms
|
||||||
|
|
@ -140,8 +144,8 @@ typedef struct {
|
||||||
|
|
||||||
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
The IIgs is the machine with three device choices; elsewhere `JL_SERIAL_MODEM`
|
||||||
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
and `JL_SERIAL_PRINTER` both mean the default port. `JL_SERIAL_DEFAULT` selects
|
||||||
the IIgs modem port, DOS COM1, the ST Modem port, Amiga unit 0 or the X68000's
|
the IIgs modem port, DOS or DVX COM1, the ST Modem port, Amiga unit 0 or the
|
||||||
RS-232C port.
|
X68000's RS-232C port.
|
||||||
|
|
||||||
On the X68000 the serial add-on *requires* Human68k's RS-232C driver,
|
On the X68000 the serial add-on *requires* Human68k's RS-232C driver,
|
||||||
RSDRV.SYS. Every Human68k system disk has it and the stock CONFIG.SYS loads
|
RSDRV.SYS. Every Human68k system disk has it and the stock CONFIG.SYS loads
|
||||||
|
|
@ -154,7 +158,8 @@ fills the receive ring. 38400 is really 39062 bps, 1.7% fast, within the
|
||||||
usual tolerance of a UART at the far end.
|
usual tolerance of a UART at the far end.
|
||||||
|
|
||||||
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
`unit` disambiguates `JL_SERIAL_SLOT` and is ignored otherwise: an IIgs Super
|
||||||
Serial Card slot 1..7, a DOS COM number 1..4, or an Amiga `serial.device` unit.
|
Serial Card slot 1..7, a DOS or DVX COM number 1..4, or an Amiga
|
||||||
|
`serial.device` unit.
|
||||||
|
|
||||||
=== jlSerialOpen
|
=== jlSerialOpen
|
||||||
|
|
||||||
|
|
@ -195,7 +200,9 @@ Closing does *not* hang up: `jlSerialClose` leaves DTR exactly as it is, so
|
||||||
a modem call or a BBS caller survives the program handing the line back: a
|
a modem call or a BBS caller survives the program handing the line back: a
|
||||||
door returning to its BBS, or a terminal program exiting while the call stays
|
door returning to its BBS, or a terminal program exiting while the call stays
|
||||||
up. RTS is not part of that promise; on the Amiga, `serial.device` puts back
|
up. RTS is not part of that promise; on the Amiga, `serial.device` puts back
|
||||||
whatever RTS was before the open.
|
whatever RTS was before the open. DVX is the exception: its serial library
|
||||||
|
resets the modem lines when it closes a port, so there closing *does* drop
|
||||||
|
DTR, and a program that must hand a live call on has to keep the port open.
|
||||||
|
|
||||||
To hang up, drop DTR first: call `jlSerialSetDtr(false)`, wait longer than
|
To hang up, drop DTR first: call `jlSerialSetDtr(false)`, wait longer than
|
||||||
the modem needs to notice (a Hayes modem's S25 setting, 50 ms by default),
|
the modem needs to notice (a Hayes modem's S25 setting, 50 ms by default),
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ one-time startup cost for a much smaller distribution.
|
||||||
|
|
||||||
=== Music: the Portable Chip Tracker
|
=== Music: the Portable Chip Tracker
|
||||||
|
|
||||||
This is the recommended music path. One authored song plays on all five ports
|
This is the recommended music path. One authored song plays on every port
|
||||||
at register-write cost, with no CPU mixing -- which matters enormously on the
|
at register-write cost, with no CPU mixing -- which matters enormously on the
|
||||||
ST, where `.MOD` playback is only marginally viable.
|
ST, where `.MOD` playback is only marginally viable.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,29 @@ MYGAME_SRCS := $(EXAMPLES)/mygame/mygame.c \
|
||||||
----
|
----
|
||||||
|
|
||||||
Repeat for each port you want to support: `make/iigs.mk`, `make/amiga.mk`,
|
Repeat for each port you want to support: `make/iigs.mk`, `make/amiga.mk`,
|
||||||
`make/atarist.mk`, `make/dos.mk` and `make/x68000.mk`.
|
`make/atarist.mk`, `make/dos.mk`, `make/x68000.mk` and `make/dvx.mk`.
|
||||||
|
|
||||||
|
`make/dvx.mk` is the one exception to the pattern. A DVX program is a DVX SDK
|
||||||
|
project built by the SDK's own `sdk.mk`, which `make/dvx.mk` writes for every
|
||||||
|
name in its `APPS` list from three variables, plus a fourth for a program
|
||||||
|
with data files:
|
||||||
|
|
||||||
|
[source,makefile]
|
||||||
|
----
|
||||||
|
mygame_SRCS := $(EXAMPLES)/mygame/mygame.c $(EXAMPLES)/mygame/world.c
|
||||||
|
mygame_TITLE := My Game
|
||||||
|
mygame_HELP := One line on what the program is, for its help file.
|
||||||
|
mygame_DATA := $(DATADIR)/mygame/DATA # optional: staged by a rule of yours
|
||||||
|
|
||||||
|
APPS := hello pattern ... mygame
|
||||||
|
----
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
Yes, that is five makefile edits for one program. The ports genuinely differ:
|
Yes, that is six makefile edits for one program. The ports genuinely differ:
|
||||||
the IIgs needs OMF segmentation and a disk image, the Amiga a startup-sequence
|
the IIgs needs OMF segmentation and a disk image, the Amiga a startup-sequence
|
||||||
entry, DOS a DPMI stub, and the X68000 a Human68k floppy layout. There is no
|
entry, DOS a DPMI stub, the X68000 a Human68k floppy layout, and DVX a module
|
||||||
single rule that covers all five.
|
with a help file. There is no single rule that covers them all.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== Naming
|
=== Naming
|
||||||
|
|
@ -78,6 +93,7 @@ File naming conventions differ per platform and the makefiles reflect that:
|
||||||
|MS-DOS |Uppercase 8.3, `.EXE` |`MYGAME.EXE`
|
|MS-DOS |Uppercase 8.3, `.EXE` |`MYGAME.EXE`
|
||||||
|Amiga |Mixed case, no extension |`MyGame`
|
|Amiga |Mixed case, no extension |`MyGame`
|
||||||
|Sharp X68000 |Uppercase 8.3, `.X` |`MYGAME.X`
|
|Sharp X68000 |Uppercase 8.3, `.X` |`MYGAME.X`
|
||||||
|
|DVX |Lowercase, 8 characters, a folder of its own |`apps/joeylib/mygame/mygame.app`
|
||||||
|===
|
|===
|
||||||
|
|
||||||
Keep the base name to 8 characters and letters and digits only, so the same
|
Keep the base name to 8 characters and letters and digits only, so the same
|
||||||
|
|
@ -114,6 +130,13 @@ vanish.
|
||||||
|
|
||||||
*Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary.
|
*Amiga.* `joey-run` writes an `s/startup-sequence` that invokes your binary.
|
||||||
|
|
||||||
|
*DVX.* Every program is a folder laid out as it installs in DVX:
|
||||||
|
`build/dvx/bin/apps/joeylib/mygame/` holds `mygame.app`, its help file
|
||||||
|
`mygame.hlp` (DVX gives every program one; `make/dvx.mk` writes a one-topic
|
||||||
|
help from `mygame_HELP`) and its `DATA/`. Copy that folder into a DVX
|
||||||
|
installation's `APPS\JOEYLIB\` and start it from DVX, or let `joey-run` do it
|
||||||
|
(<<_dvx_programs>>).
|
||||||
|
|
||||||
=== Verifying It Actually Runs
|
=== Verifying It Actually Runs
|
||||||
|
|
||||||
Build success is weak evidence on retro targets. Run it with `joey-run` (see
|
Build success is weak evidence on retro targets. Run it with `joey-run` (see
|
||||||
|
|
@ -126,6 +149,7 @@ $ ./joey-run --port amiga mygame
|
||||||
$ ./joey-run --port atarist mygame
|
$ ./joey-run --port atarist mygame
|
||||||
$ ./joey-run --port dos mygame
|
$ ./joey-run --port dos mygame
|
||||||
$ ./joey-run --port x68000 mygame
|
$ ./joey-run --port x68000 mygame
|
||||||
|
$ ./joey-run --port dvx mygame
|
||||||
----
|
----
|
||||||
|
|
||||||
Without a window, which is what you want while iterating, run it headless and
|
Without a window, which is what you want while iterating, run it headless and
|
||||||
|
|
@ -148,8 +172,8 @@ against your own program too.
|
||||||
|
|
||||||
|`make verify-audio`
|
|`make verify-audio`
|
||||||
|Every port's audio gate: the IIgs DOC and X68000 chips are read directly,
|
|Every port's audio gate: the IIgs DOC and X68000 chips are read directly,
|
||||||
the ST's YM2149 register writes are traced, and the Amiga and DOS output is
|
the ST's YM2149 register writes are traced, and the Amiga, DOS and DVX
|
||||||
recorded and graded. Exits 3, not 0, if a gate had to be skipped.
|
output is recorded and graded. Exits 3, not 0, if a gate had to be skipped.
|
||||||
|
|
||||||
|`make iigs-verify`
|
|`make iigs-verify`
|
||||||
|The IIgs actually reached the program and drew something.
|
|The IIgs actually reached the program and drew something.
|
||||||
|
|
@ -176,7 +200,8 @@ different pixels from the others.
|
||||||
=== Logging from the Target
|
=== Logging from the Target
|
||||||
|
|
||||||
Debugging a program running on an emulated 1988 machine is awkward. JoeyLib
|
Debugging a program running on an emulated 1988 machine is awkward. JoeyLib
|
||||||
provides a logger that writes to a file next to the executable:
|
provides a logger that writes to a file next to the executable (on DVX, in
|
||||||
|
the program's settings folder):
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
=== Setting Up the Toolchains
|
=== Setting Up the Toolchains
|
||||||
|
|
||||||
JoeyLib cross-compiles for five machines. It builds on Linux x86-64, and on
|
JoeyLib cross-compiles for five machines and for DVX, a DOS desktop whose
|
||||||
Windows inside WSL2. One script installs every cross toolchain, pinned to a
|
programs run in windows (see <<_dvx_programs>>). It builds on Linux x86-64,
|
||||||
known-good version and checked against a published checksum, into
|
and on Windows inside WSL2. One script installs every cross toolchain, pinned
|
||||||
|
to a known-good version and checked against a published checksum, into
|
||||||
`toolchains/` in the repository. The only things it installs anywhere else
|
`toolchains/` in the repository. The only things it installs anywhere else
|
||||||
are ordinary packages from your distribution (apt on Debian and Ubuntu, dnf
|
are ordinary packages from your distribution (apt on Debian and Ubuntu, dnf
|
||||||
on Fedora), and only when you ask it to.
|
on Fedora), and only when you ask it to.
|
||||||
|
|
@ -55,6 +56,7 @@ Toolchain availability:
|
||||||
atarist: yes
|
atarist: yes
|
||||||
dos: yes
|
dos: yes
|
||||||
x68000: yes
|
x68000: yes
|
||||||
|
dvx: yes
|
||||||
----
|
----
|
||||||
|
|
||||||
==== Installer reference
|
==== Installer reference
|
||||||
|
|
@ -103,8 +105,8 @@ A target is a component name from `list`, or a group:
|
||||||
| Target | Means
|
| Target | Means
|
||||||
|
|
||||||
| `toolchain:PORT`
|
| `toolchain:PORT`
|
||||||
| Everything needed to build for one port: `iigs`, `amiga`, `atarist`, `dos`
|
| Everything needed to build for one port: `iigs`, `amiga`, `atarist`, `dos`,
|
||||||
or `x68000`.
|
`x68000` or `dvx`.
|
||||||
|
|
||||||
| `emulator:PORT`
|
| `emulator:PORT`
|
||||||
| Everything needed to run that port's programs: the emulator and its
|
| Everything needed to run that port's programs: the emulator and its
|
||||||
|
|
@ -120,8 +122,9 @@ firmware.
|
||||||
| Every port's emulator.
|
| Every port's emulator.
|
||||||
|
|
||||||
| `all`
|
| `all`
|
||||||
| Everything except optional components (such as `staxiData`, Space Taxi's
|
| Everything except optional components (`staxiData`, Space Taxi's level
|
||||||
level source), which are installed by name.
|
source, and `dvx`, which copies DVX from a DVX tree of yours), which are
|
||||||
|
installed by name. `toolchains` and `emulators` leave them out too.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
[cols="1,3"]
|
[cols="1,3"]
|
||||||
|
|
@ -147,6 +150,13 @@ level source), which are installed by name.
|
||||||
| Use an existing checkout of llvm816, the IIgs compiler, instead of cloning
|
| Use an existing checkout of llvm816, the IIgs compiler, instead of cloning
|
||||||
it. For working on the compiler and JoeyLib together. The setting is
|
it. For working on the compiler and JoeyLib together. The setting is
|
||||||
remembered; `--llvm816-dir clone` goes back to a managed clone.
|
remembered; `--llvm816-dir clone` goes back to a managed clone.
|
||||||
|
|
||||||
|
| `--dvx-dir PATH`
|
||||||
|
| The built DVX tree the `dvx` component copies DVX and its SDK from. The
|
||||||
|
default is a `dvxgui` checkout beside this repository; the setting is
|
||||||
|
remembered. The component only reads that tree: it copies what the builds
|
||||||
|
and the emulator runs need into `toolchains/opt/dvx`, and rebuilding DVX
|
||||||
|
there makes the component out of date, so `install dvx` copies it again.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
Everything the installer creates stays inside `toolchains/`:
|
Everything the installer creates stays inside `toolchains/`:
|
||||||
|
|
@ -167,7 +177,7 @@ trees.
|
||||||
|
|
||||||
| `toolchains/state/`
|
| `toolchains/state/`
|
||||||
| Which version of each component is installed, and settings such as
|
| Which version of each component is installed, and settings such as
|
||||||
`--llvm816-dir`.
|
`--llvm816-dir` and `--dvx-dir`.
|
||||||
|
|
||||||
| `toolchains/config.mk`, `toolchains/env.sh`
|
| `toolchains/config.mk`, `toolchains/env.sh`
|
||||||
| The tool paths, for `make` and for shells.
|
| The tool paths, for `make` and for shells.
|
||||||
|
|
@ -189,6 +199,7 @@ $ make amiga # Commodore Amiga only
|
||||||
$ make atarist # Atari ST only
|
$ make atarist # Atari ST only
|
||||||
$ make dos # MS-DOS only
|
$ make dos # MS-DOS only
|
||||||
$ make x68000 # Sharp X68000 only
|
$ make x68000 # Sharp X68000 only
|
||||||
|
$ make dvx # DVX only
|
||||||
$ make clean # remove all build outputs
|
$ make clean # remove all build outputs
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -330,6 +341,7 @@ $ ./joey-run build/dos/bin/DRAW.EXE
|
||||||
$ ./joey-run build/amiga/bin/Draw
|
$ ./joey-run build/amiga/bin/Draw
|
||||||
$ ./joey-run --port atarist draw
|
$ ./joey-run --port atarist draw
|
||||||
$ ./joey-run build/iigs/bin/STAXI.2mg
|
$ ./joey-run build/iigs/bin/STAXI.2mg
|
||||||
|
$ ./joey-run --port dvx staxi
|
||||||
----
|
----
|
||||||
|
|
||||||
It recognises the machine from the file itself. Interactive runs use the
|
It recognises the machine from the file itself. Interactive runs use the
|
||||||
|
|
@ -382,6 +394,17 @@ default is 9600.
|
||||||
pass arguments.
|
pass arguments.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
==== DVX
|
||||||
|
|
||||||
|
`joey-run` runs a DVX program in DOSBox Staging on a copy of the DVX the
|
||||||
|
installer took from a DVX tree (`./install.sh install dvx`), set up so DVX
|
||||||
|
boots straight into the program: it is DVX's desktop program, in its own
|
||||||
|
window on a 1024x768 desktop (640x480 with `--headless`), with a Sound
|
||||||
|
Blaster 16. The DVX of interactive runs is kept in `build/dvx/run`, so what
|
||||||
|
the program saves, in DVX's `CONFIG` folder, is there the next time; closing
|
||||||
|
DOSBox ends the run. When the program ends, DVX stays up with an empty
|
||||||
|
desktop.
|
||||||
|
|
||||||
A headless run with a snapshot is the quickest way to check a change:
|
A headless run with a snapshot is the quickest way to check a change:
|
||||||
|
|
||||||
[source,console]
|
[source,console]
|
||||||
|
|
@ -442,6 +465,10 @@ arrives. To connect out, `socat` joins a pseudo-terminal to the network.
|
||||||
| DOS
|
| DOS
|
||||||
| COM1
|
| COM1
|
||||||
| DOSBox does both by itself.
|
| DOSBox does both by itself.
|
||||||
|
|
||||||
|
| DVX
|
||||||
|
| COM1
|
||||||
|
| As on DOS; DVX's serial library drives the port.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
`socat` comes from your distribution: `./install.sh deps host --install`
|
`socat` comes from your distribution: `./install.sh deps host --install`
|
||||||
|
|
@ -512,3 +539,6 @@ built for every platform:
|
||||||
|`spacetaxi` |A full port of Space Taxi: the largest example.
|
|`spacetaxi` |A full port of Space Taxi: the largest example.
|
||||||
|`uber` |The validation and benchmark harness.
|
|`uber` |The validation and benchmark harness.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
On DVX every example is a folder of its own under `build/dvx/bin/apps/joeylib/`
|
||||||
|
with its help file (<<_dvx_programs>>).
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
=== What JoeyLib Is
|
=== What JoeyLib Is
|
||||||
|
|
||||||
JoeyLib is a single-codebase C game library for five 16-bit and 32-bit retro
|
JoeyLib is a single-codebase C game library for five 16-bit and 32-bit retro
|
||||||
machines. You write one program against one API, and the same source compiles
|
machines, and for DVX, a DOS desktop whose programs run in windows. You write
|
||||||
and runs on all of them:
|
one program against one API, and the same source compiles and runs on all of
|
||||||
|
them:
|
||||||
|
|
||||||
[cols="2,2,2,4", options="header"]
|
[cols="2,2,2,4", options="header"]
|
||||||
|===
|
|===
|
||||||
|
|
@ -34,6 +35,12 @@ and runs on all of them:
|
||||||
|68000 at 10 MHz
|
|68000 at 10 MHz
|
||||||
|stock
|
|stock
|
||||||
|Draws into the text plane, 320x200 centred in a 512x512 screen.
|
|Draws into the text plane, 320x200 centred in a 512x512 screen.
|
||||||
|
|
||||||
|
|DVX
|
||||||
|
|486 or later
|
||||||
|
|what DVX needs
|
||||||
|
|A program in a window on the DVX desktop, at 1x to 3x. The MS-DOS port's
|
||||||
|
stage and code; a virtual 70 Hz. See <<_dvx_programs>>.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The Apple IIgs is deliberately the slowest target and the design reference. If
|
The Apple IIgs is deliberately the slowest target and the design reference. If
|
||||||
|
|
@ -60,14 +67,14 @@ the public API, so you cannot corrupt memory by drawing off the edge.
|
||||||
|
|
||||||
=== What the Library Hides
|
=== What the Library Hides
|
||||||
|
|
||||||
The five machines store pixels in genuinely incompatible ways. JoeyLib keeps
|
The machines store pixels in genuinely incompatible ways. JoeyLib keeps
|
||||||
that out of your program:
|
that out of your program:
|
||||||
|
|
||||||
[cols="2,3", options="header"]
|
[cols="2,3", options="header"]
|
||||||
|===
|
|===
|
||||||
|Platform |In-memory pixel storage
|
|Platform |In-memory pixel storage
|
||||||
|
|
||||||
|Apple IIgs, MS-DOS
|
|Apple IIgs, MS-DOS, DVX
|
||||||
|Chunky 4bpp packed: two pixels per byte, high nibble is the left pixel.
|
|Chunky 4bpp packed: two pixels per byte, high nibble is the left pixel.
|
||||||
|
|
||||||
|Commodore Amiga
|
|Commodore Amiga
|
||||||
|
|
@ -111,7 +118,7 @@ chapters are the whole programming model.
|
||||||
library can load.
|
library can load.
|
||||||
|
|
||||||
<<_adding_your_program_to_the_build>> explains how to get your own program
|
<<_adding_your_program_to_the_build>> explains how to get your own program
|
||||||
compiling for all five targets.
|
compiling for every target.
|
||||||
|
|
||||||
The API Reference documents every public function: its exact signature, each
|
The API Reference documents every public function: its exact signature, each
|
||||||
parameter, what it returns, and any per-platform behaviour you need to know
|
parameter, what it returns, and any per-platform behaviour you need to know
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
== Writing Portable Code
|
== Writing Portable Code
|
||||||
|
|
||||||
A JoeyLib program that follows the rules in this chapter compiles and behaves
|
A JoeyLib program that follows the rules in this chapter compiles and behaves
|
||||||
the same on all five machines. Most of the rules exist because one particular
|
the same on all five machines and on DVX. Most of the rules exist because one
|
||||||
target cannot do something the others can, and the Apple IIgs is usually that
|
particular target cannot do something the others can, and the Apple IIgs is
|
||||||
target.
|
usually that target.
|
||||||
|
|
||||||
=== The Rules, in Short
|
=== The Rules, in Short
|
||||||
|
|
||||||
|
|
@ -141,7 +141,9 @@ you:
|
||||||
* `SAVES/` -- writable saves and preferences, via the `jlSave*` calls
|
* `SAVES/` -- writable saves and preferences, via the `jlSave*` calls
|
||||||
|
|
||||||
Pass bare names: `jlDataOpen("levels/title.dat", "rb")`, never
|
Pass bare names: `jlDataOpen("levels/title.dat", "rb")`, never
|
||||||
`"DATA/levels/title.dat"`.
|
`"DATA/levels/title.dat"`. On DVX the two are not in the current directory
|
||||||
|
at all (<<_dvx_programs>>), so a program that opens `"DATA/..."` itself finds
|
||||||
|
nothing there.
|
||||||
|
|
||||||
Keep names portable across the retro filesystems this library targets: *at most
|
Keep names portable across the retro filesystems this library targets: *at most
|
||||||
8 characters plus a short extension, letters and digits only*, so they satisfy
|
8 characters plus a short extension, letters and digits only*, so they satisfy
|
||||||
|
|
@ -197,9 +199,9 @@ error.
|
||||||
|
|
||||||
The API is uniform; these are the places where what you get differs.
|
The API is uniform; these are the places where what you get differs.
|
||||||
|
|
||||||
[cols="2,2,2,2,2,2", options="header"]
|
[cols="2,2,2,2,2,2,2", options="header"]
|
||||||
|===
|
|===
|
||||||
|Feature |IIgs |Amiga |Atari ST |MS-DOS |X68000
|
|Feature |IIgs |Amiga |Atari ST |MS-DOS |X68000 |DVX
|
||||||
|
|
||||||
|Frame rate
|
|Frame rate
|
||||||
|60 Hz
|
|60 Hz
|
||||||
|
|
@ -207,6 +209,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|60 Hz (NTSC)
|
|60 Hz (NTSC)
|
||||||
|70 Hz
|
|70 Hz
|
||||||
|60 Hz
|
|60 Hz
|
||||||
|
|70 Hz (virtual)
|
||||||
|
|
||||||
|Pixel storage
|
|Pixel storage
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|
|
@ -214,6 +217,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|planar, interleaved
|
|planar, interleaved
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|text-plane planar
|
|text-plane planar
|
||||||
|
|chunky 4bpp
|
||||||
|
|
||||||
|Stereo pan
|
|Stereo pan
|
||||||
|all three
|
|all three
|
||||||
|
|
@ -221,6 +225,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|ignored (mono)
|
|ignored (mono)
|
||||||
|ignored (mono)
|
|ignored (mono)
|
||||||
|ignored
|
|ignored
|
||||||
|
|all three
|
||||||
|
|
||||||
|`jlAudioTone`
|
|`jlAudioTone`
|
||||||
|DOC oscillator
|
|DOC oscillator
|
||||||
|
|
@ -228,6 +233,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|YM2149
|
|YM2149
|
||||||
|PIT counter 2
|
|PIT counter 2
|
||||||
|-
|
|-
|
||||||
|
|a DVX tone (FM chip or mixer)
|
||||||
|
|
||||||
|Joystick
|
|Joystick
|
||||||
|analog, auto-disables
|
|analog, auto-disables
|
||||||
|
|
@ -235,6 +241,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|digital
|
|digital
|
||||||
|digital
|
|digital
|
||||||
|digital
|
|digital
|
||||||
|
|analog (DVX calibrates)
|
||||||
|
|
||||||
|Keyboard layout
|
|Keyboard layout
|
||||||
|machine's own
|
|machine's own
|
||||||
|
|
@ -242,6 +249,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|machine's own
|
|machine's own
|
||||||
|fixed US
|
|fixed US
|
||||||
|machine's own
|
|machine's own
|
||||||
|
|DVX's
|
||||||
|
|
||||||
|Serial ports
|
|Serial ports
|
||||||
|modem, printer, slot card
|
|modem, printer, slot card
|
||||||
|
|
@ -249,6 +257,7 @@ The API is uniform; these are the places where what you get differs.
|
||||||
|Modem port
|
|Modem port
|
||||||
|COM1..COM4
|
|COM1..COM4
|
||||||
|-
|
|-
|
||||||
|
|COM1..COM4 (DVX's library)
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The joystick row deserves a note: the IIgs port *auto-disables* joystick
|
The joystick row deserves a note: the IIgs port *auto-disables* joystick
|
||||||
|
|
@ -271,8 +280,61 @@ Before you call a program portable, confirm:
|
||||||
* [ ] Save file names are 8.3-safe.
|
* [ ] Save file names are 8.3-safe.
|
||||||
* [ ] Only one audio layer is active per scene.
|
* [ ] Only one audio layer is active per scene.
|
||||||
* [ ] `jlShutdown()` runs on every exit path.
|
* [ ] `jlShutdown()` runs on every exit path.
|
||||||
* [ ] The program has actually been run on all five targets, not just built.
|
* [ ] The program has actually been run on every target, not just built.
|
||||||
|
|
||||||
That last item is the one that matters. All five ports have emulator run
|
That last item is the one that matters. Every port has emulator run scripts
|
||||||
scripts under `scripts/`, and building successfully proves considerably less
|
under `scripts/`, and building successfully proves considerably less than
|
||||||
than running.
|
running.
|
||||||
|
|
||||||
|
=== DVX Programs
|
||||||
|
|
||||||
|
DVX is a DOS desktop: a VESA graphics GUI with windows, cooperative
|
||||||
|
multitasking and its own libraries for sound, music, joysticks and serial
|
||||||
|
ports. On DVX a JoeyLib program is one of its programs, a `.app` module
|
||||||
|
running in a window of its own beside the others. The graphics are the
|
||||||
|
MS-DOS port's -- the same chunky stage and the same x86 code, so UBER's
|
||||||
|
hashes are DOS's -- and the rest behaves like this:
|
||||||
|
|
||||||
|
* *A window, not the screen.* The window's content is the 320x200 stage at
|
||||||
|
the largest whole scale, up to 3x, that fits the desktop: 1x at 640x480,
|
||||||
|
2x at 800x600, 3x at 1024x768. The stage reaches the desktop through one
|
||||||
|
colour table per palette, so SCB palettes are exact on 15-, 16- and 32-bit
|
||||||
|
desktops. On a 256-colour desktop every colour is the nearest one in DVX's
|
||||||
|
palette, which all programs share: gradients band.
|
||||||
|
* *A virtual 70 Hz.* A window has no vertical blank. `jlFrameHz()` is 70, as
|
||||||
|
on DOS, and `jlWaitVBL()` waits for the next seventieth of a second of
|
||||||
|
DVX's clock while the other programs run. Nothing prevents tearing.
|
||||||
|
* *Keys DVX keeps.* While the window is active every key goes to the program,
|
||||||
|
F1, F10 and Alt included, except Alt+Tab (switch programs), Ctrl+Esc (Task
|
||||||
|
Manager) and Alt+F4 (close the window). The window's Game menu has Help
|
||||||
|
(the program's help file) and Exit. Typed characters come through DVX's
|
||||||
|
keyboard layout.
|
||||||
|
* *Closing ends the program.* Alt+F4, the close box and Game > Exit end the
|
||||||
|
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent` or
|
||||||
|
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
||||||
|
`jlShutdown`, and its log is closed.
|
||||||
|
* *Sharing the machine.* DVX multitasks cooperatively: the other programs,
|
||||||
|
and this one's own window events, run only while it yields. `jlWaitVBL`
|
||||||
|
yields, and so do `jlInputPoll`, `jlStagePresent` and `jlSerialPoll`, at
|
||||||
|
most once a frame. A loop that calls none of them freezes the desktop until
|
||||||
|
it ends.
|
||||||
|
* *Files.* DVX's programs share one current directory, so JoeyLib roots its
|
||||||
|
files at run time: `DATA/` is found beside the program (its folder under
|
||||||
|
`APPS`), while `SAVES/` and `joeylog.txt` go to the program's own settings
|
||||||
|
folder, `CONFIG\APPS\JOEYLIB\NAME`, which DVX keeps apart from the program
|
||||||
|
files so a reinstall never overwrites them.
|
||||||
|
* *Sound* goes through DVX's sound and music libraries, mixed with every
|
||||||
|
other program's: `.MOD` music, SFX in stereo, and the tone voices on the FM
|
||||||
|
chip when there is one. A `jlAudioTickRegister` callback runs in program
|
||||||
|
time, at the next `jlAudioFrameTick`, because DVX's sound library may not
|
||||||
|
be called from an interrupt.
|
||||||
|
* *Joysticks* are DVX's, calibrated in its Control Panel: real analog axes.
|
||||||
|
DVX's keyboard joystick is not reported as one.
|
||||||
|
* *Serial ports* belong to DVX's serial library, one program each. Closing a
|
||||||
|
port there drops DTR, so a modem hangs up.
|
||||||
|
* A program's `main` is compiled as `jlGameMain`, which the port calls with
|
||||||
|
`argv[0]` the `.app`'s path.
|
||||||
|
|
||||||
|
`make dvx` builds every example as a DVX program (the `dvx` installer
|
||||||
|
component supplies the SDK, <<_setting_up_the_toolchains>>), and
|
||||||
|
`./joey-run --port dvx NAME` starts one in DVX under DOSBox Staging.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ native sprite blitter code into. Getting it wrong is the most common cause of
|
||||||
sprites drawing slowly, so it is worth understanding.
|
sprites drawing slowly, so it is worth understanding.
|
||||||
|
|
||||||
* `0` selects a per-platform default: 8 KB on the IIgs, 32 KB on
|
* `0` selects a per-platform default: 8 KB on the IIgs, 32 KB on
|
||||||
Amiga, ST and DOS.
|
Amiga, ST, DOS and DVX.
|
||||||
* The 68000 and x86 emitters produce code roughly four to eight times less
|
* The 68000 and x86 emitters produce code roughly four to eight times less
|
||||||
dense than the 65816 one, so the same sprite needs far more arena space on
|
dense than the 65816 one, so the same sprite needs far more arena space on
|
||||||
those ports. A 32x32 sprite can need 8 to 16 KB there.
|
those ports. A 32x32 sprite can need 8 to 16 KB there.
|
||||||
|
|
@ -140,9 +140,10 @@ rows. That is the one case where you have to mark the region yourself.
|
||||||
|
|
||||||
==== Frame-Rate Independence
|
==== Frame-Rate Independence
|
||||||
|
|
||||||
The five targets do not share a refresh rate: 50 Hz (Amiga PAL), 60 Hz (IIgs,
|
The targets do not share a refresh rate: 50 Hz (Amiga PAL), 60 Hz (IIgs,
|
||||||
ST NTSC, X68000) and 70 Hz (VGA mode 13h). A loop that moves a sprite "two
|
ST NTSC, X68000) and 70 Hz (VGA mode 13h, and DVX's virtual rate). A loop
|
||||||
pixels per frame" runs 40 percent faster on DOS than on a PAL Amiga.
|
that moves a sprite "two pixels per frame" runs 40 percent faster on DOS than
|
||||||
|
on a PAL Amiga.
|
||||||
|
|
||||||
Two ways to handle it. Ask the frame rate and scale your per-frame step:
|
Two ways to handle it. Ask the frame rate and scale your per-frame step:
|
||||||
|
|
||||||
|
|
@ -181,7 +182,7 @@ it, but the buffers *you* allocated (including tile data you handed to
|
||||||
|
|
||||||
=== The Complete Skeleton
|
=== The Complete Skeleton
|
||||||
|
|
||||||
This compiles and runs on all five targets as-is.
|
This compiles and runs on every target as-is.
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <joey/joey.h>
|
#include <joey/joey.h>
|
||||||
|
#include <joey/file.h>
|
||||||
|
|
||||||
#include "demoSong.h"
|
#include "demoSong.h"
|
||||||
|
|
||||||
|
|
@ -23,13 +24,15 @@
|
||||||
// test.mod -> test.ntp via joeymod at build time and ships the .NTP;
|
// test.mod -> test.ntp via joeymod at build time and ships the .NTP;
|
||||||
// every other platform ships the raw .MOD. test.sfx is the same raw
|
// every other platform ships the raw .MOD. test.sfx is the same raw
|
||||||
// PCM blob everywhere. The tracker song needs no per-platform form at
|
// PCM blob everywhere. The tracker song needs no per-platform form at
|
||||||
// all -- the baked JYM1 bytes are identical on every port.
|
// all -- the baked JYM1 bytes are identical on every port. The names
|
||||||
|
// are relative to DATA/ (jlDataOpen), which is not always the current
|
||||||
|
// directory's DATA/ (DVX: beside the program).
|
||||||
#if defined(JOEYLIB_PLATFORM_IIGS)
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
# define TEST_MOD_PATH "DATA/TEST.NTP"
|
# define TEST_MOD_NAME "TEST.NTP"
|
||||||
#else
|
#else
|
||||||
# define TEST_MOD_PATH "DATA/test.mod"
|
# define TEST_MOD_NAME "test.mod"
|
||||||
#endif
|
#endif
|
||||||
#define TEST_SFX_PATH "DATA/test.sfx"
|
#define TEST_SFX_NAME "test.sfx"
|
||||||
|
|
||||||
// Slot 3: the tracker's three voices ride SFX slots 0-2 on the IIgs,
|
// Slot 3: the tracker's three voices ride SFX slots 0-2 on the IIgs,
|
||||||
// so the demo's one-shot SFX stays out of their way.
|
// so the demo's one-shot SFX stays out of their way.
|
||||||
|
|
@ -46,16 +49,16 @@
|
||||||
#define BAR_H 16
|
#define BAR_H 16
|
||||||
|
|
||||||
|
|
||||||
// Read an entire file into a freshly allocated buffer. Caller must
|
// Read an entire DATA/ file into a freshly allocated buffer. Caller must
|
||||||
// free(*outBytes) once the audio engine has consumed the data.
|
// free(*outBytes) once the audio engine has consumed the data.
|
||||||
// Returns false if the file is missing, empty, or larger than maxLen.
|
// Returns false if the file is missing, empty, or larger than maxLen.
|
||||||
static bool loadFile(const char *path, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen) {
|
static bool loadFile(const char *name, uint32_t maxLen, uint8_t **outBytes, uint32_t *outLen) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
long fileSize;
|
long fileSize;
|
||||||
uint8_t *buf;
|
uint8_t *buf;
|
||||||
size_t readBytes;
|
size_t readBytes;
|
||||||
|
|
||||||
fp = fopen(path, "rb");
|
fp = jlDataOpen(name, "rb");
|
||||||
if (fp == NULL) {
|
if (fp == NULL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -149,8 +152,8 @@ int main(void) {
|
||||||
if (audioOk) {
|
if (audioOk) {
|
||||||
// Keep the MOD bytes resident so M can (re)start it later;
|
// Keep the MOD bytes resident so M can (re)start it later;
|
||||||
// jlAudioPlayMod copies them at play time.
|
// jlAudioPlayMod copies them at play time.
|
||||||
(void)loadFile(TEST_MOD_PATH, 64UL * 1024UL, &modBytes, &modLen);
|
(void)loadFile(TEST_MOD_NAME, 64UL * 1024UL, &modBytes, &modLen);
|
||||||
(void)loadFile(TEST_SFX_PATH, 64UL * 1024UL, &sfxBytes, &sfxLen);
|
(void)loadFile(TEST_SFX_NAME, 64UL * 1024UL, &sfxBytes, &sfxLen);
|
||||||
}
|
}
|
||||||
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
jlMusicPlay(gDemoSong, (uint32_t)sizeof(gDemoSong), true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -978,7 +978,7 @@ int main(void) {
|
||||||
// gets one; whatever does not fit draws interpreted). The 68k ports'
|
// gets one; whatever does not fit draws interpreted). The 68k ports'
|
||||||
// cels are far larger (9-13 KB each) and their JIT is fast, so they
|
// cels are far larger (9-13 KB each) and their JIT is fast, so they
|
||||||
// keep a modest arena and compile at level entry.
|
// keep a modest arena and compile at level entry.
|
||||||
#if defined(JOEYLIB_PLATFORM_IIGS) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_IIGS) || defined(JOEYLIB_CPU_X86)
|
||||||
config.codegenBytes = 256UL * 1024;
|
config.codegenBytes = 256UL * 1024;
|
||||||
#else
|
#else
|
||||||
config.codegenBytes = 160UL * 1024;
|
config.codegenBytes = 160UL * 1024;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
!defined(JOEYLIB_PLATFORM_ATARIST) && \
|
!defined(JOEYLIB_PLATFORM_ATARIST) && \
|
||||||
!defined(JOEYLIB_PLATFORM_DOS) && \
|
!defined(JOEYLIB_PLATFORM_DOS) && \
|
||||||
!defined(JOEYLIB_PLATFORM_X68000) && \
|
!defined(JOEYLIB_PLATFORM_X68000) && \
|
||||||
|
!defined(JOEYLIB_PLATFORM_DVX) && \
|
||||||
!defined(JOEYLIB_PLATFORM_BLANK)
|
!defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
|
|
||||||
#if defined(__DJGPP__) || defined(__MSDOS__)
|
#if defined(__DJGPP__) || defined(__MSDOS__)
|
||||||
|
|
@ -35,6 +36,10 @@
|
||||||
// JOEYLIB_PLATFORM_BLANK is the copy-to-start template for a new port (see
|
// 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
|
// src/blank/blank.c). It has no auto-detect macro -- bring it up by building
|
||||||
// with -DJOEYLIB_PLATFORM_BLANK.
|
// with -DJOEYLIB_PLATFORM_BLANK.
|
||||||
|
//
|
||||||
|
// JOEYLIB_PLATFORM_DVX (a program running in a window of the DVX desktop,
|
||||||
|
// src/dvx/) is built with the same DJGPP compiler as DOS, so it cannot be
|
||||||
|
// told apart by compiler macros: make/dvx.mk passes -DJOEYLIB_PLATFORM_DVX.
|
||||||
|
|
||||||
// ----- Validate exactly one platform is defined -----
|
// ----- Validate exactly one platform is defined -----
|
||||||
|
|
||||||
|
|
@ -43,6 +48,7 @@
|
||||||
defined(JOEYLIB_PLATFORM_ATARIST) + \
|
defined(JOEYLIB_PLATFORM_ATARIST) + \
|
||||||
defined(JOEYLIB_PLATFORM_DOS) + \
|
defined(JOEYLIB_PLATFORM_DOS) + \
|
||||||
defined(JOEYLIB_PLATFORM_X68000) + \
|
defined(JOEYLIB_PLATFORM_X68000) + \
|
||||||
|
defined(JOEYLIB_PLATFORM_DVX) + \
|
||||||
defined(JOEYLIB_PLATFORM_BLANK)) != 1
|
defined(JOEYLIB_PLATFORM_BLANK)) != 1
|
||||||
#error "JoeyLib: exactly one JOEYLIB_PLATFORM_* must be defined"
|
#error "JoeyLib: exactly one JOEYLIB_PLATFORM_* must be defined"
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -90,6 +96,13 @@
|
||||||
#define JOEYLIB_ENDIAN_BIG
|
#define JOEYLIB_ENDIAN_BIG
|
||||||
#define JOEYLIB_NATIVE_PLANAR
|
#define JOEYLIB_NATIVE_PLANAR
|
||||||
#define JOEYLIB_PLATFORM_NAME "Sharp X68000"
|
#define JOEYLIB_PLATFORM_NAME "Sharp X68000"
|
||||||
|
#elif defined(JOEYLIB_PLATFORM_DVX)
|
||||||
|
// The DOS stage (chunky 4bpp, x86 sprite JIT and tile walker from
|
||||||
|
// src/x86/), shown in a DVX window instead of on the VGA.
|
||||||
|
#define JOEYLIB_CPU_X86
|
||||||
|
#define JOEYLIB_ENDIAN_LITTLE
|
||||||
|
#define JOEYLIB_NATIVE_CHUNKY
|
||||||
|
#define JOEYLIB_PLATFORM_NAME "DVX"
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
// Chunky little-endian is the simplest baseline for a new port: the generic
|
// 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
|
// backend gives you fully-working software rendering out of the box. Adjust
|
||||||
|
|
@ -326,7 +339,7 @@
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_PLATFORM_DOS)
|
||||||
// chunky generics: surface clear, draw pixel/line/circle, fill circle/rect,
|
// chunky generics: surface clear, draw pixel/line/circle, fill circle/rect,
|
||||||
// tiles, surface readers, allocation -- DOS overrides the timing services
|
// tiles, surface readers, allocation -- DOS overrides the timing services
|
||||||
// plus the whole-map tile walker (src/dos/dosTile.h, dispatched via the
|
// plus the whole-map tile walker (src/x86/x86Tile.h, dispatched via the
|
||||||
// jlpTileMapPaste alias from tile.c's shared whole-map arm like the ST
|
// jlpTileMapPaste alias from tile.c's shared whole-map arm like the ST
|
||||||
// engine; not a jlp op, so no JL_HAS flag).
|
// engine; not a jlp op, so no JL_HAS flag).
|
||||||
// (The planar dual-write hooks are not linked to generics: every chunky port
|
// (The planar dual-write hooks are not linked to generics: every chunky port
|
||||||
|
|
@ -456,6 +469,57 @@
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX
|
#define JL_HAS_AUDIO_PLAY_SFX
|
||||||
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
#define JL_HAS_AUDIO_PLAY_SFX_STREAM
|
||||||
#define JL_HAS_AUDIO_STOP_SFX
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
|
#elif defined(JOEYLIB_PLATFORM_DVX)
|
||||||
|
// A window on the DVX desktop (src/dvx). The graphics are the DOS port's:
|
||||||
|
// the chunky generics, the x86 whole-map tile walker (src/x86/x86Tile.h,
|
||||||
|
// through the jlpTileMapPaste alias) and the x86 sprite JIT. DVX's
|
||||||
|
// libraries supply the services.
|
||||||
|
#define JL_HAS_INIT // the program's window
|
||||||
|
#define JL_HAS_SHUTDOWN
|
||||||
|
#define JL_HAS_PRESENT // dirty rows through per-palette tables into the window
|
||||||
|
#define JL_HAS_INPUT_INIT
|
||||||
|
#define JL_HAS_INPUT_SHUTDOWN
|
||||||
|
#define JL_HAS_INPUT_POLL // held-key table, onKey characters, onMouse, joystick.lib
|
||||||
|
#define JL_HAS_JOYSTICK_RESET
|
||||||
|
#define JL_HAS_WAIT_VBL // gameFrameWait at a virtual 70 Hz (the DOS rate)
|
||||||
|
#define JL_HAS_FRAME_COUNT
|
||||||
|
#define JL_HAS_FRAME_HZ
|
||||||
|
#define JL_HAS_MILLIS_ELAPSED // platformMicros
|
||||||
|
// DVX programs share one current directory: DATA/ is found beside the
|
||||||
|
// .app, SAVES/ and joeylog.txt go in its settings folder (fileRoot.h).
|
||||||
|
#define JL_HAS_FILE_ROOTS
|
||||||
|
// save files + disk space (platformMkdirRecursive, remove, platformDiskSpace)
|
||||||
|
#define JL_HAS_SAVE_DIR_ENSURE
|
||||||
|
#define JL_HAS_SAVE_DELETE
|
||||||
|
#define JL_HAS_DISK_FREE
|
||||||
|
// audio through DVX's audio.lib and music.lib (src/dvx/audio.c). The tick
|
||||||
|
// callback runs in program time (audio.lib is not interrupt-safe), so the
|
||||||
|
// critical sections stay the generic no-ops.
|
||||||
|
#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_SET_PAN
|
||||||
|
#define JL_HAS_AUDIO_STOP_SFX
|
||||||
|
#define JL_HAS_AUDIO_TONE
|
||||||
|
#define JL_HAS_AUDIO_VOICE
|
||||||
|
#define JL_HAS_AUDIO_NOISE
|
||||||
|
#define JL_HAS_AUDIO_TICK_REGISTER
|
||||||
|
#define JL_HAS_AUDIO_FRAME_TICK
|
||||||
|
// serial add-on (optional; a COM port through DVX's rs232 library, which
|
||||||
|
// owns the UARTs for every program -- src/dvx/serial.c)
|
||||||
|
#define JL_HAS_SERIAL_OPEN
|
||||||
|
#define JL_HAS_SERIAL_CLOSE
|
||||||
|
#define JL_HAS_SERIAL_POLL
|
||||||
|
#define JL_HAS_SERIAL_READ
|
||||||
|
#define JL_HAS_SERIAL_WRITE
|
||||||
|
#define JL_HAS_SERIAL_AVAILABLE
|
||||||
|
#define JL_HAS_SERIAL_FLUSH
|
||||||
|
#define JL_HAS_SERIAL_SET_DTR
|
||||||
|
#define JL_HAS_SERIAL_SET_RTS
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
// Copy-to-start template. It overrides ONLY the platform SERVICES (TODO
|
// Copy-to-start template. It overrides ONLY the platform SERVICES (TODO
|
||||||
// stubs in src/blank/blank.c). Everything graphical -- draw / tile / sprite
|
// stubs in src/blank/blank.c). Everything graphical -- draw / tile / sprite
|
||||||
|
|
|
||||||
22
install.sh
22
install.sh
|
|
@ -23,8 +23,9 @@
|
||||||
#
|
#
|
||||||
# Targets are component names (see `list`) or groups:
|
# Targets are component names (see `list`) or groups:
|
||||||
# toolchain:PORT emulator:PORT PORT toolchains emulators all
|
# toolchain:PORT emulator:PORT PORT toolchains emulators all
|
||||||
# where PORT is iigs, amiga, atarist, dos or x68000. `all` leaves out
|
# where PORT is iigs, amiga, atarist, dos, x68000 or dvx. `all`, `toolchains`
|
||||||
# optional components (such as staxiData); name those explicitly.
|
# and `emulators` leave out optional components (staxiData, and dvx, which
|
||||||
|
# copies from a DVX tree you have); name those explicitly.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -y, --yes install missing host packages without asking
|
# -y, --yes install missing host packages without asking
|
||||||
|
|
@ -34,6 +35,8 @@
|
||||||
# --keep-build keep the scratch build trees in cache/build
|
# --keep-build keep the scratch build trees in cache/build
|
||||||
# --llvm816-dir PATH use an existing llvm816 checkout for the IIgs
|
# --llvm816-dir PATH use an existing llvm816 checkout for the IIgs
|
||||||
# compiler (remembered; "--llvm816-dir clone" undoes it)
|
# compiler (remembered; "--llvm816-dir clone" undoes it)
|
||||||
|
# --dvx-dir PATH the built DVX tree the dvx component copies from
|
||||||
|
# (remembered; default: dvxgui beside this repository)
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# ./install.sh install toolchains every cross compiler
|
# ./install.sh install toolchains every cross compiler
|
||||||
|
|
@ -54,7 +57,7 @@ source "$JOEY_INSTALLER/lib/config.sh"
|
||||||
# shellcheck source=installer/lib/bundle.sh
|
# shellcheck source=installer/lib/bundle.sh
|
||||||
source "$JOEY_INSTALLER/lib/bundle.sh"
|
source "$JOEY_INSTALLER/lib/bundle.sh"
|
||||||
|
|
||||||
JOEY_PORTS="iigs amiga atarist dos x68000"
|
JOEY_PORTS="iigs amiga atarist dos x68000 dvx"
|
||||||
depsInstall=0
|
depsInstall=0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,16 +80,16 @@ groupMembers() {
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
toolchains)
|
toolchains)
|
||||||
if [[ "$groups" == *" toolchain:"* ]]; then
|
if [[ "$groups" == *" toolchain:"* && "$groups" != *" optional "* ]]; then
|
||||||
printf '%s\n' "$id"
|
printf '%s\n' "$id"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
emulators)
|
emulators)
|
||||||
if [[ "$groups" == *" emulator:"* ]]; then
|
if [[ "$groups" == *" emulator:"* && "$groups" != *" optional "* ]]; then
|
||||||
printf '%s\n' "$id"
|
printf '%s\n' "$id"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
iigs|amiga|atarist|dos|x68000)
|
iigs|amiga|atarist|dos|x68000|dvx)
|
||||||
if [[ "$groups" == *" toolchain:$group "* || "$groups" == *" emulator:$group "* ]]; then
|
if [[ "$groups" == *" toolchain:$group "* || "$groups" == *" emulator:$group "* ]]; then
|
||||||
printf '%s\n' "$id"
|
printf '%s\n' "$id"
|
||||||
fi
|
fi
|
||||||
|
|
@ -417,6 +420,13 @@ while [ $# -gt 0 ]; do
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--dvx-dir)
|
||||||
|
[ $# -ge 2 ] || die "--dvx-dir needs a path"
|
||||||
|
[ -f "$2/bin/dvx.exe" ] || die "$2 does not look like a built DVX tree (no bin/dvx.exe)"
|
||||||
|
mkdir -p "$JOEY_STATE"
|
||||||
|
(cd "$2" && pwd) > "$JOEY_STATE/dvxDir"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help|help)
|
-h|--help|help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
# DOSBox Staging 0.83.0: the DOS emulator, as the project's own prebuilt
|
# DOSBox Staging 0.83.0: the DOS emulator (for DOS and for DVX, which runs
|
||||||
# Linux release (bundled libraries), because Debian/Ubuntu only package the
|
# on it), as the project's own prebuilt Linux release (bundled libraries),
|
||||||
# old 0.74 and the gates are written for Staging.
|
# because Debian/Ubuntu only package the old 0.74 and the gates are written
|
||||||
|
# for Staging.
|
||||||
|
|
||||||
compDescription="emulator: DOSBox Staging 0.83.0 (MS-DOS; upstream Linux release)"
|
compDescription="emulator: DOSBox Staging 0.83.0 (MS-DOS, DVX; upstream Linux release)"
|
||||||
compGroups="emulator:dos"
|
compGroups="emulator:dos emulator:dvx"
|
||||||
compRequires="host"
|
compRequires="host"
|
||||||
compHostDeps="xz"
|
compHostDeps="xz"
|
||||||
compUrl="https://github.com/dosbox-staging/dosbox-staging/releases/download/v0.83.0/dosbox-staging-linux-x86_64-v0.83.0.tar.xz"
|
compUrl="https://github.com/dosbox-staging/dosbox-staging/releases/download/v0.83.0/dosbox-staging-linux-x86_64-v0.83.0.tar.xz"
|
||||||
|
|
|
||||||
101
installer/components/dvx.sh
Normal file
101
installer/components/dvx.sh
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# DVX, the DOS desktop JoeyLib's dvx port runs in: its SDK (the headers,
|
||||||
|
# sdk.mk and the host tools every DVX program is built with) and the runtime
|
||||||
|
# tree the gates and joey-run boot (dvx.exe with its libraries, widgets,
|
||||||
|
# programs, settings and system files). Copied from a built DVX tree, so the
|
||||||
|
# builds and the emulator runs never touch the DVX project itself.
|
||||||
|
#
|
||||||
|
# `install.sh --dvx-dir PATH` names the DVX tree (remembered); the default is
|
||||||
|
# a dvxgui checkout beside this repository. Rebuilding DVX there makes this
|
||||||
|
# component out of date, and `install dvx` copies it again.
|
||||||
|
|
||||||
|
compDescription="DVX: desktop runtime + SDK, copied from a DVX tree (--dvx-dir)"
|
||||||
|
# Optional: it needs a DVX tree of your own, so `all` and `toolchains` leave
|
||||||
|
# it out; `install dvx` (or toolchain:dvx) asks for it.
|
||||||
|
compGroups="toolchain:dvx optional"
|
||||||
|
compRequires="host djgpp"
|
||||||
|
compHostDeps=""
|
||||||
|
|
||||||
|
dvxDir="$(cat "$JOEY_STATE/dvxDir" 2>/dev/null || true)"
|
||||||
|
if [ -z "$dvxDir" ]; then
|
||||||
|
dvxDir="$(cd "$JOEY_REPO/.." && pwd)/dvxgui"
|
||||||
|
fi
|
||||||
|
dvxBin="$dvxDir/bin"
|
||||||
|
# What the runtime needs from bin/: not the host test tools (bin/host),
|
||||||
|
# the test modules, the link map, the log or the installer.
|
||||||
|
dvxRuntime="dvx.exe config system libs widgets apps"
|
||||||
|
dvxSdk="sdk"
|
||||||
|
|
||||||
|
|
||||||
|
# dvxListing -- every file copied, with its size and time: the pin, so a
|
||||||
|
# rebuilt DVX makes this component out of date.
|
||||||
|
dvxListing() {
|
||||||
|
local part
|
||||||
|
[ -d "$dvxBin" ] || return 0
|
||||||
|
for part in $dvxRuntime $dvxSdk; do
|
||||||
|
[ -e "$dvxBin/$part" ] || continue
|
||||||
|
find "$dvxBin/$part" -type f -printf '%P %s %T@\n'
|
||||||
|
done | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compPin="dir:$dvxDir:$(dvxListing | sha1sum | cut -d' ' -f1)"
|
||||||
|
|
||||||
|
|
||||||
|
compInstall() {
|
||||||
|
local part
|
||||||
|
[ -f "$dvxBin/dvx.exe" ] || die "$dvxDir is not a built DVX tree (no bin/dvx.exe); build DVX there, or point --dvx-dir at one"
|
||||||
|
[ -f "$dvxBin/$dvxSdk/sdk.mk" ] || die "$dvxDir has no SDK (bin/sdk/sdk.mk); build DVX there first"
|
||||||
|
rm -rf "$compDir"
|
||||||
|
mkdir -p "$compDir"
|
||||||
|
for part in $dvxRuntime $dvxSdk; do
|
||||||
|
[ -e "$dvxBin/$part" ] || die "$dvxBin/$part is missing; is the DVX build complete?"
|
||||||
|
cp -a "$dvxBin/$part" "$compDir/"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compInstalled() {
|
||||||
|
[ -f "$compDir/dvx.exe" ] &&
|
||||||
|
[ -f "$compDir/config/DVX.INI" ] &&
|
||||||
|
[ -f "$compDir/$dvxSdk/sdk.mk" ] &&
|
||||||
|
[ -x "$compDir/$dvxSdk/bin/dvxhlpc" ] &&
|
||||||
|
[ -x "$compDir/$dvxSdk/bin/dvxres" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Build a one-window DVX program with the copied SDK and JoeyLib's DJGPP:
|
||||||
|
# proves the SDK, its host tools and the compiler work together.
|
||||||
|
compVerify() {
|
||||||
|
local tmp
|
||||||
|
local status=1
|
||||||
|
verifyLinked "$compDir/$dvxSdk/bin" || return 1
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
cat > "$tmp/smoke.c" <<'EOF'
|
||||||
|
#include "dvxApp.h"
|
||||||
|
#include "shellApp.h"
|
||||||
|
|
||||||
|
int32_t appMain(DxeAppContextT *ctx);
|
||||||
|
|
||||||
|
SHELL_APP_DESCRIPTOR(.name = "Smoke", .hasMainLoop = false, .multiInstance = false, .stackSize = SHELL_STACK_DEFAULT, .priority = TS_PRIORITY_NORMAL);
|
||||||
|
|
||||||
|
|
||||||
|
int32_t appMain(DxeAppContextT *ctx) {
|
||||||
|
return dvxCreateWindowCentered(ctx->shellCtx, "Smoke", 200, 100, false) != NULL ? 0 : -1;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
printf '.topic smoke.overview\n.title Smoke\n.toc 0 Smoke\n.default\n\n.h1 Smoke\n\nA test.\n' > "$tmp/smoke.dhs"
|
||||||
|
printf 'SDK_DIR = %s\nKIND = app\nNAME = smoke\ninclude $(SDK_DIR)/sdk.mk\n' "$compDir/$dvxSdk" > "$tmp/Makefile"
|
||||||
|
if make -s -C "$tmp" DJGPP_PREFIX="$JOEY_OPT/djgpp" > "$tmp/make.log" 2>&1 && [ -s "$tmp/smoke.app" ] && [ -s "$tmp/smoke.hlp" ]; then
|
||||||
|
status=0
|
||||||
|
else
|
||||||
|
cat "$tmp/make.log" >&2
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
compEnv() {
|
||||||
|
envVar DVX_ROOT "$compDir"
|
||||||
|
envVar DVX_SDK "$compDir/$dvxSdk"
|
||||||
|
}
|
||||||
60
joey-run
60
joey-run
|
|
@ -8,15 +8,16 @@
|
||||||
# PROGRAM is any of:
|
# PROGRAM is any of:
|
||||||
# a program file build/dos/bin/DRAW.EXE, build/atarist/bin/DRAW.PRG,
|
# a program file build/dos/bin/DRAW.EXE, build/atarist/bin/DRAW.PRG,
|
||||||
# build/amiga/bin/Draw, build/x68000/bin/DRAW.X,
|
# build/amiga/bin/Draw, build/x68000/bin/DRAW.X,
|
||||||
# build/iigs/bin/DRAW
|
# build/iigs/bin/DRAW, build/dvx/bin/apps/joeylib/draw/draw.app
|
||||||
# an app folder build/amiga/bin/Taxi (the program plus its DATA)
|
# an app folder build/amiga/bin/Taxi (the program plus its DATA),
|
||||||
|
# build/dvx/bin/apps/joeylib/staxi
|
||||||
# an IIgs disk image build/iigs/bin/STAXI.2mg (make iigs-game-disk), or
|
# an IIgs disk image build/iigs/bin/STAXI.2mg (make iigs-game-disk), or
|
||||||
# a data disk such as joey.2mg (boots GS/OS with it)
|
# a data disk such as joey.2mg (boots GS/OS with it)
|
||||||
# an example name draw, staxi, uber, ... together with --port
|
# an example name draw, staxi, uber, ... together with --port
|
||||||
# The machine is recognised from the file itself.
|
# The machine is recognised from the file itself.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# --port PORT iigs, amiga, atarist, dos or x68000
|
# --port PORT iigs, amiga, atarist, dos, x68000 or dvx
|
||||||
# --data DIR copy DIR's files into the program's DATA folder for
|
# --data DIR copy DIR's files into the program's DATA folder for
|
||||||
# this run (for example an AGI game's files)
|
# this run (for example an AGI game's files)
|
||||||
# --headless no window and no sound, for scripts and CI; stops
|
# --headless no window and no sound, for scripts and CI; stops
|
||||||
|
|
@ -44,7 +45,14 @@
|
||||||
# The serial port is the machine's usual one: the IIgs modem port (the
|
# The serial port is the machine's usual one: the IIgs modem port (the
|
||||||
# printer port and a Super Serial Card are not connected), the X68000's
|
# printer port and a Super Serial Card are not connected), the X68000's
|
||||||
# RS-232C port, the ST's Modem port, the Amiga's serial port (serial.device
|
# RS-232C port, the ST's Modem port, the Amiga's serial port (serial.device
|
||||||
# unit 0) and DOS's COM1. The link is raw TCP, without telnet negotiation.
|
# unit 0) and DOS's (and DVX's) COM1.
|
||||||
|
#
|
||||||
|
# A DVX program runs in DOSBox Staging on a copy of the DVX the installer
|
||||||
|
# took from a DVX tree (install dvx), which boots straight into it: the
|
||||||
|
# program is DVX's desktop program, in its window on a 1024x768 desktop
|
||||||
|
# (640x480 with --headless). Interactive runs keep that DVX in
|
||||||
|
# build/dvx/run, so what the program saves (in DVX's CONFIG folder) is
|
||||||
|
# still there next time. The link is raw TCP, without telnet negotiation.
|
||||||
# socat relays it where the emulator cannot make it itself: the ST, the
|
# socat relays it where the emulator cannot make it itself: the ST, the
|
||||||
# Amiga with --serial, and the IIgs and X68000 with --serial-listen. When
|
# Amiga with --serial, and the IIgs and X68000 with --serial-listen. When
|
||||||
# --serial's server is not there, joey-run stops, except on the IIgs and
|
# --serial's server is not there, joey-run stops, except on the IIgs and
|
||||||
|
|
@ -134,6 +142,7 @@ magicPort() {
|
||||||
601a*) printf 'atarist\n' ;;
|
601a*) printf 'atarist\n' ;;
|
||||||
4d5a*) printf 'dos\n' ;;
|
4d5a*) printf 'dos\n' ;;
|
||||||
4855*) printf 'x68000\n' ;;
|
4855*) printf 'x68000\n' ;;
|
||||||
|
44584531*) printf 'dvx\n' ;; # "DXE1": a DVX module (.app)
|
||||||
*)
|
*)
|
||||||
# Apple IIgs OMF: segment header byte 14 (NUMLEN) is 4 and
|
# Apple IIgs OMF: segment header byte 14 (NUMLEN) is 4 and
|
||||||
# byte 15 (VERSION) is 2.
|
# byte 15 (VERSION) is 2.
|
||||||
|
|
@ -559,6 +568,46 @@ runDos() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runDvx() {
|
||||||
|
local dir
|
||||||
|
local app
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
app="$(dirname "$program")"
|
||||||
|
if [ "$headless" = "1" ] || [ -n "$dataDir" ]; then
|
||||||
|
dir="$work/dvx"
|
||||||
|
else
|
||||||
|
dir="$repo/build/dvx/run"
|
||||||
|
# DVX's own desktop size, where a program's window is the stage at 3x.
|
||||||
|
DVX_VIDEO_W=1024
|
||||||
|
DVX_VIDEO_H=768
|
||||||
|
fi
|
||||||
|
# shellcheck source=scripts/lib/dvxGate.sh
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
if [ -n "$dataDir" ]; then
|
||||||
|
stageFolder "$work/app/$(basename "$app")"
|
||||||
|
app="$work/app/$(basename "$app")"
|
||||||
|
fi
|
||||||
|
dvxStage "$dir" "$app"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$dir" sb16
|
||||||
|
# transparent:1 -- data bytes only, as for DOS.
|
||||||
|
case "$serialMode" in
|
||||||
|
connect) printf '\n[serial]\nserial1 = nullmodem server:%s port:%s transparent:1\n' "$serialHost" "$serialPort" >> "$work/dosbox.conf" ;;
|
||||||
|
listen) printf '\n[serial]\nserial1 = nullmodem port:%s transparent:1\n' "$serialPort" >> "$work/dosbox.conf" ;;
|
||||||
|
esac
|
||||||
|
if [ "$headless" = "1" ]; then
|
||||||
|
startDisplay
|
||||||
|
fi
|
||||||
|
echo "joey-run: booting DVX in DOSBox; $(basename "$program") is its desktop program. Close DOSBox to stop."
|
||||||
|
serialNote "COM1"
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" --conf "$work/dosbox.conf" \
|
||||||
|
--set "mouse_capture=seamless" > "$work/dosbox.log" 2>&1 &
|
||||||
|
emuPid=$!
|
||||||
|
dosSerialCheck
|
||||||
|
waitOrStop
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
runX68000() {
|
runX68000() {
|
||||||
local name
|
local name
|
||||||
local stage="$work/files"
|
local stage="$work/files"
|
||||||
|
|
@ -596,7 +645,7 @@ runX68000() {
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--port) port="${2:?--port needs iigs, amiga, atarist, dos or x68000}"; shift ;;
|
--port) port="${2:?--port needs iigs, amiga, atarist, dos, x68000 or dvx}"; shift ;;
|
||||||
--data) dataDir="${2:?--data needs a folder}"; shift ;;
|
--data) dataDir="${2:?--data needs a folder}"; shift ;;
|
||||||
--headless) headless=1 ;;
|
--headless) headless=1 ;;
|
||||||
--seconds) seconds="${2:?--seconds needs a number}"; shift ;;
|
--seconds) seconds="${2:?--seconds needs a number}"; shift ;;
|
||||||
|
|
@ -686,6 +735,7 @@ case "$detected" in
|
||||||
atarist) runAtarist ;;
|
atarist) runAtarist ;;
|
||||||
dos) runDos ;;
|
dos) runDos ;;
|
||||||
x68000) runX68000 ;;
|
x68000) runX68000 ;;
|
||||||
|
dvx) runDvx ;;
|
||||||
esac
|
esac
|
||||||
if [ -n "$snapshot" ] && [ -f "$snapshot" ]; then
|
if [ -n "$snapshot" ] && [ -f "$snapshot" ]; then
|
||||||
echo "joey-run: snapshot saved to $snapshot"
|
echo "joey-run: snapshot saved to $snapshot"
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,14 @@ HAVE_STAXI_DATA := $(if $(wildcard $(STAXI_RAW_DUMP)),1)
|
||||||
staxiData = $(if $(HAVE_STAXI_DATA),$(1),$(staxiNote))
|
staxiData = $(if $(HAVE_STAXI_DATA),$(1),$(staxiNote))
|
||||||
staxiNote = $(if $(staxiNoted),,$(eval staxiNoted := 1)$(info note: Space Taxi data skipped: stuff/spacetaxi/raw.bin is not present))
|
staxiNote = $(if $(staxiNoted),,$(eval staxiNoted := 1)$(info note: Space Taxi data skipped: stuff/spacetaxi/raw.bin is not present))
|
||||||
INCLUDE_DIR := $(REPO_DIR)/include
|
INCLUDE_DIR := $(REPO_DIR)/include
|
||||||
# Machine-specific code lives in src/<machine>/ (iigs, amiga, atarist, dos);
|
# 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).
|
# x68000, dvx); code shared across machines of one CPU lives in a processor
|
||||||
|
# folder (src/m68k, src/x86).
|
||||||
SRC_DIR := $(REPO_DIR)/src
|
SRC_DIR := $(REPO_DIR)/src
|
||||||
SRC_CORE := $(REPO_DIR)/src/core
|
SRC_CORE := $(REPO_DIR)/src/core
|
||||||
SRC_CG := $(REPO_DIR)/src/codegen
|
SRC_CG := $(REPO_DIR)/src/codegen
|
||||||
SRC_68K := $(REPO_DIR)/src/m68k
|
SRC_68K := $(REPO_DIR)/src/m68k
|
||||||
|
SRC_X86 := $(REPO_DIR)/src/x86
|
||||||
EXAMPLES := $(REPO_DIR)/examples
|
EXAMPLES := $(REPO_DIR)/examples
|
||||||
|
|
||||||
# The tile-bank example (examples/tilebank) loads the host gate's fixture, so
|
# The tile-bank example (examples/tilebank) loads the host gate's fixture, so
|
||||||
|
|
|
||||||
19
make/dos.mk
19
make/dos.mk
|
|
@ -16,7 +16,7 @@ BINDIR := $(BUILD)/bin
|
||||||
# it; DOS never did). Frees EBP for allocation in the hot loops under
|
# it; DOS never did). Frees EBP for allocation in the hot loops under
|
||||||
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
# DOSBox's per-instruction-charged core (perf-hunt-2 rank 2).
|
||||||
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
# so flag experiments never edit a tracked file (perf-hunt-2 rank 17).
|
||||||
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(LIBXMP_DIR)/include -MMD -MP
|
CFLAGS := $(COMMON_CFLAGS) -march=i386 -m32 -fomit-frame-pointer -I$(SRC_DIR)/dos -I$(SRC_X86) -I$(LIBXMP_DIR)/include -MMD -MP
|
||||||
ASFLAGS := -f coff
|
ASFLAGS := -f coff
|
||||||
LDFLAGS :=
|
LDFLAGS :=
|
||||||
|
|
||||||
|
|
@ -30,10 +30,12 @@ LIBXMP_OBJS := $(patsubst $(LIBXMP_DIR)/src/%.c,$(LIBXMP_OBJDIR)/%.o,$(LIBXMP
|
||||||
LIBXMP_AR := $(LIBDIR)/libxmplite.a
|
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
|
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
|
||||||
|
|
||||||
# The x86 sprite emitter (spriteEmitX86.c) now lives in src/dos/ and is built
|
# The x86 code DOS shares with DVX (the sprite emitter, the whole-map tile
|
||||||
# by the port rule below; only the CPU-independent dispatch stays in src/codegen.
|
# walker, the set-1 scan code table) lives in src/x86 and builds into obj/x86;
|
||||||
|
# only the CPU-independent sprite dispatch stays in src/codegen.
|
||||||
PORT_C_SRCS := $(wildcard $(SRC_DIR)/dos/*.c)
|
PORT_C_SRCS := $(wildcard $(SRC_DIR)/dos/*.c)
|
||||||
PORT_S_SRCS := $(wildcard $(SRC_DIR)/dos/*.asm)
|
PORT_S_SRCS := $(wildcard $(SRC_DIR)/dos/*.asm)
|
||||||
|
X86_C_SRCS := $(wildcard $(SRC_X86)/*.c)
|
||||||
CODEGEN_DIR := $(REPO_DIR)/src/codegen
|
CODEGEN_DIR := $(REPO_DIR)/src/codegen
|
||||||
|
|
||||||
LIB_OBJS := \
|
LIB_OBJS := \
|
||||||
|
|
@ -41,6 +43,7 @@ LIB_OBJS := \
|
||||||
$(patsubst $(SRC_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \
|
$(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/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||||
$(patsubst $(SRC_DIR)/dos/%.asm,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
$(patsubst $(SRC_DIR)/dos/%.asm,$(BUILD)/obj/port/%.o,$(PORT_S_SRCS)) \
|
||||||
|
$(patsubst $(SRC_X86)/%.c,$(BUILD)/obj/x86/%.o,$(X86_C_SRCS)) \
|
||||||
$(BUILD)/obj/codegen/spriteCompile.o \
|
$(BUILD)/obj/codegen/spriteCompile.o \
|
||||||
$(BUILD)/obj/codegen/spriteStage.o
|
$(BUILD)/obj/codegen/spriteStage.o
|
||||||
|
|
||||||
|
|
@ -156,9 +159,13 @@ STAXI_ASSET_DSTS += $(STAXICELS_SPC_RUN)
|
||||||
|
|
||||||
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
MKSTLEVEL_BIN := $(REPO_DIR)/build/tools/mkstlevel
|
||||||
|
|
||||||
.PHONY: all dos clean-dos clean
|
.PHONY: all dos dos-staxi-data clean-dos clean
|
||||||
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS))
|
all dos: $(LIB) $(LIBXMP_AR) $(HELLO_BIN) $(PATTERN_BIN) $(DRAW_BIN) $(KEYS_BIN) $(SERIAL_BIN) $(SERTEST_BIN) $(SAVE_BIN) $(TILEBANK_BIN) $(JOY_BIN) $(SPRITE_BIN) $(AUDIO_BIN) $(UBER_BIN) $(ADV_BIN) $(ADV2_BIN) $(AGI_BIN) $(STAXI_BIN) $(DATA_FILES) $(TILEBANK_DATA) $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
|
# Space Taxi's DATA tree alone: make/dvx.mk stages this same bake, since DVX
|
||||||
|
# runs the DOS stage and the same x86 code.
|
||||||
|
dos-staxi-data: $(call staxiData,$(STAXI_ASSET_DSTS))
|
||||||
|
|
||||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
$(DOS_CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
@ -175,6 +182,10 @@ $(BUILD)/obj/port/%.o: $(SRC_DIR)/dos/%.asm
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(DOS_AS) $(ASFLAGS) $< -o $@
|
$(DOS_AS) $(ASFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/x86/%.o: $(SRC_X86)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||||
|
|
||||||
$(BUILD)/obj/codegen/%.o: $(CODEGEN_DIR)/%.c
|
$(BUILD)/obj/codegen/%.o: $(CODEGEN_DIR)/%.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
$(DOS_CC) $(CFLAGS) -I$(CODEGEN_DIR) -c $< -o $@
|
||||||
|
|
|
||||||
249
make/dvx.mk
Normal file
249
make/dvx.mk
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
# DVX build rules: every example as a DVX program (.app).
|
||||||
|
#
|
||||||
|
# A JoeyLib program on DVX runs in a window of the DVX desktop (src/dvx). The
|
||||||
|
# library, libjoey.a, is built here with JoeyLib's own DJGPP (DVX is built
|
||||||
|
# with the same gcc 12.2.0) against the SDK the installer copied from a DVX
|
||||||
|
# tree into toolchains/opt/dvx (DVX_SDK); the stage and the graphics are the
|
||||||
|
# DOS port's (src/x86, the chunky generics). Each example is then a DVX SDK
|
||||||
|
# project in build/dvx/apps/<name>/ -- symlinks to its sources and to the
|
||||||
|
# port's entry (src/dvx/dvxApp.c), a generated one-topic help file and
|
||||||
|
# resource list -- built and packaged by the SDK's own sdk.mk (KIND = app,
|
||||||
|
# LIBS = libjoey.a, DATA = its data files) and copied into build/dvx/bin/
|
||||||
|
# laid out as it goes into DVX: apps/joeylib/<name>/<name>.app, <name>.hlp
|
||||||
|
# and DATA/. The gates and joey-run copy that tree into a DVX installation.
|
||||||
|
|
||||||
|
include $(dir $(lastword $(MAKEFILE_LIST)))/common.mk
|
||||||
|
|
||||||
|
# The asset rules below define real targets before `all`, which would
|
||||||
|
# otherwise become make's default goal.
|
||||||
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
|
$(call requireTool,$(DOS_CC),toolchain:dos)
|
||||||
|
$(call requireTool,$(DVX_SDK)/sdk.mk,toolchain:dvx)
|
||||||
|
|
||||||
|
PLATFORM := dvx
|
||||||
|
BUILD := $(REPO_DIR)/build/$(PLATFORM)
|
||||||
|
LIBDIR := $(BUILD)/lib
|
||||||
|
BINDIR := $(BUILD)/bin
|
||||||
|
APPSDIR := $(BUILD)/apps
|
||||||
|
DATADIR := $(BUILD)/data
|
||||||
|
|
||||||
|
# Where a program installs in DVX: apps/<vendor>/<name>/.
|
||||||
|
VENDOR := joeylib
|
||||||
|
|
||||||
|
PORT_DIR := $(SRC_DIR)/dvx
|
||||||
|
|
||||||
|
# sdk.mk's code generation, so the library matches the programs it links
|
||||||
|
# into; -fomit-frame-pointer as on DOS.
|
||||||
|
DVX_ARCH := -march=i486 -mtune=i586 -fomit-frame-pointer
|
||||||
|
|
||||||
|
# The SDK's header folders (sdk.mk's list: all but the BASIC and socket ones),
|
||||||
|
# for the port's own files only -- the core never sees a DVX header.
|
||||||
|
DVX_SDK_INCS := $(addprefix -I,$(filter-out %/basic %/net,$(patsubst %/,%,$(wildcard $(DVX_SDK)/include/*/))))
|
||||||
|
|
||||||
|
# A program's exit() and atexit() are the port's (src/dvx/program.c): the
|
||||||
|
# handlers run, and JoeyLib shuts down, when the program ends rather than
|
||||||
|
# when DVX does. Applied to the library and the programs, never to the port.
|
||||||
|
PROGRAM_RENAMES := -Dexit=dvxPortExit -Datexit=dvxPortAtExit
|
||||||
|
|
||||||
|
CFLAGS := $(COMMON_CFLAGS) $(DVX_ARCH) -DJOEYLIB_PLATFORM_DVX -I$(SRC_X86) -I$(SRC_CG) -MMD -MP
|
||||||
|
LIB_CFLAGS := $(CFLAGS) $(PROGRAM_RENAMES)
|
||||||
|
PORT_CFLAGS := $(CFLAGS) -I$(PORT_DIR) $(DVX_SDK_INCS)
|
||||||
|
|
||||||
|
# The flags every program adds to sdk.mk's (which bring the SDK headers and
|
||||||
|
# -Werror), with the include folders every port gives its examples: the
|
||||||
|
# program's main becomes jlGameMain, which the port calls.
|
||||||
|
APP_CFLAGS := -DJOEYLIB_PLATFORM_DVX -Dmain=jlGameMain $(PROGRAM_RENAMES) -fomit-frame-pointer -I$(INCLUDE_DIR) -I$(SRC_CORE) -I$(PORT_DIR)
|
||||||
|
|
||||||
|
# dvxApp.c is compiled into each program (it names it), not the library.
|
||||||
|
PORT_ENTRY := $(PORT_DIR)/dvxApp.c
|
||||||
|
PORT_C_SRCS := $(filter-out $(PORT_ENTRY),$(wildcard $(PORT_DIR)/*.c))
|
||||||
|
X86_C_SRCS := $(wildcard $(SRC_X86)/*.c)
|
||||||
|
|
||||||
|
LIB_OBJS := \
|
||||||
|
$(patsubst $(SRC_CORE)/%.c,$(BUILD)/obj/core/%.o,$(CORE_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_DIR)/generic/%.c,$(BUILD)/obj/generic/%.o,$(GENERIC_C_SRCS)) \
|
||||||
|
$(patsubst $(PORT_DIR)/%.c,$(BUILD)/obj/port/%.o,$(PORT_C_SRCS)) \
|
||||||
|
$(patsubst $(SRC_X86)/%.c,$(BUILD)/obj/x86/%.o,$(X86_C_SRCS)) \
|
||||||
|
$(BUILD)/obj/codegen/spriteCompile.o \
|
||||||
|
$(BUILD)/obj/codegen/spriteStage.o
|
||||||
|
|
||||||
|
LIB := $(LIBDIR)/libjoey.a
|
||||||
|
|
||||||
|
# The examples: for each, its sources, its window title and a line of help
|
||||||
|
# (no apostrophes: the help lines are written through the shell in quotes).
|
||||||
|
# Worth knowing which ones the gates use:
|
||||||
|
# UBER the golden-hash vehicle: a hash per public op, which must equal
|
||||||
|
# the DOS port's (tests/goldens/uber/dos.txt): the same stage.
|
||||||
|
# TILEBANK the tile-bank gate's program: the DOS bake loaded both ways and
|
||||||
|
# pixel-checked, every other target's bake refused.
|
||||||
|
# KEYS the input gate's program: typed characters to jlInputGetChar.
|
||||||
|
hello_SRCS := $(EXAMPLES)/hello/hello.c
|
||||||
|
hello_TITLE := Hello
|
||||||
|
hello_HELP := The smallest JoeyLib program: it clears the screen and waits for a key.
|
||||||
|
pattern_SRCS := $(EXAMPLES)/pattern/pattern.c
|
||||||
|
pattern_TITLE := Pattern
|
||||||
|
pattern_HELP := Eight bands of gradient palettes, one per band of scan lines.
|
||||||
|
draw_SRCS := $(EXAMPLES)/draw/draw.c
|
||||||
|
draw_TITLE := Draw
|
||||||
|
draw_HELP := The drawing primitives: pixels, lines, circles, rectangles and flood fills.
|
||||||
|
keys_SRCS := $(EXAMPLES)/keys/keys.c
|
||||||
|
keys_TITLE := Keys
|
||||||
|
keys_HELP := Keyboard, typed characters and mouse: held keys light up and typed text appears. Escape ends it.
|
||||||
|
serial_SRCS := $(EXAMPLES)/serial/serial.c
|
||||||
|
serial_TITLE := Serial
|
||||||
|
serial_HELP := A serial terminal on COM1.
|
||||||
|
sertest_SRCS := $(EXAMPLES)/sertest/sertest.c
|
||||||
|
sertest_TITLE := SerTest
|
||||||
|
sertest_HELP := The serial port test program.
|
||||||
|
save_SRCS := $(EXAMPLES)/save/save.c
|
||||||
|
save_TITLE := Save
|
||||||
|
save_HELP := Saved files: counts its runs in a file in its settings folder.
|
||||||
|
tilebank_SRCS := $(EXAMPLES)/tilebank/tilebank.c
|
||||||
|
tilebank_TITLE := TileBank
|
||||||
|
tilebank_HELP := The tile bank check: loads a baked tile bank two ways and checks every pixel.
|
||||||
|
joy_SRCS := $(EXAMPLES)/joy/joy.c
|
||||||
|
joy_TITLE := Joy
|
||||||
|
joy_HELP := Joysticks: shows the position and buttons of each joystick.
|
||||||
|
sprite_SRCS := $(EXAMPLES)/sprite/sprite.c
|
||||||
|
sprite_TITLE := Sprite
|
||||||
|
sprite_HELP := Compiled sprites moving over a background.
|
||||||
|
audio_SRCS := $(EXAMPLES)/audio/audio.c
|
||||||
|
audio_TITLE := Audio
|
||||||
|
audio_HELP := Sound: music, sound effects and tones.
|
||||||
|
uber_SRCS := $(EXAMPLES)/uber/uber.c
|
||||||
|
uber_TITLE := Uber
|
||||||
|
uber_HELP := The JoeyLib conformance and speed test: every drawing call, timed and checked. Its results go to joeylog.txt in its settings folder.
|
||||||
|
adv_SRCS := $(EXAMPLES)/adventure/adventure.c
|
||||||
|
adv_TITLE := Adventure
|
||||||
|
adv_HELP := A small adventure game.
|
||||||
|
adv2_SRCS := $(EXAMPLES)/adventure2/adventure2.c
|
||||||
|
adv2_TITLE := Adventure 2
|
||||||
|
adv2_HELP := A second small adventure game.
|
||||||
|
agi_SRCS := $(EXAMPLES)/agi/agi.c $(EXAMPLES)/agi/agiRes.c $(EXAMPLES)/agi/agiPic.c $(EXAMPLES)/agi/agiView.c $(EXAMPLES)/agi/agiVm.c $(EXAMPLES)/agi/agiObj.c $(EXAMPLES)/agi/agiText.c
|
||||||
|
agi_TITLE := AGI
|
||||||
|
agi_HELP := A Sierra AGI interpreter. The files of the game it plays go in its DATA folder.
|
||||||
|
staxi_SRCS := $(EXAMPLES)/spacetaxi/spacetaxi.c $(EXAMPLES)/spacetaxi/stLevel.c $(EXAMPLES)/spacetaxi/stLevelFile.c $(EXAMPLES)/spacetaxi/stRender.c $(EXAMPLES)/spacetaxi/stSim.c $(EXAMPLES)/spacetaxi/stFare.c $(EXAMPLES)/spacetaxi/stHooks.c $(EXAMPLES)/spacetaxi/stTitle.c $(EXAMPLES)/spacetaxi/stC64Data.c $(EXAMPLES)/spacetaxi/stCels.c $(EXAMPLES)/spacetaxi/stAudio.c $(EXAMPLES)/spacetaxi/stLevelCels.c
|
||||||
|
staxi_TITLE := Space Taxi
|
||||||
|
staxi_HELP := Space Taxi: fly the taxi, pick up the fares, take them where they ask to go.
|
||||||
|
|
||||||
|
APPS := hello pattern draw keys serial sertest save tilebank joy sprite audio uber adv adv2 agi staxi
|
||||||
|
|
||||||
|
# Data the examples read from DATA/ beside the program: AUDIO its music and
|
||||||
|
# sound effect; TILEBANK the DOS bake of the tile-bank fixture (the DOS
|
||||||
|
# target byte: DVX shows the DOS stage), every other target's bake it must
|
||||||
|
# refuse, and the expected pixels (common.mk); Space Taxi the DOS bake of
|
||||||
|
# its levels, compiled cel banks (x86 code, the DOS target byte) and speech,
|
||||||
|
# made by make/dos.mk -- only when the C64 image it comes from is present.
|
||||||
|
audio_DATA := $(DATADIR)/audio/DATA
|
||||||
|
tilebank_DATA := $(DATADIR)/tilebank/DATA
|
||||||
|
staxi_DATA := $(if $(HAVE_STAXI_DATA),$(DATADIR)/staxi/DATA)
|
||||||
|
AUDIO_DATA_FILES := $(audio_DATA)/test.mod $(audio_DATA)/test.sfx
|
||||||
|
TILEBANK_OWN := $(tilebank_DATA)/tilebank/own.tbk
|
||||||
|
DOS_STAXI_DATA := $(REPO_DIR)/build/dos/bin/STAXI/DATA
|
||||||
|
|
||||||
|
.PHONY: all dvx dvx-lib dvx-apps clean-dvx clean $(addprefix app-,$(APPS))
|
||||||
|
|
||||||
|
all dvx: dvx-lib dvx-apps
|
||||||
|
|
||||||
|
dvx-lib: $(LIB)
|
||||||
|
|
||||||
|
dvx-apps: $(addprefix app-,$(APPS))
|
||||||
|
|
||||||
|
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(LIB_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/generic/%.o: $(SRC_DIR)/generic/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(LIB_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/x86/%.o: $(SRC_X86)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(LIB_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/codegen/%.o: $(SRC_CG)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(LIB_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/obj/port/%.o: $(PORT_DIR)/%.c
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(DOS_CC) $(PORT_CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(LIB): $(LIB_OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
rm -f $@
|
||||||
|
$(DOS_AR) rcs $@ $^
|
||||||
|
|
||||||
|
# One program: (re)write its SDK project -- the Makefile, symlinks to its
|
||||||
|
# sources and the port's entry, the help source and the resource list --
|
||||||
|
# then let sdk.mk build and package it, and copy the package into bin/.
|
||||||
|
# sdk.mk rebuilds only what changed; the project files are rewritten only
|
||||||
|
# when their contents change, so an unchanged program is not relinked.
|
||||||
|
# $(1) = the program's name.
|
||||||
|
define appRule
|
||||||
|
app-$(1): $(LIB) $$($(1)_SRCS) $(PORT_ENTRY) $$(if $$($(1)_DATA),$(1)-data)
|
||||||
|
@mkdir -p $(APPSDIR)/$(1)
|
||||||
|
@for src in $$($(1)_SRCS) $(PORT_ENTRY); do ln -sfn $$$$src $(APPSDIR)/$(1)/$$$$(basename $$$$src); done
|
||||||
|
@printf '%s\n' \
|
||||||
|
'# Generated by make/dvx.mk: the DVX SDK project of the JoeyLib example $(1).' \
|
||||||
|
'SDK_DIR = $(DVX_SDK)' \
|
||||||
|
'KIND = app' \
|
||||||
|
'NAME = $(1)' \
|
||||||
|
'VENDOR = $(VENDOR)' \
|
||||||
|
'LIBS = $(LIB)' \
|
||||||
|
'DATA = $$($(1)_DATA)' \
|
||||||
|
"CFLAGS = $(APP_CFLAGS) -I$$(dir $$(firstword $$($(1)_SRCS))) -DDVX_PORT_APP_NAME='\"$$($(1)_TITLE)\"'" \
|
||||||
|
'include $$$$(SDK_DIR)/sdk.mk' | $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(1)/Makefile
|
||||||
|
@printf '%s\n' \
|
||||||
|
'.topic $(1).overview' \
|
||||||
|
'.title $$($(1)_TITLE)' \
|
||||||
|
'.toc 0 $$($(1)_TITLE)' \
|
||||||
|
'.default' \
|
||||||
|
'.index $$($(1)_TITLE)' \
|
||||||
|
'' \
|
||||||
|
'.h1 $$($(1)_TITLE)' \
|
||||||
|
'' \
|
||||||
|
'$$($(1)_HELP)' \
|
||||||
|
'' \
|
||||||
|
'$$($(1)_TITLE) is a JoeyLib example program. It runs in its own window at a virtual 70 frames a second; while the window is active every key goes to it except Alt+Tab (switch programs), Ctrl+Esc (Task Manager) and Alt+F4 (close it). Game > Exit also closes it.' \
|
||||||
|
| $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(1)/$(1).dhs
|
||||||
|
@printf '%s\n' \
|
||||||
|
'name text "$$($(1)_TITLE)"' \
|
||||||
|
'author text "JoeyLib"' \
|
||||||
|
'description text "JoeyLib example: $$($(1)_TITLE)"' \
|
||||||
|
| $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(1)/$(1).res
|
||||||
|
$$(MAKE) -C $(APPSDIR)/$(1) DJGPP_PREFIX=$(DOS_PREFIX) package
|
||||||
|
@mkdir -p $(BINDIR)
|
||||||
|
rm -rf $(BINDIR)/apps/$(VENDOR)/$(1)
|
||||||
|
cp -R $(APPSDIR)/$(1)/pkg/apps $(BINDIR)/
|
||||||
|
endef
|
||||||
|
$(foreach a,$(APPS),$(eval $(call appRule,$(a))))
|
||||||
|
|
||||||
|
.PHONY: audio-data tilebank-data staxi-data
|
||||||
|
audio-data: $(AUDIO_DATA_FILES)
|
||||||
|
tilebank-data: $(TILEBANK_OWN)
|
||||||
|
|
||||||
|
staxi-data:
|
||||||
|
$(MAKE) -f $(REPO_DIR)/make/dos.mk dos-staxi-data
|
||||||
|
rm -rf $(staxi_DATA)
|
||||||
|
@mkdir -p $(dir $(staxi_DATA))
|
||||||
|
cp -R $(DOS_STAXI_DATA) $(staxi_DATA)
|
||||||
|
|
||||||
|
$(audio_DATA)/%: $(REPO_DIR)/assets/%
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
$(TILEBANK_OWN): $(TILEBANK_BAKE_DEPS)
|
||||||
|
python3 $(TILEBANK_STAGE) dos $(TILEBANK_PNG) $(dir $@)
|
||||||
|
|
||||||
|
clean-dvx:
|
||||||
|
rm -rf $(BUILD)
|
||||||
|
|
||||||
|
# `clean` as an alias for `clean-dvx`. Without it a bare
|
||||||
|
# `make -f make/dvx.mk clean` FAILS -- and with stderr discarded that
|
||||||
|
# looks like success while leaving stale objects the next build links.
|
||||||
|
clean: clean-dvx
|
||||||
|
|
||||||
|
# Pull in per-object header-dependency files generated by gcc -MMD/-MP.
|
||||||
|
-include $(LIB_OBJS:.o=.d)
|
||||||
24
make/support/dvx/writeIfChanged.sh
Executable file
24
make/support/dvx/writeIfChanged.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Write standard input to FILE only when it differs from what FILE holds, so
|
||||||
|
# make/dvx.mk can regenerate a program's SDK project files on every build
|
||||||
|
# without touching their times (sdk.mk would otherwise rebuild the help and
|
||||||
|
# the resources each time).
|
||||||
|
#
|
||||||
|
# Usage: writeIfChanged.sh FILE
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "usage: $(basename "$0") FILE" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
file="$1"
|
||||||
|
tmp="$(mktemp "$file.XXXXXX")"
|
||||||
|
cat > "$tmp"
|
||||||
|
chmod 644 "$tmp"
|
||||||
|
if cmp -s "$tmp" "$file"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
mv "$tmp" "$file"
|
||||||
|
fi
|
||||||
|
|
@ -65,9 +65,9 @@ $(MKSTLEVEL_BIN): $(MKSTLEVEL_SRC)
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(HOST_CC) $(HOST_CFLAGS) $< -o $@
|
$(HOST_CC) $(HOST_CFLAGS) $< -o $@
|
||||||
|
|
||||||
$(BUILD_DIR)/spritebake-dos: $(SPRITEBAKE_SRC) $(SRC_DIR)/dos/spriteEmitX86.c
|
$(BUILD_DIR)/spritebake-dos: $(SPRITEBAKE_SRC) $(SRC_X86)/spriteEmitX86.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_DOS $(SPRITEBAKE_INC) -I$(SRC_DIR)/dos $^ -o $@
|
$(HOST_CC) $(HOST_CFLAGS) -DJOEYLIB_PLATFORM_DOS $(SPRITEBAKE_INC) -I$(SRC_X86) $^ -o $@
|
||||||
|
|
||||||
$(BUILD_DIR)/spritebake-amiga: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c
|
$(BUILD_DIR)/spritebake-amiga: $(SPRITEBAKE_SRC) $(SRC_68K)/spriteEmitPlanar68k.c
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
|
|
|
||||||
85
scripts/bench-dvx.sh
Executable file
85
scripts/bench-dvx.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Headless UBER run for DVX (DOSBox Staging, DVX's own emulator setup).
|
||||||
|
#
|
||||||
|
# Stages a scratch DVX (scripts/lib/dvxGate.sh) whose desktop is UBER, boots
|
||||||
|
# it in DOSBox Staging under Xvfb, and waits for UBER's "press any key to
|
||||||
|
# exit" line in its joeylog.txt -- which a DVX program writes to its settings
|
||||||
|
# folder, CONFIG\APPS\JOEYLIB\UBER on the staged drive. The log is copied to
|
||||||
|
# build/dvx/bin/joeylog.txt (tools/diff-uber-hashes and tools/uber-perf-table
|
||||||
|
# read it from there) and DOSBox is stopped.
|
||||||
|
#
|
||||||
|
# The stage is the DOS stage, so the hashes must equal the DOS port's:
|
||||||
|
# scripts/bench-dvx.sh && tools/diff-uber-hashes tests/goldens/uber/dos.txt build/dvx/bin/joeylog.txt
|
||||||
|
# The ops/sec are the emulated machine's at DOSBox's full host speed, not a
|
||||||
|
# reference machine's: compare them only run against run.
|
||||||
|
#
|
||||||
|
# scripts/bench-dvx.sh # default 600 s timeout
|
||||||
|
# TIMEOUT=900 scripts/bench-dvx.sh # override
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
|
timeoutSeconds=${TIMEOUT:-600}
|
||||||
|
# The emulator's hard cap, a little past the wait, whatever this script does.
|
||||||
|
wallSeconds=$((timeoutSeconds + 30))
|
||||||
|
out=$DVX_BIN/$DVX_LOG_NAME
|
||||||
|
doneLine="press any key to exit"
|
||||||
|
|
||||||
|
dvxNeedBuild uber
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-bench-dvx.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
dvxStage "$work/c" "$(dvxAppDir uber)"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
||||||
|
mkdir -p "$work/xdg"
|
||||||
|
rm -f "$out"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 1700))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
echo "bench-dvx: running UBER in DVX headless (timeout ${timeoutSeconds}s)..." >&2
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wallSeconds" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard" || true)
|
||||||
|
|
||||||
|
start=$SECONDS
|
||||||
|
log=$(dvxAwaitLog "$work/c" uber "$doneLine" "$timeoutSeconds" "$guard" || true)
|
||||||
|
waited=$((SECONDS - start))
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
log=$(dvxProgramLog "$work/c" uber)
|
||||||
|
fi
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "bench-dvx: FAIL -- UBER wrote no joeylog.txt (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
tr -d '\r' < "$log" > "$out"
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
|
||||||
|
ops=$(grep -c "ops/sec" "$out" || true)
|
||||||
|
if ! grep -q "$doneLine" "$out"; then
|
||||||
|
echo "bench-dvx: WARNING -- timed out before the completion line (partial capture)" >&2
|
||||||
|
fi
|
||||||
|
echo "bench-dvx: captured $ops op lines in ${waited}s" >&2
|
||||||
|
echo " -> $out" >&2
|
||||||
|
grep "UBER:" "$out" | sed 's/ | hash=.*//' || true
|
||||||
168
scripts/lib/dvxGate.sh
Normal file
168
scripts/lib/dvxGate.sh
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
# Shared by the DVX gates (scripts/verify-dvx-*.sh, scripts/bench-dvx.sh) and
|
||||||
|
# joey-run: a DVX installation staged in a scratch folder -- a copy of the
|
||||||
|
# runtime the installer took from a DVX tree (toolchains/opt/dvx, without
|
||||||
|
# its SDK) plus JoeyLib programs from build/dvx/bin -- whose DVX.INI makes
|
||||||
|
# one of them the desktop, so DVX boots straight into it; and the DOSBox
|
||||||
|
# Staging run of that folder as drive C:. Nothing here writes to the DVX
|
||||||
|
# project or to toolchains/opt/dvx. Source after joeyEnv.sh.
|
||||||
|
|
||||||
|
DVX_GATE="$(basename "$0" .sh)"
|
||||||
|
DVX_BIN="$JOEY_REPO/build/dvx/bin"
|
||||||
|
# The folder a program installs in under apps/, and the name of its log.
|
||||||
|
DVX_VENDOR=joeylib
|
||||||
|
DVX_LOG_NAME=joeylog.txt
|
||||||
|
# The runtime parts of toolchains/opt/dvx (all but the SDK).
|
||||||
|
DVX_RUNTIME="dvx.exe config system libs widgets apps"
|
||||||
|
# The desktop the gates run: 640x480 in 16-bit colour, DVX's own DOS test
|
||||||
|
# mode, where a program's window shows the stage at 1x. A caller may set
|
||||||
|
# others before dvxStage (joey-run shows 1024x768, the stage at 3x).
|
||||||
|
DVX_VIDEO_W=${DVX_VIDEO_W:-640}
|
||||||
|
DVX_VIDEO_H=${DVX_VIDEO_H:-480}
|
||||||
|
DVX_VIDEO_BPP=${DVX_VIDEO_BPP:-16}
|
||||||
|
# DOSBox Staging as DVX's harness runs it: an S3 card (VBE 2.0 with a linear
|
||||||
|
# frame buffer, which DVX needs) and the host's full speed.
|
||||||
|
DVX_DOSBOX_MACHINE=svga_s3
|
||||||
|
DVX_DOSBOX_MEM_MB=32
|
||||||
|
|
||||||
|
|
||||||
|
# dvxAppDir PROGRAM -- print the built folder of a program (its make/dvx.mk
|
||||||
|
# name: uber, keys, ...): the .app, its help and its DATA.
|
||||||
|
dvxAppDir() {
|
||||||
|
printf '%s\n' "$DVX_BIN/apps/$DVX_VENDOR/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxNeedBuild PROGRAM... -- exit 2 unless each program is built.
|
||||||
|
dvxNeedBuild() {
|
||||||
|
local prog
|
||||||
|
for prog in "$@"; do
|
||||||
|
if [ ! -f "$(dvxAppDir "$prog")/$prog.app" ]; then
|
||||||
|
echo "$DVX_GATE: $(dvxAppDir "$prog")/$prog.app is missing; run: make dvx" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxStage DIR APPDIR... -- stage DVX in DIR/DVX with the programs in the
|
||||||
|
# APPDIRs (each a folder holding NAME.app, from dvxAppDir or anywhere else),
|
||||||
|
# installed as apps/joeylib/NAME. The first one is the desktop. DIR may hold
|
||||||
|
# an earlier staging: the runtime and the programs are copied over it, and
|
||||||
|
# what DVX wrote there (its settings, the programs' CONFIG folders) stays.
|
||||||
|
dvxStage() {
|
||||||
|
local dir="$1"
|
||||||
|
local part
|
||||||
|
local app
|
||||||
|
local name
|
||||||
|
shift
|
||||||
|
mkdir -p "$dir/DVX/apps/$DVX_VENDOR"
|
||||||
|
for part in $DVX_RUNTIME; do
|
||||||
|
cp -a "$DVX_ROOT/$part" "$dir/DVX/"
|
||||||
|
done
|
||||||
|
for app in "$@"; do
|
||||||
|
name="$(basename "$app")"
|
||||||
|
rm -rf "${dir:?}/DVX/apps/$DVX_VENDOR/$name"
|
||||||
|
cp -a "$app" "$dir/DVX/apps/$DVX_VENDOR/"
|
||||||
|
done
|
||||||
|
name="$(basename "$1")"
|
||||||
|
dvxIniSet "$dir/DVX/config/DVX.INI" "apps/$DVX_VENDOR/$name/$name.app"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxIniSet INI DESKTOP -- DVX.INI for a run: DESKTOP as the desktop program,
|
||||||
|
# no first-run Welcome window, the gate video mode, and no desktop sounds (a
|
||||||
|
# recording gate must hear only the program). DOS line ends are kept.
|
||||||
|
dvxIniSet() {
|
||||||
|
python3 - "$1" "$2" "$DVX_VIDEO_W" "$DVX_VIDEO_H" "$DVX_VIDEO_BPP" <<'PY'
|
||||||
|
import sys
|
||||||
|
path, desktop, width, height, bpp = sys.argv[1:]
|
||||||
|
sets = {("shell", "desktop"): desktop, ("video", "width"): width, ("video", "height"): height, ("video", "bpp"): bpp}
|
||||||
|
lines = open(path, newline="").read().split("\r\n")
|
||||||
|
out = []
|
||||||
|
section = ""
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped.startswith("["):
|
||||||
|
section = stripped.strip("[]").lower()
|
||||||
|
elif "=" in stripped and not stripped.startswith(";"):
|
||||||
|
key = stripped.split("=")[0].strip()
|
||||||
|
if section == "shell" and key.lower() == "welcome":
|
||||||
|
continue
|
||||||
|
if section == "sounds":
|
||||||
|
line = "%s =" % key
|
||||||
|
elif (section, key.lower()) in sets:
|
||||||
|
line = "%s = %s" % (key, sets[(section, key.lower())])
|
||||||
|
out.append(line)
|
||||||
|
open(path, "w", newline="").write("\r\n".join(out))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxDosboxConf FILE DIR [SBTYPE] -- a DOSBox Staging configuration that boots
|
||||||
|
# DIR/DVX as C:\DVX. SBTYPE (e.g. sb16) adds a Sound Blaster; without it the
|
||||||
|
# machine is silent (DVX then runs without sound, as on a PC without a card).
|
||||||
|
dvxDosboxConf() {
|
||||||
|
local sbType="${3:-none}"
|
||||||
|
local nosound=true
|
||||||
|
[ "$sbType" = none ] || nosound=false
|
||||||
|
cat > "$1" <<EOF
|
||||||
|
[sdl]
|
||||||
|
fullscreen = false
|
||||||
|
|
||||||
|
[dosbox]
|
||||||
|
machine = $DVX_DOSBOX_MACHINE
|
||||||
|
memsize = $DVX_DOSBOX_MEM_MB
|
||||||
|
|
||||||
|
[cpu]
|
||||||
|
cpu_cycles = max
|
||||||
|
|
||||||
|
[mixer]
|
||||||
|
nosound = $nosound
|
||||||
|
|
||||||
|
[sblaster]
|
||||||
|
sbtype = $sbType
|
||||||
|
|
||||||
|
[autoexec]
|
||||||
|
mount c "$2"
|
||||||
|
c:
|
||||||
|
cd \\DVX
|
||||||
|
DVX.EXE
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxProgramLog DIR PROGRAM -- print the path of PROGRAM's log in the staged
|
||||||
|
# DVX: in its settings folder, CONFIG\ plus the folder the .app is in (DOS
|
||||||
|
# names are upper case on the host). Prints nothing while there is none.
|
||||||
|
dvxProgramLog() {
|
||||||
|
find "$1/DVX" -ipath "*/config/apps/$DVX_VENDOR/$2/$DVX_LOG_NAME" -print -quit 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxAwaitLog DIR PROGRAM REGEX SECONDS PID -- wait up to SECONDS (or until PID
|
||||||
|
# exits) for PROGRAM's log to hold a line matching REGEX; print its path if
|
||||||
|
# it does.
|
||||||
|
dvxAwaitLog() {
|
||||||
|
local log
|
||||||
|
local waited
|
||||||
|
for ((waited = 0; waited < $4; waited++)); do
|
||||||
|
log="$(dvxProgramLog "$1" "$2")"
|
||||||
|
if [ -n "$log" ] && tr -d '\r' < "$log" | grep -qE "$3"; then
|
||||||
|
printf '%s\n' "$log"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
joeyAlive "$5" || return 1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxDvxLog DIR -- print the tail of the staged DVX's own log (DVX.LOG), the
|
||||||
|
# place a program that failed to load is explained.
|
||||||
|
dvxDvxLog() {
|
||||||
|
local log
|
||||||
|
log="$(find "$1/DVX" -maxdepth 1 -iname dvx.log -print -quit)"
|
||||||
|
[ -n "$log" ] && tr -d '\r' < "$log" | tail -"${2:-15}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -65,8 +65,14 @@ joeyExampleProgram() {
|
||||||
atarist) ext=".PRG" ;;
|
atarist) ext=".PRG" ;;
|
||||||
dos) ext=".EXE" ;;
|
dos) ext=".EXE" ;;
|
||||||
x68000) ext=".X" ;;
|
x68000) ext=".X" ;;
|
||||||
|
dvx)
|
||||||
|
# Every program is a folder of its own, as it installs in DVX.
|
||||||
|
up="${up,,}"
|
||||||
|
printf '%s\n' "$bin/apps/joeylib/$up/$up.app"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "$(basename "$0"): unknown port '$2' (iigs, amiga, atarist, dos or x68000)" >&2
|
echo "$(basename "$0"): unknown port '$2' (iigs, amiga, atarist, dos, x68000 or dvx)" >&2
|
||||||
return 2
|
return 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -110,6 +116,25 @@ joeyAlive() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# joeyChild PID -- print the child of PID: the emulator a `timeout -s KILL`
|
||||||
|
# wrapper started. Stop the child first; stopping only the wrapper can orphan
|
||||||
|
# an emulator that ignores SIGTERM (Hatari does).
|
||||||
|
joeyChild() {
|
||||||
|
local tries
|
||||||
|
local child
|
||||||
|
|
||||||
|
for ((tries = 0; tries < 20; tries++)); do
|
||||||
|
child="$(pgrep -P "$1" | head -1)"
|
||||||
|
if [ -n "$child" ]; then
|
||||||
|
printf '%s\n' "$child"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# joeyStop PID... -- stop emulator processes and make sure they are gone.
|
# joeyStop PID... -- stop emulator processes and make sure they are gone.
|
||||||
# Hatari, MAME and DOSBox Staging ignore or are slow to act on SIGTERM; a
|
# Hatari, MAME and DOSBox Staging ignore or are slow to act on SIGTERM; a
|
||||||
# plain `kill` left them running (and burning CPU) after their script had
|
# plain `kill` left them running (and burning CPU) after their script had
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,6 @@ tileBankAwaitLine() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# tileBankChild PID -- print the child of PID: the emulator a `timeout -s KILL`
|
|
||||||
# wrapper started. Stop the child first; stopping only the wrapper can orphan
|
|
||||||
# an emulator that ignores SIGTERM (Hatari does).
|
|
||||||
tileBankChild() {
|
|
||||||
local tries
|
|
||||||
local child
|
|
||||||
|
|
||||||
for ((tries = 0; tries < 20; tries++)); do
|
|
||||||
child="$(pgrep -P "$1" | head -1)"
|
|
||||||
if [ -n "$child" ]; then
|
|
||||||
printf '%s\n' "$child"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 0.1
|
|
||||||
done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# tileBankData BINDIR -- print the DATA/tilebank to stage: the one
|
# tileBankData BINDIR -- print the DATA/tilebank to stage: the one
|
||||||
# make/<port>.mk staged beside the program, or TILEBANK_DATA_DIR when set (a
|
# make/<port>.mk staged beside the program, or TILEBANK_DATA_DIR when set (a
|
||||||
# negative control stages a deliberately broken bake that way).
|
# negative control stages a deliberately broken bake that way).
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,22 @@
|
||||||
# host the shared layer on the host, no emulator: the new-port
|
# host the shared layer on the host, no emulator: the new-port
|
||||||
# template (blank), input, millis, save, serial, sfxmix, tilebank
|
# template (blank), input, millis, save, serial, sfxmix, tilebank
|
||||||
# golden UBER on each port, its 51 per-op surface hashes against
|
# golden UBER on each port, its 51 per-op surface hashes against
|
||||||
# tests/goldens/uber (x68000 against the IIgs's, its own gate)
|
# tests/goldens/uber (x68000 against the IIgs's, its own gate;
|
||||||
|
# dvx against the DOS port's, whose stage it shows)
|
||||||
# visual iigs: every launchable example on joey.2mg boots and draws
|
# visual iigs: every launchable example on joey.2mg boots and draws
|
||||||
# tilebank each port: examples/tilebank loads the port's own .tbk bake
|
# tilebank each port: examples/tilebank loads the port's own .tbk bake
|
||||||
# of the host fixture both ways, pixel-exact and with the host
|
# of the host fixture both ways, pixel-exact and with the host
|
||||||
# gate's surface hash, and refuses every other target's bake (the
|
# gate's surface hash, and refuses every other target's bake (the
|
||||||
# host half is check-tilebank.sh under `host`)
|
# host half is check-tilebank.sh under `host`)
|
||||||
# input each port: a typed string reaches jlInputGetChar byte-exact
|
# input each port: a typed string reaches jlInputGetChar byte-exact
|
||||||
# audio five gates, two kinds:
|
# (dvx: and the present shows it, decoded from the desktop)
|
||||||
|
# audio six gates, two kinds:
|
||||||
# iigs DOC wavetable + sound IRQ vector (register tap)
|
# iigs DOC wavetable + sound IRQ vector (register tap)
|
||||||
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
# x68000 YM2151 noise + PPI port C + ADPCM rate (register tap)
|
||||||
# atarist YM2149 write trace, with the pc (register tap)
|
# atarist YM2149 write trace, with the pc (register tap)
|
||||||
# amiga recorded sound, four loudness phases (Paula has no tap)
|
# amiga recorded sound, four loudness phases (Paula has no tap)
|
||||||
# dos recorded sound, four loudness phases (SB has no tap)
|
# dos recorded sound, four loudness phases (SB has no tap)
|
||||||
|
# dvx recorded sound, four loudness phases (DVX's mixer, SB)
|
||||||
# serial the host check of the shared layer, then each port's loopback
|
# serial the host check of the shared layer, then each port's loopback
|
||||||
# (banner out, probe echoed back):
|
# (banner out, probe echoed back):
|
||||||
# host scripts/check-serial.sh (no emulator)
|
# host scripts/check-serial.sh (no emulator)
|
||||||
|
|
@ -28,6 +31,7 @@
|
||||||
# atarist Modem port -> Hatari pty
|
# atarist Modem port -> Hatari pty
|
||||||
# amiga serial.device -> FS-UAE TCP
|
# amiga serial.device -> FS-UAE TCP
|
||||||
# dos COM1 -> DOSBox nullmodem
|
# dos COM1 -> DOSBox nullmodem
|
||||||
|
# dvx COM1 through DVX's rs232 library -> DOSBox nullmodem
|
||||||
# mouse x68000: deltas, clamps and buttons through the IOCS mouse
|
# mouse x68000: deltas, clamps and buttons through the IOCS mouse
|
||||||
# (and with serial streaming both ways on the same SCC)
|
# (and with serial streaming both ways on the same SCC)
|
||||||
# save iigs: a SAVES/ blob written and read back through GS/OS
|
# save iigs: a SAVES/ blob written and read back through GS/OS
|
||||||
|
|
@ -40,7 +44,7 @@
|
||||||
# loss probes and game disks for the IIgs), stopping on a build failure, then
|
# loss probes and game disks for the IIgs), stopping on a build failure, then
|
||||||
# runs every kind. A gate for a port that was not selected is left out.
|
# runs every kind. A gate for a port that was not selected is left out.
|
||||||
#
|
#
|
||||||
# SEQUENTIAL, deliberately. The two recording audio gates drive the emulator on
|
# SEQUENTIAL, deliberately. The recording audio gates drive the emulator on
|
||||||
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
# wall-clock sleeps and grade fixed time windows of the capture, so a
|
||||||
# second emulator competing for CPU can slide a phase boundary and fail a
|
# second emulator competing for CPU can slide a phase boundary and fail a
|
||||||
# gate that is actually fine. Running several emulators at once also makes
|
# gate that is actually fine. Running several emulators at once also makes
|
||||||
|
|
@ -62,7 +66,7 @@
|
||||||
#
|
#
|
||||||
# Usage: scripts/verify-all.sh KIND|all [port ...]
|
# Usage: scripts/verify-all.sh KIND|all [port ...]
|
||||||
# with no ports, every port the kind has. Ports: host iigs
|
# with no ports, every port the kind has. Ports: host iigs
|
||||||
# x68000 atarist amiga dos
|
# x68000 atarist amiga dos dvx
|
||||||
# Env: VERIFY_ALL_LOG also append the full transcript to this file
|
# Env: VERIFY_ALL_LOG also append the full transcript to this file
|
||||||
# Requires: whatever each selected gate requires; missing ones SKIP.
|
# Requires: whatever each selected gate requires; missing ones SKIP.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
@ -73,7 +77,7 @@ shift || true
|
||||||
label="verify-all $kind"
|
label="verify-all $kind"
|
||||||
|
|
||||||
KINDS="host golden visual tilebank input audio serial mouse save loss disk"
|
KINDS="host golden visual tilebank input audio serial mouse save loss disk"
|
||||||
ALL_PORTS="host iigs x68000 atarist amiga dos"
|
ALL_PORTS="host iigs x68000 atarist amiga dos dvx"
|
||||||
# UBER on the Amiga needs longer than bench-amiga.sh's interactive default.
|
# UBER on the Amiga needs longer than bench-amiga.sh's interactive default.
|
||||||
AMIGA_UBER_SECONDS=400
|
AMIGA_UBER_SECONDS=400
|
||||||
|
|
||||||
|
|
@ -91,28 +95,33 @@ golden x68000 uber scripts/verify-x68000-golden.sh
|
||||||
golden atarist uber scripts/bench-atarist.sh && tools/diff-uber-hashes tests/goldens/uber/atarist.txt build/atarist/bin/joeylog.txt
|
golden atarist uber scripts/bench-atarist.sh && tools/diff-uber-hashes tests/goldens/uber/atarist.txt build/atarist/bin/joeylog.txt
|
||||||
golden amiga uber TIMEOUT=$AMIGA_UBER_SECONDS scripts/bench-amiga.sh && tools/diff-uber-hashes tests/goldens/uber/amiga.txt build/amiga/bin/joeylog.txt
|
golden amiga uber TIMEOUT=$AMIGA_UBER_SECONDS scripts/bench-amiga.sh && tools/diff-uber-hashes tests/goldens/uber/amiga.txt build/amiga/bin/joeylog.txt
|
||||||
golden dos uber scripts/bench-dos.sh && tools/diff-uber-hashes tests/goldens/uber/dos.txt build/dos/bin/JOEYLOG.TXT
|
golden dos uber scripts/bench-dos.sh && tools/diff-uber-hashes tests/goldens/uber/dos.txt build/dos/bin/JOEYLOG.TXT
|
||||||
|
golden dvx uber scripts/bench-dvx.sh && tools/diff-uber-hashes tests/goldens/uber/dos.txt build/dvx/bin/joeylog.txt
|
||||||
visual iigs examples scripts/verify-iigs-all.sh
|
visual iigs examples scripts/verify-iigs-all.sh
|
||||||
tilebank iigs tilebank make -s -f make/iigs.mk iigs-verify-tilebank || exit 1
|
tilebank iigs tilebank make -s -f make/iigs.mk iigs-verify-tilebank || exit 1
|
||||||
tilebank x68000 tilebank scripts/verify-x68000-tilebank.sh
|
tilebank x68000 tilebank scripts/verify-x68000-tilebank.sh
|
||||||
tilebank atarist tilebank scripts/verify-atarist-tilebank.sh
|
tilebank atarist tilebank scripts/verify-atarist-tilebank.sh
|
||||||
tilebank amiga tilebank scripts/verify-amiga-tilebank.sh
|
tilebank amiga tilebank scripts/verify-amiga-tilebank.sh
|
||||||
tilebank dos tilebank scripts/verify-dos-tilebank.sh
|
tilebank dos tilebank scripts/verify-dos-tilebank.sh
|
||||||
|
tilebank dvx tilebank scripts/verify-dvx-tilebank.sh
|
||||||
input iigs input scripts/verify-iigs-input.sh
|
input iigs input scripts/verify-iigs-input.sh
|
||||||
input x68000 input scripts/verify-x68000-input.sh
|
input x68000 input scripts/verify-x68000-input.sh
|
||||||
input atarist input scripts/verify-atarist-input.sh
|
input atarist input scripts/verify-atarist-input.sh
|
||||||
input amiga input scripts/verify-amiga-input.sh
|
input amiga input scripts/verify-amiga-input.sh
|
||||||
input dos input scripts/verify-dos-input.sh
|
input dos input scripts/verify-dos-input.sh
|
||||||
|
input dvx input scripts/verify-dvx-input.sh
|
||||||
audio iigs audio scripts/verify-iigs-audio.sh staxi
|
audio iigs audio scripts/verify-iigs-audio.sh staxi
|
||||||
audio x68000 audio scripts/verify-x68000-audio.sh
|
audio x68000 audio scripts/verify-x68000-audio.sh
|
||||||
audio atarist audio scripts/verify-atarist-audio.sh
|
audio atarist audio scripts/verify-atarist-audio.sh
|
||||||
audio amiga audio scripts/verify-amiga-audio.sh
|
audio amiga audio scripts/verify-amiga-audio.sh
|
||||||
audio dos audio scripts/verify-dos-audio.sh
|
audio dos audio scripts/verify-dos-audio.sh
|
||||||
|
audio dvx audio scripts/verify-dvx-audio.sh
|
||||||
serial host serial scripts/check-serial.sh
|
serial host serial scripts/check-serial.sh
|
||||||
serial iigs serial scripts/verify-iigs-serial.sh
|
serial iigs serial scripts/verify-iigs-serial.sh
|
||||||
serial x68000 serial scripts/verify-x68000-serial.sh
|
serial x68000 serial scripts/verify-x68000-serial.sh
|
||||||
serial atarist serial scripts/verify-atarist-serial.sh
|
serial atarist serial scripts/verify-atarist-serial.sh
|
||||||
serial amiga serial scripts/verify-amiga-serial.sh
|
serial amiga serial scripts/verify-amiga-serial.sh
|
||||||
serial dos serial scripts/verify-dos-serial.sh
|
serial dos serial scripts/verify-dos-serial.sh
|
||||||
|
serial dvx serial scripts/verify-dvx-serial.sh
|
||||||
mouse x68000 mouse scripts/verify-x68000-mouse.sh
|
mouse x68000 mouse scripts/verify-x68000-mouse.sh
|
||||||
mouse x68000 sermouse scripts/verify-x68000-serial-mouse.sh
|
mouse x68000 sermouse scripts/verify-x68000-serial-mouse.sh
|
||||||
save iigs save make -s -f make/iigs.mk iigs-verify-save || exit 1
|
save iigs save make -s -f make/iigs.mk iigs-verify-save || exit 1
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ DISPLAY="$display" timeout -s KILL "$wall" "$FSUAE" \
|
||||||
--fullscreen=0 \
|
--fullscreen=0 \
|
||||||
--fade_out_duration=0 > "$work/fsuae.log" 2>&1 &
|
--fade_out_duration=0 > "$work/fsuae.log" 2>&1 &
|
||||||
guard=$!
|
guard=$!
|
||||||
fsuae=$(tileBankChild "$guard")
|
fsuae=$(joeyChild "$guard")
|
||||||
|
|
||||||
log=$(tileBankAwaitLine "$work/hd" "$lineSeconds" "$guard")
|
log=$(tileBankAwaitLine "$work/hd" "$lineSeconds" "$guard")
|
||||||
joeyStop "$fsuae" "$guard" 2> /dev/null
|
joeyStop "$fsuae" "$guard" 2> /dev/null
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$HATARI" \
|
||||||
--fast-forward on \
|
--fast-forward on \
|
||||||
--auto 'C:\TILEBANK.PRG' > "$work/hatari.log" 2>&1 &
|
--auto 'C:\TILEBANK.PRG' > "$work/hatari.log" 2>&1 &
|
||||||
guard=$!
|
guard=$!
|
||||||
hatari=$(tileBankChild "$guard")
|
hatari=$(joeyChild "$guard")
|
||||||
|
|
||||||
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
||||||
joeyStop "$hatari" "$guard" 2> /dev/null
|
joeyStop "$hatari" "$guard" 2> /dev/null
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
--conf "$conf" \
|
--conf "$conf" \
|
||||||
-c "MOUNT C \"$work/c\"" -c "C:" -c "TILEBANK.EXE" > "$work/dosbox.log" 2>&1 &
|
-c "MOUNT C \"$work/c\"" -c "C:" -c "TILEBANK.EXE" > "$work/dosbox.log" 2>&1 &
|
||||||
guard=$!
|
guard=$!
|
||||||
dosbox=$(tileBankChild "$guard")
|
dosbox=$(joeyChild "$guard")
|
||||||
|
|
||||||
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
log=$(tileBankAwaitLine "$work/c" "$lineSeconds" "$guard")
|
||||||
joeyStop "$dosbox" "$guard" 2> /dev/null
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
|
|
||||||
154
scripts/verify-dvx-audio.sh
Executable file
154
scripts/verify-dvx-audio.sh
Executable file
|
|
@ -0,0 +1,154 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dvx-audio.sh - DVX audio gate, graded on recorded sound.
|
||||||
|
#
|
||||||
|
# The DVX port plays through DVX's own sound library (audio.lib, with its
|
||||||
|
# Sound Blaster driver and mixer) and music library (src/dvx/audio.c).
|
||||||
|
# Nothing in DOSBox can be tapped register by register, so, like the DOS and
|
||||||
|
# Amiga gates, this records what came out and grades it with
|
||||||
|
# tools/gradeAudioPhases.py, through a PulseAudio null sink made for this run
|
||||||
|
# (scripts/pulseRecordLib.sh: the host's own sound is never touched).
|
||||||
|
#
|
||||||
|
# AUDIO (examples/audio) is the desktop of a scratch DVX in DOSBox Staging
|
||||||
|
# with a Sound Blaster 16 (and its OPL3, where DVX plays square tones when
|
||||||
|
# it can). It is driven through the four phases the DOS gate uses:
|
||||||
|
#
|
||||||
|
# music the baked tracker song on entry (the jlAudioVoice tones), loud
|
||||||
|
# paused P pressed, near silence
|
||||||
|
# sfx SPACE during the pause, so any sound is the digital one-shot
|
||||||
|
# (DATA/test.sfx beside the program, through the DATA root)
|
||||||
|
# resumed P again, loud again
|
||||||
|
#
|
||||||
|
# Keys are HELD (keydown, pause, keyup): the demo tests jlKeyPressed, an
|
||||||
|
# edge polled once a frame. The thresholds sit at about half the levels
|
||||||
|
# measured (see the grader call below).
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dvx-audio.sh
|
||||||
|
# Env: DVX_AUDIO_KEEP keep the work folder and the recording
|
||||||
|
# Requires: DOSBox Staging, Xvfb, xdotool, parec/pactl, python3, the dvx
|
||||||
|
# component; the audio program (make dvx).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
grader=$repo/tools/gradeAudioPhases.py
|
||||||
|
pulseLib=$repo/scripts/pulseRecordLib.sh
|
||||||
|
label=verify-dvx-audio
|
||||||
|
program=audio
|
||||||
|
wall=150 # hard cap on DOSBox, whatever happens to this script
|
||||||
|
readySeconds=60 # DVX boot until its shell reports ready
|
||||||
|
settleSeconds=4 # then AUDIO opening its window and starting the song
|
||||||
|
|
||||||
|
# shellcheck source=scripts/pulseRecordLib.sh
|
||||||
|
. "$pulseLib"
|
||||||
|
for tool in Xvfb xdotool parec pactl python3; do
|
||||||
|
command -v "$tool" > /dev/null || { echo "$label: $tool not installed" >&2; exit 2; }
|
||||||
|
done
|
||||||
|
pulseEnsureServer || { echo "$label: no PulseAudio/PipeWire server to record from" >&2; exit 2; }
|
||||||
|
dvxNeedBuild "$program"
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dvx-audio.XXXXXX)
|
||||||
|
sinkModule=""
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
parecPid=""
|
||||||
|
cleanup() {
|
||||||
|
[ -n "$parecPid" ] && kill "$parecPid" 2> /dev/null
|
||||||
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
# A leaked null sink on the user's audio server is the one side effect
|
||||||
|
# this gate could have.
|
||||||
|
pulseUnloadSink "$sinkModule"
|
||||||
|
if [ -n "${DVX_AUDIO_KEEP:-}" ]; then
|
||||||
|
echo "$label: kept $work" >&2
|
||||||
|
else
|
||||||
|
rm -rf "$work"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
sinkName="joeyDvxGate$$"
|
||||||
|
sinkModule=$(pulseCreateSink "$sinkName") || sinkModule=""
|
||||||
|
[ -n "$sinkModule" ] || { echo "$label: could not create a null sink to record from" >&2; exit 2; }
|
||||||
|
|
||||||
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$work/c" sb16
|
||||||
|
mkdir -p "$work/xdg"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 2900))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" PULSE_SINK="$sinkName" SDL_AUDIODRIVER=pulse \
|
||||||
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard")
|
||||||
|
|
||||||
|
ready=""
|
||||||
|
for ((waited = 0; waited < readySeconds; waited++)); do
|
||||||
|
if dvxDvxLog "$work/c" 200 | grep -q "DVX Shell ready"; then
|
||||||
|
ready=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
joeyAlive "$guard" || break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ -z "$ready" ]; then
|
||||||
|
echo "$label: FAIL - DVX did not come up within ${readySeconds}s (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep "$settleSeconds"
|
||||||
|
|
||||||
|
win=$(joeyFindWindow "$display" "$dosbox")
|
||||||
|
if [ -z "$win" ]; then
|
||||||
|
echo "$label: FAIL - no DOSBox window to send keys to" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
parec --device="$sinkName.monitor" --format=s16le --rate=44100 --channels=2 \
|
||||||
|
--file-format=wav "$work/out.wav" > "$work/parec.log" 2>&1 &
|
||||||
|
parecPid=$!
|
||||||
|
|
||||||
|
DISPLAY="$display" xdotool windowactivate --sync "$win" 2> /dev/null
|
||||||
|
|
||||||
|
hold() { # $1 = key name
|
||||||
|
DISPLAY="$display" xdotool keydown --window "$win" "$1"
|
||||||
|
sleep 0.4
|
||||||
|
DISPLAY="$display" xdotool keyup --window "$win" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The phase boundaries below are the timestamps handed to the grader, so
|
||||||
|
# the sleeps and the spec must stay in step (the DOS gate's schedule).
|
||||||
|
sleep 6 # 0-6s music
|
||||||
|
hold p # -> paused
|
||||||
|
sleep 8 # 6-14s paused
|
||||||
|
for _ in 1 2 3; do # 14-24s sfx into silence
|
||||||
|
hold space
|
||||||
|
sleep 1.5
|
||||||
|
done
|
||||||
|
sleep 4
|
||||||
|
hold p # -> resumed
|
||||||
|
sleep 9 # 24-33s resumed
|
||||||
|
|
||||||
|
# parec writes the WAV header lazily; it must exit before the file parses.
|
||||||
|
kill "$parecPid" 2> /dev/null
|
||||||
|
wait "$parecPid" 2> /dev/null
|
||||||
|
parecPid=""
|
||||||
|
dvxDvxLog "$work/c" 200 | grep -E "^Audio:" | tail -3
|
||||||
|
|
||||||
|
[ -s "$work/out.wav" ] || { echo "$label: FAIL - recorded no audio at all" >&2; exit 1; }
|
||||||
|
|
||||||
|
# Measured under DOSBox Staging 0.83 (SB16, DVX mixing at 22,050 Hz): music
|
||||||
|
# ~1260, paused 0, sfx ~670, resumed ~1245 -- the DOS port's levels, near
|
||||||
|
# enough, so the DOS gate's bounds (about half of each) apply unchanged.
|
||||||
|
python3 "$grader" "$label" "$work/out.wav" \
|
||||||
|
"music:1:5:600:" \
|
||||||
|
"paused:8:13::200" \
|
||||||
|
"sfx:16:22:330:" \
|
||||||
|
"resumed:27:32:600:"
|
||||||
177
scripts/verify-dvx-input.sh
Executable file
177
scripts/verify-dvx-input.sh
Executable file
|
|
@ -0,0 +1,177 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dvx-input.sh - Typed-character and present gate for DVX. Runs KEYS
|
||||||
|
# (examples/keys) as the desktop of a scratch DVX in DOSBox Staging under
|
||||||
|
# Xvfb and types the RetroNet definition-of-done string with xdotool (real X
|
||||||
|
# key events: ':' needs Shift, which DVX's keyboard layout applies before the
|
||||||
|
# window's onKey hands the character to jlInputGetChar). Two checks:
|
||||||
|
# 1. the present: a raw screenshot of the desktop must show KEYS's
|
||||||
|
# verification scanline (row 198: sentinel, count, every received
|
||||||
|
# character as two pixel nibbles) somewhere -- the window's content,
|
||||||
|
# expanded from the stage through the port's palette tables into DVX's
|
||||||
|
# 16-bit desktop -- decoding to exactly the typed bytes;
|
||||||
|
# 2. the typed history: after Escape, KEYS logs every received character
|
||||||
|
# as hex to its joeylog.txt (in its settings folder), which must equal
|
||||||
|
# the typed ASCII.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dvx-input.sh
|
||||||
|
# Requires: DOSBox Staging, Xvfb, xdotool, python3 + PIL, the dvx component;
|
||||||
|
# the keys program (make dvx).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
|
program=keys
|
||||||
|
target="192.168.1.10:6510"
|
||||||
|
wall=180 # hard cap on DOSBox, whatever happens to this script
|
||||||
|
readySeconds=60 # DVX boot until its shell reports ready
|
||||||
|
settleSeconds=3 # then KEYS opening its window and taking the focus
|
||||||
|
logSeconds=30 # Escape until KEYS's typed line is in its log
|
||||||
|
|
||||||
|
dvxNeedBuild "$program"
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dvx-input.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
||||||
|
printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$work/shots" >> "$work/dosbox.conf"
|
||||||
|
mkdir -p "$work/xdg" "$work/shots"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 2500))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard")
|
||||||
|
|
||||||
|
ready=""
|
||||||
|
for ((waited = 0; waited < readySeconds; waited++)); do
|
||||||
|
if dvxDvxLog "$work/c" 200 | grep -q "DVX Shell ready"; then
|
||||||
|
ready=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
joeyAlive "$guard" || break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ -z "$ready" ]; then
|
||||||
|
echo "verify-dvx-input: FAIL - DVX did not come up within ${readySeconds}s (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep "$settleSeconds"
|
||||||
|
|
||||||
|
win=$(joeyFindWindow "$display" "$dosbox")
|
||||||
|
if [ -z "$win" ]; then
|
||||||
|
echo "verify-dvx-input: FAIL - DOSBox window not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DISPLAY="$display" xdotool windowactivate --sync "$win" 2> /dev/null || true
|
||||||
|
DISPLAY="$display" xdotool type --window "$win" --delay 150 -- "$target"
|
||||||
|
sleep 2
|
||||||
|
# Ctrl+F5 = DOSBox screenshot hotkey.
|
||||||
|
DISPLAY="$display" xdotool key --window "$win" ctrl+F5
|
||||||
|
sleep 2
|
||||||
|
DISPLAY="$display" xdotool key --window "$win" Escape
|
||||||
|
|
||||||
|
log=$(dvxAwaitLog "$work/c" "$program" '^typed=' "$logSeconds" "$guard")
|
||||||
|
cp "$log" "$work/joeylog.txt" 2> /dev/null
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
|
||||||
|
png=$(ls "$work"/shots/*.png 2> /dev/null | head -1)
|
||||||
|
if [ -z "$png" ]; then
|
||||||
|
echo "verify-dvx-input: FAIL - no screenshot captured" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
echo "verify-dvx-input: FAIL - KEYS logged no typed line within ${logSeconds}s of Escape" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 - "$png" "$target" "$work/joeylog.txt" <<'PY'
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
png, target, logPath = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
fails = []
|
||||||
|
|
||||||
|
# KEYS palette 0 (buildPalette): a gray ramp with four demo colours and red
|
||||||
|
# at 15, 12-bit $0RGB, each channel widened x17 as the port's tables do.
|
||||||
|
pal12 = [(i << 8) | (i << 4) | i for i in range(16)]
|
||||||
|
pal12[0], pal12[1], pal12[2], pal12[3] = 0x000, 0x333, 0x0F0, 0xFFF
|
||||||
|
pal12[15] = 0xF00
|
||||||
|
palRgb = [(((v >> 8) & 0xF) * 17, ((v >> 4) & 0xF) * 17, (v & 0xF) * 17) for v in pal12]
|
||||||
|
# The desktop is 16-bit (5-6-5): a channel is off by at most 8 after the
|
||||||
|
# round trip, while the palette entries sit 17 apart; nearest is exact.
|
||||||
|
maxDist = 3 * 9 ** 2
|
||||||
|
|
||||||
|
img = Image.open(png).convert("RGB")
|
||||||
|
w, h = img.size
|
||||||
|
px = img.load()
|
||||||
|
|
||||||
|
nearest = {}
|
||||||
|
|
||||||
|
def nib(x, y):
|
||||||
|
rgb = px[x, y]
|
||||||
|
if rgb not in nearest:
|
||||||
|
best, bestDist = 0, 1 << 30
|
||||||
|
for n, pal in enumerate(palRgb):
|
||||||
|
d = sum((c - p) ** 2 for c, p in zip(rgb, pal))
|
||||||
|
if d < bestDist:
|
||||||
|
best, bestDist = n, d
|
||||||
|
nearest[rgb] = best if bestDist <= maxDist else -1
|
||||||
|
return nearest[rgb]
|
||||||
|
|
||||||
|
# Find the verification row: the sentinel's four nibbles A 5 A 5, then the
|
||||||
|
# count byte, anywhere on the desktop (the window is wherever DVX put it; at
|
||||||
|
# the gate's 640x480 it shows the stage at 1x).
|
||||||
|
expect = bytes([0xA5, 0xA5, len(target), 0x00]) + target.encode("ascii")
|
||||||
|
found = None
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w - 2 * len(expect)):
|
||||||
|
if nib(x, y) == 0xA and nib(x + 1, y) == 0x5 and nib(x + 2, y) == 0xA and nib(x + 3, y) == 0x5:
|
||||||
|
row = bytes(((nib(x + 2 * i, y) & 0xF) << 4) | (nib(x + 2 * i + 1, y) & 0xF) for i in range(len(expect)))
|
||||||
|
if row[:4] == expect[:4]:
|
||||||
|
found = (x, y, row)
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
fails.append("no verification row (A5A5 %02X00) on the screen" % len(target))
|
||||||
|
else:
|
||||||
|
x, y, row = found
|
||||||
|
print("VERIFY-DVX-INPUT row at (%d,%d)=%s" % (x, y, row.hex().upper()))
|
||||||
|
if row != expect:
|
||||||
|
fails.append("screen row %s, want %s" % (row.hex().upper(), expect.hex().upper()))
|
||||||
|
|
||||||
|
typed = ""
|
||||||
|
for line in open(logPath, errors="replace"):
|
||||||
|
if line.startswith("typed="):
|
||||||
|
typed = line.strip()[len("typed="):]
|
||||||
|
want = target.encode("ascii").hex().upper()
|
||||||
|
print("VERIFY-DVX-INPUT log typed=%s" % typed)
|
||||||
|
if typed != want:
|
||||||
|
fails.append("log typed=%s, want %s" % (typed, want))
|
||||||
|
|
||||||
|
if fails:
|
||||||
|
print("verify-dvx-input: FAIL - " + "; ".join(fails))
|
||||||
|
sys.exit(1)
|
||||||
|
print('verify-dvx-input: PASS (typed "%s": all %d chars reached jlInputGetChar, and the present shows them)' % (target, len(target)))
|
||||||
|
PY
|
||||||
75
scripts/verify-dvx-serial.sh
Executable file
75
scripts/verify-dvx-serial.sh
Executable file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dvx-serial.sh - both-directions RS-232 gate for the DVX port.
|
||||||
|
#
|
||||||
|
# Runs SERTEST (examples/sertest: banner, then echo) as the desktop of a
|
||||||
|
# scratch DVX in DOSBox Staging, with COM1 a nullmodem connecting to the
|
||||||
|
# host peer (scripts/lib/serialLoopPeer.py), which waits for the banner,
|
||||||
|
# sends a probe and expects it echoed. That exercises the chain: JoeyLib's
|
||||||
|
# serial layer -> the DVX port (src/dvx/serial.c) -> DVX's rs232 library and
|
||||||
|
# its interrupt -> DOSBox's UART -> host.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dvx-serial.sh
|
||||||
|
# Env: DVX_SERIAL_PORT TCP port the peer listens on (default 6803)
|
||||||
|
# Requires: DOSBox Staging, Xvfb, python3, the dvx component; the sertest
|
||||||
|
# program (make dvx).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
|
program=sertest
|
||||||
|
port="${DVX_SERIAL_PORT:-6803}"
|
||||||
|
peerWall=90 # DVX boot, then the banner and the echo
|
||||||
|
runSeconds=100 # SERTEST echoes forever: DOSBox ends by timeout
|
||||||
|
|
||||||
|
dvxNeedBuild "$program"
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dvx-serial.XXXXXX)
|
||||||
|
peerPid=""
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$dosbox" "$guard" "$peerPid" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
||||||
|
printf '\n[serial]\nserial1 = nullmodem server:127.0.0.1 port:%s transparent:1\n' "$port" >> "$work/dosbox.conf"
|
||||||
|
mkdir -p "$work/xdg"
|
||||||
|
|
||||||
|
python3 "$repo/scripts/lib/serialLoopPeer.py" tcp-server "$port" "$peerWall" > "$work/peer.log" 2>&1 &
|
||||||
|
peerPid=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 3300))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$runSeconds" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard")
|
||||||
|
|
||||||
|
wait "$peerPid"
|
||||||
|
rc=$?
|
||||||
|
peerPid=""
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
cat "$work/peer.log"
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
echo "verify-dvx-serial: PASS (banner out, probe echoed through COM1 and DVX's serial library)"
|
||||||
|
else
|
||||||
|
echo "verify-dvx-serial: FAIL (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
fi
|
||||||
|
exit "$rc"
|
||||||
83
scripts/verify-dvx-tilebank.sh
Executable file
83
scripts/verify-dvx-tilebank.sh
Executable file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dvx-tilebank.sh - Tile-bank gate for DVX: proves the DVX port loads
|
||||||
|
# the DOS bake (chunky 4bpp, high nibble = left pixel -- DVX shows the DOS
|
||||||
|
# stage and takes the DOS target byte) through the run-time DATA root beside
|
||||||
|
# the program, and refuses a bank baked for any other target.
|
||||||
|
#
|
||||||
|
# Runs TILEBANK (examples/tilebank) as the desktop of a scratch DVX in DOSBox
|
||||||
|
# Staging, with the DATA/tilebank make/dvx.mk staged beside it from the host
|
||||||
|
# gate's fixture. The program loads its bake through jlTileBankLoadToSurface
|
||||||
|
# and through jlTileBankLoad + jlTilePaste, checks every pixel of both against
|
||||||
|
# the PNG, hashes both, and tries every other target's bake; its TILEBANK line
|
||||||
|
# in its joeylog.txt (in its settings folder) must say PASS with both hashes
|
||||||
|
# equal to the host gate's (scripts/check-tilebank.sh, run here for the value)
|
||||||
|
# and every other target refused. DOSBox is stopped as soon as the line
|
||||||
|
# appears.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dvx-tilebank.sh
|
||||||
|
# Env: TILEBANK_DATA_DIR stage this DATA/tilebank instead (negative controls)
|
||||||
|
# Requires: DOSBox Staging, Xvfb, a host cc, the dvx component; the tilebank
|
||||||
|
# program and its DATA/tilebank (make dvx).
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
source "$repo/scripts/lib/joeyEnv.sh"
|
||||||
|
source "$repo/scripts/lib/tileBankGate.sh"
|
||||||
|
source "$repo/scripts/lib/dvxGate.sh"
|
||||||
|
joeyNeed DOSBOX dosbox
|
||||||
|
joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
|
program=tilebank
|
||||||
|
programDir=$(dvxAppDir "$program")
|
||||||
|
dataDir=$(tileBankData "$programDir")
|
||||||
|
wall=150 # hard cap on DOSBox, whatever happens to this script
|
||||||
|
lineSeconds=120 # DVX boot plus the checks
|
||||||
|
|
||||||
|
tileBankNeedBuild "$programDir/$program.app" "$dataDir" "make dvx"
|
||||||
|
tileBankReference
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dvx-tilebank.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
# 2>/dev/null: bash reports the SIGKILLed timeout wrapper as "Killed".
|
||||||
|
cleanup() {
|
||||||
|
joeyStop "$dosbox" "$guard" "$xvfb" 2> /dev/null
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
||||||
|
staged=$work/c/DVX/apps/$DVX_VENDOR/$program/DATA/tilebank
|
||||||
|
rm -rf "$staged"
|
||||||
|
mkdir -p "$(dirname "$staged")"
|
||||||
|
cp -r "$dataDir" "$staged"
|
||||||
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
||||||
|
mkdir -p "$work/xdg"
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 2100))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME="$work/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$work/dosbox.conf" > "$work/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard")
|
||||||
|
|
||||||
|
log=$(dvxAwaitLog "$work/c" "$program" '^TILEBANK: ' "$lineSeconds" "$guard")
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
echo "verify-dvx-tilebank: FAIL - no TILEBANK line within ${lineSeconds}s (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Judged from a copy: the emulator is stopped first, as the other gates do.
|
||||||
|
cp "$log" "$work/joeylog.txt"
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
tileBankJudge "$work/joeylog.txt"
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Shared emit-time source-pixel helpers for the chunky 4bpp sprite
|
// Shared emit-time source-pixel helpers for the chunky 4bpp sprite
|
||||||
// emitters -- spriteEmitX86.c (DOS) and spriteEmitIigs.c. These two
|
// emitters -- spriteEmitX86.c (DOS, DVX) and spriteEmitIigs.c. These two
|
||||||
// functions ARE the transparency/shift contract that the emitted
|
// functions ARE the transparency/shift contract that the emitted
|
||||||
// routines must share with the interpreted walkers bit-for-bit, so
|
// routines must share with the interpreted walkers bit-for-bit, so
|
||||||
// they exist exactly once: a fix applied here reaches both CPUs.
|
// they exist exactly once: a fix applied here reaches both CPUs.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// Internal interface for per-CPU sprite emitters.
|
// Internal interface for per-CPU sprite emitters.
|
||||||
//
|
//
|
||||||
// Each per-CPU emitter file (src/dos/spriteEmitX86.c,
|
// Each per-CPU emitter file (src/x86/spriteEmitX86.c,
|
||||||
// src/iigs/spriteEmitIigs.c, src/m68k/spriteEmitPlanar68k.c,
|
// src/iigs/spriteEmitIigs.c, src/m68k/spriteEmitPlanar68k.c,
|
||||||
// src/m68k/spriteEmitInterleaved68k.c) implements its own emit
|
// src/m68k/spriteEmitInterleaved68k.c) implements its own emit
|
||||||
// functions. spriteCompile.c picks the right one at compile time
|
// functions. spriteCompile.c picks the right one at compile time
|
||||||
// (via #ifdef on JOEYLIB_PLATFORM_*) for the runtime build.
|
// (via #ifdef on JOEYLIB_PLATFORM_* / JOEYLIB_CPU_*) for the runtime build.
|
||||||
//
|
//
|
||||||
// Each emit function takes the sprite + shift variant and a capacity
|
// Each emit function takes the sprite + shift variant and a capacity
|
||||||
// `cap` (bytes available at `out`), and writes position-independent
|
// `cap` (bytes available at `out`), and writes position-independent
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86 10u
|
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86 10u
|
||||||
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS 9u
|
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS 9u
|
||||||
|
|
||||||
#if defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_CPU_X86)
|
||||||
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86
|
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_X86
|
||||||
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS
|
#define SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE SPRITE_EMIT_MAX_BYTES_PER_DESTBYTE_IIGS
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
|
|
||||||
|
|
||||||
// Compile-time selection of the per-CPU emitter. One per-CPU
|
// Compile-time selection of the per-CPU emitter. One per-CPU
|
||||||
// spriteEmit*.c file (src/dos, src/iigs, src/m68k) is built per
|
// spriteEmit*.c file (src/x86, src/iigs, src/m68k) is built per
|
||||||
// platform, but the dispatch lives in this file so jlSpriteCompile +
|
// platform, but the dispatch lives in this file so jlSpriteCompile +
|
||||||
// spriteCompiledDraw aren't duplicated per port.
|
// spriteCompiledDraw aren't duplicated per port.
|
||||||
static uint32_t emitDrawForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
static uint32_t emitDrawForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||||
#if defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_CPU_X86)
|
||||||
return spriteEmitDrawX86(out, cap, sp, shift);
|
return spriteEmitDrawX86(out, cap, sp, shift);
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
||||||
return spriteEmitDrawPlanar68k(out, cap, sp, shift);
|
return spriteEmitDrawPlanar68k(out, cap, sp, shift);
|
||||||
|
|
@ -41,7 +41,7 @@ static uint32_t emitDrawForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *s
|
||||||
// != SPRITE_NOT_COMPILED and falls back to the interpreted memcpy
|
// != SPRITE_NOT_COMPILED and falls back to the interpreted memcpy
|
||||||
// path otherwise.
|
// path otherwise.
|
||||||
static uint32_t emitSaveForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
static uint32_t emitSaveForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||||
#if defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_CPU_X86)
|
||||||
return spriteEmitSaveX86(out, cap, sp, shift);
|
return spriteEmitSaveX86(out, cap, sp, shift);
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
||||||
return spriteEmitSavePlanar68k(out, cap, sp, shift);
|
return spriteEmitSavePlanar68k(out, cap, sp, shift);
|
||||||
|
|
@ -57,7 +57,7 @@ static uint32_t emitSaveForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *s
|
||||||
|
|
||||||
|
|
||||||
static uint32_t emitRestoreForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
static uint32_t emitRestoreForTarget(uint8_t *out, uint32_t cap, const jlSpriteT *sp, uint8_t shift) {
|
||||||
#if defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_CPU_X86)
|
||||||
return spriteEmitRestoreX86(out, cap, sp, shift);
|
return spriteEmitRestoreX86(out, cap, sp, shift);
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_X68000)
|
||||||
return spriteEmitRestorePlanar68k(out, cap, sp, shift);
|
return spriteEmitRestorePlanar68k(out, cap, sp, shift);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "joey/file.h"
|
#include "joey/file.h"
|
||||||
#include "joey/sprite.h"
|
#include "joey/sprite.h"
|
||||||
#include "joey/tile.h"
|
#include "joey/tile.h"
|
||||||
|
#include "fileRoot.h"
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
#include "spriteInternal.h"
|
#include "spriteInternal.h"
|
||||||
#include "spritePrecompiled.h"
|
#include "spritePrecompiled.h"
|
||||||
|
|
@ -93,8 +94,8 @@ typedef char AssetTileSizeCheckT[(sizeof(jlTileT) == TILE_BYTES) ? 1 : -1];
|
||||||
#define EXPECTED_TILE_TARGET 1u
|
#define EXPECTED_TILE_TARGET 1u
|
||||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
#define EXPECTED_TILE_TARGET 2u
|
#define EXPECTED_TILE_TARGET 2u
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_PLATFORM_DOS) || defined(JOEYLIB_PLATFORM_DVX)
|
||||||
#define EXPECTED_TILE_TARGET 3u
|
#define EXPECTED_TILE_TARGET 3u // DVX shows the DOS stage: same chunky banks
|
||||||
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
#define EXPECTED_TILE_TARGET 4u
|
#define EXPECTED_TILE_TARGET 4u
|
||||||
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
|
|
@ -115,8 +116,8 @@ typedef char AssetTileSizeCheckT[(sizeof(jlTileT) == TILE_BYTES) ? 1 : -1];
|
||||||
#define EXPECTED_SPC_TARGET 1u
|
#define EXPECTED_SPC_TARGET 1u
|
||||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
#define EXPECTED_SPC_TARGET 2u
|
#define EXPECTED_SPC_TARGET 2u
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS) || defined(JOEYLIB_PLATFORM_BLANK)
|
#elif defined(JOEYLIB_CPU_X86) || defined(JOEYLIB_PLATFORM_BLANK)
|
||||||
#define EXPECTED_SPC_TARGET 3u
|
#define EXPECTED_SPC_TARGET 3u // x86 code: DOS and DVX run the same banks
|
||||||
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
#elif defined(JOEYLIB_PLATFORM_IIGS)
|
||||||
#define EXPECTED_SPC_TARGET 4u
|
#define EXPECTED_SPC_TARGET 4u
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
|
|
@ -181,9 +182,17 @@ static void readPaletteFromHeader(const uint8_t *header, uint16_t *outPalette) {
|
||||||
// loader and example routes file opens through this, so callers never
|
// loader and example routes file opens through this, so callers never
|
||||||
// hard-code "DATA/" and the runtime tree layout stays consistent across
|
// hard-code "DATA/" and the runtime tree layout stays consistent across
|
||||||
// ports. Returns NULL if the joined path overflows DATA_PATH_MAX or the
|
// ports. Returns NULL if the joined path overflows DATA_PATH_MAX or the
|
||||||
// file cannot be opened.
|
// file cannot be opened. A port with run-time roots (fileRoot.h) puts
|
||||||
|
// DATA/ under its read root.
|
||||||
FILE *jlDataOpen(const char *name, const char *mode) {
|
FILE *jlDataOpen(const char *name, const char *mode) {
|
||||||
char path[DATA_PATH_MAX];
|
char path[DATA_PATH_MAX];
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
|
||||||
|
if (name == NULL || !fileRootJoin(path, sizeof(path), jlpReadRoot(), DATA_DIR_PREFIX, name)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return fopen(path, mode);
|
||||||
|
#else
|
||||||
size_t prefixLen = sizeof(DATA_DIR_PREFIX) - 1u; // literal length, no NUL
|
size_t prefixLen = sizeof(DATA_DIR_PREFIX) - 1u; // literal length, no NUL
|
||||||
size_t nameLen;
|
size_t nameLen;
|
||||||
|
|
||||||
|
|
@ -202,6 +211,7 @@ FILE *jlDataOpen(const char *name, const char *mode) {
|
||||||
memcpy(path, DATA_DIR_PREFIX, prefixLen);
|
memcpy(path, DATA_DIR_PREFIX, prefixLen);
|
||||||
memcpy(path + prefixLen, name, nameLen + 1u); // copy the NUL too
|
memcpy(path + prefixLen, name, nameLen + 1u); // copy the NUL too
|
||||||
return fopen(path, mode);
|
return fopen(path, mode);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include "joey/platform.h"
|
#include "joey/platform.h"
|
||||||
#include "joey/debug.h"
|
#include "joey/debug.h"
|
||||||
|
#include "fileRoot.h"
|
||||||
|
|
||||||
|
|
||||||
static const char *kLogPath = "joeylog.txt";
|
static const char *kLogPath = "joeylog.txt";
|
||||||
|
|
@ -154,26 +155,68 @@ void jlLogReset(void) {
|
||||||
#else /* not JOEYLIB_PLATFORM_IIGS */
|
#else /* not JOEYLIB_PLATFORM_IIGS */
|
||||||
|
|
||||||
static FILE *gLogFp = NULL;
|
static FILE *gLogFp = NULL;
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
// The log FILE belongs to the process hosting the program and can outlive
|
||||||
|
// it (a program ended by force never reaches jlLogClose), so it keeps the
|
||||||
|
// host's own buffer rather than one inside the program. (DVX does not even
|
||||||
|
// offer its programs setvbuf.) jlLog/jlLogF still flush once per line.
|
||||||
|
#define LOG_PATH_MAX 256
|
||||||
|
#else
|
||||||
/* 16 KB full buffer so a single log line's many small vfprintf writes
|
/* 16 KB full buffer so a single log line's many small vfprintf writes
|
||||||
* coalesce into one host write; jlLog/jlLogF then fflush once per
|
* coalesce into one host write; jlLog/jlLogF then fflush once per
|
||||||
* line. Default stdio buffers are only ~512 bytes, which would split a
|
* line. Default stdio buffers are only ~512 bytes, which would split a
|
||||||
* long line across multiple writes. */
|
* long line across multiple writes. */
|
||||||
#define JOEY_LOG_BUF_BYTES 16384
|
#define JOEY_LOG_BUF_BYTES 16384
|
||||||
static char gLogBuf[JOEY_LOG_BUF_BYTES];
|
static char gLogBuf[JOEY_LOG_BUF_BYTES];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static void logClose(void);
|
||||||
|
static FILE *logFile(void);
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
static FILE *logOpen(const char *mode);
|
||||||
|
#else
|
||||||
|
// joeylog.txt is relative to the current directory.
|
||||||
|
#define logOpen(_mode) fopen(kLogPath, (_mode))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static void logClose(void) {
|
||||||
|
if (gLogFp != NULL) {
|
||||||
|
fclose(gLogFp);
|
||||||
|
gLogFp = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Lazy-open. Returns NULL if the open failed (silently disable). */
|
/* Lazy-open. Returns NULL if the open failed (silently disable). */
|
||||||
static FILE *logFile(void) {
|
static FILE *logFile(void) {
|
||||||
if (gLogFp == NULL) {
|
if (gLogFp == NULL) {
|
||||||
gLogFp = fopen(kLogPath, "a");
|
gLogFp = logOpen("a");
|
||||||
|
#if !defined(JL_HAS_FILE_ROOTS)
|
||||||
if (gLogFp != NULL) {
|
if (gLogFp != NULL) {
|
||||||
(void)setvbuf(gLogFp, gLogBuf, _IOFBF, sizeof(gLogBuf));
|
(void)setvbuf(gLogFp, gLogBuf, _IOFBF, sizeof(gLogBuf));
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return gLogFp;
|
return gLogFp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
// joeylog.txt under the port's write root (fileRoot.h).
|
||||||
|
static FILE *logOpen(const char *mode) {
|
||||||
|
char path[LOG_PATH_MAX];
|
||||||
|
|
||||||
|
if (!fileRootJoin(path, sizeof(path), jlpWriteRoot(), kLogPath, "")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return fopen(path, mode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void jlLog(const char *msg) {
|
void jlLog(const char *msg) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
if (msg == NULL) {
|
if (msg == NULL) {
|
||||||
|
|
@ -193,6 +236,13 @@ void jlLog(const char *msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
void jlLogClose(void) {
|
||||||
|
logClose();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void jlLogF(const char *fmt, ...) {
|
void jlLogF(const char *fmt, ...) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
@ -241,14 +291,11 @@ void jlLogFlush(void) {
|
||||||
|
|
||||||
|
|
||||||
void jlLogReset(void) {
|
void jlLogReset(void) {
|
||||||
if (gLogFp != NULL) {
|
logClose();
|
||||||
fclose(gLogFp);
|
|
||||||
gLogFp = NULL;
|
|
||||||
}
|
|
||||||
/* Truncate by opening for write then closing; subsequent
|
/* Truncate by opening for write then closing; subsequent
|
||||||
* jlLog* will reopen for append. */
|
* jlLog* will reopen for append. */
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(kLogPath, "w");
|
FILE *fp = logOpen("w");
|
||||||
if (fp != NULL) {
|
if (fp != NULL) {
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/core/fileRoot.h
Normal file
46
src/core/fileRoot.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Run-time file roots (JL_HAS_FILE_ROOTS).
|
||||||
|
//
|
||||||
|
// Every machine port runs its program with the program's own folder as the
|
||||||
|
// current directory, so the core opens DATA/, SAVES/ and joeylog.txt by
|
||||||
|
// those relative names (literals in assetLoad.c, save.c and debug.c). A port
|
||||||
|
// whose programs share one current directory with other programs and end
|
||||||
|
// without ending the process (DVX: each program is a module loaded into the
|
||||||
|
// desktop) defines JL_HAS_FILE_ROOTS in platform.h and supplies the folders
|
||||||
|
// they live in. Without the flag this header declares nothing and the core
|
||||||
|
// keeps its literals, so no other port's code changes.
|
||||||
|
#ifndef JOEYLIB_FILE_ROOT_H
|
||||||
|
#define JOEYLIB_FILE_ROOT_H
|
||||||
|
|
||||||
|
#include "joey/platform.h"
|
||||||
|
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// The roots are folder prefixes: "" or a path ending in a separator, valid
|
||||||
|
// from before the program's main runs until it ends.
|
||||||
|
// jlpReadRoot holds DATA/, the program's own read-only files
|
||||||
|
// jlpWriteRoot holds SAVES/ and joeylog.txt; the port creates the folder
|
||||||
|
// the first time it is asked for it
|
||||||
|
const char *jlpReadRoot(void);
|
||||||
|
const char *jlpWriteRoot(void);
|
||||||
|
|
||||||
|
// Close joeylog.txt (debug.c). The port calls it when its program ends:
|
||||||
|
// the file handle belongs to the host process, which lives on. A later
|
||||||
|
// jlLog opens the file again, appending.
|
||||||
|
void jlLogClose(void);
|
||||||
|
|
||||||
|
|
||||||
|
// out = root + rel + name. False when that does not fit in cap bytes.
|
||||||
|
static inline bool fileRootJoin(char *out, size_t cap, const char *root, const char *rel, const char *name) {
|
||||||
|
int written;
|
||||||
|
|
||||||
|
written = snprintf(out, cap, "%s%s%s", root, rel, name);
|
||||||
|
return written >= 0 && (size_t)written < cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -418,10 +418,10 @@ void iigsIrqLatencyTighten(void);
|
||||||
#define jlpTilePasteGlyph planarTilePasteGlyph
|
#define jlpTilePasteGlyph planarTilePasteGlyph
|
||||||
#define jlpTileSnap planarTileSnap
|
#define jlpTileSnap planarTileSnap
|
||||||
#define jlpTileMapPaste planarTileMapPaste
|
#define jlpTileMapPaste planarTileMapPaste
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_CPU_X86)
|
||||||
// DOS overrides only the whole-map walker (dosTile.h, W3); the
|
// DOS and DVX override only the whole-map walker (src/x86/x86Tile.h,
|
||||||
// single-tile ops stay on the chunky generics.
|
// W3); the single-tile ops stay on the chunky generics.
|
||||||
#define jlpTileMapPaste dosTileMapPaste
|
#define jlpTileMapPaste x86TileMapPaste
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
@ -1136,14 +1136,14 @@ void jlpGenericBigFree(void *p);
|
||||||
// Per-port tile-op inline bodies (see the alias block above the
|
// Per-port tile-op inline bodies (see the alias block above the
|
||||||
// dispatch tail). The 68k headers are included LAST so their
|
// dispatch tail). The 68k headers are included LAST so their
|
||||||
// portData == NULL fallbacks can call the jlpGenericTile* prototypes
|
// portData == NULL fallbacks can call the jlpGenericTile* prototypes
|
||||||
// declared above; the DOS header (whole-map engine only, W3) needs
|
// declared above; the x86 header (whole-map engine only, W3) needs
|
||||||
// nothing from the tail but sits here for the same one-include-point.
|
// nothing from the tail but sits here for the same one-include-point.
|
||||||
#if defined(JOEYLIB_PLATFORM_ATARIST)
|
#if defined(JOEYLIB_PLATFORM_ATARIST)
|
||||||
#include "stTile.h"
|
#include "stTile.h"
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA)
|
||||||
#include "amigaTile.h"
|
#include "amigaTile.h"
|
||||||
#elif defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_CPU_X86)
|
||||||
#include "dosTile.h"
|
#include "x86Tile.h"
|
||||||
#elif defined(JOEYLIB_PLATFORM_X68000)
|
#elif defined(JOEYLIB_PLATFORM_X68000)
|
||||||
#include "x68kTile.h"
|
#include "x68kTile.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "joey/file.h"
|
#include "joey/file.h"
|
||||||
|
#include "fileRoot.h"
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,17 +31,40 @@
|
||||||
// ----- Helpers -----
|
// ----- Helpers -----
|
||||||
|
|
||||||
static bool buildSavePath(char *out, size_t cap, const char *name);
|
static bool buildSavePath(char *out, size_t cap, const char *name);
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
static void ensureSaveDir(void);
|
||||||
|
#else
|
||||||
|
// SAVES/ is relative to the current directory: ensure it by its literal name.
|
||||||
|
#define ensureSaveDir() ((void)jlpSaveDirEnsure(SAVE_DIR))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Join SAVE_DIR_PREFIX + name into `out`. Returns false if it would overflow.
|
// Join SAVE_DIR_PREFIX + name into `out` (under the write root on a port
|
||||||
|
// with run-time roots, fileRoot.h). Returns false if it would overflow.
|
||||||
static bool buildSavePath(char *out, size_t cap, const char *name) {
|
static bool buildSavePath(char *out, size_t cap, const char *name) {
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
return fileRootJoin(out, cap, jlpWriteRoot(), SAVE_DIR_PREFIX, name);
|
||||||
|
#else
|
||||||
int written;
|
int written;
|
||||||
|
|
||||||
written = snprintf(out, cap, SAVE_DIR_PREFIX "%s", name);
|
written = snprintf(out, cap, SAVE_DIR_PREFIX "%s", name);
|
||||||
return written >= 0 && (size_t)written < cap;
|
return written >= 0 && (size_t)written < cap;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(JL_HAS_FILE_ROOTS)
|
||||||
|
// Create SAVES/ under the write root. Advisory, like the literal form above.
|
||||||
|
static void ensureSaveDir(void) {
|
||||||
|
char dir[SAVE_PATH_MAX];
|
||||||
|
|
||||||
|
if (fileRootJoin(dir, sizeof(dir), jlpWriteRoot(), SAVE_DIR, "")) {
|
||||||
|
(void)jlpSaveDirEnsure(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// ----- Public API (alphabetical) -----
|
// ----- Public API (alphabetical) -----
|
||||||
|
|
||||||
uint32_t jlDiskFree(void) {
|
uint32_t jlDiskFree(void) {
|
||||||
|
|
@ -86,7 +110,7 @@ FILE *jlSaveOpen(const char *name, const char *mode) {
|
||||||
// the directory cannot be made, fopen below simply fails and the caller
|
// the directory cannot be made, fopen below simply fails and the caller
|
||||||
// sees NULL.
|
// sees NULL.
|
||||||
if (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL) {
|
if (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL) {
|
||||||
(void)jlpSaveDirEnsure(SAVE_DIR);
|
ensureSaveDir();
|
||||||
}
|
}
|
||||||
if (!buildSavePath(path, sizeof(path), name)) {
|
if (!buildSavePath(path, sizeof(path), name)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
||||||
|
|
@ -697,7 +697,7 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y) {
|
||||||
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
if (!COMPILED_SPRITE_WRITES_PLANES) {
|
||||||
jlpSpriteDrawPlanes(s, sp, x, y);
|
jlpSpriteDrawPlanes(s, sp, x, y);
|
||||||
}
|
}
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
// P7-1 lever b: inline the dominant h==16 stage mark to drop
|
// P7-1 lever b: inline the dominant h==16 stage mark to drop
|
||||||
// the spriteMarkDirty call boundary (see the restore arm in
|
// the spriteMarkDirty call boundary (see the restore arm in
|
||||||
// jlSpriteRestoreUnder). Other heights / non-stage draws keep
|
// jlSpriteRestoreUnder). Other heights / non-stage draws keep
|
||||||
|
|
@ -725,7 +725,7 @@ void jlSpriteDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y) {
|
||||||
// fully-validated jlSpriteDraw for uncompiled sprites and on DOS /
|
// fully-validated jlSpriteDraw for uncompiled sprites and on DOS /
|
||||||
// generic.
|
// generic.
|
||||||
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y) {
|
void jlSpriteDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y) {
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
if (sp->slot != NULL) {
|
if (sp->slot != NULL) {
|
||||||
uint8_t shift;
|
uint8_t shift;
|
||||||
uint16_t drawOffset;
|
uint16_t drawOffset;
|
||||||
|
|
@ -848,7 +848,7 @@ void jlSpriteSaveAndDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlS
|
||||||
jlpSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
jlpSpriteSavePlanes(s, backup->x, backup->y, backup->width, backup->height, backup->bytes);
|
||||||
jlpSpriteDrawPlanes(s, sp, x, y);
|
jlpSpriteDrawPlanes(s, sp, x, y);
|
||||||
}
|
}
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
// P7-1 lever b: inline the h==16 stage mark (see jlSpriteDraw).
|
// P7-1 lever b: inline the h==16 stage mark (see jlSpriteDraw).
|
||||||
if (s == gStage && heightPx == 16) {
|
if (s == gStage && heightPx == 16) {
|
||||||
SPRITE_MARK_STAGE_ROWS16(x, widthPx, y);
|
SPRITE_MARK_STAGE_ROWS16(x, widthPx, y);
|
||||||
|
|
@ -881,7 +881,7 @@ void jlSpriteSaveAndDraw(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlS
|
||||||
// fully-validated jlSpriteSaveAndDraw for uncompiled sprites and on
|
// fully-validated jlSpriteSaveAndDraw for uncompiled sprites and on
|
||||||
// DOS / generic.
|
// DOS / generic.
|
||||||
void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlSpriteBackupT *backup) {
|
void jlSpriteSaveAndDrawTrusted(jlSurfaceT *s, jlSpriteT *sp, int16_t x, int16_t y, jlSpriteBackupT *backup) {
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
ArenaSlotT *slot;
|
ArenaSlotT *slot;
|
||||||
|
|
||||||
slot = sp->slot;
|
slot = sp->slot;
|
||||||
|
|
@ -1008,7 +1008,7 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#elif defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
// Fused stage restore (P7-1 lever b): the compiled copy body
|
// Fused stage restore (P7-1 lever b): the compiled copy body
|
||||||
// stays INLINED (spriteCompiledRestoreUnder is always-inline,
|
// stays INLINED (spriteCompiledRestoreUnder is always-inline,
|
||||||
// sharing the backup-field loads with the validation above),
|
// sharing the backup-field loads with the validation above),
|
||||||
|
|
@ -1085,7 +1085,7 @@ void jlSpriteRestoreUnder(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
||||||
// restore+mark JSL, mirroring the safe fused arm); on DOS / generic this
|
// restore+mark JSL, mirroring the safe fused arm); on DOS / generic this
|
||||||
// is just the validated call.
|
// is just the validated call.
|
||||||
void jlSpriteRestoreUnderTrusted(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
void jlSpriteRestoreUnderTrusted(jlSurfaceT *s, const jlSpriteBackupT *backup) {
|
||||||
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_PLATFORM_DOS)
|
#if defined(JOEYLIB_PLATFORM_AMIGA) || defined(JOEYLIB_PLATFORM_ATARIST) || defined(JOEYLIB_CPU_X86)
|
||||||
jlSpriteT *sp;
|
jlSpriteT *sp;
|
||||||
int16_t bx;
|
int16_t bx;
|
||||||
int16_t by;
|
int16_t by;
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,7 @@ void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uin
|
||||||
#elif defined(jlpTileMapPaste)
|
#elif defined(jlpTileMapPaste)
|
||||||
// Whole-map engine (NATIVE-PERF Phase 4 ST / Phase 6 W3 seam):
|
// Whole-map engine (NATIVE-PERF Phase 4 ST / Phase 6 W3 seam):
|
||||||
// port.h aliases jlpTileMapPaste to the port's engine (ST
|
// port.h aliases jlpTileMapPaste to the port's engine (ST
|
||||||
// stTileMapPaste, DOS dosTileMapPaste), which hoists the setup --
|
// stTileMapPaste, DOS/DVX x86TileMapPaste), which hoists the setup --
|
||||||
// portData/pixels check, row-base derive, group or dword walk --
|
// portData/pixels check, row-base derive, group or dword walk --
|
||||||
// out of the per-cell loop the generic arm below pays. One arm,
|
// out of the per-cell loop the generic arm below pays. One arm,
|
||||||
// one mark stanza, however many ports bring engines.
|
// one mark stanza, however many ports bring engines.
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
#include "inputInternal.h"
|
#include "inputInternal.h"
|
||||||
#include "joey/surface.h"
|
#include "joey/surface.h"
|
||||||
|
#include "scanSet1.h"
|
||||||
|
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
|
|
||||||
|
|
@ -54,7 +55,6 @@
|
||||||
#define SCAN_BREAK_BIT 0x80
|
#define SCAN_BREAK_BIT 0x80
|
||||||
#define SCAN_CODE_MASK 0x7F
|
#define SCAN_CODE_MASK 0x7F
|
||||||
#define SCAN_EXTENDED 0xE0
|
#define SCAN_EXTENDED 0xE0
|
||||||
#define SCAN_TABLE_SIZE 128
|
|
||||||
#define ISR_LOCK_SIZE 4096
|
#define ISR_LOCK_SIZE 4096
|
||||||
|
|
||||||
// Caps/num-lock make codes, and the BIOS keyboard shift-flag byte in
|
// Caps/num-lock make codes, and the BIOS keyboard shift-flag byte in
|
||||||
|
|
@ -141,73 +141,11 @@ static int8_t scaleAxis(uint16_t v);
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
static const uint8_t gScanToKey[SCAN_TABLE_SIZE] = {
|
|
||||||
[0x01] = KEY_ESCAPE,
|
|
||||||
[0x02] = KEY_1,
|
|
||||||
[0x03] = KEY_2,
|
|
||||||
[0x04] = KEY_3,
|
|
||||||
[0x05] = KEY_4,
|
|
||||||
[0x06] = KEY_5,
|
|
||||||
[0x07] = KEY_6,
|
|
||||||
[0x08] = KEY_7,
|
|
||||||
[0x09] = KEY_8,
|
|
||||||
[0x0A] = KEY_9,
|
|
||||||
[0x0B] = KEY_0,
|
|
||||||
[0x0E] = KEY_BACKSPACE,
|
|
||||||
[0x0F] = KEY_TAB,
|
|
||||||
[0x10] = KEY_Q,
|
|
||||||
[0x11] = KEY_W,
|
|
||||||
[0x12] = KEY_E,
|
|
||||||
[0x13] = KEY_R,
|
|
||||||
[0x14] = KEY_T,
|
|
||||||
[0x15] = KEY_Y,
|
|
||||||
[0x16] = KEY_U,
|
|
||||||
[0x17] = KEY_I,
|
|
||||||
[0x18] = KEY_O,
|
|
||||||
[0x19] = KEY_P,
|
|
||||||
[0x1C] = KEY_RETURN,
|
|
||||||
[0x1D] = KEY_LCTRL,
|
|
||||||
[0x1E] = KEY_A,
|
|
||||||
[0x1F] = KEY_S,
|
|
||||||
[0x20] = KEY_D,
|
|
||||||
[0x21] = KEY_F,
|
|
||||||
[0x22] = KEY_G,
|
|
||||||
[0x23] = KEY_H,
|
|
||||||
[0x24] = KEY_J,
|
|
||||||
[0x25] = KEY_K,
|
|
||||||
[0x26] = KEY_L,
|
|
||||||
[0x2A] = KEY_LSHIFT,
|
|
||||||
[0x2C] = KEY_Z,
|
|
||||||
[0x2D] = KEY_X,
|
|
||||||
[0x2E] = KEY_C,
|
|
||||||
[0x2F] = KEY_V,
|
|
||||||
[0x30] = KEY_B,
|
|
||||||
[0x31] = KEY_N,
|
|
||||||
[0x32] = KEY_M,
|
|
||||||
[0x36] = KEY_RSHIFT,
|
|
||||||
[0x38] = KEY_LALT,
|
|
||||||
[0x39] = KEY_SPACE,
|
|
||||||
[0x3B] = KEY_F1,
|
|
||||||
[0x3C] = KEY_F2,
|
|
||||||
[0x3D] = KEY_F3,
|
|
||||||
[0x3E] = KEY_F4,
|
|
||||||
[0x3F] = KEY_F5,
|
|
||||||
[0x40] = KEY_F6,
|
|
||||||
[0x41] = KEY_F7,
|
|
||||||
[0x42] = KEY_F8,
|
|
||||||
[0x43] = KEY_F9,
|
|
||||||
[0x44] = KEY_F10,
|
|
||||||
[0x48] = KEY_UP,
|
|
||||||
[0x4B] = KEY_LEFT,
|
|
||||||
[0x4D] = KEY_RIGHT,
|
|
||||||
[0x50] = KEY_DOWN,
|
|
||||||
};
|
|
||||||
|
|
||||||
// US-layout set-1 scancode -> ASCII, normal and shifted. Only make
|
// US-layout set-1 scancode -> ASCII, normal and shifted. Only make
|
||||||
// codes reach these; slots left at 0 produce no character. Letters
|
// codes reach these; slots left at 0 produce no character. Letters
|
||||||
// are lowercase here -- the ISR flips to the shifted table when the
|
// are lowercase here -- the ISR flips to the shifted table when the
|
||||||
// effective shift (shift XOR caps, letters only) is active.
|
// effective shift (shift XOR caps, letters only) is active.
|
||||||
static const uint8_t gScanAsciiNormal[SCAN_TABLE_SIZE] = {
|
static const uint8_t gScanAsciiNormal[SCAN_SET1_CODES] = {
|
||||||
[0x01] = JL_CHAR_ESCAPE,
|
[0x01] = JL_CHAR_ESCAPE,
|
||||||
[0x02] = '1', [0x03] = '2', [0x04] = '3', [0x05] = '4',
|
[0x02] = '1', [0x03] = '2', [0x04] = '3', [0x05] = '4',
|
||||||
[0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8',
|
[0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8',
|
||||||
|
|
@ -228,7 +166,7 @@ static const uint8_t gScanAsciiNormal[SCAN_TABLE_SIZE] = {
|
||||||
[0x4A] = '-', [0x4E] = '+',
|
[0x4A] = '-', [0x4E] = '+',
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t gScanAsciiShift[SCAN_TABLE_SIZE] = {
|
static const uint8_t gScanAsciiShift[SCAN_SET1_CODES] = {
|
||||||
[0x01] = JL_CHAR_ESCAPE,
|
[0x01] = JL_CHAR_ESCAPE,
|
||||||
[0x02] = '!', [0x03] = '@', [0x04] = '#', [0x05] = '$',
|
[0x02] = '!', [0x03] = '@', [0x04] = '#', [0x05] = '$',
|
||||||
[0x06] = '%', [0x07] = '^', [0x08] = '&', [0x09] = '*',
|
[0x06] = '%', [0x07] = '^', [0x08] = '&', [0x09] = '*',
|
||||||
|
|
@ -328,7 +266,7 @@ static void keyboardIsr(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = gScanToKey[code];
|
key = gScanSet1ToKey[code];
|
||||||
if (key != KEY_NONE) {
|
if (key != KEY_NONE) {
|
||||||
gIsrState[key] = !isBreak;
|
gIsrState[key] = !isBreak;
|
||||||
}
|
}
|
||||||
|
|
@ -559,20 +497,20 @@ void jlpInputInit(void) {
|
||||||
gIsrCharTail = 0;
|
gIsrCharTail = 0;
|
||||||
|
|
||||||
_go32_dpmi_lock_code(keyboardIsr, ISR_LOCK_SIZE);
|
_go32_dpmi_lock_code(keyboardIsr, ISR_LOCK_SIZE);
|
||||||
_go32_dpmi_lock_data((void *)gScanToKey, sizeof(gScanToKey));
|
_go32_dpmi_lock_data((void *)gScanSet1ToKey, sizeof(gScanSet1ToKey));
|
||||||
_go32_dpmi_lock_data((void *)gIsrState, sizeof(gIsrState));
|
_go32_dpmi_lock_data((void *)gIsrState, sizeof(gIsrState));
|
||||||
_go32_dpmi_lock_data((void *)gScanAsciiNormal, sizeof(gScanAsciiNormal));
|
_go32_dpmi_lock_data((void *)gScanAsciiNormal, sizeof(gScanAsciiNormal));
|
||||||
_go32_dpmi_lock_data((void *)gScanAsciiShift, sizeof(gScanAsciiShift));
|
_go32_dpmi_lock_data((void *)gScanAsciiShift, sizeof(gScanAsciiShift));
|
||||||
_go32_dpmi_lock_data((void *)gKeypadAscii, sizeof(gKeypadAscii));
|
_go32_dpmi_lock_data((void *)gKeypadAscii, sizeof(gKeypadAscii));
|
||||||
_go32_dpmi_lock_data((void *)gIsrCharQueue, sizeof(gIsrCharQueue));
|
_go32_dpmi_lock_data((void *)gIsrCharQueue, sizeof(gIsrCharQueue));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrExtended, sizeof(gIsrExtended));
|
_go32_dpmi_lock_data((void *)&gIsrExtended, sizeof(gIsrExtended));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrE1Skip, sizeof(gIsrE1Skip));
|
_go32_dpmi_lock_data((void *)&gIsrE1Skip, sizeof(gIsrE1Skip));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrCapsLock, sizeof(gIsrCapsLock));
|
_go32_dpmi_lock_data((void *)&gIsrCapsLock, sizeof(gIsrCapsLock));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrCapsHeld, sizeof(gIsrCapsHeld));
|
_go32_dpmi_lock_data((void *)&gIsrCapsHeld, sizeof(gIsrCapsHeld));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrNumLock, sizeof(gIsrNumLock));
|
_go32_dpmi_lock_data((void *)&gIsrNumLock, sizeof(gIsrNumLock));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrNumHeld, sizeof(gIsrNumHeld));
|
_go32_dpmi_lock_data((void *)&gIsrNumHeld, sizeof(gIsrNumHeld));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrCharHead, sizeof(gIsrCharHead));
|
_go32_dpmi_lock_data((void *)&gIsrCharHead, sizeof(gIsrCharHead));
|
||||||
_go32_dpmi_lock_data((void *)&gIsrCharTail, sizeof(gIsrCharTail));
|
_go32_dpmi_lock_data((void *)&gIsrCharTail, sizeof(gIsrCharTail));
|
||||||
|
|
||||||
_go32_dpmi_get_protected_mode_interrupt_vector(9, &gOldHandler);
|
_go32_dpmi_get_protected_mode_interrupt_vector(9, &gOldHandler);
|
||||||
|
|
||||||
|
|
|
||||||
491
src/dvx/audio.c
Normal file
491
src/dvx/audio.c
Normal file
|
|
@ -0,0 +1,491 @@
|
||||||
|
// DVX audio HAL: JoeyLib's sound through DVX's sound library (audio.lib)
|
||||||
|
// and music library (music.lib), which own the sound card for every
|
||||||
|
// program on the desktop and mix them all together.
|
||||||
|
//
|
||||||
|
// MOD music.lib (libxmp-lite inside DVX: no second copy here),
|
||||||
|
// loaded from memory; music.lib plays it through audio.lib.
|
||||||
|
// SFX five slots, each at most one sound: a one-shot is an
|
||||||
|
// audio.lib sample made from the program's signed 8-bit PCM
|
||||||
|
// (the top bit flipped: DVX's 8-bit samples are unsigned); a
|
||||||
|
// stream is an audio.lib pull stream whose fill calls the
|
||||||
|
// program's, flipping the same bit. JoeyLib's end of stream
|
||||||
|
// (a fill returning 0) closes the stream once what it queued
|
||||||
|
// has played. Pan sticks to the slot, as on every port.
|
||||||
|
// Tone, audio.lib tones that play until changed: jlAudioTone a
|
||||||
|
// voices, square, the three jlAudioVoice voices squares, jlAudioNoise
|
||||||
|
// noise a noise tone. DVX puts squares on the FM chip when there is
|
||||||
|
// one and mixes them otherwise, and keeps noise in the mixer.
|
||||||
|
//
|
||||||
|
// None of this may run inside an interrupt (audio.lib's rule), so
|
||||||
|
// jlAudioTickRegister's callback runs in program time: a DVX timer counts
|
||||||
|
// the ticks at the asked rate, and jlAudioFrameTick delivers the ones that
|
||||||
|
// came due, in order. A callback can therefore do anything; what DVX loses
|
||||||
|
// against DOS is the sub-frame placement of what it does. jlAudioCritical*
|
||||||
|
// stay the generic no-ops: nothing the callback touches can be interrupted
|
||||||
|
// by it.
|
||||||
|
//
|
||||||
|
// jlAudioFrameTick is also where DVX's music and streams are fed
|
||||||
|
// (musicPump, which pumps audio.lib too) when the program keeps the
|
||||||
|
// desktop busy. A context that could not be opened -- DVX without a sound
|
||||||
|
// library -- leaves everything silent; DVX without a sound card still opens
|
||||||
|
// one (its null driver plays nothing).
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "port.h"
|
||||||
|
#include "joey/audio.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define NO_VOICE 0 // audio.lib voice ids are > 0
|
||||||
|
#define NO_TIMER (-1)
|
||||||
|
|
||||||
|
// JoeyLib SFX are signed 8-bit; DVX's 8-bit PCM is unsigned.
|
||||||
|
#define PCM8_SIGN_FLIP 0x80u
|
||||||
|
#define SFX_BITS 8
|
||||||
|
#define SFX_CHANNELS 1
|
||||||
|
#define SFX_VOLUME AUDIO_VOLUME_MAX
|
||||||
|
// A stream's own buffer: DVX refills it every desktop frame or so.
|
||||||
|
#define STREAM_BUFFER_MS 100
|
||||||
|
|
||||||
|
// Tone voices at half scale: the three voices, the noise and the tone
|
||||||
|
// together stay clear of the mixer's clipping at full volume.
|
||||||
|
#define TONE_VOLUME (AUDIO_VOLUME_MAX / 2)
|
||||||
|
#define TONE_UNTIL_STOPPED 0
|
||||||
|
// Attenuation 15 (and up) is silence (joey/audio.h).
|
||||||
|
#define ATTEN_SILENT 15u
|
||||||
|
|
||||||
|
// jlAudioNoise pitch 0..31, spread linearly over the noise rates like the
|
||||||
|
// shared curve the other ports follow (26000 - pitch * 750 Hz, whose top
|
||||||
|
// audio.lib cannot reach): from the fastest rate a tone may have down to
|
||||||
|
// that curve's slowest.
|
||||||
|
#define NOISE_PITCH_MAX 31u
|
||||||
|
#define NOISE_CURVE_TOP_HZ 26000u
|
||||||
|
#define NOISE_CURVE_STEP_HZ 750u
|
||||||
|
#define NOISE_MAX_HZ AUDIO_TONE_MAX_HZ
|
||||||
|
#define NOISE_MIN_HZ (NOISE_CURVE_TOP_HZ - NOISE_PITCH_MAX * NOISE_CURVE_STEP_HZ)
|
||||||
|
|
||||||
|
// Pinned for the timer interrupt: the tick counter's code (one small
|
||||||
|
// function; a page is as fine a grain as pinning has) and data.
|
||||||
|
#define TICK_ISR_LOCK_BYTES 4096u
|
||||||
|
|
||||||
|
// ----- Types -----
|
||||||
|
|
||||||
|
// One SFX slot: a one-shot sample or a stream (or neither), its voice and
|
||||||
|
// its sticky pan.
|
||||||
|
typedef struct {
|
||||||
|
AudioSampleT *sample;
|
||||||
|
AudioStreamT *stream;
|
||||||
|
jlAudioStreamFillT fill;
|
||||||
|
void *fillCtx;
|
||||||
|
int32_t voice;
|
||||||
|
int32_t pan;
|
||||||
|
bool streamEnded;
|
||||||
|
} SfxSlotT;
|
||||||
|
|
||||||
|
// One tone channel (the tone, a voice, the noise): an audio.lib tone that
|
||||||
|
// plays until changed, or NO_VOICE while silent.
|
||||||
|
typedef struct {
|
||||||
|
AudioWaveE wave;
|
||||||
|
int32_t voice;
|
||||||
|
} ToneChannelT;
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static AudioCtxT *audioContext(void);
|
||||||
|
static void sfxFormat(uint16_t rateHz, AudioFormatT *fmt, int32_t *pitch);
|
||||||
|
static void sfxStart(uint8_t slot, int32_t voice, uint16_t rateHz);
|
||||||
|
static void sfxStop(uint8_t slot);
|
||||||
|
static void streamEndReap(void);
|
||||||
|
static uint32_t streamFill(AudioStreamT *stream, void *buf, uint32_t frames, void *userData);
|
||||||
|
static void tickDeliver(void);
|
||||||
|
static void tickIsr(void *userData);
|
||||||
|
static void tickStop(void);
|
||||||
|
static void toneSet(ToneChannelT *channel, uint32_t hz, int32_t volume);
|
||||||
|
static int32_t voiceVolume(uint8_t atten);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static AudioCtxT *gCtx = NULL;
|
||||||
|
static MusicT *gMusic = NULL;
|
||||||
|
static SfxSlotT gSlots[JOEY_AUDIO_SFX_SLOTS];
|
||||||
|
|
||||||
|
// DVX pan for each JoeyLib pan.
|
||||||
|
static const int32_t gPanToDvx[AUDIO_PAN_COUNT] = {
|
||||||
|
[AUDIO_PAN_LEFT] = DVX_PAN_LEFT,
|
||||||
|
[AUDIO_PAN_CENTER] = DVX_PAN_CENTER,
|
||||||
|
[AUDIO_PAN_RIGHT] = DVX_PAN_RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
static ToneChannelT gTone = { AUDIO_WAVE_SQUARE, NO_VOICE };
|
||||||
|
static ToneChannelT gVoices[JOEY_AUDIO_VOICES] = {
|
||||||
|
{ AUDIO_WAVE_SQUARE, NO_VOICE },
|
||||||
|
{ AUDIO_WAVE_SQUARE, NO_VOICE },
|
||||||
|
{ AUDIO_WAVE_SQUARE, NO_VOICE }
|
||||||
|
};
|
||||||
|
static ToneChannelT gNoise = { AUDIO_WAVE_NOISE, NO_VOICE };
|
||||||
|
|
||||||
|
// Volume for an SN76489 / AGI attenuation below silence: 2 dB per step,
|
||||||
|
// AUDIO_VOLUME_MAX * 10^(-atten / 10).
|
||||||
|
static const uint16_t gAttenVolume[ATTEN_SILENT] = {
|
||||||
|
256, 203, 162, 128, 102, 81, 64, 51, 41, 32, 26, 20, 16, 13, 10
|
||||||
|
};
|
||||||
|
|
||||||
|
// The converted copy of the last one-shot's PCM (audio.lib copies it again).
|
||||||
|
static uint8_t *gPcm = NULL;
|
||||||
|
static uint32_t gPcmSize = 0;
|
||||||
|
|
||||||
|
// jlAudioTickRegister: the callback, its DVX timer and the ticks the timer
|
||||||
|
// has counted that are not delivered yet.
|
||||||
|
static jlAudioTickFnT gTickFn = NULL;
|
||||||
|
static int32_t gTickTimer = NO_TIMER;
|
||||||
|
static volatile uint32_t gTicksDue = 0;
|
||||||
|
static bool gTickPinned = false;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// The program's audio context, opened on first use: the voice layer may be
|
||||||
|
// used without jlAudioInit, as on the other ports.
|
||||||
|
static AudioCtxT *audioContext(void) {
|
||||||
|
if (gCtx == NULL) {
|
||||||
|
gCtx = audioOpen();
|
||||||
|
}
|
||||||
|
return gCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A one-shot's PCM format, and the pitch that makes a rate audio.lib cannot
|
||||||
|
// take play right: it is created at the nearest rate it takes and played
|
||||||
|
// slower or faster.
|
||||||
|
static void sfxFormat(uint16_t rateHz, AudioFormatT *fmt, int32_t *pitch) {
|
||||||
|
uint32_t rate;
|
||||||
|
|
||||||
|
rate = rateHz < AUDIO_RATE_MIN ? AUDIO_RATE_MIN : (rateHz > AUDIO_RATE_MAX ? AUDIO_RATE_MAX : rateHz);
|
||||||
|
fmt->rate = rate;
|
||||||
|
fmt->bits = SFX_BITS;
|
||||||
|
fmt->channels = SFX_CHANNELS;
|
||||||
|
*pitch = (int32_t)(AUDIO_PITCH_NORMAL * (uint32_t)rateHz / rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A slot's new voice: its pan, and its pitch for a rate audio.lib cannot
|
||||||
|
// take directly.
|
||||||
|
static void sfxStart(uint8_t slot, int32_t voice, uint16_t rateHz) {
|
||||||
|
AudioFormatT fmt;
|
||||||
|
int32_t pitch;
|
||||||
|
|
||||||
|
if (voice <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gSlots[slot].voice = voice;
|
||||||
|
sfxFormat(rateHz, &fmt, &pitch);
|
||||||
|
if (pitch != AUDIO_PITCH_NORMAL) {
|
||||||
|
(void)audioVoiceSetPitch(gCtx, voice, pitch);
|
||||||
|
}
|
||||||
|
(void)audioVoiceSetPan(gCtx, voice, gSlots[slot].pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stop a slot: its stream closed or its sample freed (either stops the
|
||||||
|
// voice).
|
||||||
|
static void sfxStop(uint8_t slot) {
|
||||||
|
SfxSlotT *s;
|
||||||
|
|
||||||
|
s = &gSlots[slot];
|
||||||
|
audioStreamClose(s->stream);
|
||||||
|
audioSampleFree(s->sample);
|
||||||
|
s->stream = NULL;
|
||||||
|
s->sample = NULL;
|
||||||
|
s->fill = NULL;
|
||||||
|
s->voice = NO_VOICE;
|
||||||
|
s->streamEnded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Close the streams whose program fill has ended, once what they queued
|
||||||
|
// has played.
|
||||||
|
static void streamEndReap(void) {
|
||||||
|
uint8_t slot;
|
||||||
|
|
||||||
|
for (slot = 0; slot < JOEY_AUDIO_SFX_SLOTS; slot++) {
|
||||||
|
if (gSlots[slot].streamEnded && audioStreamQueued(gSlots[slot].stream) == 0) {
|
||||||
|
sfxStop(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// audio.lib asks a slot's stream for PCM: the program's fill writes signed
|
||||||
|
// bytes in place, which then become unsigned. Its 0 is the end of the
|
||||||
|
// stream; audio.lib's 0 only means "nothing yet", so the slot remembers.
|
||||||
|
static uint32_t streamFill(AudioStreamT *stream, void *buf, uint32_t frames, void *userData) {
|
||||||
|
SfxSlotT *s;
|
||||||
|
uint8_t *pcm;
|
||||||
|
uint32_t got;
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
(void)stream;
|
||||||
|
s = (SfxSlotT *)userData;
|
||||||
|
if (s->streamEnded) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
got = s->fill(s->fillCtx, (int8_t *)buf, frames);
|
||||||
|
if (got == 0) {
|
||||||
|
s->streamEnded = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
got = got < frames ? got : frames;
|
||||||
|
pcm = (uint8_t *)buf;
|
||||||
|
for (i = 0; i < got; i++) {
|
||||||
|
pcm[i] ^= PCM8_SIGN_FLIP;
|
||||||
|
}
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Run the tick callback once for every tick the timer counted since last
|
||||||
|
// time.
|
||||||
|
static void tickDeliver(void) {
|
||||||
|
uint32_t state;
|
||||||
|
uint32_t ticks;
|
||||||
|
|
||||||
|
if (gTickFn == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state = platformIrqSave();
|
||||||
|
ticks = gTicksDue;
|
||||||
|
gTicksDue = 0;
|
||||||
|
platformIrqRestore(state);
|
||||||
|
while (ticks > 0 && gTickFn != NULL) {
|
||||||
|
ticks--;
|
||||||
|
gTickFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The DVX timer callback, inside the timer interrupt: count, nothing more.
|
||||||
|
static void tickIsr(void *userData) {
|
||||||
|
(void)userData;
|
||||||
|
gTicksDue++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void tickStop(void) {
|
||||||
|
if (gTickTimer != NO_TIMER) {
|
||||||
|
platformTimerRemove(gTickTimer);
|
||||||
|
gTickTimer = NO_TIMER;
|
||||||
|
}
|
||||||
|
gTickFn = NULL;
|
||||||
|
gTicksDue = 0;
|
||||||
|
if (gTickPinned) {
|
||||||
|
(void)platformUnlockRegion((const void *)tickIsr, TICK_ISR_LOCK_BYTES, true);
|
||||||
|
(void)platformUnlockRegion((const void *)&gTicksDue, sizeof(gTicksDue), false);
|
||||||
|
gTickPinned = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A tone channel at hz and volume, 0 for either being silence. A playing
|
||||||
|
// tone is changed in place; one audio.lib dropped (its voice limit) or
|
||||||
|
// never started is started.
|
||||||
|
static void toneSet(ToneChannelT *channel, uint32_t hz, int32_t volume) {
|
||||||
|
AudioCtxT *ctx;
|
||||||
|
|
||||||
|
// Silence needs no context: a channel only has a voice when there is one.
|
||||||
|
if (hz == 0 || volume == 0) {
|
||||||
|
if (channel->voice != NO_VOICE) {
|
||||||
|
(void)audioVoiceStop(gCtx, channel->voice);
|
||||||
|
channel->voice = NO_VOICE;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx = audioContext();
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hz = hz < AUDIO_TONE_MIN_HZ ? AUDIO_TONE_MIN_HZ : (hz > AUDIO_TONE_MAX_HZ ? AUDIO_TONE_MAX_HZ : hz);
|
||||||
|
if (channel->voice != NO_VOICE &&
|
||||||
|
audioVoiceSetFrequency(ctx, channel->voice, hz) == AUDIO_OK &&
|
||||||
|
audioVoiceSetVolume(ctx, channel->voice, volume) == AUDIO_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel->voice = audioTone(ctx, channel->wave, hz, volume, TONE_UNTIL_STOPPED);
|
||||||
|
if (channel->voice < 0) {
|
||||||
|
channel->voice = NO_VOICE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int32_t voiceVolume(uint8_t atten) {
|
||||||
|
if (atten >= ATTEN_SILENT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int32_t)gAttenVolume[atten] * TONE_VOLUME / AUDIO_VOLUME_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
void jlpAudioFrameTick(void) {
|
||||||
|
tickDeliver();
|
||||||
|
streamEndReap();
|
||||||
|
// Feeds the music and the streams; pumps audio.lib too.
|
||||||
|
musicPump();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpAudioInit(void) {
|
||||||
|
uint8_t slot;
|
||||||
|
|
||||||
|
for (slot = 0; slot < JOEY_AUDIO_SFX_SLOTS; slot++) {
|
||||||
|
gSlots[slot].pan = DVX_PAN_CENTER;
|
||||||
|
}
|
||||||
|
return audioContext() != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpAudioIsPlayingMod(void) {
|
||||||
|
return gMusic != NULL && musicIsPlaying(gMusic);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioNoise(uint8_t pitch, uint8_t atten) {
|
||||||
|
uint32_t hz;
|
||||||
|
|
||||||
|
hz = NOISE_MAX_HZ - (uint32_t)pitch * (NOISE_MAX_HZ - NOISE_MIN_HZ) / NOISE_PITCH_MAX;
|
||||||
|
toneSet(&gNoise, hz, voiceVolume(atten));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioPlayMod(const uint8_t *data, uint32_t length, bool loop) {
|
||||||
|
jlpAudioStopMod();
|
||||||
|
if (musicLoadMem(data, length, &gMusic) != MUSIC_OK) {
|
||||||
|
gMusic = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(void)musicPlay(gMusic, loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioPlaySfx(uint8_t slot, const uint8_t *sample, uint32_t length, uint16_t rateHz) {
|
||||||
|
AudioFormatT fmt;
|
||||||
|
int32_t pitch;
|
||||||
|
uint8_t *grown;
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
sfxStop(slot);
|
||||||
|
if (length > gPcmSize) {
|
||||||
|
grown = (uint8_t *)realloc(gPcm, length);
|
||||||
|
if (grown == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gPcm = grown;
|
||||||
|
gPcmSize = length;
|
||||||
|
}
|
||||||
|
for (i = 0; i < length; i++) {
|
||||||
|
gPcm[i] = (uint8_t)(sample[i] ^ PCM8_SIGN_FLIP);
|
||||||
|
}
|
||||||
|
sfxFormat(rateHz, &fmt, &pitch);
|
||||||
|
if (audioSampleCreate(gCtx, gPcm, length, &fmt, &gSlots[slot].sample) != AUDIO_OK) {
|
||||||
|
gSlots[slot].sample = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sfxStart(slot, audioPlay(gSlots[slot].sample, SFX_VOLUME, gSlots[slot].pan, false), rateHz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioPlaySfxStream(uint8_t slot, jlAudioStreamFillT fill, void *ctx, uint16_t rateHz) {
|
||||||
|
AudioFormatT fmt;
|
||||||
|
int32_t pitch;
|
||||||
|
SfxSlotT *s;
|
||||||
|
|
||||||
|
sfxStop(slot);
|
||||||
|
s = &gSlots[slot];
|
||||||
|
s->fill = fill;
|
||||||
|
s->fillCtx = ctx;
|
||||||
|
sfxFormat(rateHz, &fmt, &pitch);
|
||||||
|
if (audioStreamOpen(gCtx, &fmt, STREAM_BUFFER_MS, streamFill, s, &s->stream) != AUDIO_OK) {
|
||||||
|
s->stream = NULL;
|
||||||
|
s->fill = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sfxStart(slot, audioStreamVoice(s->stream), rateHz);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioSetPan(uint8_t slot, uint8_t pan) {
|
||||||
|
gSlots[slot].pan = gPanToDvx[pan];
|
||||||
|
if (gSlots[slot].voice != NO_VOICE) {
|
||||||
|
(void)audioVoiceSetPan(gCtx, gSlots[slot].voice, gSlots[slot].pan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioShutdown(void) {
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
tickStop();
|
||||||
|
jlpAudioStopMod();
|
||||||
|
for (i = 0; i < JOEY_AUDIO_SFX_SLOTS; i++) {
|
||||||
|
sfxStop(i);
|
||||||
|
}
|
||||||
|
toneSet(&gTone, 0, 0);
|
||||||
|
toneSet(&gNoise, 0, 0);
|
||||||
|
for (i = 0; i < JOEY_AUDIO_VOICES; i++) {
|
||||||
|
toneSet(&gVoices[i], 0, 0);
|
||||||
|
}
|
||||||
|
free(gPcm);
|
||||||
|
gPcm = NULL;
|
||||||
|
gPcmSize = 0;
|
||||||
|
audioClose(gCtx);
|
||||||
|
gCtx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioStopMod(void) {
|
||||||
|
musicFree(gMusic);
|
||||||
|
gMusic = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioStopSfx(uint8_t slot) {
|
||||||
|
sfxStop(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpAudioTickRegister(jlAudioTickFnT fn, uint16_t hz) {
|
||||||
|
tickStop();
|
||||||
|
if (fn == NULL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (platformLockRegion((const void *)tickIsr, TICK_ISR_LOCK_BYTES, true) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (platformLockRegion((const void *)&gTicksDue, sizeof(gTicksDue), false) != 0) {
|
||||||
|
(void)platformUnlockRegion((const void *)tickIsr, TICK_ISR_LOCK_BYTES, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gTickPinned = true;
|
||||||
|
gTickFn = fn;
|
||||||
|
gTickTimer = gameTimerAdd(gDvxPortProgram->shellCtx, hz, tickIsr, NULL);
|
||||||
|
if (gTickTimer < 0) {
|
||||||
|
gTickTimer = NO_TIMER;
|
||||||
|
tickStop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioTone(uint16_t freqHz) {
|
||||||
|
toneSet(&gTone, freqHz, TONE_VOLUME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||||
|
toneSet(&gVoices[voice], freqHz, voiceVolume(atten));
|
||||||
|
}
|
||||||
38
src/dvx/dvxApp.c
Normal file
38
src/dvx/dvxApp.c
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// The DVX entry of a JoeyLib program: the .app descriptor DVX reads when it
|
||||||
|
// loads the module, and appMain, which DVX runs in the program's own task.
|
||||||
|
//
|
||||||
|
// Not part of libjoey.a: make/dvx.mk compiles this file into every program,
|
||||||
|
// because the descriptor carries the program's name (DVX_PORT_APP_NAME) and
|
||||||
|
// the linker would never pull an entry point nothing calls out of an
|
||||||
|
// archive. Everything else is in the library (program.c).
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
#ifndef DVX_PORT_APP_NAME
|
||||||
|
#error "make/dvx.mk passes -DDVX_PORT_APP_NAME=\"Name\" for each program"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
int32_t appMain(DxeAppContextT *ctx);
|
||||||
|
|
||||||
|
// ----- App descriptor -----
|
||||||
|
|
||||||
|
// A main-loop program: DVX gives it a task of its own, in which the JoeyLib
|
||||||
|
// program's main runs its frame loop, yielding in jlWaitVBL.
|
||||||
|
SHELL_APP_DESCRIPTOR(
|
||||||
|
.name = DVX_PORT_APP_NAME,
|
||||||
|
.hasMainLoop = true,
|
||||||
|
.multiInstance = false,
|
||||||
|
.stackSize = DVX_PORT_STACK_BYTES,
|
||||||
|
.priority = TS_PRIORITY_NORMAL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
int32_t appMain(DxeAppContextT *ctx) {
|
||||||
|
return dvxPortRun(ctx);
|
||||||
|
}
|
||||||
61
src/dvx/dvxPort.h
Normal file
61
src/dvx/dvxPort.h
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Shared by the files of the DVX port (src/dvx). Include after dvxSdk.h.
|
||||||
|
//
|
||||||
|
// A JoeyLib program on DVX is a DVX main-loop program (an .app module):
|
||||||
|
// dvxApp.c, compiled into each program, is its entry; program.c runs the
|
||||||
|
// program's main and ends it; hal.c owns its window (the stage at a whole
|
||||||
|
// scale), the frame clock and the present; input.c the keyboard, mouse and
|
||||||
|
// joysticks; audio.c, save.c and serial.c the add-on services. Everything
|
||||||
|
// here is named dvxPort* so nothing collides with DVX's own dvx* names.
|
||||||
|
#ifndef JOEY_DVX_PORT_H
|
||||||
|
#define JOEY_DVX_PORT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// The program's task stack. DVX's default (32 KB) is far below what a DOS
|
||||||
|
// program gets, and JoeyLib programs keep sizable buffers on the stack.
|
||||||
|
#define DVX_PORT_STACK_BYTES (256 * 1024)
|
||||||
|
|
||||||
|
// ----- dvxApp.c -----
|
||||||
|
|
||||||
|
// The program's descriptor; its name titles the window.
|
||||||
|
extern AppDescriptorT appDescriptor;
|
||||||
|
|
||||||
|
// ----- program.c -----
|
||||||
|
|
||||||
|
// The running program's DVX context (appMain's argument).
|
||||||
|
extern DxeAppContextT *gDvxPortProgram;
|
||||||
|
|
||||||
|
// The program's atexit() and exit(): make/dvx.mk renames the calls in the
|
||||||
|
// library and the program to these. exit() returns to dvxPortRun, so the
|
||||||
|
// handlers run, and JoeyLib shuts down and closes its log, when the program
|
||||||
|
// ends, however it ends, and appMain returns as DVX expects. DVX's own
|
||||||
|
// atexit would keep the handlers until DVX itself exits, long after the
|
||||||
|
// program's code is gone.
|
||||||
|
int dvxPortAtExit(void (*fn)(void));
|
||||||
|
void dvxPortExit(int status) __attribute__((noreturn));
|
||||||
|
|
||||||
|
// appMain's body: run the program's main, then end the program; returns
|
||||||
|
// main's status.
|
||||||
|
int32_t dvxPortRun(DxeAppContextT *ctx);
|
||||||
|
|
||||||
|
// ----- hal.c -----
|
||||||
|
|
||||||
|
extern WindowT *gDvxPortWindow; // the program's window; NULL outside jlInit..jlShutdown
|
||||||
|
extern int32_t gDvxPortScale; // window pixels per stage pixel, each way
|
||||||
|
|
||||||
|
// End the program now if the user closed its window (the close box,
|
||||||
|
// Alt+F4, Game > Exit).
|
||||||
|
void dvxPortEndIfClosed(void);
|
||||||
|
|
||||||
|
// Called where a JoeyLib program comes back to the port every frame (the
|
||||||
|
// input poll, the present): yield to the desktop if the program has not
|
||||||
|
// this virtual frame, then dvxPortEndIfClosed.
|
||||||
|
void dvxPortService(void);
|
||||||
|
|
||||||
|
// ----- input.c: the window's input callbacks, installed by hal.c -----
|
||||||
|
|
||||||
|
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||||
|
void dvxPortOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||||
|
|
||||||
|
#endif
|
||||||
53
src/dvx/dvxSdk.h
Normal file
53
src/dvx/dvxSdk.h
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// The DVX SDK headers the DVX port uses, made safe to mix with JoeyLib's.
|
||||||
|
// Include it FIRST in every src/dvx file, before any JoeyLib header.
|
||||||
|
//
|
||||||
|
// The SDK #defines names JoeyLib uses as enum constants: KEY_ESCAPE, KEY_F1,
|
||||||
|
// KEY_UP ... (jlKeyE, joey/input.h) and AUDIO_PAN_LEFT / CENTER / RIGHT
|
||||||
|
// (jlAudioPanE, joey/audio.h), with DVX's own values. The port needs none of
|
||||||
|
// the SDK's key macros; its pan values are kept as the DVX_PAN_* constants
|
||||||
|
// below. All of them are then #undef-ed, so after this header every one of
|
||||||
|
// those names is JoeyLib's.
|
||||||
|
#ifndef JOEY_DVX_SDK_H
|
||||||
|
#define JOEY_DVX_SDK_H
|
||||||
|
|
||||||
|
#include "dvxApp.h"
|
||||||
|
#include "dvxPal.h"
|
||||||
|
#include "dvxPlat.h"
|
||||||
|
#include "dvxVideo.h"
|
||||||
|
#include "dvxWm.h"
|
||||||
|
#include "shellApp.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "joystick.h"
|
||||||
|
#include "music.h"
|
||||||
|
|
||||||
|
// DVX's pan range, taken while its macros still stand.
|
||||||
|
enum {
|
||||||
|
DVX_PAN_LEFT = AUDIO_PAN_LEFT,
|
||||||
|
DVX_PAN_CENTER = AUDIO_PAN_CENTER,
|
||||||
|
DVX_PAN_RIGHT = AUDIO_PAN_RIGHT
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef AUDIO_PAN_CENTER
|
||||||
|
#undef AUDIO_PAN_LEFT
|
||||||
|
#undef AUDIO_PAN_RIGHT
|
||||||
|
#undef KEY_BACKSPACE
|
||||||
|
#undef KEY_DOWN
|
||||||
|
#undef KEY_ESCAPE
|
||||||
|
#undef KEY_F1
|
||||||
|
#undef KEY_F10
|
||||||
|
#undef KEY_F2
|
||||||
|
#undef KEY_F3
|
||||||
|
#undef KEY_F4
|
||||||
|
#undef KEY_F5
|
||||||
|
#undef KEY_F6
|
||||||
|
#undef KEY_F7
|
||||||
|
#undef KEY_F8
|
||||||
|
#undef KEY_F9
|
||||||
|
#undef KEY_LEFT
|
||||||
|
#undef KEY_RIGHT
|
||||||
|
#undef KEY_SPACE
|
||||||
|
#undef KEY_TAB
|
||||||
|
#undef KEY_UP
|
||||||
|
|
||||||
|
#endif
|
||||||
487
src/dvx/hal.c
Normal file
487
src/dvx/hal.c
Normal file
|
|
@ -0,0 +1,487 @@
|
||||||
|
// DVX HAL: the stage in a window, the frame clock and the present.
|
||||||
|
//
|
||||||
|
// jlpInit opens the program's window: a game input window (every key but
|
||||||
|
// Alt+Tab, Ctrl+Esc and Alt+F4 comes to the program) whose content area is
|
||||||
|
// the 320x200 stage at the largest whole scale up to DVX_SCALE_MAX that fits
|
||||||
|
// the desktop, under a Game menu (Help, Exit). Closing the window by any of
|
||||||
|
// those routes ends the program at its next frame (dvxPortEndIfClosed).
|
||||||
|
//
|
||||||
|
// The window's content buffer is in the desktop's own pixel format (8, 15,
|
||||||
|
// 16 or 32 bits), so the present expands the stage's changed rows through
|
||||||
|
// one table per palette: the desktop pixel for each of its 16 entries, the
|
||||||
|
// $0RGB channels scaled to 8 bits and packed (packColor), or on a
|
||||||
|
// 256-colour desktop the nearest desktop colour (gamePalNearest: that
|
||||||
|
// palette belongs to the whole desktop). Each row takes the table its SCB
|
||||||
|
// picks, so SCB palettes are exact at 15 bits and up. A palette change
|
||||||
|
// rebuilds its table and expands every row using it again; an SCB change,
|
||||||
|
// or the user changing the video mode while the program runs, expands every
|
||||||
|
// row. Runs of consecutive changed rows go to DVX as one rectangle each.
|
||||||
|
//
|
||||||
|
// Timing: a window has no vertical blank, so the frame rate is a virtual
|
||||||
|
// one, DVX_FRAME_HZ -- the DOS VGA rate, so programs, and the UBER timings,
|
||||||
|
// behave as on DOS. One clock (DVX's platformMicros, from the program's first
|
||||||
|
// question) gives the milliseconds and the frame count, and jlWaitVBL waits
|
||||||
|
// for that clock's next frame boundary with gameFrameWait, giving the time to
|
||||||
|
// the other programs meanwhile.
|
||||||
|
//
|
||||||
|
// DVX multitasks cooperatively: the desktop -- the other programs, this
|
||||||
|
// program's window events (typed keys, the mouse, the close box) and the
|
||||||
|
// compositing of its window -- runs only while the program yields. A JoeyLib
|
||||||
|
// program need not call jlWaitVBL (plenty loop on jlInputPoll alone, as a
|
||||||
|
// DOS program may), so the input poll and the present yield too, at most
|
||||||
|
// once a virtual frame (dvxPortService), which keeps their cost low.
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "port.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define DVX_FRAME_HZ 70u
|
||||||
|
#define DVX_SCALE_MAX 3
|
||||||
|
#define US_PER_MS 1000u
|
||||||
|
|
||||||
|
// $0RGB: 4 bits per channel, widened to 8 by x17 (0xF -> 0xFF).
|
||||||
|
#define RGB12_RED_SHIFT 8
|
||||||
|
#define RGB12_GREEN_SHIFT 4
|
||||||
|
#define RGB12_CHANNEL_MASK 0x0Fu
|
||||||
|
#define RGB12_TO_RGB24 17u
|
||||||
|
|
||||||
|
// Chunky stage layout: two pixels per byte, high nibble left.
|
||||||
|
#define STAGE_PIXELS_PER_BYTE 2
|
||||||
|
#define STAGE_PIXELS_PER_WORD (SURFACE_WIDTH / SURFACE_WORDS_PER_ROW)
|
||||||
|
#define NIBBLE_BITS 4
|
||||||
|
#define NIBBLE_MASK 0x0Fu
|
||||||
|
|
||||||
|
// Row masks for presentRows: bit p = every row using palette p.
|
||||||
|
#define PALETTES_NONE 0x0000u
|
||||||
|
#define PALETTES_ALL 0xFFFFu
|
||||||
|
|
||||||
|
// Game menu commands.
|
||||||
|
#define CMD_GAME_HELP 100
|
||||||
|
#define CMD_GAME_EXIT 101
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static uint64_t clockFrame(void);
|
||||||
|
static uint64_t clockMicros(void);
|
||||||
|
static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1);
|
||||||
|
static void lutBuild(const jlSurfaceT *src, uint16_t palettes);
|
||||||
|
static bool lutSync(const jlSurfaceT *src);
|
||||||
|
static void onClose(WindowT *win);
|
||||||
|
static void onMenu(WindowT *win, int32_t menuId);
|
||||||
|
static void onPaint(WindowT *win, RectT *dirty);
|
||||||
|
static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidate);
|
||||||
|
static bool windowCreate(AppContextT *ac);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
WindowT *gDvxPortWindow = NULL;
|
||||||
|
int32_t gDvxPortScale = 1;
|
||||||
|
|
||||||
|
static GameFramePacerT gPacer;
|
||||||
|
static bool gClockStarted = false;
|
||||||
|
static bool gCloseRequested = false;
|
||||||
|
static uint64_t gYieldFrame = 0; // the frame the program last yielded in
|
||||||
|
|
||||||
|
// Desktop pixel per palette entry, and what the tables were built for: the
|
||||||
|
// desktop's bits per pixel (0 = not built) and, at 8 bits, its palette.
|
||||||
|
static uint32_t gLut[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||||
|
static int32_t gLutBits = 0;
|
||||||
|
static uint8_t gLutDesktopPalette[PAL_ENTRIES * PAL_CHANNELS];
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// The virtual frame now running: frame n starts n / DVX_FRAME_HZ seconds
|
||||||
|
// after the clock's zero.
|
||||||
|
static uint64_t clockFrame(void) {
|
||||||
|
return clockMicros() * DVX_FRAME_HZ / PLATFORM_US_PER_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Microseconds since the program first asked for the time. The pacer's
|
||||||
|
// start is the clock's zero, so the frame count and jlWaitVBL agree.
|
||||||
|
static uint64_t clockMicros(void) {
|
||||||
|
if (!gClockStarted) {
|
||||||
|
gameFrameInit(&gPacer, DVX_FRAME_HZ);
|
||||||
|
gClockStarted = true;
|
||||||
|
}
|
||||||
|
return platformMicros() - gPacer.startUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stage row y, pixels x0..x1-1 (both even), into the content buffer at the
|
||||||
|
// window's scale: each pixel repeated across, the row repeated down.
|
||||||
|
static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1) {
|
||||||
|
const uint32_t *lut;
|
||||||
|
const uint8_t *in;
|
||||||
|
uint8_t *row;
|
||||||
|
int32_t scale;
|
||||||
|
int32_t bytesPerPixel;
|
||||||
|
int32_t pitch;
|
||||||
|
int32_t pairs;
|
||||||
|
int32_t i;
|
||||||
|
int32_t s;
|
||||||
|
|
||||||
|
lut = gLut[src->scb[y]];
|
||||||
|
in = &src->pixels[y * SURFACE_BYTES_PER_ROW + x0 / STAGE_PIXELS_PER_BYTE];
|
||||||
|
scale = gDvxPortScale;
|
||||||
|
bytesPerPixel = dvxGetDisplay(gDvxPortProgram->shellCtx)->format.bytesPerPixel;
|
||||||
|
pitch = gDvxPortWindow->contentPitch;
|
||||||
|
row = gDvxPortWindow->contentBuf + y * scale * pitch + x0 * scale * bytesPerPixel;
|
||||||
|
pairs = (x1 - x0) / STAGE_PIXELS_PER_BYTE;
|
||||||
|
|
||||||
|
switch (bytesPerPixel) {
|
||||||
|
case 1: {
|
||||||
|
uint8_t *d = row;
|
||||||
|
|
||||||
|
for (i = 0; i < pairs; i++) {
|
||||||
|
memset(d, (int)lut[in[i] >> NIBBLE_BITS], (size_t)scale);
|
||||||
|
d += scale;
|
||||||
|
memset(d, (int)lut[in[i] & NIBBLE_MASK], (size_t)scale);
|
||||||
|
d += scale;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
uint16_t *d = (uint16_t *)row;
|
||||||
|
uint16_t left;
|
||||||
|
uint16_t right;
|
||||||
|
|
||||||
|
for (i = 0; i < pairs; i++) {
|
||||||
|
left = (uint16_t)lut[in[i] >> NIBBLE_BITS];
|
||||||
|
right = (uint16_t)lut[in[i] & NIBBLE_MASK];
|
||||||
|
for (s = 0; s < scale; s++) {
|
||||||
|
*d++ = left;
|
||||||
|
}
|
||||||
|
for (s = 0; s < scale; s++) {
|
||||||
|
*d++ = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
uint32_t *d = (uint32_t *)row;
|
||||||
|
uint32_t left;
|
||||||
|
uint32_t right;
|
||||||
|
|
||||||
|
for (i = 0; i < pairs; i++) {
|
||||||
|
left = lut[in[i] >> NIBBLE_BITS];
|
||||||
|
right = lut[in[i] & NIBBLE_MASK];
|
||||||
|
for (s = 0; s < scale; s++) {
|
||||||
|
*d++ = left;
|
||||||
|
}
|
||||||
|
for (s = 0; s < scale; s++) {
|
||||||
|
*d++ = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (s = 1; s < scale; s++) {
|
||||||
|
memcpy(row + s * pitch, row, (size_t)((x1 - x0) * scale * bytesPerPixel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rebuild the tables of the palettes whose bits are set.
|
||||||
|
static void lutBuild(const jlSurfaceT *src, uint16_t palettes) {
|
||||||
|
const DisplayT *d;
|
||||||
|
uint16_t rgb;
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
uint8_t p;
|
||||||
|
uint8_t c;
|
||||||
|
|
||||||
|
d = dvxGetDisplay(gDvxPortProgram->shellCtx);
|
||||||
|
for (p = 0; p < SURFACE_PALETTE_COUNT; p++) {
|
||||||
|
if ((palettes & (1u << p)) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (c = 0; c < SURFACE_COLORS_PER_PALETTE; c++) {
|
||||||
|
rgb = src->palette[p][c];
|
||||||
|
r = (uint8_t)(((rgb >> RGB12_RED_SHIFT) & RGB12_CHANNEL_MASK) * RGB12_TO_RGB24);
|
||||||
|
g = (uint8_t)(((rgb >> RGB12_GREEN_SHIFT) & RGB12_CHANNEL_MASK) * RGB12_TO_RGB24);
|
||||||
|
b = (uint8_t)((rgb & RGB12_CHANNEL_MASK) * RGB12_TO_RGB24);
|
||||||
|
gLut[p][c] = d->palette != NULL ? gamePalNearest(d->palette, r, g, b) : packColor(d, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rebuild every table when the desktop's pixel format, or its 256-colour
|
||||||
|
// palette, is not the one they were built for (the user can change the
|
||||||
|
// video mode while the program runs). True when it did: every row must be
|
||||||
|
// expanded again.
|
||||||
|
static bool lutSync(const jlSurfaceT *src) {
|
||||||
|
const DisplayT *d;
|
||||||
|
|
||||||
|
d = dvxGetDisplay(gDvxPortProgram->shellCtx);
|
||||||
|
if (d->format.bitsPerPixel == gLutBits && (d->palette == NULL || memcmp(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette)) == 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gLutBits = d->format.bitsPerPixel;
|
||||||
|
if (d->palette != NULL) {
|
||||||
|
memcpy(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette));
|
||||||
|
}
|
||||||
|
lutBuild(src, PALETTES_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onClose(WindowT *win) {
|
||||||
|
(void)win;
|
||||||
|
gCloseRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onMenu(WindowT *win, int32_t menuId) {
|
||||||
|
(void)win;
|
||||||
|
switch (menuId) {
|
||||||
|
case CMD_GAME_HELP:
|
||||||
|
shellOpenHelp(gDvxPortProgram->shellCtx, gDvxPortProgram->helpFile);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_GAME_EXIT:
|
||||||
|
gCloseRequested = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DVX wants the whole content buffer (the window's first showing, a video
|
||||||
|
// mode change): expand the whole stage, which DVX then puts on the screen.
|
||||||
|
static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
|
(void)win;
|
||||||
|
(void)dirty;
|
||||||
|
if (gStage == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(void)lutSync(gStage);
|
||||||
|
presentRows(gStage, PALETTES_ALL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Expand every row that is dirty, or whose palette bit is set in palettes,
|
||||||
|
// into the content buffer; with invalidate, tell DVX about each run of
|
||||||
|
// consecutive expanded rows as one rectangle.
|
||||||
|
static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidate) {
|
||||||
|
AppContextT *ac;
|
||||||
|
int32_t scale;
|
||||||
|
int32_t rows;
|
||||||
|
int32_t cols;
|
||||||
|
int32_t y;
|
||||||
|
int32_t x0;
|
||||||
|
int32_t x1;
|
||||||
|
int32_t runY0;
|
||||||
|
int32_t runX0;
|
||||||
|
int32_t runX1;
|
||||||
|
uint8_t maxWord;
|
||||||
|
|
||||||
|
if (gDvxPortWindow == NULL || gDvxPortWindow->contentBuf == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ac = gDvxPortProgram->shellCtx;
|
||||||
|
scale = gDvxPortScale;
|
||||||
|
// Clip to the content area (it is the scaled stage unless DVX sized the
|
||||||
|
// window otherwise); whole pixel pairs only.
|
||||||
|
rows = gDvxPortWindow->contentH / scale;
|
||||||
|
cols = gDvxPortWindow->contentW / scale;
|
||||||
|
rows = rows < SURFACE_HEIGHT ? rows : SURFACE_HEIGHT;
|
||||||
|
cols = (cols < SURFACE_WIDTH ? cols : SURFACE_WIDTH) & ~(STAGE_PIXELS_PER_BYTE - 1);
|
||||||
|
runY0 = -1;
|
||||||
|
runX0 = 0;
|
||||||
|
runX1 = 0;
|
||||||
|
for (y = 0; y < rows; y++) {
|
||||||
|
if ((palettes & (1u << src->scb[y])) != 0) {
|
||||||
|
x0 = 0;
|
||||||
|
x1 = SURFACE_WIDTH;
|
||||||
|
} else if (STAGE_DIRTY_ROW_CLEAN(y)) {
|
||||||
|
if (runY0 >= 0 && invalidate) {
|
||||||
|
dvxInvalidateRect(ac, gDvxPortWindow, runX0 * scale, runY0 * scale, (runX1 - runX0) * scale, (y - runY0) * scale);
|
||||||
|
}
|
||||||
|
runY0 = -1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// Clamp a band past the row end, as the DOS present does.
|
||||||
|
maxWord = gStageMaxWord[y] < SURFACE_WORDS_PER_ROW ? gStageMaxWord[y] : (uint8_t)(SURFACE_WORDS_PER_ROW - 1);
|
||||||
|
x0 = gStageMinWord[y] * STAGE_PIXELS_PER_WORD;
|
||||||
|
x1 = (maxWord + 1) * STAGE_PIXELS_PER_WORD;
|
||||||
|
}
|
||||||
|
x1 = x1 < cols ? x1 : cols;
|
||||||
|
if (x0 >= x1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
expandRow(src, y, x0, x1);
|
||||||
|
gPresentCopiedBytes += (uint32_t)(x1 - x0);
|
||||||
|
if (runY0 < 0) {
|
||||||
|
runY0 = y;
|
||||||
|
runX0 = x0;
|
||||||
|
runX1 = x1;
|
||||||
|
} else {
|
||||||
|
runX0 = x0 < runX0 ? x0 : runX0;
|
||||||
|
runX1 = x1 > runX1 ? x1 : runX1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (runY0 >= 0 && invalidate) {
|
||||||
|
dvxInvalidateRect(ac, gDvxPortWindow, runX0 * scale, runY0 * scale, (runX1 - runX0) * scale, (y - runY0) * scale);
|
||||||
|
}
|
||||||
|
gPresentScannedRows += SURFACE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The window: its content area the stage at the largest whole scale that
|
||||||
|
// fits the desktop (up to DVX_SCALE_MAX), not resizable, under a Game menu.
|
||||||
|
static bool windowCreate(AppContextT *ac) {
|
||||||
|
int32_t frameW;
|
||||||
|
int32_t frameH;
|
||||||
|
int32_t scaleW;
|
||||||
|
int32_t scaleH;
|
||||||
|
int32_t wantW;
|
||||||
|
int32_t wantH;
|
||||||
|
WindowT *win;
|
||||||
|
MenuBarT *bar;
|
||||||
|
MenuT *game;
|
||||||
|
|
||||||
|
frameW = 2 * CHROME_TOTAL_SIDE;
|
||||||
|
frameH = CHROME_TOTAL_TOP + CHROME_MENU_HEIGHT + CHROME_TOTAL_BOTTOM;
|
||||||
|
scaleW = (ac->display.width - frameW) / SURFACE_WIDTH;
|
||||||
|
scaleH = (ac->display.height - frameH) / SURFACE_HEIGHT;
|
||||||
|
gDvxPortScale = scaleW < scaleH ? scaleW : scaleH;
|
||||||
|
gDvxPortScale = gDvxPortScale < 1 ? 1 : (gDvxPortScale > DVX_SCALE_MAX ? DVX_SCALE_MAX : gDvxPortScale);
|
||||||
|
wantW = SURFACE_WIDTH * gDvxPortScale;
|
||||||
|
wantH = SURFACE_HEIGHT * gDvxPortScale;
|
||||||
|
|
||||||
|
win = dvxCreateWindowCentered(ac, appDescriptor.name, wantW + frameW, wantH + frameH, false);
|
||||||
|
if (win == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
win->gameInput = true;
|
||||||
|
win->onClose = onClose;
|
||||||
|
win->onKey = dvxPortOnKey;
|
||||||
|
win->onMenu = onMenu;
|
||||||
|
win->onMouse = dvxPortOnMouse;
|
||||||
|
win->onPaint = onPaint;
|
||||||
|
|
||||||
|
bar = wmAddMenuBar(win);
|
||||||
|
game = bar != NULL ? wmAddMenu(bar, "&Game") : NULL;
|
||||||
|
if (game != NULL) {
|
||||||
|
wmAddMenuItem(game, "&Help", CMD_GAME_HELP);
|
||||||
|
wmAddMenuSeparator(game);
|
||||||
|
wmAddMenuItem(game, "E&xit", CMD_GAME_EXIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The frame was sized from the chrome constants; make the content area
|
||||||
|
// exactly the scaled stage whatever the menu bar took.
|
||||||
|
if (win->contentW != wantW || win->contentH != wantH) {
|
||||||
|
dvxResizeWindow(ac, win, win->w + wantW - win->contentW, win->h + wantH - win->contentH);
|
||||||
|
}
|
||||||
|
gDvxPortWindow = win;
|
||||||
|
dvxLog("JoeyLib: %s: %" PRId32 "x%" PRId32 " at %" PRId32 "x on a %" PRId32 "x%" PRId32 " %" PRId32 "-bit desktop", appDescriptor.name, win->contentW, win->contentH, gDvxPortScale, ac->display.width, ac->display.height, ac->display.format.bitsPerPixel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Port API (alphabetical) -----
|
||||||
|
|
||||||
|
void dvxPortEndIfClosed(void) {
|
||||||
|
if (gCloseRequested) {
|
||||||
|
dvxPortExit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dvxPortService(void) {
|
||||||
|
uint64_t frame;
|
||||||
|
|
||||||
|
frame = clockFrame();
|
||||||
|
if (frame != gYieldFrame) {
|
||||||
|
gYieldFrame = frame;
|
||||||
|
tsYield();
|
||||||
|
}
|
||||||
|
dvxPortEndIfClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
uint16_t jlpFrameCount(void) {
|
||||||
|
return (uint16_t)clockFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t jlpFrameHz(void) {
|
||||||
|
return DVX_FRAME_HZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpInit(const jlConfigT *config) {
|
||||||
|
(void)config;
|
||||||
|
gCloseRequested = false;
|
||||||
|
gLutBits = 0;
|
||||||
|
(void)clockMicros();
|
||||||
|
return windowCreate(gDvxPortProgram->shellCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t jlpMillisElapsed(void) {
|
||||||
|
return (uint32_t)(clockMicros() / US_PER_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpPresent(const jlSurfaceT *src) {
|
||||||
|
uint16_t palettes;
|
||||||
|
|
||||||
|
if (src == NULL || gDvxPortWindow == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
palettes = PALETTES_NONE;
|
||||||
|
if (lutSync(src)) {
|
||||||
|
palettes = PALETTES_ALL;
|
||||||
|
} else if (gStagePaletteDirty) {
|
||||||
|
lutBuild(src, gStagePaletteDirtyMask);
|
||||||
|
palettes = gStagePaletteDirtyMask;
|
||||||
|
}
|
||||||
|
// An SCB change moves rows to other tables: expand them all again.
|
||||||
|
if (gStageScbDirty) {
|
||||||
|
palettes = PALETTES_ALL;
|
||||||
|
}
|
||||||
|
gStagePaletteDirty = false;
|
||||||
|
gStagePaletteDirtyMask = PALETTES_NONE;
|
||||||
|
gStageScbDirty = false;
|
||||||
|
presentRows(src, palettes, true);
|
||||||
|
dvxPortService();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpShutdown(void) {
|
||||||
|
if (gDvxPortWindow != NULL) {
|
||||||
|
dvxDestroyWindow(gDvxPortProgram->shellCtx, gDvxPortWindow);
|
||||||
|
gDvxPortWindow = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Block until the clock's next frame boundary, the other programs running
|
||||||
|
// meanwhile. Re-aiming the pacer at the frame now running makes every call
|
||||||
|
// wait for the NEXT boundary, as a vertical blank wait does, instead of the
|
||||||
|
// pacer's own catch-up rule.
|
||||||
|
void jlpWaitVBL(void) {
|
||||||
|
gPacer.frame = clockFrame();
|
||||||
|
(void)gameFrameWait(&gPacer);
|
||||||
|
gYieldFrame = gPacer.frame;
|
||||||
|
dvxPortEndIfClosed();
|
||||||
|
}
|
||||||
205
src/dvx/input.c
Normal file
205
src/dvx/input.c
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
// DVX input: keyboard, typed characters, mouse and joysticks.
|
||||||
|
//
|
||||||
|
// Keyboard: DVX keeps a table of held keys by raw PC scan code (set 1, plus
|
||||||
|
// KEY_RAW_EXT for the keys sent behind 0xE0) from its INT 9 chain. The poll
|
||||||
|
// copies it once and maps it through the set-1 table shared with DOS
|
||||||
|
// (src/x86/scanSet1.h); a key is down when either of its codes is, so the
|
||||||
|
// grey arrows are the arrows and right Ctrl is Ctrl, as on DOS. Every key
|
||||||
|
// reads up while the window does not have the focus.
|
||||||
|
//
|
||||||
|
// Typed characters come from the window's onKey, with Shift and the DVX
|
||||||
|
// keyboard layout already applied, and go straight into the core's queue:
|
||||||
|
// the callback runs in DVX's own task while the program is yielded, never
|
||||||
|
// in an interrupt, so nothing can race the program's jlInputGetChar. Ctrl
|
||||||
|
// and Alt chords are commands, not text, as on DOS.
|
||||||
|
//
|
||||||
|
// Mouse: onMouse reports content-area pixels; they are divided by the scale
|
||||||
|
// and clamped to the stage, and published by the next poll, so the core's
|
||||||
|
// button edges compare two stable snapshots.
|
||||||
|
//
|
||||||
|
// Joysticks: DVX's joystick library, calibrated in DVX's Control Panel:
|
||||||
|
// axes -32768..32767 scaled to -127..127, real analog where DOS reads the
|
||||||
|
// BIOS digitally. DVX's keyboard joystick (keys standing in when no
|
||||||
|
// joystick is plugged in) is left out: JoeyLib then reports no joystick, as
|
||||||
|
// every port does, and programs read those keys themselves.
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "port.h"
|
||||||
|
#include "inputInternal.h"
|
||||||
|
#include "scanSet1.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
// DVX axes (-32768..32767) to JoeyLib's (-127..127): >> 8, then -128 up to -127.
|
||||||
|
#define JOY_AXIS_SHIFT 8
|
||||||
|
#define JOY_AXIS_X 0
|
||||||
|
#define JOY_AXIS_Y 1
|
||||||
|
#define JOY_NO_HANDLE (-1)
|
||||||
|
|
||||||
|
// Modifiers that make a key a command rather than text.
|
||||||
|
#define CHORD_MODIFIERS (KEY_MOD_CTRL | KEY_MOD_ALT)
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static int8_t joyAxis(int32_t value);
|
||||||
|
static void joystickClose(void);
|
||||||
|
static void joystickOpen(void);
|
||||||
|
static void joystickPoll(void);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static int32_t gJoyHandle[JOYSTICK_COUNT];
|
||||||
|
static int16_t gNextMouseX = 0;
|
||||||
|
static int16_t gNextMouseY = 0;
|
||||||
|
static int32_t gNextMouseButtons = 0;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
static int8_t joyAxis(int32_t value) {
|
||||||
|
value >>= JOY_AXIS_SHIFT;
|
||||||
|
return (int8_t)(value < JOYSTICK_AXIS_MIN ? JOYSTICK_AXIS_MIN : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void joystickClose(void) {
|
||||||
|
uint8_t js;
|
||||||
|
|
||||||
|
for (js = 0; js < JOYSTICK_COUNT; js++) {
|
||||||
|
if (gJoyHandle[js] != JOY_NO_HANDLE) {
|
||||||
|
joyClose(gJoyHandle[js]);
|
||||||
|
gJoyHandle[js] = JOY_NO_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// JoeyLib's two sticks are DVX's first two devices that are real
|
||||||
|
// joysticks, not the keyboard joystick.
|
||||||
|
static void joystickOpen(void) {
|
||||||
|
JoyInfoT info;
|
||||||
|
int32_t count;
|
||||||
|
int32_t device;
|
||||||
|
uint8_t js;
|
||||||
|
|
||||||
|
for (js = 0; js < JOYSTICK_COUNT; js++) {
|
||||||
|
gJoyHandle[js] = JOY_NO_HANDLE;
|
||||||
|
}
|
||||||
|
count = joyCount();
|
||||||
|
js = 0;
|
||||||
|
for (device = 0; device < count && js < JOYSTICK_COUNT; device++) {
|
||||||
|
if (!joyGetInfo(device, &info) || (info.caps & JOY_CAP_KEYBOARD) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gJoyHandle[js] = joyOpen(gDvxPortProgram->shellCtx, device);
|
||||||
|
if (gJoyHandle[js] != JOY_NO_HANDLE) {
|
||||||
|
js++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void joystickPoll(void) {
|
||||||
|
JoyStateT st;
|
||||||
|
uint8_t js;
|
||||||
|
uint8_t b;
|
||||||
|
|
||||||
|
for (js = 0; js < JOYSTICK_COUNT; js++) {
|
||||||
|
if (gJoyHandle[js] == JOY_NO_HANDLE || !joyRead(gJoyHandle[js], &st) || !st.connected) {
|
||||||
|
gJoyConnected[js] = false;
|
||||||
|
gJoyAxisX[js] = 0;
|
||||||
|
gJoyAxisY[js] = 0;
|
||||||
|
memset(gJoyButtonState[js], 0, sizeof(gJoyButtonState[js]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gJoyConnected[js] = true;
|
||||||
|
gJoyAxisX[js] = joyAxis(st.axes[JOY_AXIS_X]);
|
||||||
|
gJoyAxisY[js] = joyAxis(st.axes[JOY_AXIS_Y]);
|
||||||
|
for (b = 0; b < JOY_BUTTON_COUNT; b++) {
|
||||||
|
gJoyButtonState[js][b] = (st.buttons & (1u << b)) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Port API (alphabetical) -----
|
||||||
|
|
||||||
|
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
|
(void)win;
|
||||||
|
if ((mod & CHORD_MODIFIERS) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Forward delete is DVX's extended Delete key; every other character is
|
||||||
|
// its own code, and jlInputCharPush drops what JoeyLib does not carry.
|
||||||
|
if (key == KEY_DELETE) {
|
||||||
|
jlInputCharPush(JL_CHAR_DELETE);
|
||||||
|
} else if (key < KEY_EXT_FLAG) {
|
||||||
|
jlInputCharPush((uint8_t)key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dvxPortOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
|
(void)win;
|
||||||
|
x /= gDvxPortScale;
|
||||||
|
y /= gDvxPortScale;
|
||||||
|
gNextMouseX = (int16_t)(x < 0 ? 0 : (x >= SURFACE_WIDTH ? SURFACE_WIDTH - 1 : x));
|
||||||
|
gNextMouseY = (int16_t)(y < 0 ? 0 : (y >= SURFACE_HEIGHT ? SURFACE_HEIGHT - 1 : y));
|
||||||
|
gNextMouseButtons = buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
void jlpInputInit(void) {
|
||||||
|
memset(gKeyState, 0, sizeof(gKeyState));
|
||||||
|
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||||
|
// DVX always has a mouse; it starts in the middle of the stage.
|
||||||
|
gNextMouseX = SURFACE_WIDTH / 2;
|
||||||
|
gNextMouseY = SURFACE_HEIGHT / 2;
|
||||||
|
gNextMouseButtons = 0;
|
||||||
|
gMouseAttached = true;
|
||||||
|
joystickOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpInputPoll(void) {
|
||||||
|
uint8_t held[KEY_RAW_COUNT];
|
||||||
|
uint8_t scan;
|
||||||
|
uint8_t key;
|
||||||
|
|
||||||
|
dvxPortService();
|
||||||
|
if (gDvxPortWindow != NULL && gDvxPortWindow->focused) {
|
||||||
|
platformKeyStateGet(held);
|
||||||
|
} else {
|
||||||
|
memset(held, 0, sizeof(held));
|
||||||
|
}
|
||||||
|
for (scan = 0; scan < SCAN_SET1_CODES; scan++) {
|
||||||
|
key = gScanSet1ToKey[scan];
|
||||||
|
if (key != KEY_NONE) {
|
||||||
|
gKeyState[key] = (held[scan] | held[scan | KEY_RAW_EXT]) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gMouseX = gNextMouseX;
|
||||||
|
gMouseY = gNextMouseY;
|
||||||
|
gMouseButtonState[MOUSE_BUTTON_LEFT] = (gNextMouseButtons & MOUSE_LEFT) != 0;
|
||||||
|
gMouseButtonState[MOUSE_BUTTON_RIGHT] = (gNextMouseButtons & MOUSE_RIGHT) != 0;
|
||||||
|
gMouseButtonState[MOUSE_BUTTON_MIDDLE] = (gNextMouseButtons & MOUSE_MIDDLE) != 0;
|
||||||
|
|
||||||
|
joystickPoll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpInputShutdown(void) {
|
||||||
|
joystickClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DVX's joysticks are calibrated in its Control Panel: nothing to reset.
|
||||||
|
void jlpJoystickReset(jlJoystickE js) {
|
||||||
|
(void)js;
|
||||||
|
}
|
||||||
168
src/dvx/program.c
Normal file
168
src/dvx/program.c
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Running and ending a JoeyLib program on DVX, and the folders its files
|
||||||
|
// live in.
|
||||||
|
//
|
||||||
|
// dvxPortRun (appMain, in the program's own DVX task) records the program's
|
||||||
|
// DVX context, then calls the program's main -- compiled as jlGameMain
|
||||||
|
// (make/dvx.mk: -Dmain=jlGameMain) -- with argv[0] the .app's path and the
|
||||||
|
// launch arguments after it. A program with `int main(void)` simply ignores
|
||||||
|
// them. The program ends the same way whether main returns, it calls exit(),
|
||||||
|
// or the user closes its window (hal.c): exit() jumps back to dvxPortRun, so
|
||||||
|
// appMain always returns, the end DVX expects of a program. Then its atexit
|
||||||
|
// handlers run, then jlShutdown (nothing to do if the program already shut
|
||||||
|
// down), then the log is closed, since DVX -- which owns the file handle --
|
||||||
|
// lives on.
|
||||||
|
//
|
||||||
|
// DVX shares one current directory between all its programs, so the file
|
||||||
|
// roots (fileRoot.h) come from the context instead: DATA/ beside the .app
|
||||||
|
// (appDir), SAVES/ and joeylog.txt in the program's own settings folder
|
||||||
|
// (configDir, CONFIG\ plus the .app's folder), which DVX keeps apart from the
|
||||||
|
// program files so a reinstall never overwrites them.
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "joey/audio.h"
|
||||||
|
#include "joey/core.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
#include "fileRoot.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
// atexit handlers a program may register: the C standard's minimum.
|
||||||
|
#define ATEXIT_MAX 32
|
||||||
|
// argv slots: argv[0] plus the launch arguments (the rest are dropped).
|
||||||
|
#define ARGV_MAX 16
|
||||||
|
#define ARG_SEPARATOR ' '
|
||||||
|
// A root: a DVX path and the separator after it (sizeof counts the NUL).
|
||||||
|
#define ROOT_MAX (DVX_MAX_PATH + sizeof(DVX_PATH_SEP))
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
// The program's main, as make/dvx.mk renames it (-Dmain=jlGameMain).
|
||||||
|
int jlGameMain(int argc, char **argv);
|
||||||
|
|
||||||
|
static int argsSplit(char *args, char **argv, int max);
|
||||||
|
static void programEnd(void);
|
||||||
|
static void rootSet(char *out, size_t cap, const char *dir);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
DxeAppContextT *gDvxPortProgram = NULL;
|
||||||
|
|
||||||
|
static void (*gAtExit[ATEXIT_MAX])(void);
|
||||||
|
static int gAtExitCount = 0;
|
||||||
|
static jmp_buf gEndJump; // back to dvxPortRun from exit()
|
||||||
|
static int gEndStatus = 0; // main's return or exit()'s status
|
||||||
|
static char gArgs[SHELL_ARGS_MAX];
|
||||||
|
static char gReadRoot[ROOT_MAX];
|
||||||
|
static char gWriteRoot[ROOT_MAX];
|
||||||
|
static bool gWriteRootMade = false;
|
||||||
|
|
||||||
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// Split args in place at spaces into at most max words; returns the count.
|
||||||
|
static int argsSplit(char *args, char **argv, int max) {
|
||||||
|
int count;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
p = args;
|
||||||
|
while (*p != '\0' && count < max) {
|
||||||
|
while (*p == ARG_SEPARATOR) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
argv[count] = p;
|
||||||
|
count++;
|
||||||
|
while (*p != '\0' && *p != ARG_SEPARATOR) {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p == ARG_SEPARATOR) {
|
||||||
|
*p = '\0';
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The end of the program, however it ends: atexit handlers newest first
|
||||||
|
// (as exit() runs them), JoeyLib -- its sound too, whose timer DVX would
|
||||||
|
// otherwise keep counting -- then the log.
|
||||||
|
static void programEnd(void) {
|
||||||
|
while (gAtExitCount > 0) {
|
||||||
|
gAtExitCount--;
|
||||||
|
gAtExit[gAtExitCount]();
|
||||||
|
}
|
||||||
|
jlAudioShutdown();
|
||||||
|
jlShutdown();
|
||||||
|
jlLogClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A root is the folder as a prefix: out = dir + separator.
|
||||||
|
static void rootSet(char *out, size_t cap, const char *dir) {
|
||||||
|
(void)snprintf(out, cap, "%s" DVX_PATH_SEP, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Port API (alphabetical) -----
|
||||||
|
|
||||||
|
int dvxPortAtExit(void (*fn)(void)) {
|
||||||
|
if (fn == NULL || gAtExitCount == ATEXIT_MAX) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
gAtExit[gAtExitCount] = fn;
|
||||||
|
gAtExitCount++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dvxPortExit(int status) {
|
||||||
|
gEndStatus = status;
|
||||||
|
longjmp(gEndJump, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t dvxPortRun(DxeAppContextT *ctx) {
|
||||||
|
char *argv[ARGV_MAX + 1];
|
||||||
|
int argc;
|
||||||
|
|
||||||
|
gDvxPortProgram = ctx;
|
||||||
|
gAtExitCount = 0;
|
||||||
|
gWriteRootMade = false;
|
||||||
|
rootSet(gReadRoot, sizeof(gReadRoot), ctx->appDir);
|
||||||
|
rootSet(gWriteRoot, sizeof(gWriteRoot), ctx->configDir);
|
||||||
|
(void)snprintf(gArgs, sizeof(gArgs), "%s", ctx->args);
|
||||||
|
argv[0] = ctx->appPath;
|
||||||
|
argc = 1 + argsSplit(gArgs, &argv[1], ARGV_MAX - 1);
|
||||||
|
argv[argc] = NULL;
|
||||||
|
|
||||||
|
if (setjmp(gEndJump) == 0) {
|
||||||
|
gEndStatus = jlGameMain(argc, argv);
|
||||||
|
}
|
||||||
|
programEnd();
|
||||||
|
return gEndStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
const char *jlpReadRoot(void) {
|
||||||
|
return gReadRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The settings folder is made on first use: a program that never writes
|
||||||
|
// leaves no folder behind.
|
||||||
|
const char *jlpWriteRoot(void) {
|
||||||
|
if (!gWriteRootMade) {
|
||||||
|
gWriteRootMade = shellEnsureConfigDir(gDvxPortProgram) == 0;
|
||||||
|
}
|
||||||
|
return gWriteRoot;
|
||||||
|
}
|
||||||
39
src/dvx/save.c
Normal file
39
src/dvx/save.c
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
// DVX save-file + disk-space HAL.
|
||||||
|
//
|
||||||
|
// SAVES/ lives in the program's settings folder (program.c's write root);
|
||||||
|
// DVX's platform layer makes it, parents and all, and measures the disk it
|
||||||
|
// is on. Delete is stdio remove().
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "joey/file.h"
|
||||||
|
#include "port.h"
|
||||||
|
#include "fileRoot.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
// jlDiskFree saturates here: FAT16 disks reach 2 GB, and FAT32 more.
|
||||||
|
#define DISK_FREE_MAX 0xFFFFFFFFu
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t jlpDiskFree(void) {
|
||||||
|
uint64_t totalBytes;
|
||||||
|
uint64_t freeBytes;
|
||||||
|
|
||||||
|
if (!platformDiskSpace(jlpWriteRoot(), &totalBytes, &freeBytes)) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
return freeBytes > DISK_FREE_MAX ? DISK_FREE_MAX : (uint32_t)freeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpSaveDelete(const char *path) {
|
||||||
|
return remove(path) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpSaveDirEnsure(const char *dir) {
|
||||||
|
return platformMkdirRecursive(dir) == 0;
|
||||||
|
}
|
||||||
151
src/dvx/serial.c
Normal file
151
src/dvx/serial.c
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
// DVX serial HAL: a COM port through DVX's serial library (rs232), which
|
||||||
|
// owns the UARTs for every program on the desktop -- one owner per port, so
|
||||||
|
// a port another program has open is refused. Its interrupt fills a receive
|
||||||
|
// ring in the background; the calls here only move bytes.
|
||||||
|
//
|
||||||
|
// DEFAULT, MODEM and PRINTER are COM1; SLOT is COM<unit> (1..4), as on DOS.
|
||||||
|
//
|
||||||
|
// One difference from every other port: DVX's library resets the modem
|
||||||
|
// lines when it closes a port, so jlSerialClose drops DTR there (a modem
|
||||||
|
// hangs up). A program handing a live call back to another has to keep the
|
||||||
|
// port open.
|
||||||
|
//
|
||||||
|
// jlSerialPoll is also a point where the program comes back to the port
|
||||||
|
// every frame, so it yields to the desktop like the input poll does: a
|
||||||
|
// program that only talks serial would otherwise freeze DVX.
|
||||||
|
|
||||||
|
#include "dvxSdk.h"
|
||||||
|
#include "rs232/rs232.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "joey/serial.h"
|
||||||
|
#include "port.h"
|
||||||
|
#include "dvxPort.h"
|
||||||
|
|
||||||
|
// ----- Constants -----
|
||||||
|
|
||||||
|
#define NO_PORT (-1)
|
||||||
|
#define COM_FIRST 1u // jlSerialConfigT.unit numbers COM ports from 1
|
||||||
|
|
||||||
|
// ----- Prototypes -----
|
||||||
|
|
||||||
|
static int32_t comForDevice(jlSerialDeviceE device, const jlSerialConfigT *config);
|
||||||
|
|
||||||
|
// ----- Module state -----
|
||||||
|
|
||||||
|
static int32_t gCom = NO_PORT;
|
||||||
|
|
||||||
|
// rs232 parity letters and handshakes for JoeyLib's.
|
||||||
|
static const char gParity[] = {
|
||||||
|
[JL_SERIAL_PARITY_NONE] = 'N',
|
||||||
|
[JL_SERIAL_PARITY_ODD] = 'O',
|
||||||
|
[JL_SERIAL_PARITY_EVEN] = 'E'
|
||||||
|
};
|
||||||
|
static const int32_t gHandshake[] = {
|
||||||
|
[JL_SERIAL_FLOW_NONE] = RS232_HANDSHAKE_NONE,
|
||||||
|
[JL_SERIAL_FLOW_RTSCTS] = RS232_HANDSHAKE_RTSCTS,
|
||||||
|
[JL_SERIAL_FLOW_XONXOFF] = RS232_HANDSHAKE_XONXOFF
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----- Internal helpers -----
|
||||||
|
|
||||||
|
static int32_t comForDevice(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
|
if (device != JL_SERIAL_SLOT) {
|
||||||
|
return RS232_COM1;
|
||||||
|
}
|
||||||
|
if (config->unit < COM_FIRST || config->unit >= COM_FIRST + RS232_NUM_PORTS) {
|
||||||
|
return NO_PORT;
|
||||||
|
}
|
||||||
|
return (int32_t)(config->unit - COM_FIRST);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
|
uint16_t jlpSerialAvailable(void) {
|
||||||
|
int32_t n;
|
||||||
|
|
||||||
|
if (gCom == NO_PORT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
n = rs232GetRxBuffered(gCom);
|
||||||
|
return n > 0 ? (uint16_t)n : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialClose(void) {
|
||||||
|
if (gCom == NO_PORT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(void)rs232Close(gCom);
|
||||||
|
gCom = NO_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialFlush(void) {
|
||||||
|
if (gCom != NO_PORT) {
|
||||||
|
(void)rs232ClearRxBuffer(gCom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
|
||||||
|
int32_t com;
|
||||||
|
|
||||||
|
if (gCom != NO_PORT || config->parity > JL_SERIAL_PARITY_EVEN || config->flow > JL_SERIAL_FLOW_XONXOFF) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
com = comForDevice(device, config);
|
||||||
|
if (com == NO_PORT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rs232Open(com, (int32_t)config->baud, config->dataBits, gParity[config->parity], config->stopBits, gHandshake[config->flow]) != RS232_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gCom = com;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialPoll(void) {
|
||||||
|
dvxPortService();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
|
||||||
|
int32_t n;
|
||||||
|
|
||||||
|
if (gCom == NO_PORT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
n = rs232Read(gCom, (char *)buf, max);
|
||||||
|
return n > 0 ? (uint16_t)n : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialSetDtr(bool on) {
|
||||||
|
if (gCom != NO_PORT) {
|
||||||
|
(void)rs232SetDtr(gCom, on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlpSerialSetRts(bool on) {
|
||||||
|
if (gCom != NO_PORT) {
|
||||||
|
(void)rs232SetRts(gCom, on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// rs232Write waits on the UART itself and gives up while flow control
|
||||||
|
// holds the line, returning what went: the bounded poll JoeyLib asks for.
|
||||||
|
uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
|
||||||
|
int32_t n;
|
||||||
|
|
||||||
|
if (gCom == NO_PORT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
n = rs232Write(gCom, (const char *)buf, len);
|
||||||
|
return n > 0 ? (uint16_t)n : 0u;
|
||||||
|
}
|
||||||
82
src/x86/scanSet1.h
Normal file
82
src/x86/scanSet1.h
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// PC keyboard scan code set 1 -> JoeyLib key, shared by the ports that see
|
||||||
|
// raw PC scan codes: DOS (its own INT 9 handler) and DVX (the held-key table
|
||||||
|
// DVX keeps from its INT 9 chain). Indexed by the make code with the break
|
||||||
|
// bit and any 0xE0 prefix stripped, so the grey arrows land on the same
|
||||||
|
// keys as the keypad ones, right Ctrl on KEY_LCTRL and right Alt on
|
||||||
|
// KEY_LALT. Slots left 0 (KEY_NONE) are keys JoeyLib does not name.
|
||||||
|
//
|
||||||
|
// A static table in a header: each includer gets its own read-only copy
|
||||||
|
// (DOS locks its copy for the interrupt handler that reads it).
|
||||||
|
#ifndef JOEY_SCAN_SET1_H
|
||||||
|
#define JOEY_SCAN_SET1_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "joey/input.h"
|
||||||
|
|
||||||
|
// Make codes are 7 bits: the top bit of a scan byte is the break flag.
|
||||||
|
#define SCAN_SET1_CODES 128
|
||||||
|
|
||||||
|
static const uint8_t gScanSet1ToKey[SCAN_SET1_CODES] = {
|
||||||
|
[0x01] = KEY_ESCAPE,
|
||||||
|
[0x02] = KEY_1,
|
||||||
|
[0x03] = KEY_2,
|
||||||
|
[0x04] = KEY_3,
|
||||||
|
[0x05] = KEY_4,
|
||||||
|
[0x06] = KEY_5,
|
||||||
|
[0x07] = KEY_6,
|
||||||
|
[0x08] = KEY_7,
|
||||||
|
[0x09] = KEY_8,
|
||||||
|
[0x0A] = KEY_9,
|
||||||
|
[0x0B] = KEY_0,
|
||||||
|
[0x0E] = KEY_BACKSPACE,
|
||||||
|
[0x0F] = KEY_TAB,
|
||||||
|
[0x10] = KEY_Q,
|
||||||
|
[0x11] = KEY_W,
|
||||||
|
[0x12] = KEY_E,
|
||||||
|
[0x13] = KEY_R,
|
||||||
|
[0x14] = KEY_T,
|
||||||
|
[0x15] = KEY_Y,
|
||||||
|
[0x16] = KEY_U,
|
||||||
|
[0x17] = KEY_I,
|
||||||
|
[0x18] = KEY_O,
|
||||||
|
[0x19] = KEY_P,
|
||||||
|
[0x1C] = KEY_RETURN,
|
||||||
|
[0x1D] = KEY_LCTRL,
|
||||||
|
[0x1E] = KEY_A,
|
||||||
|
[0x1F] = KEY_S,
|
||||||
|
[0x20] = KEY_D,
|
||||||
|
[0x21] = KEY_F,
|
||||||
|
[0x22] = KEY_G,
|
||||||
|
[0x23] = KEY_H,
|
||||||
|
[0x24] = KEY_J,
|
||||||
|
[0x25] = KEY_K,
|
||||||
|
[0x26] = KEY_L,
|
||||||
|
[0x2A] = KEY_LSHIFT,
|
||||||
|
[0x2C] = KEY_Z,
|
||||||
|
[0x2D] = KEY_X,
|
||||||
|
[0x2E] = KEY_C,
|
||||||
|
[0x2F] = KEY_V,
|
||||||
|
[0x30] = KEY_B,
|
||||||
|
[0x31] = KEY_N,
|
||||||
|
[0x32] = KEY_M,
|
||||||
|
[0x36] = KEY_RSHIFT,
|
||||||
|
[0x38] = KEY_LALT,
|
||||||
|
[0x39] = KEY_SPACE,
|
||||||
|
[0x3B] = KEY_F1,
|
||||||
|
[0x3C] = KEY_F2,
|
||||||
|
[0x3D] = KEY_F3,
|
||||||
|
[0x3E] = KEY_F4,
|
||||||
|
[0x3F] = KEY_F5,
|
||||||
|
[0x40] = KEY_F6,
|
||||||
|
[0x41] = KEY_F7,
|
||||||
|
[0x42] = KEY_F8,
|
||||||
|
[0x43] = KEY_F9,
|
||||||
|
[0x44] = KEY_F10,
|
||||||
|
[0x48] = KEY_UP,
|
||||||
|
[0x4B] = KEY_LEFT,
|
||||||
|
[0x4D] = KEY_RIGHT,
|
||||||
|
[0x50] = KEY_DOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// x86 sprite codegen (DOS port). Emits 32-bit cdecl-callable PIC
|
// x86 sprite codegen (the DOS and DVX ports). Emits 32-bit cdecl-callable PIC
|
||||||
// draw / save / restore routines that read or write 4bpp packed
|
// draw / save / restore routines that read or write 4bpp packed
|
||||||
// surface bytes via [esi+disp8] chains. The C side calls them
|
// surface bytes via [esi+disp8] chains. The C side calls them
|
||||||
// through a function pointer cast.
|
// through a function pointer cast.
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
// DOS whole-map tile paste engine (NATIVE-PERF Phase 6 W3). DOS was
|
// x86 whole-map tile paste engine (NATIVE-PERF Phase 6 W3), shared by
|
||||||
// the only port whose jlTileMapPaste ran the generic per-cell path:
|
// the DOS and DVX ports. DOS was the only port whose jlTileMapPaste ran
|
||||||
// each cell paid a cdecl call into jlpGenericTilePaste, a pixels
|
// the generic per-cell path: each cell paid a cdecl call into
|
||||||
// check, a fresh (by+ty)*160 derive, and a ROLLED 8-iteration row
|
// jlpGenericTilePaste, a pixels check, a fresh (by+ty)*160 derive, and
|
||||||
// loop. This engine hoists the check and the derive out of the loop
|
// a ROLLED 8-iteration row loop. This engine hoists the check and the derive out of the loop
|
||||||
// and pastes each cell as 8 dword row moves at constant
|
// and pastes each cell as 8 dword row moves at constant
|
||||||
// displacements -- an 8x8 4bpp tile row is 4 bytes, exactly one
|
// displacements -- an 8x8 4bpp tile row is 4 bytes, exactly one
|
||||||
// 32-bit load/store on the 386. Dispatched through the
|
// 32-bit load/store on the 386. Dispatched through the
|
||||||
// jlpTileMapPaste alias from tile.c's shared whole-map arm (the W3
|
// jlpTileMapPaste alias from tile.c's shared whole-map arm (the W3
|
||||||
// seam; not a JL_HAS jlp op); tile.c owns the single union dirty
|
// seam; not a JL_HAS jlp op); tile.c owns the single union dirty
|
||||||
// mark. Included from the tail of src/core/port.h like stTile.h.
|
// mark. Included from the tail of src/core/port.h like stTile.h.
|
||||||
#ifndef JOEY_DOS_TILE_H
|
#ifndef JOEY_X86_TILE_H
|
||||||
#define JOEY_DOS_TILE_H
|
#define JOEY_X86_TILE_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
@ -26,9 +26,9 @@
|
||||||
// caller's jlTileT source is odd-aligned, never a fault), so no
|
// caller's jlTileT source is odd-aligned, never a fault), so no
|
||||||
// 68k-style parity guard or C fallback is needed; the destination is
|
// 68k-style parity guard or C fallback is needed; the destination is
|
||||||
// always dword-aligned (row stride 160, column offset bx*4).
|
// always dword-aligned (row stride 160, column offset bx*4).
|
||||||
#define DOS_MAP_CELL_ROW(_r) memcpy(d + (_r) * SURFACE_BYTES_PER_ROW, s + (_r) * TILE_BYTES_PER_ROW, TILE_BYTES_PER_ROW)
|
#define X86_MAP_CELL_ROW(_r) memcpy(d + (_r) * SURFACE_BYTES_PER_ROW, s + (_r) * TILE_BYTES_PER_ROW, TILE_BYTES_PER_ROW)
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void dosTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
static inline __attribute__((always_inline)) void x86TileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
||||||
const uint8_t *tileBytes;
|
const uint8_t *tileBytes;
|
||||||
const uint8_t *s;
|
const uint8_t *s;
|
||||||
const uint8_t *m;
|
const uint8_t *m;
|
||||||
|
|
@ -60,20 +60,20 @@ static inline __attribute__((always_inline)) void dosTileMapPaste(jlSurfaceT *ds
|
||||||
// sizeof(jlTileT) is the tileset stride (32; pinned by
|
// sizeof(jlTileT) is the tileset stride (32; pinned by
|
||||||
// tile.c's tileMapStrideProof) -- gcc folds this to shl 5.
|
// tile.c's tileMapStrideProof) -- gcc folds this to shl 5.
|
||||||
s = tileBytes + (size_t)idx * sizeof(jlTileT);
|
s = tileBytes + (size_t)idx * sizeof(jlTileT);
|
||||||
DOS_MAP_CELL_ROW(0);
|
X86_MAP_CELL_ROW(0);
|
||||||
DOS_MAP_CELL_ROW(1);
|
X86_MAP_CELL_ROW(1);
|
||||||
DOS_MAP_CELL_ROW(2);
|
X86_MAP_CELL_ROW(2);
|
||||||
DOS_MAP_CELL_ROW(3);
|
X86_MAP_CELL_ROW(3);
|
||||||
DOS_MAP_CELL_ROW(4);
|
X86_MAP_CELL_ROW(4);
|
||||||
DOS_MAP_CELL_ROW(5);
|
X86_MAP_CELL_ROW(5);
|
||||||
DOS_MAP_CELL_ROW(6);
|
X86_MAP_CELL_ROW(6);
|
||||||
DOS_MAP_CELL_ROW(7);
|
X86_MAP_CELL_ROW(7);
|
||||||
}
|
}
|
||||||
d += TILE_BYTES_PER_ROW;
|
d += TILE_BYTES_PER_ROW;
|
||||||
}
|
}
|
||||||
d += dRowStep;
|
d += dRowStep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#undef DOS_MAP_CELL_ROW
|
#undef X86_MAP_CELL_ROW
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
Loading…
Add table
Reference in a new issue