More DVX target work.
This commit is contained in:
parent
17343a0b4f
commit
c520b16179
29 changed files with 1269 additions and 366 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -63,6 +63,7 @@ nvram/
|
||||||
# Crap I added
|
# Crap I added
|
||||||
stuff/*
|
stuff/*
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
screenshots/
|
||||||
|
|
||||||
# Claude's working notes (design scratch, port write-ups, session state).
|
# Claude's working notes (design scratch, port write-ups, session state).
|
||||||
# NOT the user manual -- that lives in docs/ and IS tracked.
|
# NOT the user manual -- that lives in docs/ and IS tracked.
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -8,8 +8,9 @@ platforms, and one DOS desktop, from a single codebase:
|
||||||
- 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`,
|
- DVX (the DOS desktop GUI in `../dvxgui`: each program a windowed `.app`
|
||||||
built with the DOS compiler against DVX's SDK)
|
that can also take the whole screen, 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.
|
||||||
|
|
@ -49,10 +50,10 @@ the example programs (`hello`, `pattern`, `draw`, `keys`, `serial`,
|
||||||
`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. DVX needs the `dvx` installer component,
|
packages them onto a disk image. DVX needs the `dvx` installer component,
|
||||||
which copies DVX and its SDK from a built DVX tree
|
which unpacks DVX and its SDK from a DVX tree's distribution, `dist/dvx.zip`
|
||||||
(`./install.sh --dvx-dir PATH install dvx`; the default is `../dvxgui`); its
|
and `dist/sdk.zip` (`./install.sh --dvx-dir PATH install dvx`; the default
|
||||||
examples land in `build/dvx/bin/apps/joeylib/<name>/`, laid out as they
|
is `../dvxgui`); its examples land in `build/dvx/bin/apps/joeylib/<name>/`,
|
||||||
install in DVX.
|
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
|
||||||
|
|
|
||||||
|
|
@ -194,8 +194,9 @@ uint16_t jlFrameHz(void);
|
||||||
|
|
||||||
*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 and on DVX, whose window has a virtual
|
ST NTSC and X68000, 70 on VGA mode 13h and on DVX, whose window has a virtual
|
||||||
70 Hz. The real cadence may drift slightly; this is the figure to divide by
|
70 Hz (and whose full screen is mode 13h's, or a VBE mode's, with the same
|
||||||
when converting frames to seconds.
|
frame count). The real cadence may drift slightly; this is the figure to
|
||||||
|
divide by when converting frames to seconds.
|
||||||
|
|
||||||
=== jlMillisElapsed
|
=== jlMillisElapsed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,8 @@ bool jlMousePresent(void);
|
||||||
the INT 33h driver reset successfully; on Amiga, ST, IIgs, X68000 and DVX the
|
the INT 33h driver reset successfully; on Amiga, ST, IIgs, X68000 and DVX the
|
||||||
OS mouse is always wired. On DVX the position is the pointer's over the
|
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
|
program's window, divided by the window's scale; it keeps its last value
|
||||||
while the pointer is elsewhere.
|
while the pointer is elsewhere. On DVX's full screen it is the pointer's
|
||||||
|
place on the stage (DVX draws no pointer there).
|
||||||
|
|
||||||
=== jlMouseX / jlMouseY
|
=== jlMouseX / jlMouseY
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,9 +200,7 @@ 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. DVX is the exception: its serial library
|
whatever RTS was before the open.
|
||||||
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),
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ firmware.
|
||||||
|
|
||||||
| `all`
|
| `all`
|
||||||
| Everything except optional components (`staxiData`, Space Taxi's level
|
| Everything except optional components (`staxiData`, Space Taxi's level
|
||||||
source, and `dvx`, which copies DVX from a DVX tree of yours), which are
|
source, and `dvx`, which unpacks DVX from a DVX tree of yours), which are
|
||||||
installed by name. `toolchains` and `emulators` leave them out too.
|
installed by name. `toolchains` and `emulators` leave them out too.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
@ -152,11 +152,13 @@ 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`
|
| `--dvx-dir PATH`
|
||||||
| The built DVX tree the `dvx` component copies DVX and its SDK from. The
|
| The DVX tree the `dvx` component takes DVX and its SDK from: its
|
||||||
default is a `dvxgui` checkout beside this repository; the setting is
|
distribution, `dist/dvx.zip` (DVX as it installs) and `dist/sdk.zip` (the
|
||||||
remembered. The component only reads that tree: it copies what the builds
|
SDK). The default is a `dvxgui` checkout beside this repository; the
|
||||||
and the emulator runs need into `toolchains/opt/dvx`, and rebuilding DVX
|
setting is remembered. The component only reads that tree: it unpacks both
|
||||||
there makes the component out of date, so `install dvx` copies it again.
|
zips into `toolchains/opt/dvx`, and a new zip there makes the component out
|
||||||
|
of date, so `install dvx` unpacks it again. (A tree whose `dist/` has no
|
||||||
|
`sdk.zip` yet gives its built SDK, `bin/sdk`, instead.)
|
||||||
|===
|
|===
|
||||||
|
|
||||||
Everything the installer creates stays inside `toolchains/`:
|
Everything the installer creates stays inside `toolchains/`:
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ them:
|
||||||
|DVX
|
|DVX
|
||||||
|486 or later
|
|486 or later
|
||||||
|what DVX needs
|
|what DVX needs
|
||||||
|A program in a window on the DVX desktop, at 1x to 3x. The MS-DOS port's
|
|A program in a window on the DVX desktop, at 1x to 3x, or on the whole
|
||||||
stage and code; a virtual 70 Hz. See <<_dvx_programs>>.
|
screen at 320x200. 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
|
||||||
|
|
|
||||||
|
|
@ -209,7 +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)
|
|70 Hz (virtual; the real retrace on the whole screen)
|
||||||
|
|
||||||
|Pixel storage
|
|Pixel storage
|
||||||
|chunky 4bpp
|
|chunky 4bpp
|
||||||
|
|
@ -295,20 +295,38 @@ 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
|
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:
|
hashes are DOS's -- and the rest behaves like this:
|
||||||
|
|
||||||
* *A window, not the screen.* The window's content is the 320x200 stage at
|
* *A window, or the whole screen.* The window's content is the 320x200 stage
|
||||||
the largest whole scale, up to 3x, that fits the desktop: 1x at 640x480,
|
at the largest whole scale, up to 3x, that fits the desktop: 1x at
|
||||||
2x at 800x600, 3x at 1024x768. The stage reaches the desktop through one
|
640x480, 2x at 800x600, 3x at 1024x768. The stage reaches the desktop
|
||||||
colour table per palette, so SCB palettes are exact on 15-, 16- and 32-bit
|
through one colour table per palette, so SCB palettes are exact on 15-,
|
||||||
desktops. On a 256-colour desktop every colour is the nearest one in DVX's
|
16- and 32-bit desktops. On a 256-colour desktop every colour is the
|
||||||
palette, which all programs share: gradients band.
|
nearest one in DVX's palette, which all programs share: gradients band.
|
||||||
|
* *Full screen.* Game > Full Screen, or Alt+Enter, puts the program on the
|
||||||
|
whole screen in DVX's full-screen mode, 320x200 in 256 colours (VGA mode
|
||||||
|
13h, or a VBE mode of that size), and back in its window. There it looks
|
||||||
|
exactly as on MS-DOS: the screen's 256 colours hold all 16 palettes, so
|
||||||
|
SCB palettes are exact at any desktop depth and palette changes are free.
|
||||||
|
The other programs run on meanwhile. Alt+Tab, Ctrl+Esc or another window
|
||||||
|
coming to the front brings the desktop back, with the program still
|
||||||
|
running and drawing in its window; giving its window the focus again puts
|
||||||
|
it back on the whole screen. The choice is kept in the program's settings
|
||||||
|
folder (`JOEYLIB.INI`), so it starts the way it was left. It is the user's
|
||||||
|
choice: JoeyLib's API has no full-screen call. (Under DOSBox, Alt+Enter is
|
||||||
|
DOSBox's own full-screen key; use Alt with the keypad's Enter, or the
|
||||||
|
menu.)
|
||||||
* *A virtual 70 Hz.* A window has no vertical blank. `jlFrameHz()` is 70, as
|
* *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
|
on DOS, and `jlWaitVBL()` waits for the next seventieth of a second of
|
||||||
DVX's clock while the other programs run. Nothing prevents tearing.
|
DVX's clock while the other programs run; nothing prevents tearing. On the
|
||||||
* *Keys DVX keeps.* While the window is active every key goes to the program,
|
whole screen `jlWaitVBL()` gives the other programs a turn, then waits for
|
||||||
F1, F10 and Alt included, except Alt+Tab (switch programs), Ctrl+Esc (Task
|
the screen's real vertical retrace, as on DOS, so a present right after it
|
||||||
Manager) and Alt+F4 (close the window). The window's Game menu has Help
|
does not tear. The frame count is the same clock in both, so switching
|
||||||
(the program's help file) and Exit. Typed characters come through DVX's
|
never makes it jump.
|
||||||
keyboard layout.
|
* *Keys DVX keeps.* While the window is active, or the program has the whole
|
||||||
|
screen, 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), and Alt+Enter, which the port keeps for full screen. The window's
|
||||||
|
Game menu has Full Screen, 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
|
* *Closing ends the program.* Alt+F4, the close box and Game > Exit end the
|
||||||
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent` or
|
program at its next `jlWaitVBL`, `jlInputPoll`, `jlStagePresent` or
|
||||||
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
`jlSerialPoll`, as `exit()` does anywhere: its `atexit` handlers run, then
|
||||||
|
|
@ -330,11 +348,11 @@ hashes are DOS's -- and the rest behaves like this:
|
||||||
be called from an interrupt.
|
be called from an interrupt.
|
||||||
* *Joysticks* are DVX's, calibrated in its Control Panel: real analog axes.
|
* *Joysticks* are DVX's, calibrated in its Control Panel: real analog axes.
|
||||||
DVX's keyboard joystick is not reported as one.
|
DVX's keyboard joystick is not reported as one.
|
||||||
* *Serial ports* belong to DVX's serial library, one program each. Closing a
|
* *Serial ports* belong to DVX's serial library, one program each. As on
|
||||||
port there drops DTR, so a modem hangs up.
|
every port, closing one leaves DTR as it is.
|
||||||
* A program's `main` is compiled as `jlGameMain`, which the port calls with
|
* A program's `main` is compiled as `jlGameMain`, which the port calls with
|
||||||
`argv[0]` the `.app`'s path.
|
`argv[0]` the `.app`'s path.
|
||||||
|
|
||||||
`make dvx` builds every example as a DVX program (the `dvx` installer
|
`make dvx` builds every example as a DVX program (the `dvx` installer
|
||||||
component supplies the SDK, <<_setting_up_the_toolchains>>), and
|
component supplies DVX and its SDK, <<_setting_up_the_toolchains>>), and
|
||||||
`./joey-run --port dvx NAME` starts one in DVX under DOSBox Staging.
|
`./joey-run --port dvx NAME` starts one in DVX under DOSBox Staging.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,65 @@
|
||||||
// runs even where the graphics HAL can't (e.g. AROS on the Amiga). Opens the
|
// runs even where the graphics HAL can't (e.g. AROS on the Amiga). Opens the
|
||||||
// DEFAULT port at 9600 8N1, sends a banner (TX heartbeat), then echoes every
|
// DEFAULT port at 9600 8N1, sends a banner (TX heartbeat), then echoes every
|
||||||
// received byte forever. A host serial peer verifies banner (TX) + echo (RX).
|
// received byte forever. A host serial peer verifies banner (TX) + echo (RX).
|
||||||
|
//
|
||||||
|
// A peer that sees the modem lines (DOSBox's nullmodem reports them in the
|
||||||
|
// byte stream as they change) can also send DTR_CHECK: the program then
|
||||||
|
// drops and raises DTR, closes and reopens the port, drops DTR and closes
|
||||||
|
// the port for good, with a marker line after each step, so the peer can
|
||||||
|
// check that jlSerialSetDtr reaches the line and that jlSerialClose leaves
|
||||||
|
// DTR as it is, up or down. After the check the port stays closed.
|
||||||
#include <joey/serial.h>
|
#include <joey/serial.h>
|
||||||
|
|
||||||
|
#define DTR_CHECK 0x04u // ASCII EOT: run the DTR check (not echoed)
|
||||||
|
#define DTR_ACK 0x06u // ASCII ACK: the peer has the whole marker line
|
||||||
|
|
||||||
|
static void dtrCheck(const jlSerialConfigT *cfg);
|
||||||
|
static void sendMark(const char *text);
|
||||||
|
|
||||||
|
|
||||||
|
static void dtrCheck(const jlSerialConfigT *cfg) {
|
||||||
|
jlSerialSetDtr(false);
|
||||||
|
sendMark("JLDTR-OFF\r\n");
|
||||||
|
jlSerialSetDtr(true);
|
||||||
|
sendMark("JLDTR-ON\r\n");
|
||||||
|
jlSerialClose();
|
||||||
|
if (!jlSerialOpen(JL_SERIAL_DEFAULT, cfg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMark("JLDTR-REOPENED\r\n");
|
||||||
|
jlSerialSetDtr(false);
|
||||||
|
sendMark("JLDTR-HANGUP\r\n");
|
||||||
|
jlSerialClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A marker line, then the peer's DTR_ACK: the line has gone out whole, so
|
||||||
|
// the next line change comes after it (a changed line is reported at once,
|
||||||
|
// while bytes wait their turn in the UART), and a close cannot throw any of
|
||||||
|
// it away.
|
||||||
|
static void sendMark(const char *text) {
|
||||||
|
uint16_t len;
|
||||||
|
uint16_t sent;
|
||||||
|
uint8_t byte;
|
||||||
|
|
||||||
|
len = 0u;
|
||||||
|
while (text[len] != '\0') {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
sent = 0u;
|
||||||
|
while (sent < len) {
|
||||||
|
jlSerialPoll();
|
||||||
|
sent = (uint16_t)(sent + jlSerialWrite((const uint8_t *)&text[sent], (uint16_t)(len - sent)));
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
jlSerialPoll();
|
||||||
|
if (jlSerialRead(&byte, 1u) == 1u && byte == DTR_ACK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
jlSerialConfigT cfg;
|
jlSerialConfigT cfg;
|
||||||
static const char banner[] = "JLSERTEST-READY\r\n";
|
static const char banner[] = "JLSERTEST-READY\r\n";
|
||||||
|
|
@ -22,12 +79,21 @@ int main(void) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
uint16_t n;
|
uint16_t n;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
jlSerialPoll();
|
jlSerialPoll();
|
||||||
n = jlSerialRead(buf, (uint16_t)sizeof(buf));
|
n = jlSerialRead(buf, (uint16_t)sizeof(buf));
|
||||||
if (n > 0u) {
|
for (i = 0u; i < n; i++) {
|
||||||
jlSerialWrite(buf, n);
|
if (buf[i] == DTR_CHECK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i > 0u) {
|
||||||
|
jlSerialWrite(buf, i);
|
||||||
|
}
|
||||||
|
if (i < n) {
|
||||||
|
dtrCheck(&cfg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* not reached */
|
// not reached
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,8 @@
|
||||||
#define JOEYLIB_PLATFORM_NAME "Sharp X68000"
|
#define JOEYLIB_PLATFORM_NAME "Sharp X68000"
|
||||||
#elif defined(JOEYLIB_PLATFORM_DVX)
|
#elif defined(JOEYLIB_PLATFORM_DVX)
|
||||||
// The DOS stage (chunky 4bpp, x86 sprite JIT and tile walker from
|
// The DOS stage (chunky 4bpp, x86 sprite JIT and tile walker from
|
||||||
// src/x86/), shown in a DVX window instead of on the VGA.
|
// src/x86/), shown in a DVX window, or on DVX's full screen, instead of
|
||||||
|
// on the VGA.
|
||||||
#define JOEYLIB_CPU_X86
|
#define JOEYLIB_CPU_X86
|
||||||
#define JOEYLIB_ENDIAN_LITTLE
|
#define JOEYLIB_ENDIAN_LITTLE
|
||||||
#define JOEYLIB_NATIVE_CHUNKY
|
#define JOEYLIB_NATIVE_CHUNKY
|
||||||
|
|
@ -470,13 +471,14 @@
|
||||||
#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)
|
#elif defined(JOEYLIB_PLATFORM_DVX)
|
||||||
// A window on the DVX desktop (src/dvx). The graphics are the DOS port's:
|
// A window on the DVX desktop, or DVX's full screen (src/dvx). The
|
||||||
|
// graphics are the DOS port's:
|
||||||
// the chunky generics, the x86 whole-map tile walker (src/x86/x86Tile.h,
|
// the chunky generics, the x86 whole-map tile walker (src/x86/x86Tile.h,
|
||||||
// through the jlpTileMapPaste alias) and the x86 sprite JIT. DVX's
|
// through the jlpTileMapPaste alias) and the x86 sprite JIT. DVX's
|
||||||
// libraries supply the services.
|
// libraries supply the services.
|
||||||
#define JL_HAS_INIT // the program's window
|
#define JL_HAS_INIT // the program's window
|
||||||
#define JL_HAS_SHUTDOWN
|
#define JL_HAS_SHUTDOWN
|
||||||
#define JL_HAS_PRESENT // dirty rows through per-palette tables into the window
|
#define JL_HAS_PRESENT // dirty rows through per-palette tables into the window or onto the full screen
|
||||||
#define JL_HAS_INPUT_INIT
|
#define JL_HAS_INPUT_INIT
|
||||||
#define JL_HAS_INPUT_SHUTDOWN
|
#define JL_HAS_INPUT_SHUTDOWN
|
||||||
#define JL_HAS_INPUT_POLL // held-key table, onKey characters, onMouse, joystick.lib
|
#define JL_HAS_INPUT_POLL // held-key table, onKey characters, onMouse, joystick.lib
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
# toolchain:PORT emulator:PORT PORT toolchains emulators all
|
# toolchain:PORT emulator:PORT PORT toolchains emulators all
|
||||||
# where PORT is iigs, amiga, atarist, dos, x68000 or dvx. `all`, `toolchains`
|
# where PORT is iigs, amiga, atarist, dos, x68000 or dvx. `all`, `toolchains`
|
||||||
# and `emulators` leave out optional components (staxiData, and dvx, which
|
# and `emulators` leave out optional components (staxiData, and dvx, which
|
||||||
# copies from a DVX tree you have); name those explicitly.
|
# unpacks a DVX tree's dist/ zips you have); name those explicitly.
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# -y, --yes install missing host packages without asking
|
# -y, --yes install missing host packages without asking
|
||||||
|
|
@ -35,8 +35,9 @@
|
||||||
# --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
|
# --dvx-dir PATH the DVX tree the dvx component unpacks DVX and its SDK
|
||||||
# (remembered; default: dvxgui beside this repository)
|
# from (its dist/dvx.zip and dist/sdk.zip; remembered;
|
||||||
|
# default: dvxgui beside this repository)
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# ./install.sh install toolchains every cross compiler
|
# ./install.sh install toolchains every cross compiler
|
||||||
|
|
@ -422,7 +423,7 @@ while [ $# -gt 0 ]; do
|
||||||
;;
|
;;
|
||||||
--dvx-dir)
|
--dvx-dir)
|
||||||
[ $# -ge 2 ] || die "--dvx-dir needs a path"
|
[ $# -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)"
|
[ -f "$2/dist/dvx.zip" ] || die "$2 does not look like a DVX tree with its distribution made (no dist/dvx.zip)"
|
||||||
mkdir -p "$JOEY_STATE"
|
mkdir -p "$JOEY_STATE"
|
||||||
(cd "$2" && pwd) > "$JOEY_STATE/dvxDir"
|
(cd "$2" && pwd) > "$JOEY_STATE/dvxDir"
|
||||||
shift
|
shift
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,68 @@
|
||||||
# DVX, the DOS desktop JoeyLib's dvx port runs in: its SDK (the headers,
|
# DVX, the DOS desktop JoeyLib's dvx port runs in: the runtime the gates and
|
||||||
# sdk.mk and the host tools every DVX program is built with) and the runtime
|
# joey-run boot (dvx.exe with its libraries, widgets, programs, settings and
|
||||||
# tree the gates and joey-run boot (dvx.exe with its libraries, widgets,
|
# system files) and its SDK (the headers, sdk.mk and the host tools every DVX
|
||||||
# programs, settings and system files). Copied from a built DVX tree, so the
|
# program is built with). Both are unpacked from a DVX tree's distribution:
|
||||||
# builds and the emulator runs never touch the DVX project itself.
|
# dist/dvx.zip is the DVX installation and dist/sdk.zip the SDK (all of it
|
||||||
|
# under DVX-SDK/). A tree whose dist/ has no sdk.zip gives its built SDK,
|
||||||
|
# bin/sdk, instead. The builds and the emulator runs use the copy in
|
||||||
|
# toolchains/opt/dvx and never touch the DVX project itself.
|
||||||
#
|
#
|
||||||
# `install.sh --dvx-dir PATH` names the DVX tree (remembered); the default is
|
# `install.sh --dvx-dir PATH` names the DVX tree (remembered); the default is
|
||||||
# a dvxgui checkout beside this repository. Rebuilding DVX there makes this
|
# a dvxgui checkout beside this repository. The pin is the zips' checksums, so
|
||||||
# component out of date, and `install dvx` copies it again.
|
# a new dvx.zip or sdk.zip there makes this component out of date, and
|
||||||
|
# `install dvx` unpacks it again.
|
||||||
|
|
||||||
compDescription="DVX: desktop runtime + SDK, copied from a DVX tree (--dvx-dir)"
|
compDescription="DVX: desktop runtime + SDK, unpacked from a DVX tree's dist/ zips (--dvx-dir)"
|
||||||
# Optional: it needs a DVX tree of your own, so `all` and `toolchains` leave
|
# Optional: it needs a DVX tree of your own, so `all` and `toolchains` leave
|
||||||
# it out; `install dvx` (or toolchain:dvx) asks for it.
|
# it out; `install dvx` (or toolchain:dvx) asks for it.
|
||||||
compGroups="toolchain:dvx optional"
|
compGroups="toolchain:dvx optional"
|
||||||
compRequires="host djgpp"
|
compRequires="host djgpp"
|
||||||
compHostDeps=""
|
compHostDeps="unzip"
|
||||||
|
|
||||||
dvxDir="$(cat "$JOEY_STATE/dvxDir" 2>/dev/null || true)"
|
dvxDir="$(cat "$JOEY_STATE/dvxDir" 2>/dev/null || true)"
|
||||||
if [ -z "$dvxDir" ]; then
|
if [ -z "$dvxDir" ]; then
|
||||||
dvxDir="$(cd "$JOEY_REPO/.." && pwd)/dvxgui"
|
dvxDir="$(cd "$JOEY_REPO/.." && pwd)/dvxgui"
|
||||||
fi
|
fi
|
||||||
dvxBin="$dvxDir/bin"
|
dvxRuntimeZip="$dvxDir/dist/dvx.zip"
|
||||||
# What the runtime needs from bin/: not the host test tools (bin/host),
|
dvxSdkZip="$dvxDir/dist/sdk.zip"
|
||||||
# the test modules, the link map, the log or the installer.
|
# The folder sdk.zip holds the SDK in.
|
||||||
dvxRuntime="dvx.exe config system libs widgets apps"
|
dvxSdkZipRoot="DVX-SDK"
|
||||||
|
# The SDK as DVX's build leaves it: used only when dist/ has no sdk.zip.
|
||||||
|
dvxSdkTree="$dvxDir/bin/sdk"
|
||||||
|
# Where the SDK goes in the install.
|
||||||
dvxSdk="sdk"
|
dvxSdk="sdk"
|
||||||
|
|
||||||
|
|
||||||
# dvxListing -- every file copied, with its size and time: the pin, so a
|
# dvxSdkPin -- what the SDK comes from: sdk.zip's checksum, or without it
|
||||||
# rebuilt DVX makes this component out of date.
|
# every file of bin/sdk with its size and time.
|
||||||
dvxListing() {
|
dvxSdkPin() {
|
||||||
local part
|
if [ -f "$dvxSdkZip" ]; then
|
||||||
[ -d "$dvxBin" ] || return 0
|
printf 'zip:%s\n' "$(sha256Of "$dvxSdkZip")"
|
||||||
for part in $dvxRuntime $dvxSdk; do
|
elif [ -d "$dvxSdkTree" ]; then
|
||||||
[ -e "$dvxBin/$part" ] || continue
|
printf 'tree:%s\n' "$(find "$dvxSdkTree" -type f -printf '%P %s %T@\n' | sort | sha1sum | cut -d' ' -f1)"
|
||||||
find "$dvxBin/$part" -type f -printf '%P %s %T@\n'
|
fi
|
||||||
done | sort
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compPin="dir:$dvxDir:$(dvxListing | sha1sum | cut -d' ' -f1)"
|
compPin="dir:$dvxDir:dvx:$([ -f "$dvxRuntimeZip" ] && sha256Of "$dvxRuntimeZip"):sdk:$(dvxSdkPin)"
|
||||||
|
|
||||||
|
|
||||||
compInstall() {
|
compInstall() {
|
||||||
local part
|
local tmp
|
||||||
[ -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 "$dvxRuntimeZip" ] || die "$dvxDir has no dist/dvx.zip; make DVX's distribution there, or point --dvx-dir at a DVX tree that has one"
|
||||||
[ -f "$dvxBin/$dvxSdk/sdk.mk" ] || die "$dvxDir has no SDK (bin/sdk/sdk.mk); build DVX there first"
|
|
||||||
rm -rf "$compDir"
|
rm -rf "$compDir"
|
||||||
mkdir -p "$compDir"
|
extractArchive "$dvxRuntimeZip" "$compDir"
|
||||||
for part in $dvxRuntime $dvxSdk; do
|
if [ -f "$dvxSdkZip" ]; then
|
||||||
[ -e "$dvxBin/$part" ] || die "$dvxBin/$part is missing; is the DVX build complete?"
|
tmp="$(freshBuildDir "$compId")"
|
||||||
cp -a "$dvxBin/$part" "$compDir/"
|
extractArchive "$dvxSdkZip" "$tmp"
|
||||||
done
|
[ -f "$tmp/$dvxSdkZipRoot/sdk.mk" ] || die "$dvxSdkZip has no $dvxSdkZipRoot/sdk.mk"
|
||||||
|
mv "$tmp/$dvxSdkZipRoot" "$compDir/$dvxSdk"
|
||||||
|
dropBuildDir "$compId"
|
||||||
|
else
|
||||||
|
[ -f "$dvxSdkTree/sdk.mk" ] || die "$dvxDir has neither dist/sdk.zip nor a built SDK (bin/sdk/sdk.mk)"
|
||||||
|
warn "$dvxDir has no dist/sdk.zip: copying its built SDK (bin/sdk) instead"
|
||||||
|
cp -a "$dvxSdkTree" "$compDir/$dvxSdk"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,8 +75,8 @@ compInstalled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Build a one-window DVX program with the copied SDK and JoeyLib's DJGPP:
|
# Build a one-window DVX program with the SDK and JoeyLib's DJGPP: proves the
|
||||||
# proves the SDK, its host tools and the compiler work together.
|
# SDK, its host tools and the compiler work together.
|
||||||
compVerify() {
|
compVerify() {
|
||||||
local tmp
|
local tmp
|
||||||
local status=1
|
local status=1
|
||||||
|
|
|
||||||
12
joey-run
12
joey-run
|
|
@ -48,11 +48,13 @@
|
||||||
# unit 0) and DOS's (and DVX's) COM1.
|
# unit 0) and DOS's (and DVX's) COM1.
|
||||||
#
|
#
|
||||||
# A DVX program runs in DOSBox Staging on a copy of the DVX the installer
|
# 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
|
# unpacked from a DVX tree's dist/dvx.zip (install dvx), which boots
|
||||||
# program is DVX's desktop program, in its window on a 1024x768 desktop
|
# straight into it: the program is DVX's desktop program, in its window on a
|
||||||
# (640x480 with --headless). Interactive runs keep that DVX in
|
# 1024x768 desktop (640x480 with --headless). Game > Full Screen, or Alt
|
||||||
# build/dvx/run, so what the program saves (in DVX's CONFIG folder) is
|
# with the keypad's Enter (DOSBox keeps Alt+Enter for its own full screen),
|
||||||
# still there next time. The link is raw TCP, without telnet negotiation.
|
# puts it on the whole screen. Interactive runs keep that DVX in
|
||||||
|
# build/dvx/run, so what the program saves (in DVX's CONFIG folder), and
|
||||||
|
# whether it was left on the whole screen, 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
|
||||||
|
|
|
||||||
13
make/dvx.mk
13
make/dvx.mk
|
|
@ -1,10 +1,11 @@
|
||||||
# DVX build rules: every example as a DVX program (.app).
|
# 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
|
# A JoeyLib program on DVX runs in a window of the DVX desktop, or on the
|
||||||
# library, libjoey.a, is built here with JoeyLib's own DJGPP (DVX is built
|
# whole screen (src/dvx). The library, libjoey.a, is built here with
|
||||||
# with the same gcc 12.2.0) against the SDK the installer copied from a DVX
|
# JoeyLib's own DJGPP (DVX is built with the same gcc 12.2.0) against the SDK
|
||||||
# tree into toolchains/opt/dvx (DVX_SDK); the stage and the graphics are the
|
# the installer unpacked from a DVX tree's dist/sdk.zip into
|
||||||
# DOS port's (src/x86, the chunky generics). Each example is then a DVX SDK
|
# toolchains/opt/dvx/sdk (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
|
# 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
|
# 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,
|
# resource list -- built and packaged by the SDK's own sdk.mk (KIND = app,
|
||||||
|
|
@ -207,6 +208,8 @@ app-$(1): $(LIB) $$($(1)_SRCS) $(PORT_ENTRY) $$(if $$($(1)_DATA),$(1)-data)
|
||||||
'$$($(1)_HELP)' \
|
'$$($(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.' \
|
'$$($(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.' \
|
||||||
|
'' \
|
||||||
|
'Game > Full Screen, or Alt+Enter, puts it on the whole screen at 320x200 in 256 colours, and back in its window. It starts the way it was left. Alt+Tab or Ctrl+Esc brings the desktop back while it runs on; coming back to it gives it the whole screen again.' \
|
||||||
| $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(1)/$(1).dhs
|
| $(REPO_DIR)/make/support/dvx/writeIfChanged.sh $(APPSDIR)/$(1)/$(1).dhs
|
||||||
@printf '%s\n' \
|
@printf '%s\n' \
|
||||||
'name text "$$($(1)_TITLE)"' \
|
'name text "$$($(1)_TITLE)"' \
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,14 @@
|
||||||
# The ops/sec are the emulated machine's at DOSBox's full host speed, not a
|
# 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.
|
# reference machine's: compare them only run against run.
|
||||||
#
|
#
|
||||||
# scripts/bench-dvx.sh # default 600 s timeout
|
# --full-screen runs UBER on the whole screen (the port's full-screen
|
||||||
# TIMEOUT=900 scripts/bench-dvx.sh # override
|
# setting staged as Game > Full Screen leaves it, DVX's exclusive mode at
|
||||||
|
# 320x200x8, VGA mode 13h here), and fails unless DVX.LOG says the mode was
|
||||||
|
# set: the same hashes, the full-screen present's timings.
|
||||||
|
#
|
||||||
|
# scripts/bench-dvx.sh # default 600 s timeout
|
||||||
|
# scripts/bench-dvx.sh --full-screen # on the whole screen
|
||||||
|
# TIMEOUT=900 scripts/bench-dvx.sh # override
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -24,6 +30,13 @@ source "$repo/scripts/lib/dvxGate.sh"
|
||||||
joeyNeed DOSBOX dosbox
|
joeyNeed DOSBOX dosbox
|
||||||
joeyNeed DVX_ROOT dvx
|
joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
|
fullScreen=0
|
||||||
|
if [ "${1:-}" = "--full-screen" ]; then
|
||||||
|
fullScreen=1
|
||||||
|
elif [ $# -gt 0 ]; then
|
||||||
|
echo "usage: scripts/bench-dvx.sh [--full-screen]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
timeoutSeconds=${TIMEOUT:-600}
|
timeoutSeconds=${TIMEOUT:-600}
|
||||||
# The emulator's hard cap, a little past the wait, whatever this script does.
|
# The emulator's hard cap, a little past the wait, whatever this script does.
|
||||||
wallSeconds=$((timeoutSeconds + 30))
|
wallSeconds=$((timeoutSeconds + 30))
|
||||||
|
|
@ -44,6 +57,9 @@ cleanup() {
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
dvxStage "$work/c" "$(dvxAppDir uber)"
|
dvxStage "$work/c" "$(dvxAppDir uber)"
|
||||||
|
if [ "$fullScreen" = 1 ]; then
|
||||||
|
dvxFullScreen "$work/c" uber
|
||||||
|
fi
|
||||||
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
||||||
mkdir -p "$work/xdg"
|
mkdir -p "$work/xdg"
|
||||||
rm -f "$out"
|
rm -f "$out"
|
||||||
|
|
@ -76,6 +92,11 @@ joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
dosbox=""
|
dosbox=""
|
||||||
guard=""
|
guard=""
|
||||||
|
|
||||||
|
if [ "$fullScreen" = 1 ] && ! dvxDvxLog "$work/c" 200 | grep -q "$DVX_FULL_SCREEN_LOG"; then
|
||||||
|
echo "bench-dvx: FAIL -- --full-screen, but DVX.LOG has no \"$DVX_FULL_SCREEN_LOG\" (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
ops=$(grep -c "ops/sec" "$out" || true)
|
ops=$(grep -c "ops/sec" "$out" || true)
|
||||||
if ! grep -q "$doneLine" "$out"; then
|
if ! grep -q "$doneLine" "$out"; then
|
||||||
echo "bench-dvx: WARNING -- timed out before the completion line (partial capture)" >&2
|
echo "bench-dvx: WARNING -- timed out before the completion line (partial capture)" >&2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Shared by the DVX gates (scripts/verify-dvx-*.sh, scripts/bench-dvx.sh) and
|
# 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
|
# 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
|
# runtime the installer unpacked from DVX's dist/dvx.zip (toolchains/opt/dvx,
|
||||||
# its SDK) plus JoeyLib programs from build/dvx/bin -- whose DVX.INI makes
|
# 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
|
# 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
|
# Staging run of that folder as drive C:. Nothing here writes to the DVX
|
||||||
# project or to toolchains/opt/dvx. Source after joeyEnv.sh.
|
# project or to toolchains/opt/dvx. Source after joeyEnv.sh.
|
||||||
|
|
@ -11,6 +11,14 @@ DVX_BIN="$JOEY_REPO/build/dvx/bin"
|
||||||
# The folder a program installs in under apps/, and the name of its log.
|
# The folder a program installs in under apps/, and the name of its log.
|
||||||
DVX_VENDOR=joeylib
|
DVX_VENDOR=joeylib
|
||||||
DVX_LOG_NAME=joeylog.txt
|
DVX_LOG_NAME=joeylog.txt
|
||||||
|
# The port's own settings file in a program's settings folder, and its
|
||||||
|
# full-screen switch (src/dvx/hal.c).
|
||||||
|
DVX_SETTINGS_FILE=JOEYLIB.INI
|
||||||
|
DVX_SETTINGS_SECTION=display
|
||||||
|
DVX_SETTINGS_FULL_SCREEN=fullScreen
|
||||||
|
# DVX.LOG's line when a program's full-screen mode is set (the port asks for
|
||||||
|
# the stage's size at 8 bits a pixel).
|
||||||
|
DVX_FULL_SCREEN_LOG="Video: full screen 320x200 at 8 bpp"
|
||||||
# The runtime parts of toolchains/opt/dvx (all but the SDK).
|
# The runtime parts of toolchains/opt/dvx (all but the SDK).
|
||||||
DVX_RUNTIME="dvx.exe config system libs widgets apps"
|
DVX_RUNTIME="dvx.exe config system libs widgets apps"
|
||||||
# The desktop the gates run: 640x480 in 16-bit colour, DVX's own DOS test
|
# The desktop the gates run: 640x480 in 16-bit colour, DVX's own DOS test
|
||||||
|
|
@ -69,6 +77,21 @@ dvxStage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxFullScreen DIR PROGRAM -- have PROGRAM start on the whole screen in the
|
||||||
|
# DVX staged in DIR: the port's own setting (JOEYLIB.INI, [display]
|
||||||
|
# fullScreen, in the program's settings folder), as Game > Full Screen leaves
|
||||||
|
# it. DOS names are case-blind, so an existing folder of any case is used.
|
||||||
|
dvxFullScreen() {
|
||||||
|
local conf
|
||||||
|
conf="$(find "$1/DVX" -ipath "*/config/apps/$DVX_VENDOR/$2" -type d -print -quit 2>/dev/null)"
|
||||||
|
if [ -z "$conf" ]; then
|
||||||
|
conf="$1/DVX/config/apps/$DVX_VENDOR/$2"
|
||||||
|
mkdir -p "$conf"
|
||||||
|
fi
|
||||||
|
printf '[%s]\r\n%s = true\r\n' "$DVX_SETTINGS_SECTION" "$DVX_SETTINGS_FULL_SCREEN" > "$conf/$DVX_SETTINGS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# dvxIniSet INI DESKTOP -- DVX.INI for a run: DESKTOP as the desktop program,
|
# 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
|
# 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.
|
# recording gate must hear only the program). DOS line ends are kept.
|
||||||
|
|
@ -158,6 +181,18 @@ dvxAwaitLog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# dvxKeysTyped LOG TARGET -- KEYS's typed line in LOG (its joeylog.txt: the
|
||||||
|
# hex of every character it received) must be TARGET's bytes.
|
||||||
|
dvxKeysTyped() {
|
||||||
|
local typed
|
||||||
|
local want
|
||||||
|
typed="$(tr -d '\r' < "$1" | sed -n 's/^typed=//p' | tail -1)"
|
||||||
|
want="$(printf '%s' "$2" | od -An -tx1 | tr -d ' \n' | tr 'a-f' 'A-F')"
|
||||||
|
echo "$DVX_GATE: log typed=$typed"
|
||||||
|
[ "$typed" = "$want" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# dvxDvxLog DIR -- print the tail of the staged DVX's own log (DVX.LOG), the
|
# dvxDvxLog DIR -- print the tail of the staged DVX's own log (DVX.LOG), the
|
||||||
# place a program that failed to load is explained.
|
# place a program that failed to load is explained.
|
||||||
dvxDvxLog() {
|
dvxDvxLog() {
|
||||||
|
|
|
||||||
98
scripts/lib/keysRow.py
Executable file
98
scripts/lib/keysRow.py
Executable file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""keysRow.py - read KEYS's verification row off a screenshot.
|
||||||
|
|
||||||
|
KEYS (examples/keys) draws every character it received on its row 198 as
|
||||||
|
pixel nibbles: the sentinel A 5 A 5, the count byte, a zero byte, then each
|
||||||
|
character, two pixels a byte, high nibble first. This finds that row in a
|
||||||
|
screenshot (on a desktop the window is wherever DVX put it) and checks it
|
||||||
|
holds exactly TARGET.
|
||||||
|
|
||||||
|
keysRow.py PNG TARGET [--size WxH] [--at X,Y]
|
||||||
|
|
||||||
|
--size: the screenshot must be that size (the full-screen mode, not the
|
||||||
|
desktop). --at: the row must start exactly there (the stage's own row 198
|
||||||
|
at 1x on a full screen). Prints the row it found; exit 0 when it is right.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# KEYS palette 0 (buildPalette): a gray ramp with four demo colours and red
|
||||||
|
# at 15, 12-bit $0RGB, each channel widened x17 as the DVX 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
|
||||||
|
PAL_RGB = [(((v >> 8) & 0xF) * 17, ((v >> 4) & 0xF) * 17, (v & 0xF) * 17) for v in PAL12]
|
||||||
|
# A 16-bit desktop (5-6-5) is off by at most 8 a channel after the round
|
||||||
|
# trip, the 6-bit VGA DAC by at most 3, while the palette entries sit 17
|
||||||
|
# apart: nearest is exact.
|
||||||
|
MAX_DIST = 3 * 9 ** 2
|
||||||
|
SENTINEL = (0xA, 0x5, 0xA, 0x5)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
if len(argv) < 3:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
png, target = argv[1], argv[2]
|
||||||
|
size = None
|
||||||
|
at = None
|
||||||
|
args = argv[3:]
|
||||||
|
while args:
|
||||||
|
if args[0] == "--size" and len(args) > 1:
|
||||||
|
size = tuple(int(n) for n in args[1].split("x"))
|
||||||
|
elif args[0] == "--at" and len(args) > 1:
|
||||||
|
at = tuple(int(n) for n in args[1].split(","))
|
||||||
|
else:
|
||||||
|
sys.stderr.write(__doc__)
|
||||||
|
return 2
|
||||||
|
args = args[2:]
|
||||||
|
|
||||||
|
img = Image.open(png).convert("RGB")
|
||||||
|
w, h = img.size
|
||||||
|
px = img.load()
|
||||||
|
if size is not None and img.size != size:
|
||||||
|
print("keysRow: FAIL - %s is %dx%d, want %dx%d" % (png, w, h, size[0], size[1]))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
nearest = {}
|
||||||
|
|
||||||
|
def nib(x, y):
|
||||||
|
rgb = px[x, y]
|
||||||
|
if rgb not in nearest:
|
||||||
|
best, bestDist = 0, 1 << 30
|
||||||
|
for n, pal in enumerate(PAL_RGB):
|
||||||
|
d = sum((c - p) ** 2 for c, p in zip(rgb, pal))
|
||||||
|
if d < bestDist:
|
||||||
|
best, bestDist = n, d
|
||||||
|
nearest[rgb] = best if bestDist <= MAX_DIST else -1
|
||||||
|
return nearest[rgb]
|
||||||
|
|
||||||
|
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 tuple(nib(x + i, y) for i in range(len(SENTINEL))) == SENTINEL:
|
||||||
|
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:
|
||||||
|
print("keysRow: FAIL - no verification row (A5A5 %02X00) on the screen" % len(target))
|
||||||
|
return 1
|
||||||
|
x, y, row = found
|
||||||
|
print("keysRow: row at (%d,%d)=%s" % (x, y, row.hex().upper()))
|
||||||
|
if row != expect:
|
||||||
|
print("keysRow: FAIL - screen row %s, want %s" % (row.hex().upper(), expect.hex().upper()))
|
||||||
|
return 1
|
||||||
|
if at is not None and (x, y) != at:
|
||||||
|
print("keysRow: FAIL - the row is at (%d,%d), want (%d,%d)" % (x, y, at[0], at[1]))
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
|
|
@ -13,6 +13,17 @@ works too).
|
||||||
|
|
||||||
WALL is the wall-clock budget in seconds. Prints one LOOP PASS / LOOP FAIL
|
WALL is the wall-clock budget in seconds. Prints one LOOP PASS / LOOP FAIL
|
||||||
line; exit 0 on pass, 1 on fail.
|
line; exit 0 on pass, 1 on fail.
|
||||||
|
|
||||||
|
--dtr-check (TCP, with DOSBox's nullmodem in transparent:0 mode, which puts
|
||||||
|
the guest's modem lines in the byte stream as they change: 0xFF 0xFF is a
|
||||||
|
data 0xFF, 0xFF <b> the lines, bit 0 RTS, bit 1 DTR) then runs SERTEST's
|
||||||
|
DTR check: it sends DTR_CHECK, acknowledges each of SERTEST's marker lines
|
||||||
|
(so SERTEST changes a line only once the marker before has gone out whole),
|
||||||
|
and reads the line states against the markers. jlSerialSetDtr(false) and
|
||||||
|
(true) must reach the line, closing and opening the port again must keep
|
||||||
|
DTR up, and after a hang-up (DTR dropped, then the port closed) DTR must
|
||||||
|
stay down for HANGUP_WATCH_SECONDS. Prints one DTR PASS / DTR FAIL line
|
||||||
|
more.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
@ -30,6 +41,59 @@ CONNECT_RETRY_SECONDS = 0.5
|
||||||
READ_SIZE = 256
|
READ_SIZE = 256
|
||||||
SHOWN_BYTES = 80
|
SHOWN_BYTES = 80
|
||||||
|
|
||||||
|
# SERTEST's DTR check (examples/sertest/sertest.c).
|
||||||
|
DTR_CHECK = b"\x04"
|
||||||
|
DTR_ACK = b"\x06"
|
||||||
|
MARKS = (("off", b"JLDTR-OFF\r\n"), ("on", b"JLDTR-ON\r\n"), ("reopened", b"JLDTR-REOPENED\r\n"), ("hangup", b"JLDTR-HANGUP\r\n"))
|
||||||
|
HANGUP_WATCH_SECONDS = 3.0
|
||||||
|
|
||||||
|
# DOSBox nullmodem transparent:0 framing.
|
||||||
|
LINE_ESCAPE = 0xFF
|
||||||
|
LINE_DTR = 0x02
|
||||||
|
|
||||||
|
|
||||||
|
class LineDecoder:
|
||||||
|
"""Splits a transparent:0 stream into data and the guest's DTR states,
|
||||||
|
each state kept with the count of data bytes that came before it."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.pending = b""
|
||||||
|
self.dataCount = 0
|
||||||
|
self.dtr = []
|
||||||
|
|
||||||
|
def feed(self, raw):
|
||||||
|
buf = self.pending + raw
|
||||||
|
data = bytearray()
|
||||||
|
i = 0
|
||||||
|
while i < len(buf):
|
||||||
|
if buf[i] != LINE_ESCAPE:
|
||||||
|
data.append(buf[i])
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if i + 1 >= len(buf):
|
||||||
|
break
|
||||||
|
if buf[i + 1] == LINE_ESCAPE:
|
||||||
|
data.append(LINE_ESCAPE)
|
||||||
|
else:
|
||||||
|
self.dtr.append((self.dataCount + len(data), (buf[i + 1] & LINE_DTR) != 0))
|
||||||
|
i += 2
|
||||||
|
self.pending = buf[i:]
|
||||||
|
self.dataCount += len(data)
|
||||||
|
return bytes(data)
|
||||||
|
|
||||||
|
def dtrBefore(self, position):
|
||||||
|
"""DTR as it was when the data byte at position arrived (None: never reported)."""
|
||||||
|
state = None
|
||||||
|
for at, dtr in self.dtr:
|
||||||
|
if at > position:
|
||||||
|
break
|
||||||
|
state = dtr
|
||||||
|
return state
|
||||||
|
|
||||||
|
def dtrChanges(self, first, last, value):
|
||||||
|
"""Whether DTR was reported as value after data byte first, up to last."""
|
||||||
|
return any(first < at <= last and dtr == value for at, dtr in self.dtr)
|
||||||
|
|
||||||
|
|
||||||
def acceptOne(port, wall):
|
def acceptOne(port, wall):
|
||||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
@ -64,8 +128,57 @@ def openPty(pathFile):
|
||||||
return master
|
return master
|
||||||
|
|
||||||
|
|
||||||
|
def dtrCheck(send, receive, wall, start, lines, got):
|
||||||
|
# After the loopback: the stream so far is got; lines has decoded it.
|
||||||
|
base = len(got)
|
||||||
|
send(DTR_CHECK)
|
||||||
|
marks = {}
|
||||||
|
hangupAt = None
|
||||||
|
while time.time() - start < wall:
|
||||||
|
if hangupAt is not None and time.time() - hangupAt >= HANGUP_WATCH_SECONDS:
|
||||||
|
break
|
||||||
|
data = receive()
|
||||||
|
if data is None:
|
||||||
|
break
|
||||||
|
got += data
|
||||||
|
# Acknowledge each marker line as it completes; the last starts the
|
||||||
|
# watch.
|
||||||
|
while len(marks) < len(MARKS):
|
||||||
|
name, mark = MARKS[len(marks)]
|
||||||
|
at = got.find(mark, base)
|
||||||
|
if at < 0:
|
||||||
|
break
|
||||||
|
marks[name] = at
|
||||||
|
send(DTR_ACK)
|
||||||
|
if hangupAt is None and len(marks) == len(MARKS):
|
||||||
|
hangupAt = time.time()
|
||||||
|
if len(marks) < len(MARKS):
|
||||||
|
print("DTR FAIL: no %r from SERTEST; stream=%r" % (MARKS[len(marks)][1], got[base:base + SHOWN_BYTES]))
|
||||||
|
return 1
|
||||||
|
if time.time() - hangupAt < HANGUP_WATCH_SECONDS:
|
||||||
|
print("DTR FAIL: out of time before the watch after the hang-up ended")
|
||||||
|
return 1
|
||||||
|
fails = []
|
||||||
|
if lines.dtrBefore(marks["off"]) is not False:
|
||||||
|
fails.append("jlSerialSetDtr(false) did not drop DTR")
|
||||||
|
if lines.dtrBefore(marks["on"]) is not True:
|
||||||
|
fails.append("jlSerialSetDtr(true) did not raise DTR")
|
||||||
|
if lines.dtrChanges(marks["on"], marks["reopened"], False):
|
||||||
|
fails.append("closing and opening the port dropped DTR")
|
||||||
|
if lines.dtrBefore(marks["hangup"]) is not False:
|
||||||
|
fails.append("jlSerialSetDtr(false) after the reopen did not drop DTR")
|
||||||
|
if lines.dtrChanges(marks["hangup"], lines.dataCount, True):
|
||||||
|
fails.append("closing the port after the hang-up raised DTR")
|
||||||
|
if fails:
|
||||||
|
print("DTR FAIL: %s; DTR states (data byte, DTR) since the check: %r" % ("; ".join(fails), [x for x in lines.dtr if x[0] >= base]))
|
||||||
|
return 1
|
||||||
|
print("DTR PASS: SetDtr(false/true) reached the line, close+open kept DTR up, a hang-up stayed down %.0f s after the close" % HANGUP_WATCH_SECONDS)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def loop(send, receive, wall, start, banner):
|
def loop(send, receive, wall, start, banner):
|
||||||
# send(bytes); receive() -> bytes, b"" for "nothing yet", None at EOF.
|
# send(bytes); receive() -> bytes, b"" for "nothing yet", None at EOF.
|
||||||
|
# Returns the exit status and the stream.
|
||||||
got = b""
|
got = b""
|
||||||
sentAt = None
|
sentAt = None
|
||||||
lastSend = 0.0
|
lastSend = 0.0
|
||||||
|
|
@ -81,13 +194,15 @@ def loop(send, receive, wall, start, banner):
|
||||||
got += data
|
got += data
|
||||||
if sentAt is not None and PROBE in got[sentAt:]:
|
if sentAt is not None and PROBE in got[sentAt:]:
|
||||||
print("LOOP PASS: banner %r seen, probe echoed; stream=%r" % (banner, got[:SHOWN_BYTES]))
|
print("LOOP PASS: banner %r seen, probe echoed; stream=%r" % (banner, got[:SHOWN_BYTES]))
|
||||||
return 0
|
return 0, got
|
||||||
print("LOOP FAIL: stream=%r" % got[:SHOWN_BYTES + 40])
|
print("LOOP FAIL: stream=%r" % got[:SHOWN_BYTES + 40])
|
||||||
return 1
|
return 1, got
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
if len(argv) < 4 or argv[1] not in ("tcp-server", "tcp-client", "pty"):
|
withDtrCheck = "--dtr-check" in argv
|
||||||
|
argv = [arg for arg in argv if arg != "--dtr-check"]
|
||||||
|
if len(argv) < 4 or argv[1] not in ("tcp-server", "tcp-client", "pty") or (withDtrCheck and argv[1] == "pty"):
|
||||||
sys.stderr.write(__doc__)
|
sys.stderr.write(__doc__)
|
||||||
return 2
|
return 2
|
||||||
mode = argv[1]
|
mode = argv[1]
|
||||||
|
|
@ -107,7 +222,7 @@ def main(argv):
|
||||||
except OSError:
|
except OSError:
|
||||||
return b""
|
return b""
|
||||||
|
|
||||||
return loop(lambda data: os.write(master, data), receive, wall, start, banner)
|
return loop(lambda data: os.write(master, data), receive, wall, start, banner)[0]
|
||||||
|
|
||||||
port = int(argv[2])
|
port = int(argv[2])
|
||||||
conn = acceptOne(port, wall) if mode == "tcp-server" else connectOne(port, wall, start)
|
conn = acceptOne(port, wall) if mode == "tcp-server" else connectOne(port, wall, start)
|
||||||
|
|
@ -116,6 +231,8 @@ def main(argv):
|
||||||
return 1
|
return 1
|
||||||
conn.settimeout(POLL_SECONDS)
|
conn.settimeout(POLL_SECONDS)
|
||||||
|
|
||||||
|
lines = LineDecoder()
|
||||||
|
|
||||||
def receive():
|
def receive():
|
||||||
try:
|
try:
|
||||||
data = conn.recv(READ_SIZE)
|
data = conn.recv(READ_SIZE)
|
||||||
|
|
@ -123,9 +240,14 @@ def main(argv):
|
||||||
return b""
|
return b""
|
||||||
except OSError:
|
except OSError:
|
||||||
return None
|
return None
|
||||||
return data if data else None
|
if not data:
|
||||||
|
return None
|
||||||
|
return lines.feed(data) if withDtrCheck else data
|
||||||
|
|
||||||
return loop(conn.sendall, receive, wall, start, banner)
|
status, got = loop(conn.sendall, receive, wall, start, banner)
|
||||||
|
if status != 0 or not withDtrCheck:
|
||||||
|
return status
|
||||||
|
return dtrCheck(conn.sendall, receive, wall, start, lines, got)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
# 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)
|
# 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
|
||||||
|
# dvx: full screen -- UBER's hashes through a suspend and a
|
||||||
|
# resume, and KEYS's row read off the 320x200 VGA screen, then
|
||||||
|
# in its window, then on the whole screen again
|
||||||
# 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
|
||||||
|
|
@ -97,6 +100,7 @@ golden amiga uber TIMEOUT=$AMIGA_UBER_SECONDS scripts/bench-amiga.sh &&
|
||||||
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
|
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
|
||||||
|
visual dvx full scripts/verify-dvx-fullscreen.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
|
||||||
|
|
|
||||||
226
scripts/verify-dvx-fullscreen.sh
Executable file
226
scripts/verify-dvx-fullscreen.sh
Executable file
|
|
@ -0,0 +1,226 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-dvx-fullscreen.sh - Full-screen gate for DVX: a program on the whole
|
||||||
|
# screen in DVX's exclusive mode (src/dvx/hal.c), started there by the port's
|
||||||
|
# own setting as Game > Full Screen leaves it (dvxFullScreen), and read back
|
||||||
|
# from DOSBox's raw screenshots of the VGA screen. Two runs of a scratch DVX
|
||||||
|
# in DOSBox Staging under Xvfb:
|
||||||
|
# 1. UBER on the whole screen, suspended part way (Ctrl+Esc: DVX's Task
|
||||||
|
# Manager takes the focus, the desktop comes back and UBER presents into
|
||||||
|
# its window) and resumed (the Task Manager's Switch To): the
|
||||||
|
# screenshots must be the 320x200 mode, the desktop, then 320x200 again;
|
||||||
|
# DVX.LOG must show the mode set twice; and UBER's 51 hashes must equal
|
||||||
|
# the DOS port's (tests/goldens/uber/dos.txt): the stage is DOS's
|
||||||
|
# wherever it is shown.
|
||||||
|
# 2. KEYS on the whole screen: the typed string must reach jlInputGetChar
|
||||||
|
# (its log), and its verification row must read back off the 320x200
|
||||||
|
# screen at exactly the stage's own (0,198) (scripts/lib/keysRow.py):
|
||||||
|
# the full-screen present, its palette and its placement. Alt+keypad
|
||||||
|
# Enter (DOSBox keeps Alt+Enter for its own full screen) takes KEYS back
|
||||||
|
# to its window, where the row must be found on the desktop; the same
|
||||||
|
# keys again, and the row must be at (0,198) once more. The setting must
|
||||||
|
# still say full screen.
|
||||||
|
#
|
||||||
|
# Usage: scripts/verify-dvx-fullscreen.sh
|
||||||
|
# Env: DVX_FULLSCREEN_SHOTS also copy the screenshots into this folder
|
||||||
|
# Requires: DOSBox Staging, Xvfb, xdotool, python3 + PIL, the dvx component;
|
||||||
|
# the uber and keys programs (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
|
||||||
|
|
||||||
|
target="192.168.1.10:6510"
|
||||||
|
wall=180 # hard cap on each DOSBox run, whatever happens to this script
|
||||||
|
readySeconds=60 # DVX boot until its shell reports ready
|
||||||
|
settleSeconds=3 # then the program taking the screen
|
||||||
|
stepSeconds=2 # a key, then DVX and the program acting on it
|
||||||
|
shotSeconds=5 # Ctrl+F5 until DOSBox has written the screenshot
|
||||||
|
holdSeconds=0.3 # a key held that long: a quick tap can fall between two polls
|
||||||
|
uberSeconds=120 # UBER's run until its done line
|
||||||
|
logSeconds=30 # Escape until KEYS's typed line is in its log
|
||||||
|
fullSize=320x200 # DVX's full-screen mode for the stage: VGA mode 13h here
|
||||||
|
desktopSize="${DVX_VIDEO_W}x${DVX_VIDEO_H}"
|
||||||
|
rowAt=0,198 # KEYS's verification row at 1x: its place on the stage
|
||||||
|
uberDone="press any key to exit"
|
||||||
|
|
||||||
|
dvxNeedBuild uber keys
|
||||||
|
|
||||||
|
work=$(mktemp -d -t joeylib-dvx-fullscreen.XXXXXX)
|
||||||
|
guard=""
|
||||||
|
dosbox=""
|
||||||
|
xvfb=""
|
||||||
|
win=""
|
||||||
|
fails=""
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
# boot PROGRAM -- DVX staged in $work/PROGRAM with PROGRAM its desktop, on the
|
||||||
|
# whole screen from the start; sets guard, dosbox and win.
|
||||||
|
boot() {
|
||||||
|
local dir="$work/$1"
|
||||||
|
local waited
|
||||||
|
dvxStage "$dir/c" "$(dvxAppDir "$1")"
|
||||||
|
dvxFullScreen "$dir/c" "$1"
|
||||||
|
dvxDosboxConf "$dir/dosbox.conf" "$dir/c"
|
||||||
|
printf '\n[capture]\ncapture_dir = %s\ndefault_image_capture_formats = raw\n' "$dir/shots" >> "$dir/dosbox.conf"
|
||||||
|
mkdir -p "$dir/xdg" "$dir/shots"
|
||||||
|
XDG_CONFIG_HOME="$dir/xdg" DISPLAY="$display" SDL_AUDIODRIVER=dummy \
|
||||||
|
timeout -s KILL "$wall" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
|
||||||
|
--conf "$dir/dosbox.conf" > "$dir/dosbox.log" 2>&1 &
|
||||||
|
guard=$!
|
||||||
|
dosbox=$(joeyChild "$guard")
|
||||||
|
for ((waited = 0; waited < readySeconds; waited++)); do
|
||||||
|
if dvxDvxLog "$dir/c" 200 | grep -q "DVX Shell ready"; then
|
||||||
|
sleep "$settleSeconds"
|
||||||
|
win=$(joeyFindWindow "$display" "$dosbox")
|
||||||
|
[ -n "$win" ] || return 1
|
||||||
|
DISPLAY="$display" xdotool windowactivate --sync "$win" 2> /dev/null || true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
joeyAlive "$guard" || return 1
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# fail WHAT -- note a failed check; the gate goes on to report the rest.
|
||||||
|
fail() {
|
||||||
|
echo "verify-dvx-fullscreen: FAIL - $1"
|
||||||
|
fails="${fails:+$fails; }$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# fullScreenSets PROGRAM -- how often DVX.LOG says the full-screen mode was set.
|
||||||
|
fullScreenSets() {
|
||||||
|
dvxDvxLog "$work/$1/c" 400 | grep -c "$DVX_FULL_SCREEN_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# hold KEY... -- press the keys in order, each held holdSeconds before the
|
||||||
|
# next (a chord's modifier goes down first), release them in reverse, then
|
||||||
|
# give DVX and the program time to act.
|
||||||
|
hold() {
|
||||||
|
local k
|
||||||
|
local i
|
||||||
|
for k in "$@"; do
|
||||||
|
DISPLAY="$display" xdotool keydown --window "$win" "$k"
|
||||||
|
sleep "$holdSeconds"
|
||||||
|
done
|
||||||
|
for ((i = $#; i >= 1; i--)); do
|
||||||
|
DISPLAY="$display" xdotool keyup --window "$win" "${!i}"
|
||||||
|
done
|
||||||
|
sleep "$stepSeconds"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# shot PROGRAM NAME SIZE -- a DOSBox screenshot (Ctrl+F5: the raw VGA picture)
|
||||||
|
# as $work/NAME.png, which must be SIZE.
|
||||||
|
shot() {
|
||||||
|
local png
|
||||||
|
local size
|
||||||
|
local waited
|
||||||
|
DISPLAY="$display" xdotool key --window "$win" ctrl+F5
|
||||||
|
for ((waited = 0; waited < shotSeconds; waited++)); do
|
||||||
|
sleep 1
|
||||||
|
png=$(ls "$work/$1/shots/"*.png 2> /dev/null | head -1)
|
||||||
|
if [ -n "$png" ]; then
|
||||||
|
mv "$png" "$work/$2.png"
|
||||||
|
size=$(python3 -c 'import sys; from PIL import Image; print("%dx%d" % Image.open(sys.argv[1]).size)' "$work/$2.png")
|
||||||
|
echo "verify-dvx-fullscreen: $2: $size"
|
||||||
|
[ "$size" = "$3" ] || fail "$2 is $size, want $3"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fail "no screenshot for $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# stop -- end the DOSBox run.
|
||||||
|
stop() {
|
||||||
|
joeyStop "$dosbox" "$guard" 2> /dev/null
|
||||||
|
dosbox=""
|
||||||
|
guard=""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
display=":$((RANDOM % 400 + 3700))"
|
||||||
|
Xvfb "$display" -screen 0 1024x768x24 -nolisten tcp > /dev/null 2>&1 &
|
||||||
|
xvfb=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# 1. UBER: the hashes on the whole screen, through a suspend and a resume.
|
||||||
|
if ! boot uber; then
|
||||||
|
echo "verify-dvx-fullscreen: FAIL - DVX with UBER did not come up (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/uber/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shot uber uberFull "$fullSize"
|
||||||
|
hold ctrl Escape
|
||||||
|
shot uber uberSuspended "$desktopSize"
|
||||||
|
hold alt s
|
||||||
|
shot uber uberResumed "$fullSize"
|
||||||
|
log=$(dvxAwaitLog "$work/uber/c" uber "$uberDone" "$uberSeconds" "$guard")
|
||||||
|
sets=$(fullScreenSets uber)
|
||||||
|
stop
|
||||||
|
echo "verify-dvx-fullscreen: UBER: full-screen mode set $sets times"
|
||||||
|
[ "$sets" -ge 2 ] || fail "UBER: DVX set the full-screen mode $sets times, want 2 (start, resume)"
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
fail "UBER wrote no \"$uberDone\" within ${uberSeconds}s"
|
||||||
|
else
|
||||||
|
tr -d '\r' < "$log" > "$work/uber.txt"
|
||||||
|
if hashes=$("$repo/tools/diff-uber-hashes" "$repo/tests/goldens/uber/dos.txt" "$work/uber.txt"); then
|
||||||
|
echo "verify-dvx-fullscreen: UBER hashes: $(printf '%s\n' "$hashes" | tail -1)"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$hashes"
|
||||||
|
fail "UBER's hashes on the whole screen differ from the DOS port's"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. KEYS: the typed row read off the screen, full screen, window, full again.
|
||||||
|
if ! boot keys; then
|
||||||
|
echo "verify-dvx-fullscreen: FAIL - DVX with KEYS did not come up (DVX.LOG follows)" >&2
|
||||||
|
dvxDvxLog "$work/keys/c" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DISPLAY="$display" xdotool type --window "$win" --delay 150 -- "$target"
|
||||||
|
sleep "$stepSeconds"
|
||||||
|
shot keys keysFull "$fullSize"
|
||||||
|
python3 "$repo/scripts/lib/keysRow.py" "$work/keysFull.png" "$target" --at "$rowAt" || fail "KEYS's row on the whole screen"
|
||||||
|
hold alt KP_Enter
|
||||||
|
shot keys keysWindow "$desktopSize"
|
||||||
|
python3 "$repo/scripts/lib/keysRow.py" "$work/keysWindow.png" "$target" || fail "KEYS's row in its window after leaving the whole screen"
|
||||||
|
hold alt KP_Enter
|
||||||
|
shot keys keysFullAgain "$fullSize"
|
||||||
|
python3 "$repo/scripts/lib/keysRow.py" "$work/keysFullAgain.png" "$target" --at "$rowAt" || fail "KEYS's row on the whole screen again"
|
||||||
|
hold Escape
|
||||||
|
log=$(dvxAwaitLog "$work/keys/c" keys '^typed=' "$logSeconds" "$guard")
|
||||||
|
sets=$(fullScreenSets keys)
|
||||||
|
stop
|
||||||
|
echo "verify-dvx-fullscreen: KEYS: full-screen mode set $sets times"
|
||||||
|
[ "$sets" -ge 2 ] || fail "KEYS: DVX set the full-screen mode $sets times, want 2 (start, Alt+Enter)"
|
||||||
|
if [ -z "$log" ]; then
|
||||||
|
fail "KEYS logged no typed line within ${logSeconds}s of Escape"
|
||||||
|
else
|
||||||
|
dvxKeysTyped "$log" "$target" || fail "KEYS's typed log"
|
||||||
|
fi
|
||||||
|
setting=$(find "$work/keys/c/DVX" -ipath "*/config/apps/$DVX_VENDOR/keys/$DVX_SETTINGS_FILE" -print -quit)
|
||||||
|
tr -d '\r' < "$setting" 2> /dev/null | grep -qi "^$DVX_SETTINGS_FULL_SCREEN *= *true" || fail "the setting does not say full screen after switching back ($setting)"
|
||||||
|
|
||||||
|
if [ -n "${DVX_FULLSCREEN_SHOTS:-}" ]; then
|
||||||
|
mkdir -p "$DVX_FULLSCREEN_SHOTS"
|
||||||
|
cp "$work"/*.png "$DVX_FULLSCREEN_SHOTS/"
|
||||||
|
fi
|
||||||
|
if [ -n "$fails" ]; then
|
||||||
|
echo "verify-dvx-fullscreen: FAIL - $fails"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "verify-dvx-fullscreen: PASS (UBER 51/51 on the whole screen through a suspend and resume; KEYS's typed row read off the 320x200 screen at (0,198), in its window, and full screen again)"
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
# verification scanline (row 198: sentinel, count, every received
|
# verification scanline (row 198: sentinel, count, every received
|
||||||
# character as two pixel nibbles) somewhere -- the window's content,
|
# character as two pixel nibbles) somewhere -- the window's content,
|
||||||
# expanded from the stage through the port's palette tables into DVX's
|
# expanded from the stage through the port's palette tables into DVX's
|
||||||
# 16-bit desktop -- decoding to exactly the typed bytes;
|
# 16-bit desktop -- decoding to exactly the typed bytes
|
||||||
|
# (scripts/lib/keysRow.py);
|
||||||
# 2. the typed history: after Escape, KEYS logs every received character
|
# 2. the typed history: after Escape, KEYS logs every received character
|
||||||
# as hex to its joeylog.txt (in its settings folder), which must equal
|
# as hex to its joeylog.txt (in its settings folder), which must equal
|
||||||
# the typed ASCII.
|
# the typed ASCII.
|
||||||
|
|
@ -105,73 +106,11 @@ if [ -z "$log" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 - "$png" "$target" "$work/joeylog.txt" <<'PY'
|
fails=""
|
||||||
import sys
|
python3 "$repo/scripts/lib/keysRow.py" "$png" "$target" || fails="the present"
|
||||||
from PIL import Image
|
dvxKeysTyped "$work/joeylog.txt" "$target" || fails="${fails:+$fails, }the log"
|
||||||
|
if [ -n "$fails" ]; then
|
||||||
png, target, logPath = sys.argv[1], sys.argv[2], sys.argv[3]
|
echo "verify-dvx-input: FAIL - $fails"
|
||||||
fails = []
|
exit 1
|
||||||
|
fi
|
||||||
# KEYS palette 0 (buildPalette): a gray ramp with four demo colours and red
|
echo "verify-dvx-input: PASS (typed \"$target\": all ${#target} chars reached jlInputGetChar, and the present shows them)"
|
||||||
# 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
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@
|
||||||
# serial layer -> the DVX port (src/dvx/serial.c) -> DVX's rs232 library and
|
# serial layer -> the DVX port (src/dvx/serial.c) -> DVX's rs232 library and
|
||||||
# its interrupt -> DOSBox's UART -> host.
|
# its interrupt -> DOSBox's UART -> host.
|
||||||
#
|
#
|
||||||
|
# Then the DTR check (--dtr-check): the nullmodem runs non-transparent, so
|
||||||
|
# DOSBox reports the guest's modem lines to the peer in the byte stream, and
|
||||||
|
# SERTEST drops and raises DTR, closes and reopens the port, then hangs up
|
||||||
|
# (DTR dropped, port closed). jlSerialSetDtr must reach the line, and
|
||||||
|
# jlSerialClose must leave DTR as it is, up or down: DVX's serial library
|
||||||
|
# used to reset the modem lines when it closed a port.
|
||||||
|
#
|
||||||
# Usage: scripts/verify-dvx-serial.sh
|
# Usage: scripts/verify-dvx-serial.sh
|
||||||
# Env: DVX_SERIAL_PORT TCP port the peer listens on (default 6803)
|
# Env: DVX_SERIAL_PORT TCP port the peer listens on (default 6803)
|
||||||
# Requires: DOSBox Staging, Xvfb, python3, the dvx component; the sertest
|
# Requires: DOSBox Staging, Xvfb, python3, the dvx component; the sertest
|
||||||
|
|
@ -22,8 +29,8 @@ joeyNeed DVX_ROOT dvx
|
||||||
|
|
||||||
program=sertest
|
program=sertest
|
||||||
port="${DVX_SERIAL_PORT:-6803}"
|
port="${DVX_SERIAL_PORT:-6803}"
|
||||||
peerWall=90 # DVX boot, then the banner and the echo
|
peerWall=95 # DVX boot, the banner and the echo, then the DTR check
|
||||||
runSeconds=100 # SERTEST echoes forever: DOSBox ends by timeout
|
runSeconds=110 # SERTEST idles forever after the check: DOSBox ends by timeout
|
||||||
|
|
||||||
dvxNeedBuild "$program"
|
dvxNeedBuild "$program"
|
||||||
|
|
||||||
|
|
@ -41,10 +48,10 @@ trap cleanup EXIT
|
||||||
|
|
||||||
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
dvxStage "$work/c" "$(dvxAppDir "$program")"
|
||||||
dvxDosboxConf "$work/dosbox.conf" "$work/c"
|
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"
|
printf '\n[serial]\nserial1 = nullmodem server:127.0.0.1 port:%s transparent:0\n' "$port" >> "$work/dosbox.conf"
|
||||||
mkdir -p "$work/xdg"
|
mkdir -p "$work/xdg"
|
||||||
|
|
||||||
python3 "$repo/scripts/lib/serialLoopPeer.py" tcp-server "$port" "$peerWall" > "$work/peer.log" 2>&1 &
|
python3 "$repo/scripts/lib/serialLoopPeer.py" tcp-server "$port" "$peerWall" --dtr-check > "$work/peer.log" 2>&1 &
|
||||||
peerPid=$!
|
peerPid=$!
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
|
|
@ -67,7 +74,7 @@ dosbox=""
|
||||||
guard=""
|
guard=""
|
||||||
cat "$work/peer.log"
|
cat "$work/peer.log"
|
||||||
if [ "$rc" -eq 0 ]; then
|
if [ "$rc" -eq 0 ]; then
|
||||||
echo "verify-dvx-serial: PASS (banner out, probe echoed through COM1 and DVX's serial library)"
|
echo "verify-dvx-serial: PASS (banner out, probe echoed through COM1 and DVX's serial library; DTR follows jlSerialSetDtr and survives jlSerialClose)"
|
||||||
else
|
else
|
||||||
echo "verify-dvx-serial: FAIL (DVX.LOG follows)" >&2
|
echo "verify-dvx-serial: FAIL (DVX.LOG follows)" >&2
|
||||||
dvxDvxLog "$work/c" >&2
|
dvxDvxLog "$work/c" >&2
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
#include "surfaceInternal.h"
|
#include "surfaceInternal.h"
|
||||||
|
#include "x86Expand.h"
|
||||||
|
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
|
|
||||||
|
|
@ -39,9 +40,7 @@
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static void crashHandler(int sig);
|
static void crashHandler(int sig);
|
||||||
static void expandAndWriteLine(const jlSurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst);
|
|
||||||
static void installCrashLog(void);
|
static void installCrashLog(void);
|
||||||
static void rebuildExpandLut(uint8_t palBase);
|
|
||||||
static void uploadPalette(const jlSurfaceT *src);
|
static void uploadPalette(const jlSurfaceT *src);
|
||||||
static void uploadPaletteIfNeeded(const jlSurfaceT *src);
|
static void uploadPaletteIfNeeded(const jlSurfaceT *src);
|
||||||
|
|
||||||
|
|
@ -58,72 +57,8 @@ static FILE *gCrashLog = NULL;
|
||||||
// gStagePaletteDirty flag (surfaceInternal.h) says the stage palette
|
// gStagePaletteDirty flag (surfaceInternal.h) says the stage palette
|
||||||
// is unchanged -- see uploadPaletteIfNeeded.
|
// is unchanged -- see uploadPaletteIfNeeded.
|
||||||
|
|
||||||
// Expand LUT for jlpPresent. Maps a 4bpp-packed source byte to the
|
|
||||||
// 2-byte little-endian mode-13h output pair for the current scanline's
|
|
||||||
// SCB. The low byte (= dst[x], lower VGA address = LEFT pixel) holds
|
|
||||||
// the packed byte's HIGH nibble; the high byte (= dst[x+1] = RIGHT
|
|
||||||
// pixel) holds the LOW nibble. Storing lut[byte] as one 16-bit x86
|
|
||||||
// write therefore lands the left pixel at the lower address, matching
|
|
||||||
// the old two-byte sequence exactly. Rebuilt only when the SCB-derived
|
|
||||||
// palBase changes from the previous row (most stages run long runs of
|
|
||||||
// equal SCB), so the per-row cost is one compare, not a 256-entry fill.
|
|
||||||
static uint16_t gExpandLut[256];
|
|
||||||
static uint8_t gExpandLutBase = 0;
|
|
||||||
static bool gExpandLutValid = false;
|
|
||||||
|
|
||||||
// ----- Internal helpers (alphabetical) -----
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
static void expandAndWriteLine(const jlSurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst) {
|
|
||||||
uint8_t palBase;
|
|
||||||
const uint8_t *srcBytes;
|
|
||||||
uint32_t *out;
|
|
||||||
uint16_t quads;
|
|
||||||
uint16_t i;
|
|
||||||
|
|
||||||
palBase = (uint8_t)(src->scb[y] << 4);
|
|
||||||
if (!gExpandLutValid || palBase != gExpandLutBase) {
|
|
||||||
rebuildExpandLut(palBase);
|
|
||||||
}
|
|
||||||
// Dirty bands are chunky-word aligned (jlpPresent, the only
|
|
||||||
// caller, computes pixelX = minWord*4 and pixelW = words*4), so x
|
|
||||||
// and w are multiples of 4: &dst[x] is 32-bit aligned and the
|
|
||||||
// dword fusion below is tail-free by construction. Two source
|
|
||||||
// bytes (= 4 nibbles) fuse into ONE aligned 32-bit store straight
|
|
||||||
// into VGA RAM (perf-hunt-2 rank 6) -- little-endian puts the
|
|
||||||
// first LUT word in the low half, so the written bytes are
|
|
||||||
// identical to the old per-pair 16-bit stores. DOSBox's
|
|
||||||
// instruction-counting core charges the old loop ~3.5
|
|
||||||
// instructions per VGA byte; halving the loop trips halves that.
|
|
||||||
srcBytes = &src->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
|
||||||
out = (uint32_t *)&dst[x];
|
|
||||||
quads = (uint16_t)(w >> 2);
|
|
||||||
|
|
||||||
for (i = 0; i < quads; i++) {
|
|
||||||
*out++ = (uint32_t)gExpandLut[srcBytes[0]] | ((uint32_t)gExpandLut[srcBytes[1]] << 16);
|
|
||||||
srcBytes += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void rebuildExpandLut(uint8_t palBase) {
|
|
||||||
uint16_t byte;
|
|
||||||
uint8_t leftPixel;
|
|
||||||
uint8_t rightPixel;
|
|
||||||
|
|
||||||
for (byte = 0; byte < 256u; byte++) {
|
|
||||||
// Packed high nibble = LEFT pixel (lower x); packed low nibble
|
|
||||||
// = RIGHT pixel. The little-endian word puts the LEFT pixel in
|
|
||||||
// the low byte so a 16-bit store lands it at the lower (even)
|
|
||||||
// VGA address -- byte-for-byte identical to the old pair store.
|
|
||||||
leftPixel = (uint8_t)(palBase | (byte >> 4));
|
|
||||||
rightPixel = (uint8_t)(palBase | (byte & 0x0Fu));
|
|
||||||
gExpandLut[byte] = (uint16_t)((uint16_t)rightPixel << 8 | (uint16_t)leftPixel);
|
|
||||||
}
|
|
||||||
gExpandLutBase = palBase;
|
|
||||||
gExpandLutValid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void uploadPalette(const jlSurfaceT *src) {
|
static void uploadPalette(const jlSurfaceT *src) {
|
||||||
uint16_t p;
|
uint16_t p;
|
||||||
uint16_t c;
|
uint16_t c;
|
||||||
|
|
@ -287,7 +222,7 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
|
|
||||||
// Walk per-row dirty bands: each chunky word holds 4 mode-13h
|
// Walk per-row dirty bands: each chunky word holds 4 mode-13h
|
||||||
// bytes, so pixelX = minWord*4 and pixelW = (maxWord-minWord+1)*4
|
// bytes, so pixelX = minWord*4 and pixelW = (maxWord-minWord+1)*4
|
||||||
// gives the byte range expandAndWriteLine needs.
|
// gives the byte range x86ExpandRow needs.
|
||||||
// Group test, 4 rows per dword (perf-hunt-2 rank 5, the landed
|
// Group test, 4 rows per dword (perf-hunt-2 rank 5, the landed
|
||||||
// ST/Amiga idle-scan trick): a clean row's minWord is the 0xFF
|
// ST/Amiga idle-scan trick): a clean row's minWord is the 0xFF
|
||||||
// CLEAN sentinel and any touched row's is <= STAGE_DIRTY_FULL_MAX
|
// CLEAN sentinel and any touched row's is <= STAGE_DIRTY_FULL_MAX
|
||||||
|
|
@ -324,7 +259,7 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
}
|
}
|
||||||
pixelX = (int16_t)((uint16_t)minWord << 2);
|
pixelX = (int16_t)((uint16_t)minWord << 2);
|
||||||
pixelW = (uint16_t)(((uint16_t)maxWord - minWord + 1u) << 2);
|
pixelW = (uint16_t)(((uint16_t)maxWord - minWord + 1u) << 2);
|
||||||
expandAndWriteLine(src, y, pixelX, pixelW, &gVgaMem[y * VGA_STRIDE]);
|
x86ExpandRow(src, y, pixelX, pixelW, &gVgaMem[y * VGA_STRIDE]);
|
||||||
gPresentCopiedBytes += pixelW;
|
gPresentCopiedBytes += pixelW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
// A JoeyLib program on DVX is a DVX main-loop program (an .app module):
|
// 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
|
// 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
|
// 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
|
// scale) and full screen, the frame clock and the present; input.c the
|
||||||
// joysticks; audio.c, save.c and serial.c the add-on services. Everything
|
// keyboard, mouse and joysticks; audio.c, save.c and serial.c the add-on
|
||||||
// here is named dvxPort* so nothing collides with DVX's own dvx* names.
|
// services. Everything here is named dvxPort* so nothing collides with
|
||||||
|
// DVX's own dvx* names.
|
||||||
#ifndef JOEY_DVX_PORT_H
|
#ifndef JOEY_DVX_PORT_H
|
||||||
#define JOEY_DVX_PORT_H
|
#define JOEY_DVX_PORT_H
|
||||||
|
|
||||||
|
|
@ -42,17 +43,25 @@ int32_t dvxPortRun(DxeAppContextT *ctx);
|
||||||
// ----- hal.c -----
|
// ----- hal.c -----
|
||||||
|
|
||||||
extern WindowT *gDvxPortWindow; // the program's window; NULL outside jlInit..jlShutdown
|
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,
|
// End the program now if the user closed its window (the close box,
|
||||||
// Alt+F4, Game > Exit).
|
// Alt+F4, Game > Exit).
|
||||||
void dvxPortEndIfClosed(void);
|
void dvxPortEndIfClosed(void);
|
||||||
|
|
||||||
|
// Full screen on if it is off, off if it is on (Game > Full Screen,
|
||||||
|
// Alt+Enter); the choice is kept for the program's next run.
|
||||||
|
void dvxPortFullScreenToggle(void);
|
||||||
|
|
||||||
// Called where a JoeyLib program comes back to the port every frame (the
|
// 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
|
// input poll, the present): yield to the desktop if the program has not
|
||||||
// this virtual frame, then dvxPortEndIfClosed.
|
// this virtual frame, then dvxPortEndIfClosed.
|
||||||
void dvxPortService(void);
|
void dvxPortService(void);
|
||||||
|
|
||||||
|
// A point in the coordinates the window's onMouse gets (content-area pixels,
|
||||||
|
// or screen pixels while the program has the whole screen) to the stage
|
||||||
|
// pixel under it, clamped to the stage.
|
||||||
|
void dvxPortStagePoint(int32_t *x, int32_t *y);
|
||||||
|
|
||||||
// ----- input.c: the window's input callbacks, installed by hal.c -----
|
// ----- input.c: the window's input callbacks, installed by hal.c -----
|
||||||
|
|
||||||
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod);
|
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "dvxPal.h"
|
#include "dvxPal.h"
|
||||||
#include "dvxPlat.h"
|
#include "dvxPlat.h"
|
||||||
|
#include "dvxPrefs.h"
|
||||||
#include "dvxVideo.h"
|
#include "dvxVideo.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
|
|
|
||||||
522
src/dvx/hal.c
522
src/dvx/hal.c
|
|
@ -1,10 +1,12 @@
|
||||||
// DVX HAL: the stage in a window, the frame clock and the present.
|
// DVX HAL: the stage in a window or on the whole screen, the frame clock and
|
||||||
|
// the present.
|
||||||
//
|
//
|
||||||
// jlpInit opens the program's window: a game input window (every key but
|
// 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
|
// 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 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
|
// the desktop, under a Game menu (Full Screen, Help, Exit). Closing the
|
||||||
// those routes ends the program at its next frame (dvxPortEndIfClosed).
|
// 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,
|
// 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
|
// 16 or 32 bits), so the present expands the stage's changed rows through
|
||||||
|
|
@ -17,12 +19,31 @@
|
||||||
// or the user changing the video mode while the program runs, expands every
|
// 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.
|
// row. Runs of consecutive changed rows go to DVX as one rectangle each.
|
||||||
//
|
//
|
||||||
|
// Full screen (Game > Full Screen, or Alt+Enter; the choice is kept in the
|
||||||
|
// program's settings folder, so it starts the way it was left): DVX's
|
||||||
|
// exclusive mode at 320x200 and 8 bits a pixel, which on DOS is VGA mode 13h
|
||||||
|
// or a VBE mode of that size. The model is the DOS port's: the screen's 256
|
||||||
|
// colours hold all 16 palettes (entry = palette * 16 + colour), so a pixel
|
||||||
|
// is the stage's palette index, (SCB << 4) | nibble, SCB palettes are exact,
|
||||||
|
// and a palette change is only the screen's palette: no pixel is expanded
|
||||||
|
// again. The same present writes the changed rows into the full-screen
|
||||||
|
// backbuffer (centred at the largest whole scale, should DVX give a larger
|
||||||
|
// mode or another depth, whose tables are then packed colours) and flushes
|
||||||
|
// each run to the screen. When the window loses the focus DVX gives the
|
||||||
|
// desktop back (Suspended): the present goes to the window again, the
|
||||||
|
// window redrawn first; with the focus back the screen is the program's
|
||||||
|
// again (Active), redrawn whole. At 8 bits and 1x the rows are expanded by
|
||||||
|
// the DOS port's own mode 13h expander (src/x86/x86Expand.h).
|
||||||
|
//
|
||||||
// Timing: a window has no vertical blank, so the frame rate is a virtual
|
// 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,
|
// 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
|
// 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
|
// question) gives the milliseconds and the frame count, and jlWaitVBL waits
|
||||||
// for that clock's next frame boundary with gameFrameWait, giving the time to
|
// for that clock's next frame boundary with gameFrameWait, giving the time to
|
||||||
// the other programs meanwhile.
|
// the other programs meanwhile. On the whole screen jlWaitVBL gives the other
|
||||||
|
// programs one turn, then waits for the screen's real vertical retrace, as
|
||||||
|
// the DOS port does, so a present right after it does not tear; the frame
|
||||||
|
// count stays the same clock, so switching never makes it jump.
|
||||||
//
|
//
|
||||||
// DVX multitasks cooperatively: the desktop -- the other programs, this
|
// DVX multitasks cooperatively: the desktop -- the other programs, this
|
||||||
// program's window events (typed keys, the mouse, the close box) and the
|
// program's window events (typed keys, the mouse, the close box) and the
|
||||||
|
|
@ -39,6 +60,7 @@
|
||||||
|
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
#include "surfaceInternal.h"
|
#include "surfaceInternal.h"
|
||||||
|
#include "x86Expand.h"
|
||||||
#include "dvxPort.h"
|
#include "dvxPort.h"
|
||||||
|
|
||||||
// ----- Constants -----
|
// ----- Constants -----
|
||||||
|
|
@ -47,6 +69,9 @@
|
||||||
#define DVX_SCALE_MAX 3
|
#define DVX_SCALE_MAX 3
|
||||||
#define US_PER_MS 1000u
|
#define US_PER_MS 1000u
|
||||||
|
|
||||||
|
// Full screen: 8 bits a pixel, the stage's 16 palettes in the screen's 256.
|
||||||
|
#define FULL_SCREEN_BPP 8
|
||||||
|
|
||||||
// $0RGB: 4 bits per channel, widened to 8 by x17 (0xF -> 0xFF).
|
// $0RGB: 4 bits per channel, widened to 8 by x17 (0xF -> 0xFF).
|
||||||
#define RGB12_RED_SHIFT 8
|
#define RGB12_RED_SHIFT 8
|
||||||
#define RGB12_GREEN_SHIFT 4
|
#define RGB12_GREEN_SHIFT 4
|
||||||
|
|
@ -64,38 +89,87 @@
|
||||||
#define PALETTES_ALL 0xFFFFu
|
#define PALETTES_ALL 0xFFFFu
|
||||||
|
|
||||||
// Game menu commands.
|
// Game menu commands.
|
||||||
#define CMD_GAME_HELP 100
|
#define CMD_GAME_FULL_SCREEN 100
|
||||||
#define CMD_GAME_EXIT 101
|
#define CMD_GAME_HELP 101
|
||||||
|
#define CMD_GAME_EXIT 102
|
||||||
|
|
||||||
|
// The port's own settings, a file in the program's settings folder.
|
||||||
|
#define SETTINGS_FILE "JOEYLIB.INI"
|
||||||
|
#define SETTINGS_SECTION "display"
|
||||||
|
#define SETTINGS_FULL_SCREEN "fullScreen"
|
||||||
|
|
||||||
|
// ----- Types -----
|
||||||
|
|
||||||
|
// What the tables hold: the pixel for each palette entry.
|
||||||
|
typedef enum {
|
||||||
|
LUT_PACKED = 0, // the colour packed in the display's format (15 bits and up)
|
||||||
|
LUT_NEAREST, // the nearest colour of the 256-colour desktop's palette
|
||||||
|
LUT_INDEXED // the palette index itself: the whole screen at 8 bits, its palette the stage's
|
||||||
|
} LutKindE;
|
||||||
|
|
||||||
|
// Where a present goes: the window's content buffer, or while the program
|
||||||
|
// has the whole screen the full-screen backbuffer.
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *pixels; // the stage's top-left pixel (NULL: nowhere to draw)
|
||||||
|
int32_t pitch; // bytes per row
|
||||||
|
int32_t bytesPerPixel;
|
||||||
|
int32_t scale; // pixels per stage pixel, each way
|
||||||
|
int32_t x; // the stage's top-left pixel in DVX's coordinates
|
||||||
|
int32_t y; // (the content area's, or the screen's)
|
||||||
|
int32_t rows; // the stage rows and columns that fit
|
||||||
|
int32_t cols;
|
||||||
|
bool full; // the whole screen
|
||||||
|
} PresentTargetT;
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static uint64_t clockFrame(void);
|
static uint64_t clockFrame(void);
|
||||||
static uint64_t clockMicros(void);
|
static uint64_t clockMicros(void);
|
||||||
static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1);
|
static void expandRow(const PresentTargetT *t, const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1);
|
||||||
static void lutBuild(const jlSurfaceT *src, uint16_t palettes);
|
static void fullSet(bool on);
|
||||||
static bool lutSync(const jlSurfaceT *src);
|
static void lutBuild(const jlSurfaceT *src, uint16_t palettes);
|
||||||
static void onClose(WindowT *win);
|
static LutKindE lutKind(const DisplayT *d);
|
||||||
static void onMenu(WindowT *win, int32_t menuId);
|
static bool lutSync(const jlSurfaceT *src);
|
||||||
static void onPaint(WindowT *win, RectT *dirty);
|
static void onClose(WindowT *win);
|
||||||
static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidate);
|
static void onFullChange(WindowT *win, ExclusiveStateE state, void *userData);
|
||||||
static bool windowCreate(AppContextT *ac);
|
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 show);
|
||||||
|
static void redrawAll(void);
|
||||||
|
static bool settingFullScreenLoad(void);
|
||||||
|
static void settingFullScreenSave(bool on);
|
||||||
|
static const DisplayT *targetDisplay(void);
|
||||||
|
static bool targetGet(PresentTargetT *t);
|
||||||
|
static void targetShow(const PresentTargetT *t, int32_t x, int32_t y, int32_t w, int32_t h);
|
||||||
|
static bool windowCreate(AppContextT *ac);
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
WindowT *gDvxPortWindow = NULL;
|
WindowT *gDvxPortWindow = NULL;
|
||||||
int32_t gDvxPortScale = 1;
|
|
||||||
|
|
||||||
|
static int32_t gScale = 1; // window pixels per stage pixel, each way
|
||||||
static GameFramePacerT gPacer;
|
static GameFramePacerT gPacer;
|
||||||
static bool gClockStarted = false;
|
static bool gClockStarted = false;
|
||||||
static bool gCloseRequested = false;
|
static bool gCloseRequested = false;
|
||||||
static uint64_t gYieldFrame = 0; // the frame the program last yielded in
|
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
|
// Full screen: whether the program holds it (DVX's owner: the user's choice,
|
||||||
// desktop's bits per pixel (0 = not built) and, at 8 bits, its palette.
|
// until the mode can no longer be set) and whether the screen is its own now
|
||||||
|
// (not suspended). DVX tells the port each change through onFullChange.
|
||||||
|
static bool gFullWanted = false;
|
||||||
|
static bool gFullActive = false;
|
||||||
|
|
||||||
|
// The pixel per palette entry, and what the tables were built for: their
|
||||||
|
// kind, the display's bits per pixel (0 = not built) and, for LUT_NEAREST,
|
||||||
|
// the desktop's palette.
|
||||||
static uint32_t gLut[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
static uint32_t gLut[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||||
|
static LutKindE gLutKind = LUT_PACKED;
|
||||||
static int32_t gLutBits = 0;
|
static int32_t gLutBits = 0;
|
||||||
static uint8_t gLutDesktopPalette[PAL_ENTRIES * PAL_CHANNELS];
|
static uint8_t gLutDesktopPalette[PAL_ENTRIES * PAL_CHANNELS];
|
||||||
|
|
||||||
|
// The full-screen palette (LUT_INDEXED): entry palette * 16 + colour.
|
||||||
|
static uint8_t gFullPalette[PAL_ENTRIES * PAL_CHANNELS];
|
||||||
|
|
||||||
// ----- Internal helpers (alphabetical) -----
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
// The virtual frame now running: frame n starts n / DVX_FRAME_HZ seconds
|
// The virtual frame now running: frame n starts n / DVX_FRAME_HZ seconds
|
||||||
|
|
@ -116,36 +190,42 @@ static uint64_t clockMicros(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Stage row y, pixels x0..x1-1 (both even), into the content buffer at the
|
// Stage row y, pixels x0..x1-1 (both even), into the target at its scale:
|
||||||
// window's scale: each pixel repeated across, the row repeated down.
|
// each pixel repeated across, the row repeated down. On the whole screen at
|
||||||
static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1) {
|
// 8 bits and 1x the pixels are the DOS port's mode 13h bytes, so its
|
||||||
|
// expander does them (x0 and x1 are then whole chunky words, as it needs).
|
||||||
|
static void expandRow(const PresentTargetT *t, const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1) {
|
||||||
const uint32_t *lut;
|
const uint32_t *lut;
|
||||||
const uint8_t *in;
|
const uint8_t *in;
|
||||||
uint8_t *row;
|
uint8_t *row;
|
||||||
int32_t scale;
|
|
||||||
int32_t bytesPerPixel;
|
|
||||||
int32_t pitch;
|
|
||||||
int32_t pairs;
|
int32_t pairs;
|
||||||
int32_t i;
|
int32_t i;
|
||||||
int32_t s;
|
int32_t s;
|
||||||
|
|
||||||
lut = gLut[src->scb[y]];
|
if (gLutKind == LUT_INDEXED && t->scale == 1) {
|
||||||
in = &src->pixels[y * SURFACE_BYTES_PER_ROW + x0 / STAGE_PIXELS_PER_BYTE];
|
x86ExpandRow(src, (int16_t)y, (int16_t)x0, (uint16_t)(x1 - x0), t->pixels + y * t->pitch);
|
||||||
scale = gDvxPortScale;
|
return;
|
||||||
bytesPerPixel = dvxGetDisplay(gDvxPortProgram->shellCtx)->format.bytesPerPixel;
|
}
|
||||||
pitch = gDvxPortWindow->contentPitch;
|
lut = gLut[src->scb[y]];
|
||||||
row = gDvxPortWindow->contentBuf + y * scale * pitch + x0 * scale * bytesPerPixel;
|
in = &src->pixels[y * SURFACE_BYTES_PER_ROW + x0 / STAGE_PIXELS_PER_BYTE];
|
||||||
pairs = (x1 - x0) / STAGE_PIXELS_PER_BYTE;
|
row = t->pixels + y * t->scale * t->pitch + x0 * t->scale * t->bytesPerPixel;
|
||||||
|
pairs = (x1 - x0) / STAGE_PIXELS_PER_BYTE;
|
||||||
|
|
||||||
switch (bytesPerPixel) {
|
switch (t->bytesPerPixel) {
|
||||||
case 1: {
|
case 1: {
|
||||||
uint8_t *d = row;
|
uint8_t *d = row;
|
||||||
|
uint8_t left;
|
||||||
|
uint8_t right;
|
||||||
|
|
||||||
for (i = 0; i < pairs; i++) {
|
for (i = 0; i < pairs; i++) {
|
||||||
memset(d, (int)lut[in[i] >> NIBBLE_BITS], (size_t)scale);
|
left = (uint8_t)lut[in[i] >> NIBBLE_BITS];
|
||||||
d += scale;
|
right = (uint8_t)lut[in[i] & NIBBLE_MASK];
|
||||||
memset(d, (int)lut[in[i] & NIBBLE_MASK], (size_t)scale);
|
for (s = 0; s < t->scale; s++) {
|
||||||
d += scale;
|
*d++ = left;
|
||||||
|
}
|
||||||
|
for (s = 0; s < t->scale; s++) {
|
||||||
|
*d++ = right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -158,10 +238,10 @@ static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1)
|
||||||
for (i = 0; i < pairs; i++) {
|
for (i = 0; i < pairs; i++) {
|
||||||
left = (uint16_t)lut[in[i] >> NIBBLE_BITS];
|
left = (uint16_t)lut[in[i] >> NIBBLE_BITS];
|
||||||
right = (uint16_t)lut[in[i] & NIBBLE_MASK];
|
right = (uint16_t)lut[in[i] & NIBBLE_MASK];
|
||||||
for (s = 0; s < scale; s++) {
|
for (s = 0; s < t->scale; s++) {
|
||||||
*d++ = left;
|
*d++ = left;
|
||||||
}
|
}
|
||||||
for (s = 0; s < scale; s++) {
|
for (s = 0; s < t->scale; s++) {
|
||||||
*d++ = right;
|
*d++ = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -176,10 +256,10 @@ static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1)
|
||||||
for (i = 0; i < pairs; i++) {
|
for (i = 0; i < pairs; i++) {
|
||||||
left = lut[in[i] >> NIBBLE_BITS];
|
left = lut[in[i] >> NIBBLE_BITS];
|
||||||
right = lut[in[i] & NIBBLE_MASK];
|
right = lut[in[i] & NIBBLE_MASK];
|
||||||
for (s = 0; s < scale; s++) {
|
for (s = 0; s < t->scale; s++) {
|
||||||
*d++ = left;
|
*d++ = left;
|
||||||
}
|
}
|
||||||
for (s = 0; s < scale; s++) {
|
for (s = 0; s < t->scale; s++) {
|
||||||
*d++ = right;
|
*d++ = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,51 +267,109 @@ static void expandRow(const jlSurfaceT *src, int32_t y, int32_t x0, int32_t x1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (s = 1; s < scale; s++) {
|
for (s = 1; s < t->scale; s++) {
|
||||||
memcpy(row + s * pitch, row, (size_t)((x1 - x0) * scale * bytesPerPixel));
|
memcpy(row + s * t->pitch, row, (size_t)((x1 - x0) * t->scale * t->bytesPerPixel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Rebuild the tables of the palettes whose bits are set.
|
// Full screen on or off; the Game menu's check mark follows. On: DVX sets
|
||||||
|
// the mode and tells onFullChange (Active) before this returns. Off: the
|
||||||
|
// desktop comes back, and the window is redrawn.
|
||||||
|
static void fullSet(bool on) {
|
||||||
|
AppContextT *ac;
|
||||||
|
|
||||||
|
ac = gDvxPortProgram->shellCtx;
|
||||||
|
if (on && dvxExclusiveBegin(ac, gDvxPortWindow, SURFACE_WIDTH, SURFACE_HEIGHT, FULL_SCREEN_BPP, onFullChange, NULL) != DVX_EXCL_OK) {
|
||||||
|
dvxLog("JoeyLib: %s: no full-screen mode", appDescriptor.name);
|
||||||
|
on = false;
|
||||||
|
}
|
||||||
|
if (!on && gFullWanted) {
|
||||||
|
dvxExclusiveEnd(ac, gDvxPortWindow);
|
||||||
|
gFullActive = false;
|
||||||
|
redrawAll();
|
||||||
|
}
|
||||||
|
gFullWanted = on;
|
||||||
|
wmMenuItemSetChecked(gDvxPortWindow->menuBar, CMD_GAME_FULL_SCREEN, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rebuild the tables of the palettes whose bits are set, for where the stage
|
||||||
|
// goes now; on the whole screen at 8 bits (LUT_INDEXED) their colours go to
|
||||||
|
// the screen's palette instead, and the tables are the indexes themselves.
|
||||||
static void lutBuild(const jlSurfaceT *src, uint16_t palettes) {
|
static void lutBuild(const jlSurfaceT *src, uint16_t palettes) {
|
||||||
const DisplayT *d;
|
const DisplayT *d;
|
||||||
|
uint8_t *entry;
|
||||||
uint16_t rgb;
|
uint16_t rgb;
|
||||||
uint8_t r;
|
uint8_t r;
|
||||||
uint8_t g;
|
uint8_t g;
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
uint8_t p;
|
uint8_t p;
|
||||||
uint8_t c;
|
uint8_t c;
|
||||||
|
uint8_t index;
|
||||||
|
|
||||||
d = dvxGetDisplay(gDvxPortProgram->shellCtx);
|
d = targetDisplay();
|
||||||
for (p = 0; p < SURFACE_PALETTE_COUNT; p++) {
|
for (p = 0; p < SURFACE_PALETTE_COUNT; p++) {
|
||||||
if ((palettes & (1u << p)) == 0) {
|
if ((palettes & (1u << p)) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (c = 0; c < SURFACE_COLORS_PER_PALETTE; c++) {
|
for (c = 0; c < SURFACE_COLORS_PER_PALETTE; c++) {
|
||||||
rgb = src->palette[p][c];
|
rgb = src->palette[p][c];
|
||||||
r = (uint8_t)(((rgb >> RGB12_RED_SHIFT) & RGB12_CHANNEL_MASK) * RGB12_TO_RGB24);
|
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);
|
g = (uint8_t)(((rgb >> RGB12_GREEN_SHIFT) & RGB12_CHANNEL_MASK) * RGB12_TO_RGB24);
|
||||||
b = (uint8_t)((rgb & 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);
|
index = (uint8_t)((p << NIBBLE_BITS) | c);
|
||||||
|
switch (gLutKind) {
|
||||||
|
case LUT_INDEXED:
|
||||||
|
entry = &gFullPalette[index * PAL_CHANNELS];
|
||||||
|
entry[0] = r;
|
||||||
|
entry[1] = g;
|
||||||
|
entry[2] = b;
|
||||||
|
gLut[p][c] = index;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LUT_NEAREST:
|
||||||
|
gLut[p][c] = gamePalNearest(d->palette, r, g, b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
gLut[p][c] = packColor(d, r, g, b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gLutKind == LUT_INDEXED) {
|
||||||
|
dvxExclusiveSetPalette(gDvxPortProgram->shellCtx, gFullPalette, p * SURFACE_COLORS_PER_PALETTE, SURFACE_COLORS_PER_PALETTE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Rebuild every table when the desktop's pixel format, or its 256-colour
|
// The kind of table display d takes. On the whole screen d->palette is the
|
||||||
// palette, is not the one they were built for (the user can change the
|
// program's own palette (at any depth), not a desktop's to match.
|
||||||
// video mode while the program runs). True when it did: every row must be
|
static LutKindE lutKind(const DisplayT *d) {
|
||||||
// expanded again.
|
if (gFullActive) {
|
||||||
|
return d->format.bitsPerPixel == FULL_SCREEN_BPP ? LUT_INDEXED : LUT_PACKED;
|
||||||
|
}
|
||||||
|
return d->palette != NULL ? LUT_NEAREST : LUT_PACKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Rebuild every table when they were not built for where the stage goes now
|
||||||
|
// (full screen came or went, or the user changed the video mode, or the
|
||||||
|
// 256-colour desktop's palette, while the program runs). True when it did:
|
||||||
|
// every row must be expanded again.
|
||||||
static bool lutSync(const jlSurfaceT *src) {
|
static bool lutSync(const jlSurfaceT *src) {
|
||||||
const DisplayT *d;
|
const DisplayT *d;
|
||||||
|
LutKindE kind;
|
||||||
|
|
||||||
d = dvxGetDisplay(gDvxPortProgram->shellCtx);
|
d = targetDisplay();
|
||||||
if (d->format.bitsPerPixel == gLutBits && (d->palette == NULL || memcmp(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette)) == 0)) {
|
kind = lutKind(d);
|
||||||
|
if (kind == gLutKind && d->format.bitsPerPixel == gLutBits && (kind != LUT_NEAREST || memcmp(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette)) == 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
gLutKind = kind;
|
||||||
gLutBits = d->format.bitsPerPixel;
|
gLutBits = d->format.bitsPerPixel;
|
||||||
if (d->palette != NULL) {
|
if (kind == LUT_NEAREST) {
|
||||||
memcpy(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette));
|
memcpy(gLutDesktopPalette, d->palette, sizeof(gLutDesktopPalette));
|
||||||
}
|
}
|
||||||
lutBuild(src, PALETTES_ALL);
|
lutBuild(src, PALETTES_ALL);
|
||||||
|
|
@ -245,9 +383,29 @@ static void onClose(WindowT *win) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DVX's word on full screen: the screen is the program's (Active), the
|
||||||
|
// desktop is back while the window does not have the focus (Suspended), or
|
||||||
|
// the mode could not be set again and full screen is over (Ended). The
|
||||||
|
// stage is drawn again, whole, wherever it goes now.
|
||||||
|
static void onFullChange(WindowT *win, ExclusiveStateE state, void *userData) {
|
||||||
|
(void)win;
|
||||||
|
(void)userData;
|
||||||
|
gFullActive = state == ExclusiveActiveE;
|
||||||
|
if (state == ExclusiveEndedE) {
|
||||||
|
gFullWanted = false;
|
||||||
|
wmMenuItemSetChecked(gDvxPortWindow->menuBar, CMD_GAME_FULL_SCREEN, false);
|
||||||
|
}
|
||||||
|
redrawAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void onMenu(WindowT *win, int32_t menuId) {
|
static void onMenu(WindowT *win, int32_t menuId) {
|
||||||
(void)win;
|
(void)win;
|
||||||
switch (menuId) {
|
switch (menuId) {
|
||||||
|
case CMD_GAME_FULL_SCREEN:
|
||||||
|
dvxPortFullScreenToggle();
|
||||||
|
break;
|
||||||
|
|
||||||
case CMD_GAME_HELP:
|
case CMD_GAME_HELP:
|
||||||
shellOpenHelp(gDvxPortProgram->shellCtx, gDvxPortProgram->helpFile);
|
shellOpenHelp(gDvxPortProgram->shellCtx, gDvxPortProgram->helpFile);
|
||||||
break;
|
break;
|
||||||
|
|
@ -264,10 +422,12 @@ static void onMenu(WindowT *win, int32_t menuId) {
|
||||||
|
|
||||||
// DVX wants the whole content buffer (the window's first showing, a video
|
// 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.
|
// mode change): expand the whole stage, which DVX then puts on the screen.
|
||||||
|
// Not while the program has the whole screen: the window is not on show,
|
||||||
|
// and it is redrawn when the desktop comes back.
|
||||||
static void onPaint(WindowT *win, RectT *dirty) {
|
static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
(void)win;
|
(void)win;
|
||||||
(void)dirty;
|
(void)dirty;
|
||||||
if (gStage == NULL) {
|
if (gStage == NULL || gFullActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(void)lutSync(gStage);
|
(void)lutSync(gStage);
|
||||||
|
|
@ -276,42 +436,31 @@ static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
|
|
||||||
|
|
||||||
// Expand every row that is dirty, or whose palette bit is set in palettes,
|
// 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
|
// into the target; with show, hand DVX each run of consecutive expanded rows
|
||||||
// consecutive expanded rows as one rectangle.
|
// as one rectangle (targetShow).
|
||||||
static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidate) {
|
static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool show) {
|
||||||
AppContextT *ac;
|
PresentTargetT t;
|
||||||
int32_t scale;
|
int32_t y;
|
||||||
int32_t rows;
|
int32_t x0;
|
||||||
int32_t cols;
|
int32_t x1;
|
||||||
int32_t y;
|
int32_t runY0;
|
||||||
int32_t x0;
|
int32_t runX0;
|
||||||
int32_t x1;
|
int32_t runX1;
|
||||||
int32_t runY0;
|
uint8_t maxWord;
|
||||||
int32_t runX0;
|
|
||||||
int32_t runX1;
|
|
||||||
uint8_t maxWord;
|
|
||||||
|
|
||||||
if (gDvxPortWindow == NULL || gDvxPortWindow->contentBuf == NULL) {
|
if (!targetGet(&t) || t.pixels == NULL) {
|
||||||
return;
|
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;
|
runY0 = -1;
|
||||||
runX0 = 0;
|
runX0 = 0;
|
||||||
runX1 = 0;
|
runX1 = 0;
|
||||||
for (y = 0; y < rows; y++) {
|
for (y = 0; y < t.rows; y++) {
|
||||||
if ((palettes & (1u << src->scb[y])) != 0) {
|
if ((palettes & (1u << src->scb[y])) != 0) {
|
||||||
x0 = 0;
|
x0 = 0;
|
||||||
x1 = SURFACE_WIDTH;
|
x1 = SURFACE_WIDTH;
|
||||||
} else if (STAGE_DIRTY_ROW_CLEAN(y)) {
|
} else if (STAGE_DIRTY_ROW_CLEAN(y)) {
|
||||||
if (runY0 >= 0 && invalidate) {
|
if (runY0 >= 0 && show) {
|
||||||
dvxInvalidateRect(ac, gDvxPortWindow, runX0 * scale, runY0 * scale, (runX1 - runX0) * scale, (y - runY0) * scale);
|
targetShow(&t, runX0, runY0, runX1 - runX0, y - runY0);
|
||||||
}
|
}
|
||||||
runY0 = -1;
|
runY0 = -1;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -321,11 +470,11 @@ static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidat
|
||||||
x0 = gStageMinWord[y] * STAGE_PIXELS_PER_WORD;
|
x0 = gStageMinWord[y] * STAGE_PIXELS_PER_WORD;
|
||||||
x1 = (maxWord + 1) * STAGE_PIXELS_PER_WORD;
|
x1 = (maxWord + 1) * STAGE_PIXELS_PER_WORD;
|
||||||
}
|
}
|
||||||
x1 = x1 < cols ? x1 : cols;
|
x1 = x1 < t.cols ? x1 : t.cols;
|
||||||
if (x0 >= x1) {
|
if (x0 >= x1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
expandRow(src, y, x0, x1);
|
expandRow(&t, src, y, x0, x1);
|
||||||
gPresentCopiedBytes += (uint32_t)(x1 - x0);
|
gPresentCopiedBytes += (uint32_t)(x1 - x0);
|
||||||
if (runY0 < 0) {
|
if (runY0 < 0) {
|
||||||
runY0 = y;
|
runY0 = y;
|
||||||
|
|
@ -336,13 +485,131 @@ static void presentRows(const jlSurfaceT *src, uint16_t palettes, bool invalidat
|
||||||
runX1 = x1 > runX1 ? x1 : runX1;
|
runX1 = x1 > runX1 ? x1 : runX1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (runY0 >= 0 && invalidate) {
|
if (runY0 >= 0 && show) {
|
||||||
dvxInvalidateRect(ac, gDvxPortWindow, runX0 * scale, runY0 * scale, (runX1 - runX0) * scale, (y - runY0) * scale);
|
targetShow(&t, runX0, runY0, runX1 - runX0, y - runY0);
|
||||||
}
|
}
|
||||||
gPresentScannedRows += SURFACE_HEIGHT;
|
gPresentScannedRows += SURFACE_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Full screen came or went: new tables, and every row again, wherever the
|
||||||
|
// stage goes now. Before jlInit has the stage the first present does it.
|
||||||
|
static void redrawAll(void) {
|
||||||
|
gLutBits = 0;
|
||||||
|
if (gStage == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(void)lutSync(gStage);
|
||||||
|
presentRows(gStage, PALETTES_ALL, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The saved choice: whether the program was on the whole screen when the
|
||||||
|
// user last switched.
|
||||||
|
static bool settingFullScreenLoad(void) {
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
PrefsHandleT *prefs;
|
||||||
|
bool on;
|
||||||
|
|
||||||
|
shellConfigPath(gDvxPortProgram, SETTINGS_FILE, path, (int32_t)sizeof(path));
|
||||||
|
prefs = prefsLoad(path);
|
||||||
|
if (prefs == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
on = prefsGetBool(prefs, SETTINGS_SECTION, SETTINGS_FULL_SCREEN, false);
|
||||||
|
prefsClose(prefs);
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void settingFullScreenSave(bool on) {
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
PrefsHandleT *prefs;
|
||||||
|
|
||||||
|
if (shellEnsureConfigDir(gDvxPortProgram) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shellConfigPath(gDvxPortProgram, SETTINGS_FILE, path, (int32_t)sizeof(path));
|
||||||
|
prefs = prefsLoad(path);
|
||||||
|
if (prefs == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prefsSetBool(prefs, SETTINGS_SECTION, SETTINGS_FULL_SCREEN, on);
|
||||||
|
if (!prefsSave(prefs)) {
|
||||||
|
dvxLog("JoeyLib: %s: could not save %s", appDescriptor.name, path);
|
||||||
|
}
|
||||||
|
prefsClose(prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The display the stage goes to now.
|
||||||
|
static const DisplayT *targetDisplay(void) {
|
||||||
|
AppContextT *ac;
|
||||||
|
|
||||||
|
ac = gDvxPortProgram->shellCtx;
|
||||||
|
return gFullActive ? dvxExclusiveDisplay(ac) : dvxGetDisplay(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Where the stage goes now. On the whole screen, centred at the largest
|
||||||
|
// whole scale that fits (as dvxExclusivePresent8 places a picture; DVX's
|
||||||
|
// mode is at least the stage's size); in the window, at the window's scale,
|
||||||
|
// clipped to the content area in whole pixel pairs. False without a window.
|
||||||
|
static bool targetGet(PresentTargetT *t) {
|
||||||
|
const DisplayT *d;
|
||||||
|
int32_t scaleW;
|
||||||
|
int32_t scaleH;
|
||||||
|
|
||||||
|
if (gDvxPortWindow == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
d = targetDisplay();
|
||||||
|
t->bytesPerPixel = d->format.bytesPerPixel;
|
||||||
|
t->full = gFullActive;
|
||||||
|
if (t->full) {
|
||||||
|
scaleW = d->width / SURFACE_WIDTH;
|
||||||
|
scaleH = d->height / SURFACE_HEIGHT;
|
||||||
|
t->scale = scaleW < scaleH ? scaleW : scaleH;
|
||||||
|
t->x = (d->width - SURFACE_WIDTH * t->scale) / 2;
|
||||||
|
t->y = (d->height - SURFACE_HEIGHT * t->scale) / 2;
|
||||||
|
t->pitch = d->pitch;
|
||||||
|
t->pixels = d->backBuf + t->y * t->pitch + t->x * t->bytesPerPixel;
|
||||||
|
t->rows = SURFACE_HEIGHT;
|
||||||
|
t->cols = SURFACE_WIDTH;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
t->scale = gScale;
|
||||||
|
t->x = 0;
|
||||||
|
t->y = 0;
|
||||||
|
t->pitch = gDvxPortWindow->contentPitch;
|
||||||
|
t->pixels = gDvxPortWindow->contentBuf;
|
||||||
|
// The content area is the scaled stage unless DVX sized the window
|
||||||
|
// otherwise.
|
||||||
|
t->rows = gDvxPortWindow->contentH / t->scale;
|
||||||
|
t->cols = gDvxPortWindow->contentW / t->scale;
|
||||||
|
t->rows = t->rows < SURFACE_HEIGHT ? t->rows : SURFACE_HEIGHT;
|
||||||
|
t->cols = (t->cols < SURFACE_WIDTH ? t->cols : SURFACE_WIDTH) & ~(STAGE_PIXELS_PER_BYTE - 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stage rectangle (x, y, w, h) has changed in the target: flush it to the
|
||||||
|
// screen on the whole screen, or have DVX composite it from the window.
|
||||||
|
static void targetShow(const PresentTargetT *t, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
|
RectT r;
|
||||||
|
|
||||||
|
r.x = t->x + x * t->scale;
|
||||||
|
r.y = t->y + y * t->scale;
|
||||||
|
r.w = w * t->scale;
|
||||||
|
r.h = h * t->scale;
|
||||||
|
if (t->full) {
|
||||||
|
dvxExclusiveFlush(gDvxPortProgram->shellCtx, &r);
|
||||||
|
} else {
|
||||||
|
dvxInvalidateRect(gDvxPortProgram->shellCtx, gDvxPortWindow, r.x, r.y, r.w, r.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// The window: its content area the stage at the largest whole scale that
|
// 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.
|
// fits the desktop (up to DVX_SCALE_MAX), not resizable, under a Game menu.
|
||||||
static bool windowCreate(AppContextT *ac) {
|
static bool windowCreate(AppContextT *ac) {
|
||||||
|
|
@ -356,14 +623,14 @@ static bool windowCreate(AppContextT *ac) {
|
||||||
MenuBarT *bar;
|
MenuBarT *bar;
|
||||||
MenuT *game;
|
MenuT *game;
|
||||||
|
|
||||||
frameW = 2 * CHROME_TOTAL_SIDE;
|
frameW = 2 * CHROME_TOTAL_SIDE;
|
||||||
frameH = CHROME_TOTAL_TOP + CHROME_MENU_HEIGHT + CHROME_TOTAL_BOTTOM;
|
frameH = CHROME_TOTAL_TOP + CHROME_MENU_HEIGHT + CHROME_TOTAL_BOTTOM;
|
||||||
scaleW = (ac->display.width - frameW) / SURFACE_WIDTH;
|
scaleW = (ac->display.width - frameW) / SURFACE_WIDTH;
|
||||||
scaleH = (ac->display.height - frameH) / SURFACE_HEIGHT;
|
scaleH = (ac->display.height - frameH) / SURFACE_HEIGHT;
|
||||||
gDvxPortScale = scaleW < scaleH ? scaleW : scaleH;
|
gScale = scaleW < scaleH ? scaleW : scaleH;
|
||||||
gDvxPortScale = gDvxPortScale < 1 ? 1 : (gDvxPortScale > DVX_SCALE_MAX ? DVX_SCALE_MAX : gDvxPortScale);
|
gScale = gScale < 1 ? 1 : (gScale > DVX_SCALE_MAX ? DVX_SCALE_MAX : gScale);
|
||||||
wantW = SURFACE_WIDTH * gDvxPortScale;
|
wantW = SURFACE_WIDTH * gScale;
|
||||||
wantH = SURFACE_HEIGHT * gDvxPortScale;
|
wantH = SURFACE_HEIGHT * gScale;
|
||||||
|
|
||||||
win = dvxCreateWindowCentered(ac, appDescriptor.name, wantW + frameW, wantH + frameH, false);
|
win = dvxCreateWindowCentered(ac, appDescriptor.name, wantW + frameW, wantH + frameH, false);
|
||||||
if (win == NULL) {
|
if (win == NULL) {
|
||||||
|
|
@ -379,6 +646,7 @@ static bool windowCreate(AppContextT *ac) {
|
||||||
bar = wmAddMenuBar(win);
|
bar = wmAddMenuBar(win);
|
||||||
game = bar != NULL ? wmAddMenu(bar, "&Game") : NULL;
|
game = bar != NULL ? wmAddMenu(bar, "&Game") : NULL;
|
||||||
if (game != NULL) {
|
if (game != NULL) {
|
||||||
|
wmAddMenuCheckItem(game, "&Full Screen\tAlt+Enter", CMD_GAME_FULL_SCREEN, false);
|
||||||
wmAddMenuItem(game, "&Help", CMD_GAME_HELP);
|
wmAddMenuItem(game, "&Help", CMD_GAME_HELP);
|
||||||
wmAddMenuSeparator(game);
|
wmAddMenuSeparator(game);
|
||||||
wmAddMenuItem(game, "E&xit", CMD_GAME_EXIT);
|
wmAddMenuItem(game, "E&xit", CMD_GAME_EXIT);
|
||||||
|
|
@ -390,7 +658,7 @@ static bool windowCreate(AppContextT *ac) {
|
||||||
dvxResizeWindow(ac, win, win->w + wantW - win->contentW, win->h + wantH - win->contentH);
|
dvxResizeWindow(ac, win, win->w + wantW - win->contentW, win->h + wantH - win->contentH);
|
||||||
}
|
}
|
||||||
gDvxPortWindow = win;
|
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);
|
dvxLog("JoeyLib: %s: %" PRId32 "x%" PRId32 " at %" PRId32 "x on a %" PRId32 "x%" PRId32 " %" PRId32 "-bit desktop", appDescriptor.name, win->contentW, win->contentH, gScale, ac->display.width, ac->display.height, ac->display.format.bitsPerPixel);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,6 +672,13 @@ void dvxPortEndIfClosed(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The user's switch (Game > Full Screen, Alt+Enter), kept for the next run.
|
||||||
|
void dvxPortFullScreenToggle(void) {
|
||||||
|
fullSet(!gFullWanted);
|
||||||
|
settingFullScreenSave(gFullWanted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void dvxPortService(void) {
|
void dvxPortService(void) {
|
||||||
uint64_t frame;
|
uint64_t frame;
|
||||||
|
|
||||||
|
|
@ -416,6 +691,20 @@ void dvxPortService(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Content-area pixels in the window, screen pixels on the whole screen: the
|
||||||
|
// stage pixel under them, clamped to the stage.
|
||||||
|
void dvxPortStagePoint(int32_t *x, int32_t *y) {
|
||||||
|
PresentTargetT t;
|
||||||
|
|
||||||
|
if (targetGet(&t)) {
|
||||||
|
*x = (*x - t.x) / t.scale;
|
||||||
|
*y = (*y - t.y) / t.scale;
|
||||||
|
}
|
||||||
|
*x = *x < 0 ? 0 : (*x >= SURFACE_WIDTH ? SURFACE_WIDTH - 1 : *x);
|
||||||
|
*y = *y < 0 ? 0 : (*y >= SURFACE_HEIGHT ? SURFACE_HEIGHT - 1 : *y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- HAL API (alphabetical) -----
|
// ----- HAL API (alphabetical) -----
|
||||||
|
|
||||||
uint16_t jlpFrameCount(void) {
|
uint16_t jlpFrameCount(void) {
|
||||||
|
|
@ -433,7 +722,13 @@ bool jlpInit(const jlConfigT *config) {
|
||||||
gCloseRequested = false;
|
gCloseRequested = false;
|
||||||
gLutBits = 0;
|
gLutBits = 0;
|
||||||
(void)clockMicros();
|
(void)clockMicros();
|
||||||
return windowCreate(gDvxPortProgram->shellCtx);
|
if (!windowCreate(gDvxPortProgram->shellCtx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (settingFullScreenLoad()) {
|
||||||
|
fullSet(true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -453,7 +748,9 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
palettes = PALETTES_ALL;
|
palettes = PALETTES_ALL;
|
||||||
} else if (gStagePaletteDirty) {
|
} else if (gStagePaletteDirty) {
|
||||||
lutBuild(src, gStagePaletteDirtyMask);
|
lutBuild(src, gStagePaletteDirtyMask);
|
||||||
palettes = gStagePaletteDirtyMask;
|
// On the whole screen at 8 bits the new colours are the screen's
|
||||||
|
// palette: no pixel changes.
|
||||||
|
palettes = gLutKind == LUT_INDEXED ? PALETTES_NONE : gStagePaletteDirtyMask;
|
||||||
}
|
}
|
||||||
// An SCB change moves rows to other tables: expand them all again.
|
// An SCB change moves rows to other tables: expand them all again.
|
||||||
if (gStageScbDirty) {
|
if (gStageScbDirty) {
|
||||||
|
|
@ -467,21 +764,32 @@ void jlpPresent(const jlSurfaceT *src) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Destroying the window gives the screen back too when the program has it
|
||||||
|
// (DVX tells onFullChange nothing then).
|
||||||
void jlpShutdown(void) {
|
void jlpShutdown(void) {
|
||||||
if (gDvxPortWindow != NULL) {
|
if (gDvxPortWindow != NULL) {
|
||||||
dvxDestroyWindow(gDvxPortProgram->shellCtx, gDvxPortWindow);
|
dvxDestroyWindow(gDvxPortProgram->shellCtx, gDvxPortWindow);
|
||||||
gDvxPortWindow = NULL;
|
gDvxPortWindow = NULL;
|
||||||
}
|
}
|
||||||
|
gFullWanted = false;
|
||||||
|
gFullActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Block until the clock's next frame boundary, the other programs running
|
// In the window: block until the clock's next frame boundary, the other
|
||||||
// meanwhile. Re-aiming the pacer at the frame now running makes every call
|
// programs running meanwhile. Re-aiming the pacer at the frame now running
|
||||||
// wait for the NEXT boundary, as a vertical blank wait does, instead of the
|
// makes every call wait for the NEXT boundary, as a vertical blank wait
|
||||||
// pacer's own catch-up rule.
|
// does, instead of the pacer's own catch-up rule. On the whole screen: the
|
||||||
|
// other programs' turn, then the screen's real vertical retrace.
|
||||||
void jlpWaitVBL(void) {
|
void jlpWaitVBL(void) {
|
||||||
gPacer.frame = clockFrame();
|
if (gFullActive) {
|
||||||
(void)gameFrameWait(&gPacer);
|
tsYield();
|
||||||
gYieldFrame = gPacer.frame;
|
dvxExclusiveWaitRetrace();
|
||||||
|
gYieldFrame = clockFrame();
|
||||||
|
} else {
|
||||||
|
gPacer.frame = clockFrame();
|
||||||
|
(void)gameFrameWait(&gPacer);
|
||||||
|
gYieldFrame = gPacer.frame;
|
||||||
|
}
|
||||||
dvxPortEndIfClosed();
|
dvxPortEndIfClosed();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@
|
||||||
// keyboard layout already applied, and go straight into the core's queue:
|
// 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
|
// 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
|
// in an interrupt, so nothing can race the program's jlInputGetChar. Ctrl
|
||||||
// and Alt chords are commands, not text, as on DOS.
|
// and Alt chords are commands, not text, as on DOS; Alt+Enter switches full
|
||||||
|
// screen on and off (hal.c).
|
||||||
//
|
//
|
||||||
// Mouse: onMouse reports content-area pixels; they are divided by the scale
|
// Mouse: onMouse reports content-area pixels (screen pixels on the whole
|
||||||
// and clamped to the stage, and published by the next poll, so the core's
|
// screen); hal.c maps them to the stage pixel under them, and the next poll
|
||||||
// button edges compare two stable snapshots.
|
// publishes it, so the core's button edges compare two stable snapshots.
|
||||||
//
|
//
|
||||||
// Joysticks: DVX's joystick library, calibrated in DVX's Control Panel:
|
// Joysticks: DVX's joystick library, calibrated in DVX's Control Panel:
|
||||||
// axes -32768..32767 scaled to -127..127, real analog where DOS reads the
|
// axes -32768..32767 scaled to -127..127, real analog where DOS reads the
|
||||||
|
|
@ -43,6 +44,11 @@
|
||||||
// Modifiers that make a key a command rather than text.
|
// Modifiers that make a key a command rather than text.
|
||||||
#define CHORD_MODIFIERS (KEY_MOD_CTRL | KEY_MOD_ALT)
|
#define CHORD_MODIFIERS (KEY_MOD_CTRL | KEY_MOD_ALT)
|
||||||
|
|
||||||
|
// Alt+Enter comes as Enter or as the Enter key's scan code with Alt held,
|
||||||
|
// or (the keypad's Enter) as the BIOS code of Alt with the keypad Enter,
|
||||||
|
// which means Alt by itself: DVX may report no modifier with it.
|
||||||
|
#define SCAN_ALT_KEYPAD_ENTER 0xA6
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static int8_t joyAxis(int32_t value);
|
static int8_t joyAxis(int32_t value);
|
||||||
|
|
@ -129,6 +135,10 @@ static void joystickPoll(void) {
|
||||||
|
|
||||||
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod) {
|
void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
(void)win;
|
(void)win;
|
||||||
|
if (key == (SCAN_ALT_KEYPAD_ENTER | KEY_EXT_FLAG) || ((mod & KEY_MOD_ALT) != 0 && (key == KEY_ENTER || key == (KEY_SCAN_ENTER | KEY_EXT_FLAG)))) {
|
||||||
|
dvxPortFullScreenToggle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ((mod & CHORD_MODIFIERS) != 0) {
|
if ((mod & CHORD_MODIFIERS) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -144,10 +154,9 @@ void dvxPortOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
void dvxPortOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
void dvxPortOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
(void)win;
|
(void)win;
|
||||||
x /= gDvxPortScale;
|
dvxPortStagePoint(&x, &y);
|
||||||
y /= gDvxPortScale;
|
gNextMouseX = (int16_t)x;
|
||||||
gNextMouseX = (int16_t)(x < 0 ? 0 : (x >= SURFACE_WIDTH ? SURFACE_WIDTH - 1 : x));
|
gNextMouseY = (int16_t)y;
|
||||||
gNextMouseY = (int16_t)(y < 0 ? 0 : (y >= SURFACE_HEIGHT ? SURFACE_HEIGHT - 1 : y));
|
|
||||||
gNextMouseButtons = buttons;
|
gNextMouseButtons = buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
//
|
//
|
||||||
// DEFAULT, MODEM and PRINTER are COM1; SLOT is COM<unit> (1..4), as on DOS.
|
// 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
|
// rs232Open raises DTR and RTS; rs232Close leaves them as they are, so
|
||||||
// lines when it closes a port, so jlSerialClose drops DTR there (a modem
|
// jlSerialClose does not hang up a modem, and a port closed after
|
||||||
// hangs up). A program handing a live call back to another has to keep the
|
// jlSerialSetDtr(false) stays down (scripts/verify-dvx-serial.sh checks
|
||||||
// port open.
|
// both at the far end).
|
||||||
//
|
//
|
||||||
// jlSerialPoll is also a point where the program comes back to the port
|
// 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
|
// every frame, so it yields to the desktop like the input poll does: a
|
||||||
|
|
|
||||||
80
src/x86/x86Expand.h
Normal file
80
src/x86/x86Expand.h
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// x86 stage-row expansion to 8-bit palette indexes, shared by the DOS port
|
||||||
|
// (VGA mode 13h) and the DVX port (its full screen at 8 bits a pixel). Both
|
||||||
|
// load all 16 palettes x 16 colours into the screen's 256 entries, so a
|
||||||
|
// pixel is (SCB << 4) | nibble and no per-scanline palette programming is
|
||||||
|
// needed at display time. Each including file gets its own table.
|
||||||
|
#ifndef JOEY_X86_EXPAND_H
|
||||||
|
#define JOEY_X86_EXPAND_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
// Expand LUT for x86ExpandRow. Maps a 4bpp-packed source byte to the
|
||||||
|
// 2-byte little-endian 8-bit output pair for the current scanline's
|
||||||
|
// SCB. The low byte (= dst[x], lower address = LEFT pixel) holds the
|
||||||
|
// packed byte's HIGH nibble; the high byte (= dst[x+1] = RIGHT pixel)
|
||||||
|
// holds the LOW nibble. Storing lut[byte] as one 16-bit x86 write
|
||||||
|
// therefore lands the left pixel at the lower address, matching the
|
||||||
|
// old two-byte sequence exactly. Rebuilt only when the SCB-derived
|
||||||
|
// palBase changes from the previous row (most stages run long runs of
|
||||||
|
// equal SCB), so the per-row cost is one compare, not a 256-entry fill.
|
||||||
|
static uint16_t gX86ExpandLut[256];
|
||||||
|
static uint8_t gX86ExpandLutBase = 0;
|
||||||
|
static bool gX86ExpandLutValid = false;
|
||||||
|
|
||||||
|
|
||||||
|
static inline void x86ExpandLutBuild(uint8_t palBase) {
|
||||||
|
uint16_t byte;
|
||||||
|
uint8_t leftPixel;
|
||||||
|
uint8_t rightPixel;
|
||||||
|
|
||||||
|
for (byte = 0; byte < 256u; byte++) {
|
||||||
|
// Packed high nibble = LEFT pixel (lower x); packed low nibble
|
||||||
|
// = RIGHT pixel. The little-endian word puts the LEFT pixel in
|
||||||
|
// the low byte so a 16-bit store lands it at the lower (even)
|
||||||
|
// address -- byte-for-byte identical to the old pair store.
|
||||||
|
leftPixel = (uint8_t)(palBase | (byte >> 4));
|
||||||
|
rightPixel = (uint8_t)(palBase | (byte & 0x0Fu));
|
||||||
|
gX86ExpandLut[byte] = (uint16_t)((uint16_t)rightPixel << 8 | (uint16_t)leftPixel);
|
||||||
|
}
|
||||||
|
gX86ExpandLutBase = palBase;
|
||||||
|
gX86ExpandLutValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Stage row y, pixels x..x+w-1, as palette indexes into dst[x..x+w-1]
|
||||||
|
// (dst is the row's first pixel).
|
||||||
|
static inline void x86ExpandRow(const jlSurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst) {
|
||||||
|
uint8_t palBase;
|
||||||
|
const uint8_t *srcBytes;
|
||||||
|
uint32_t *out;
|
||||||
|
uint16_t quads;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
palBase = (uint8_t)(src->scb[y] << 4);
|
||||||
|
if (!gX86ExpandLutValid || palBase != gX86ExpandLutBase) {
|
||||||
|
x86ExpandLutBuild(palBase);
|
||||||
|
}
|
||||||
|
// Dirty bands are chunky-word aligned (both presents compute
|
||||||
|
// pixelX = minWord*4 and pixelW = words*4), so x and w are
|
||||||
|
// multiples of 4: &dst[x] is 32-bit aligned and the dword fusion
|
||||||
|
// below is tail-free by construction. Two source bytes (= 4
|
||||||
|
// nibbles) fuse into ONE aligned 32-bit store straight into the
|
||||||
|
// screen (perf-hunt-2 rank 6) -- little-endian puts the first LUT
|
||||||
|
// word in the low half, so the written bytes are identical to the
|
||||||
|
// old per-pair 16-bit stores. DOSBox's instruction-counting core
|
||||||
|
// charges the old loop ~3.5 instructions per VGA byte; halving the
|
||||||
|
// loop trips halves that.
|
||||||
|
srcBytes = &src->pixels[y * SURFACE_BYTES_PER_ROW + (x >> 1)];
|
||||||
|
out = (uint32_t *)&dst[x];
|
||||||
|
quads = (uint16_t)(w >> 2);
|
||||||
|
|
||||||
|
for (i = 0; i < quads; i++) {
|
||||||
|
*out++ = (uint32_t)gX86ExpandLut[srcBytes[0]] | ((uint32_t)gX86ExpandLut[srcBytes[1]] << 16);
|
||||||
|
srcBytes += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Add table
Reference in a new issue