More optimizations. New indexed-to-display conversion routines.
This commit is contained in:
parent
423f992583
commit
154b116208
51 changed files with 2916 additions and 164 deletions
|
|
@ -417,15 +417,6 @@ above.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### libxmp-lite 4.7.0
|
|
||||||
|
|
||||||
- **Used on:** none. It played modules on DOS, the ST, the X68000 and the
|
|
||||||
hosted ports until 2026-10-14, when JoeyLib's own module player replaced
|
|
||||||
it; the installer still installs it (component `libxmp`).
|
|
||||||
- **URL:** <https://github.com/libxmp/libxmp>
|
|
||||||
- **License:** MIT
|
|
||||||
- **Installed at:** `toolchains/opt/libxmp/` (installer component `libxmp`)
|
|
||||||
|
|
||||||
## Content tools (optional; not shipped, not linked)
|
## Content tools (optional; not shipped, not linked)
|
||||||
|
|
||||||
### Furnace 0.6.8.3
|
### Furnace 0.6.8.3
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ and the PSP, from a single codebase:
|
||||||
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.
|
||||||
|
|
||||||
See `claudeDocs/DESIGN.md` for the full 1.0 design.
|
The manual (`docs/`, built by `make docs`) documents the whole API.
|
||||||
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ There are three layers, and on some platforms they share physical hardware:
|
||||||
|
|
||||||
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
||||||
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
||||||
voices plus noise at register-write cost, with no CPU mixing. The recommended
|
voices plus noise at register-write cost on the machines' sound chips (DVX
|
||||||
music path.
|
and the hosted ports mix them in software). The recommended music path.
|
||||||
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and
|
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and
|
||||||
the streaming forms.
|
the streaming forms.
|
||||||
|
|
||||||
|
|
@ -412,10 +412,11 @@ fastest rate, 28.6 kHz, pitches 0 to 4 all play at; the IIgs a resident noise
|
||||||
sample on slot 3, up to 26 kHz, likewise for pitches 0 to 4; and DVX and the
|
sample on slot 3, up to 26 kHz, likewise for pitches 0 to 4; and DVX and the
|
||||||
hosted ports mix the YM2149's own noise in software (its 17-bit generator),
|
hosted ports mix the YM2149's own noise in software (its 17-bit generator),
|
||||||
so it sounds as on the ST (DVX with only the PC speaker: a noise tone of its
|
so it sounds as on the ST (DVX with only the PC speaker: a noise tone of its
|
||||||
sound library). DOS uses the OPL2
|
sound library). DOS mixes the same generator into the Sound Blaster's
|
||||||
rhythm-mode snare, leaving the three tone channels untouched: a tuned drum,
|
stream, at about 22 kHz, so pitches 0 to 5 sound alike there; an AdLib with
|
||||||
not free-running noise, whose pitch spreads over the snare's range rather than
|
no Sound Blaster plays the OPL2 rhythm-mode snare instead, leaving the three
|
||||||
following the YM2149's rate.
|
tone channels untouched: a tuned drum, not free-running noise, whose pitch
|
||||||
|
spreads over the snare's range rather than following the YM2149's rate.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
|
|
@ -432,9 +433,10 @@ bool jlMusicPlay(const uint8_t *data, uint32_t length, bool loop);
|
||||||
----
|
----
|
||||||
|
|
||||||
Plays a JYM1 event stream baked by `songbake` through the tone and noise layer.
|
Plays a JYM1 event stream baked by `songbake` through the tone and noise layer.
|
||||||
One authored song plays on every port at register-write cost. Because there
|
One authored song plays on every port, at register-write cost wherever a
|
||||||
is no CPU mixing, this is fully viable on the ST and cheap on DOS, which
|
sound chip makes it (DVX and the hosted ports mix it in software, and DOS
|
||||||
`.MOD` playback is not.
|
mixes its noise into the Sound Blaster's stream), so it is fully viable on
|
||||||
|
the ST and cheap on DOS, which `.MOD` playback is not.
|
||||||
|
|
||||||
Playback is pumped by `jlAudioFrameTick` and paced by `jlMillisElapsed`, so
|
Playback is pumped by `jlAudioFrameTick` and paced by `jlMillisElapsed`, so
|
||||||
tempo holds across 50, 60, and 70 Hz ports.
|
tempo holds across 50, 60, and 70 Hz ports.
|
||||||
|
|
|
||||||
|
|
@ -234,10 +234,11 @@ uint16_t jlFrameHz(void);
|
||||||
|
|
||||||
*Returns:* the nominal display frame rate in Hz -- 60 on the IIgs, or 50 on a
|
*Returns:* the nominal display frame rate in Hz -- 60 on the IIgs, or 50 on a
|
||||||
PAL machine; 50 on the Amiga, whose screen is PAL, or 60 on an NTSC Amiga that
|
PAL machine; 50 on the Amiga, whose screen is PAL, or 60 on an NTSC Amiga that
|
||||||
cannot show PAL; 50 or 60 on the ST, as the machine is set; 55 on the X68000;
|
cannot show PAL; 50 or 60 on the ST, as the machine is set (71 with a
|
||||||
70 on VGA mode 13h and on DVX, whose window has a virtual 70 Hz (and whose full
|
monochrome monitor); 55 on the X68000; 70 on DOS's VGA and on DVX, whose
|
||||||
screen is mode 13h's, or a VBE mode's, with the same frame count); and 60 on
|
window has a virtual 70 Hz (and whose full screen is mode 13h's, or a VBE
|
||||||
every hosted port, whose window has a virtual 60 Hz. The X68000 times its
|
mode's, with the same frame count); and 60 on every hosted port, whose window
|
||||||
|
has a virtual 60 Hz. The X68000 times its
|
||||||
display at `jlInit` and rounds to the nearest whole number: 55.46 Hz on the
|
display at `jlInit` and rounds to the nearest whole number: 55.46 Hz on the
|
||||||
machine, 56.07 Hz under MAME, which therefore reports 56. The real cadence may
|
machine, 56.07 Hz under MAME, which therefore reports 56. The real cadence may
|
||||||
drift slightly; this is the figure to divide by when converting frames to
|
drift slightly; this is the figure to divide by when converting frames to
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,77 @@ Erase *all* regions, then draw *all* sprites. Never mix this with per-sprite
|
||||||
save and restore in the same region.
|
save and restore in the same region.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
=== jlSurfacePasteIndexed
|
||||||
|
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
||||||
|
----
|
||||||
|
|
||||||
|
Writes a rectangle of colour indices from your memory onto a surface: one byte
|
||||||
|
a pixel, whatever the port stores pixels as. Row `r` of the source starts at
|
||||||
|
`src + r * srcStride`. Each byte's low nibble is the colour index; the high
|
||||||
|
nibble is ignored, so a byte array of 0..15 with flags in the top bits pastes
|
||||||
|
as it is.
|
||||||
|
|
||||||
|
With `doubleWide` each byte fills two pixels side by side, so `w` bytes cover
|
||||||
|
`2 * w` pixels: a 160-wide picture fills the 320-wide surface, the way Sierra's
|
||||||
|
AGI draws.
|
||||||
|
|
||||||
|
The rectangle is clipped to the surface pixel by pixel (a doubled byte cut by
|
||||||
|
an edge shows its one pixel there). Only the pasted pixels change and are
|
||||||
|
marked for the next `jlStagePresent`.
|
||||||
|
|
||||||
|
[cols="1,1,4", options="header"]
|
||||||
|
|===
|
||||||
|
|Parameter |Type |Description
|
||||||
|
|`dst` |`jlSurfaceT *` |Destination surface.
|
||||||
|
|`x`, `y` |`int16_t` |Top-left pixel on the surface.
|
||||||
|
|`w`, `h` |`uint16_t` |Bytes a row and rows.
|
||||||
|
|`src` |`const uint8_t *` |The colour indices, row by row.
|
||||||
|
|`srcStride` |`uint16_t` |Bytes from one source row to the next.
|
||||||
|
|`doubleWide` |`bool` |Each byte fills two pixels.
|
||||||
|
|===
|
||||||
|
|
||||||
|
*Returns:* nothing.
|
||||||
|
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
// A 160x168 AGI picture, doubled to 320 wide, below an 8-row status line.
|
||||||
|
jlSurfacePasteIndexed(jlStageGet(), 0, 8, 160, 168, picture, 160, true);
|
||||||
|
----
|
||||||
|
|
||||||
|
Every port has a fast path: asm on the IIgs (two pixel pairs a 16-bit
|
||||||
|
operation) and on the planar ports (table-driven chunky-to-planar, eight
|
||||||
|
pixels a step), four bytes a step in C on the chunky ports.
|
||||||
|
|
||||||
|
=== jlSurfaceSnapIndexed
|
||||||
|
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
||||||
|
----
|
||||||
|
|
||||||
|
The reverse of `jlSurfacePasteIndexed`: reads a rectangle of a surface into your
|
||||||
|
memory as colour indices, one byte a pixel (0..15), row `r` at
|
||||||
|
`dst + r * dstStride`. With `doubleWide` the rectangle is `2 * w` pixels wide
|
||||||
|
and each byte takes the left pixel of its pair. A byte whose pixel is off the
|
||||||
|
surface reads 0, as `jlSamplePixel` does. Bytes between `w` and `dstStride` are
|
||||||
|
left alone.
|
||||||
|
|
||||||
|
[cols="1,1,4", options="header"]
|
||||||
|
|===
|
||||||
|
|Parameter |Type |Description
|
||||||
|
|`src` |`const jlSurfaceT *` |Source surface.
|
||||||
|
|`x`, `y` |`int16_t` |Top-left pixel on the surface.
|
||||||
|
|`w`, `h` |`uint16_t` |Bytes a row and rows.
|
||||||
|
|`dst` |`uint8_t *` |Where the colour indices go, row by row.
|
||||||
|
|`dstStride` |`uint16_t` |Bytes from one destination row to the next.
|
||||||
|
|`doubleWide` |`bool` |Read every other pixel.
|
||||||
|
|===
|
||||||
|
|
||||||
|
*Returns:* nothing.
|
||||||
|
|
||||||
=== jlSurfaceSaveFile
|
=== jlSurfaceSaveFile
|
||||||
|
|
||||||
[source,c]
|
[source,c]
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@
|
||||||
|
|
||||||
|`surface.h`
|
|`surface.h`
|
||||||
|`jlSurfaceCreate`, `jlSurfaceDestroy`, `jlStageGet`, `jlSurfaceCopy`,
|
|`jlSurfaceCreate`, `jlSurfaceDestroy`, `jlStageGet`, `jlSurfaceCopy`,
|
||||||
`jlSurfaceCopyRect`, `jlSurfaceSaveFile`, `jlSurfaceLoadFile`,
|
`jlSurfaceCopyRect`, `jlSurfacePasteIndexed`, `jlSurfaceSnapIndexed`,
|
||||||
`jlSurfaceHash`
|
`jlSurfaceSaveFile`, `jlSurfaceLoadFile`, `jlSurfaceHash`
|
||||||
|
|
||||||
|`present.h`
|
|`present.h`
|
||||||
|`jlStagePresent`
|
|`jlStagePresent`
|
||||||
|
|
@ -86,7 +86,8 @@
|
||||||
|`tile.h`
|
|`tile.h`
|
||||||
|`jlTileCopy`, `jlTileCopyMasked`, `jlTileFill`, `jlTileBankLoad`,
|
|`jlTileCopy`, `jlTileCopyMasked`, `jlTileFill`, `jlTileBankLoad`,
|
||||||
`jlTileBankLoadToSurface`, `jlTileSnap`, `jlTilePaste`, `jlTilePasteMono`,
|
`jlTileBankLoadToSurface`, `jlTileSnap`, `jlTilePaste`, `jlTilePasteMono`,
|
||||||
`jlTilePasteGlyph`, `jlDrawText`, `jlTileMapPaste`
|
`jlTilePasteGlyph`, `jlDrawText`, `jlTileMapPaste`, `jlTileLayerCreate`,
|
||||||
|
`jlTileLayerDestroy`, `jlTileLayerDraw`, `jlTileLayerSetTile`
|
||||||
|
|
||||||
|`sprite.h`
|
|`sprite.h`
|
||||||
|`jlSpriteBankLoad`, `jlSpriteBankLoadPrecompiled`,
|
|`jlSpriteBankLoad`, `jlSpriteBankLoadPrecompiled`,
|
||||||
|
|
|
||||||
|
|
@ -243,8 +243,8 @@ one-time startup cost for a much smaller distribution.
|
||||||
=== Music: the Portable Chip Tracker
|
=== Music: the Portable Chip Tracker
|
||||||
|
|
||||||
This is the recommended music path. One authored song plays on every port
|
This is the recommended music path. One authored song plays on every port
|
||||||
at register-write cost, with no CPU mixing -- which matters enormously on the
|
at register-write cost on the machines' sound chips -- which matters
|
||||||
ST, where `.MOD` playback is only marginally viable.
|
enormously on the ST, where `.MOD` playback is only marginally viable.
|
||||||
|
|
||||||
Songs are plain text, one row per line:
|
Songs are plain text, one row per line:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ directory -- the paths in the Makefile are relative to it -- or with `make -C`:
|
||||||
[source,console]
|
[source,console]
|
||||||
----
|
----
|
||||||
$ make # every target whose toolchain is installed
|
$ make # every target whose toolchain is installed
|
||||||
$ make dos # one target: iigs amiga atarist ste dos x68000 dvx
|
$ make dos # one target (any PLATFORM name, below)
|
||||||
$ make clean # the project's build/ (clean-dos: one target's)
|
$ make clean # the project's build/ (clean-dos: one target's)
|
||||||
$ make help
|
$ make help
|
||||||
----
|
----
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,8 @@ hardware:
|
||||||
|
|
||||||
|Chip tracker
|
|Chip tracker
|
||||||
|`jlMusicPlay` -- a portable event stream driving three tone voices plus
|
|`jlMusicPlay` -- a portable event stream driving three tone voices plus
|
||||||
noise, at register-write cost with no CPU mixing.
|
noise, at register-write cost on the machines' sound chips (mixed in
|
||||||
|
software on DVX and the hosted ports).
|
||||||
|
|
||||||
|Voices and effects
|
|Voices and effects
|
||||||
|`jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and the streaming forms.
|
|`jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and the streaming forms.
|
||||||
|
|
|
||||||
|
|
@ -583,6 +583,38 @@ static void settleLayer(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Indexed rows: an AGI picture, 160x168 color indices each doubled to two
|
||||||
|
// pixels, pushed below an 8-row status line, and snapped back. The source
|
||||||
|
// bytes carry high nibbles the paste must ignore.
|
||||||
|
#define UBER_INDEXED_W 160u
|
||||||
|
#define UBER_INDEXED_H 168u
|
||||||
|
#define UBER_INDEXED_Y 8
|
||||||
|
|
||||||
|
static uint8_t *gIndexed = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
static void indexedData(void) {
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
|
||||||
|
for (y = 0; y < UBER_INDEXED_H; y++) {
|
||||||
|
for (x = 0; x < UBER_INDEXED_W; x++) {
|
||||||
|
gIndexed[y * UBER_INDEXED_W + x] = (uint8_t)(x * 7u + y * 13u + (x ^ y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void op_indexedPaste(void) {
|
||||||
|
jlSurfacePasteIndexed(jlgStage, 0, UBER_INDEXED_Y, UBER_INDEXED_W, UBER_INDEXED_H, gIndexed, UBER_INDEXED_W, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void op_indexedSnap(void) {
|
||||||
|
jlSurfaceSnapIndexed(jlgStage, 0, UBER_INDEXED_Y, UBER_INDEXED_W, UBER_INDEXED_H, gIndexed, UBER_INDEXED_W, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef UBER_PROBE
|
#ifdef UBER_PROBE
|
||||||
// NATIVE-PERF Phase 6 W0 probe rows. NOT part of the golden contract:
|
// NATIVE-PERF Phase 6 W0 probe rows. NOT part of the golden contract:
|
||||||
// this block only exists in the scratch probe binary (-DUBER_PROBE,
|
// this block only exists in the scratch probe binary (-DUBER_PROBE,
|
||||||
|
|
@ -2053,6 +2085,157 @@ static void __attribute__((noinline)) checkFillRectRows(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// jlSurfacePasteIndexed and jlSurfaceSnapIndexed: rectangles single and
|
||||||
|
// doubled, at odd and even columns, cut by every edge (a doubled byte
|
||||||
|
// showing its one pixel at column 0 or 319), from a source whose high
|
||||||
|
// nibbles are set -- pasted on the stage and on another surface, then each
|
||||||
|
// spot snapped back from both (off-surface bytes read 0). One hash folds
|
||||||
|
// both surfaces and every snapped row whole, the stride padding the snap
|
||||||
|
// must leave alone included. (checkIndexedSweep checks the pixels one by
|
||||||
|
// one.)
|
||||||
|
#define UBER_CHK_INDEXED_W 24u
|
||||||
|
#define UBER_CHK_INDEXED_H 12u
|
||||||
|
#define UBER_CHK_INDEXED_STRIDE 27u
|
||||||
|
#define UBER_CHK_INDEXED_SPOTS 12u
|
||||||
|
|
||||||
|
typedef struct UberIndexedSpotT {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
bool wide;
|
||||||
|
} UberIndexedSpotT;
|
||||||
|
|
||||||
|
static const UberIndexedSpotT kIndexedSpots[UBER_CHK_INDEXED_SPOTS] = {
|
||||||
|
{ 0, 0, false },
|
||||||
|
{ 33, 14, false },
|
||||||
|
{ -5, 30, false },
|
||||||
|
{ 301, 44, false },
|
||||||
|
{ 2, 60, true },
|
||||||
|
{ 77, 75, true },
|
||||||
|
{ -7, 90, true },
|
||||||
|
{ 297, 105, true },
|
||||||
|
{ 140, 194, true },
|
||||||
|
{ 200, -6, false },
|
||||||
|
{ -30, 150, true },
|
||||||
|
{ 319, 170, false }
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t gChkIndexedSrc[UBER_CHK_INDEXED_STRIDE * UBER_CHK_INDEXED_H];
|
||||||
|
static uint8_t gChkIndexedSnap[UBER_CHK_INDEXED_STRIDE * UBER_CHK_INDEXED_H];
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t indexedFold(uint32_t h, const jlSurfaceT *s) {
|
||||||
|
const UberIndexedSpotT *spot;
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t n;
|
||||||
|
|
||||||
|
for (n = 0; n < UBER_CHK_INDEXED_SPOTS; n++) {
|
||||||
|
spot = &kIndexedSpots[n];
|
||||||
|
jlSurfaceSnapIndexed(s, spot->x, spot->y, UBER_CHK_INDEXED_W, UBER_CHK_INDEXED_H, gChkIndexedSnap, UBER_CHK_INDEXED_STRIDE, spot->wide);
|
||||||
|
for (i = 0; i < UBER_CHK_INDEXED_STRIDE * UBER_CHK_INDEXED_H; i++) {
|
||||||
|
h = h * 31u + gChkIndexedSnap[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void __attribute__((noinline)) checkIndexed(void) {
|
||||||
|
const UberIndexedSpotT *spot;
|
||||||
|
jlSurfaceT *other;
|
||||||
|
uint32_t h;
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t n;
|
||||||
|
|
||||||
|
uberMbOp(124);
|
||||||
|
for (i = 0; i < UBER_CHK_INDEXED_STRIDE * UBER_CHK_INDEXED_H; i++) {
|
||||||
|
gChkIndexedSrc[i] = (uint8_t)(i * 37u + (i >> 3));
|
||||||
|
}
|
||||||
|
other = jlSurfaceCreate();
|
||||||
|
if (other == NULL) {
|
||||||
|
chkPassFail("indexed (surface alloc)", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jlSurfaceClear(jlgStage, 4);
|
||||||
|
jlSurfaceClear(other, 11);
|
||||||
|
for (n = 0; n < UBER_CHK_INDEXED_SPOTS; n++) {
|
||||||
|
spot = &kIndexedSpots[n];
|
||||||
|
jlSurfacePasteIndexed(jlgStage, spot->x, spot->y, UBER_CHK_INDEXED_W, UBER_CHK_INDEXED_H, gChkIndexedSrc, UBER_CHK_INDEXED_STRIDE, spot->wide);
|
||||||
|
jlSurfacePasteIndexed(other, (int16_t)(spot->x + 1), spot->y, UBER_CHK_INDEXED_W, UBER_CHK_INDEXED_H, gChkIndexedSrc, UBER_CHK_INDEXED_STRIDE, spot->wide);
|
||||||
|
}
|
||||||
|
h = jlSurfaceHash(jlgStage);
|
||||||
|
h = h * 31u + jlSurfaceHash(other);
|
||||||
|
h = indexedFold(h, jlgStage);
|
||||||
|
h = indexedFold(h, other);
|
||||||
|
jlSurfaceDestroy(other);
|
||||||
|
jlLogF("UBER-CHK: indexed: hash=%08lX", (unsigned long)h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// jlSurfacePasteIndexed pixel by pixel on the target, where the planar
|
||||||
|
// ports' asm runs: rectangles 1-20 bytes wide at all 16 pixel phases (both
|
||||||
|
// column parities, so heads, pairs, and tails), single and doubled, two
|
||||||
|
// rows on a cleared band; every pixel of the band from a pixel left of the
|
||||||
|
// rectangle to one right of it checked, then the rectangle snapped back.
|
||||||
|
// The hash is the number of wrong pixels and bytes: 0.
|
||||||
|
#define UBER_ISWEEP_Y 100
|
||||||
|
#define UBER_ISWEEP_H 2u
|
||||||
|
#define UBER_ISWEEP_X0 64
|
||||||
|
#define UBER_ISWEEP_PHASES 16
|
||||||
|
#define UBER_ISWEEP_MAX_W 20u
|
||||||
|
#define UBER_ISWEEP_BG 5u
|
||||||
|
|
||||||
|
static uint32_t indexedSweepCase(int16_t x, uint16_t w, bool wide) {
|
||||||
|
uint32_t bad;
|
||||||
|
int16_t px;
|
||||||
|
int16_t right;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t i;
|
||||||
|
uint8_t want;
|
||||||
|
|
||||||
|
bad = 0u;
|
||||||
|
right = (int16_t)(x + (int16_t)(w << (wide ? 1u : 0u)));
|
||||||
|
jlFillRect(jlgStage, 0, UBER_ISWEEP_Y, JL_SURFACE_WIDTH, UBER_ISWEEP_H, UBER_ISWEEP_BG);
|
||||||
|
jlSurfacePasteIndexed(jlgStage, x, UBER_ISWEEP_Y, w, UBER_ISWEEP_H, gChkIndexedSrc, UBER_CHK_INDEXED_STRIDE, wide);
|
||||||
|
for (r = 0; r < UBER_ISWEEP_H; r++) {
|
||||||
|
for (px = (int16_t)(x - 1); px <= right; px++) {
|
||||||
|
want = UBER_ISWEEP_BG;
|
||||||
|
if (px >= x && px < right) {
|
||||||
|
want = (uint8_t)(gChkIndexedSrc[r * UBER_CHK_INDEXED_STRIDE + ((uint16_t)(px - x) >> (wide ? 1u : 0u))] & 0x0Fu);
|
||||||
|
}
|
||||||
|
if (jlSamplePixel(jlgStage, px, (int16_t)(UBER_ISWEEP_Y + r)) != want) {
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jlSurfaceSnapIndexed(jlgStage, x, UBER_ISWEEP_Y, w, UBER_ISWEEP_H, gChkIndexedSnap, UBER_CHK_INDEXED_STRIDE, wide);
|
||||||
|
for (r = 0; r < UBER_ISWEEP_H; r++) {
|
||||||
|
for (i = 0; i < w; i++) {
|
||||||
|
if (gChkIndexedSnap[r * UBER_CHK_INDEXED_STRIDE + i] != (gChkIndexedSrc[r * UBER_CHK_INDEXED_STRIDE + i] & 0x0Fu)) {
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void __attribute__((noinline)) checkIndexedSweep(void) {
|
||||||
|
uint32_t bad;
|
||||||
|
int16_t phase;
|
||||||
|
uint16_t w;
|
||||||
|
|
||||||
|
uberMbOp(125);
|
||||||
|
bad = 0u;
|
||||||
|
for (phase = 0; phase < UBER_ISWEEP_PHASES; phase++) {
|
||||||
|
for (w = 1u; w <= UBER_ISWEEP_MAX_W; w++) {
|
||||||
|
bad += indexedSweepCase((int16_t)(UBER_ISWEEP_X0 + phase), w, false);
|
||||||
|
bad += indexedSweepCase((int16_t)(UBER_ISWEEP_X0 + phase), w, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jlLogF("UBER-CHK: indexed sweep: hash=%08lX", (unsigned long)bad);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
||||||
uberMbPhase(UBER_PHASE_CHECKS);
|
uberMbPhase(UBER_PHASE_CHECKS);
|
||||||
jlLogF("UBER-CHK: ----- begin -----");
|
jlLogF("UBER-CHK: ----- begin -----");
|
||||||
|
|
@ -2068,6 +2251,8 @@ static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
||||||
checkTileLayer();
|
checkTileLayer();
|
||||||
checkTilePasteGlyph();
|
checkTilePasteGlyph();
|
||||||
checkFillRectRows();
|
checkFillRectRows();
|
||||||
|
checkIndexed();
|
||||||
|
checkIndexedSweep();
|
||||||
// jlShutdown -> jlInit -> stale-sprite-destroy (#34) needs a teardown
|
// jlShutdown -> jlInit -> stale-sprite-destroy (#34) needs a teardown
|
||||||
// of the whole library mid-run; it gets a dedicated micro-example in
|
// of the whole library mid-run; it gets a dedicated micro-example in
|
||||||
// Phase 1 rather than risking the benchmark's display state here.
|
// Phase 1 rather than risking the benchmark's display state here.
|
||||||
|
|
@ -2209,6 +2394,19 @@ static void __attribute__((noinline)) runAllTests(void) {
|
||||||
jlLogF("UBER: jlTileLayerDraw scroll: SKIPPED (layer alloc failed)");
|
jlLogF("UBER: jlTileLayerDraw scroll: SKIPPED (layer alloc failed)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indexed rows (appended last per the cumulative-hash safety rule). The
|
||||||
|
// snap reads back what the paste wrote, so it leaves the stage as is.
|
||||||
|
gIndexed = (uint8_t *)jlAlloc((uint32_t)UBER_INDEXED_W * UBER_INDEXED_H);
|
||||||
|
if (gIndexed != NULL) {
|
||||||
|
indexedData();
|
||||||
|
timeOp("jlSurfacePasteIndexed 160x168 wide", op_indexedPaste);
|
||||||
|
timeOp("jlSurfaceSnapIndexed 160x168 wide", op_indexedSnap);
|
||||||
|
jlFree(gIndexed);
|
||||||
|
gIndexed = NULL;
|
||||||
|
} else {
|
||||||
|
jlLogF("UBER: jlSurfacePasteIndexed 160x168 wide: SKIPPED (buffer alloc failed)");
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef UBER_PROBE
|
#ifdef UBER_PROBE
|
||||||
// The presents count from here (the IIgs and hosted ones only while
|
// The presents count from here (the IIgs and hosted ones only while
|
||||||
// asked).
|
// asked).
|
||||||
|
|
|
||||||
|
|
@ -258,10 +258,11 @@ void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten);
|
||||||
// channel C. Noise shares channel C's volume register
|
// channel C. Noise shares channel C's volume register
|
||||||
// with tone voice 2: author noise and voice-2 notes
|
// with tone voice 2: author noise and voice-2 notes
|
||||||
// apart (songbake warns on overlap).
|
// apart (songbake warns on overlap).
|
||||||
// DOS -- OPL2 rhythm-mode snare drum (the chip's LFSR noise
|
// DOS -- the YM2149's 17-bit generator, mixed into the Sound
|
||||||
// source) on channel 7; the three tone channels are
|
// Blaster's stream (~22 kHz, so pitches 0-5 sound
|
||||||
// untouched. A tuned drum: its pitch spreads over the
|
// alike). An OPL2 without a Sound Blaster plays its
|
||||||
// snare's range instead of following the rate above.
|
// rhythm-mode snare drum instead, a tuned drum whose
|
||||||
|
// pitch spreads over the snare's range.
|
||||||
// Amiga -- Paula channel 3 looping a chip-RAM LFSR noise sample
|
// Amiga -- Paula channel 3 looping a chip-RAM LFSR noise sample
|
||||||
// (channels 0-2 are the tone voices).
|
// (channels 0-2 are the tone voices).
|
||||||
// IIgs -- a resident LFSR noise sample on SFX slot 3.
|
// IIgs -- a resident LFSR noise sample on SFX slot 3.
|
||||||
|
|
@ -276,9 +277,10 @@ void jlAudioNoise(uint8_t pitch, uint8_t atten);
|
||||||
|
|
||||||
// Portable chip-tracker music: play a JYM1 event stream (baked from
|
// Portable chip-tracker music: play a JYM1 event stream (baked from
|
||||||
// tools/songbake text notation) through the jlAudioVoice tone layer.
|
// tools/songbake text notation) through the jlAudioVoice tone layer.
|
||||||
// One authored song plays on every port at register-write cost --
|
// One authored song plays on every port at register-write cost on the
|
||||||
// there is no CPU mixing, so unlike jlAudioPlayMod this path is fully
|
// retro machines' chips (DOS's noise and the DVX and hosted ports mix in
|
||||||
// viable on the ST and cheap on DOS. The stream is read IN PLACE:
|
// software), so unlike jlAudioPlayMod this path is fully viable on the
|
||||||
|
// ST and cheap on DOS. The stream is read IN PLACE:
|
||||||
// data must stay valid until jlMusicStop or song end. Playback is
|
// data must stay valid until jlMusicStop or song end. Playback is
|
||||||
// pumped by jlAudioFrameTick (call it once per frame, as usual) and
|
// pumped by jlAudioFrameTick (call it once per frame, as usual) and
|
||||||
// paced by jlMillisElapsed, so tempo holds across 50/60/70 Hz ports.
|
// paced by jlMillisElapsed, so tempo holds across 50/60/70 Hz ports.
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,8 @@ uint16_t jlFrameCount(void);
|
||||||
|
|
||||||
// Display frame rate in Hz: 60 on the IIgs (50 on a PAL machine), 50 on
|
// Display frame rate in Hz: 60 on the IIgs (50 on a PAL machine), 50 on
|
||||||
// the Amiga (60 on an NTSC machine that cannot show PAL), 50 or 60 on
|
// the Amiga (60 on an NTSC machine that cannot show PAL), 50 or 60 on
|
||||||
// the ST as the machine is set, 70 on DOS and DVX, 55 on the X68000 (56
|
// the ST as the machine is set (71 with a monochrome monitor), 70 on DOS
|
||||||
// under MAME), and 60 on every hosted port. The actual VBL cadence may
|
// and DVX, 55 on the X68000 (56 under MAME), and 60 on every hosted port. The actual VBL cadence may
|
||||||
// drift slightly; the value reported here is what benchmarks divide
|
// drift slightly; the value reported here is what benchmarks divide
|
||||||
// by to convert iters-per-N-frames to ops/sec.
|
// by to convert iters-per-N-frames to ops/sec.
|
||||||
uint16_t jlFrameHz(void);
|
uint16_t jlFrameHz(void);
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
// The library forces color 0 of every palette to black; jlPaletteSet
|
// The library forces color 0 of every palette to black; jlPaletteSet
|
||||||
// silently masks that entry to $000 regardless of what the caller
|
// silently masks that entry to $000 regardless of what the caller
|
||||||
// passed, and jlSurfaceLoadFile does the same. Palette and SCB changes
|
// passed, and jlSurfaceLoadFile does the same. Palette and SCB changes
|
||||||
// to the stage reach the display at the next jlStagePresent. See
|
// to the stage reach the display at the next jlStagePresent. See the
|
||||||
// claudeDocs/DESIGN.md sections 5 and 10.
|
// manual's "Palette and Scanline Control" (docs/ApiPalette.adoc).
|
||||||
|
|
||||||
#ifndef JOEYLIB_PALETTE_H
|
#ifndef JOEYLIB_PALETTE_H
|
||||||
#define JOEYLIB_PALETTE_H
|
#define JOEYLIB_PALETTE_H
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,8 @@
|
||||||
#define JL_HAS_SURFACE_COPY_RECT // jlpSurfaceCopyRect: jlIigsCopyRectRows
|
#define JL_HAS_SURFACE_COPY_RECT // jlpSurfaceCopyRect: jlIigsCopyRectRows
|
||||||
// [dp],y row copies (short rows lose to
|
// [dp],y row copies (short rows lose to
|
||||||
// memcpy's per-call setup), function
|
// memcpy's per-call setup), function
|
||||||
|
#define JL_HAS_SURFACE_PASTE_INDEXED // whole bytes in asm (iigsIndexed.s), function
|
||||||
|
#define JL_HAS_SURFACE_SNAP_INDEXED // whole bytes in asm (iigsIndexed.s), function
|
||||||
#define JL_HAS_TILE_LAYER_BLIT // the generic rings, an unrolled stage blit (iigsTileLayer.c)
|
#define JL_HAS_TILE_LAYER_BLIT // the generic rings, an unrolled stage blit (iigsTileLayer.c)
|
||||||
#define JL_HAS_TILE_LAYER_PUT // the generic rings, an asm put (iigsTileLayer.c)
|
#define JL_HAS_TILE_LAYER_PUT // the generic rings, an asm put (iigsTileLayer.c)
|
||||||
#define JL_HAS_SPRITE_DRAW // chunky -> codegen; planes mirror is noop macro
|
#define JL_HAS_SPRITE_DRAW // chunky -> codegen; planes mirror is noop macro
|
||||||
|
|
@ -280,6 +282,8 @@
|
||||||
#define JL_HAS_SURFACE_COPY_RECT // amiga per-plane rect row copy, function
|
#define JL_HAS_SURFACE_COPY_RECT // amiga per-plane rect row copy, function
|
||||||
#define JL_HAS_SAMPLE_PIXEL // planarSamplePixel, inline (amigaTile.h)
|
#define JL_HAS_SAMPLE_PIXEL // planarSamplePixel, inline (amigaTile.h)
|
||||||
#define JL_HAS_SURFACE_HASH // amiga planar hash, function
|
#define JL_HAS_SURFACE_HASH // amiga planar hash, function
|
||||||
|
#define JL_HAS_SURFACE_PASTE_INDEXED // the shared planar paste (src/m68k/planarIndexed.c), function
|
||||||
|
#define JL_HAS_SURFACE_SNAP_INDEXED // the shared planar snap (src/m68k/planarIndexed.c), function
|
||||||
#define JL_HAS_SURFACE_LOAD_FILE // amiga planar load, function
|
#define JL_HAS_SURFACE_LOAD_FILE // amiga planar load, function
|
||||||
#define JL_HAS_SURFACE_SAVE_FILE // amiga planar save, function
|
#define JL_HAS_SURFACE_SAVE_FILE // amiga planar save, function
|
||||||
#define JL_HAS_STAGE_ALLOC_PIXELS // amiga: NULL (no chunky stage shadow), function
|
#define JL_HAS_STAGE_ALLOC_PIXELS // amiga: NULL (no chunky stage shadow), function
|
||||||
|
|
@ -364,6 +368,8 @@
|
||||||
#define JL_HAS_SURFACE_COPY_RECT // st interleaved rect row memcpy, function
|
#define JL_HAS_SURFACE_COPY_RECT // st interleaved rect row memcpy, function
|
||||||
#define JL_HAS_SAMPLE_PIXEL // stSamplePixel, inline (stTile.h)
|
#define JL_HAS_SAMPLE_PIXEL // stSamplePixel, inline (stTile.h)
|
||||||
#define JL_HAS_SURFACE_HASH // st planar hash, function
|
#define JL_HAS_SURFACE_HASH // st planar hash, function
|
||||||
|
#define JL_HAS_SURFACE_PASTE_INDEXED // the shared planar paste (src/m68k/planarIndexed.c), function
|
||||||
|
#define JL_HAS_SURFACE_SNAP_INDEXED // the shared planar snap (src/m68k/planarIndexed.c), function
|
||||||
#define JL_HAS_SURFACE_LOAD_FILE // st planar load, function
|
#define JL_HAS_SURFACE_LOAD_FILE // st planar load, function
|
||||||
#define JL_HAS_SURFACE_SAVE_FILE // st planar save, function
|
#define JL_HAS_SURFACE_SAVE_FILE // st planar save, function
|
||||||
#define JL_HAS_STAGE_ALLOC_PIXELS // st: NULL (no chunky stage shadow), function
|
#define JL_HAS_STAGE_ALLOC_PIXELS // st: NULL (no chunky stage shadow), function
|
||||||
|
|
@ -506,6 +512,8 @@
|
||||||
#define JL_HAS_SURFACE_COPY_RECT
|
#define JL_HAS_SURFACE_COPY_RECT
|
||||||
#define JL_HAS_SAMPLE_PIXEL
|
#define JL_HAS_SAMPLE_PIXEL
|
||||||
#define JL_HAS_SURFACE_HASH
|
#define JL_HAS_SURFACE_HASH
|
||||||
|
#define JL_HAS_SURFACE_PASTE_INDEXED
|
||||||
|
#define JL_HAS_SURFACE_SNAP_INDEXED
|
||||||
#define JL_HAS_SURFACE_LOAD_FILE
|
#define JL_HAS_SURFACE_LOAD_FILE
|
||||||
#define JL_HAS_SURFACE_SAVE_FILE
|
#define JL_HAS_SURFACE_SAVE_FILE
|
||||||
#define JL_HAS_STAGE_ALLOC_PIXELS
|
#define JL_HAS_STAGE_ALLOC_PIXELS
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@
|
||||||
// each row is 4 bytes (8 pixels at 4bpp packed; high nibble = left
|
// each row is 4 bytes (8 pixels at 4bpp packed; high nibble = left
|
||||||
// pixel).
|
// pixel).
|
||||||
//
|
//
|
||||||
// Color 0 is always transparent on draw (DESIGN.md contract). Use a
|
// Color 0 is always transparent on draw (a library contract). Use a
|
||||||
// tile-block draw if you need an opaque rectangle.
|
// tile-block draw if you need an opaque rectangle.
|
||||||
//
|
//
|
||||||
// Performance contract: jlSpriteDraw should be at least as fast as the
|
// Performance contract: jlSpriteDraw should be at least as fast as the
|
||||||
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
// IIgs reference (the design calls for runtime-compiled draw code per
|
||||||
// code per CPU). The machine ports have runtime emitters (IIgs 65816,
|
// CPU). The machine ports have runtime emitters (IIgs 65816,
|
||||||
// Amiga/X68000 68k planar, ST 68k interleaved, DOS/DVX x86), and so does
|
// Amiga/X68000 68k planar, ST 68k interleaved, DOS/DVX x86), and so does
|
||||||
// Win16 (x86, as DOS);
|
// Win16 (x86, as DOS);
|
||||||
// jlSpriteCompile compiles the shift/op variants into the codegen arena
|
// jlSpriteCompile compiles the shift/op variants into the codegen arena
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@
|
||||||
// does not see the storage format.
|
// does not see the storage format.
|
||||||
//
|
//
|
||||||
// Each surface carries its own 200-entry SCB (scanline control byte)
|
// Each surface carries its own 200-entry SCB (scanline control byte)
|
||||||
// table and a 16-by-16 $0RGB palette set. See claudeDocs/DESIGN.md section 6.
|
// table and a 16-by-16 $0RGB palette set. See the manual's "Surfaces and
|
||||||
|
// Presenting" (docs/ApiSurface.adoc).
|
||||||
|
|
||||||
#ifndef JOEYLIB_SURFACE_H
|
#ifndef JOEYLIB_SURFACE_H
|
||||||
#define JOEYLIB_SURFACE_H
|
#define JOEYLIB_SURFACE_H
|
||||||
|
|
@ -72,6 +73,23 @@ void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||||
// same region.
|
// same region.
|
||||||
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||||
|
|
||||||
|
// Write a rectangle of color indices onto a surface at (x, y): src holds
|
||||||
|
// h rows of w bytes, row r starting at src + r * srcStride, one pixel's
|
||||||
|
// color index in each (0..15; the high nibble is ignored). With doubleWide
|
||||||
|
// each byte fills two pixels side by side, so the rectangle is 2w pixels
|
||||||
|
// wide on the surface (a 160-wide image on the 320-wide screen). Clipped
|
||||||
|
// to the surface, pixel by pixel. Whatever a port stores pixels as, the
|
||||||
|
// same bytes give the same pixels on every port.
|
||||||
|
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
||||||
|
|
||||||
|
// The reverse of jlSurfacePasteIndexed: read a rectangle of a surface at
|
||||||
|
// (x, y) into dst as color indices, h rows of w bytes, row r starting at
|
||||||
|
// dst + r * dstStride, one pixel's color index (0..15) in each. With
|
||||||
|
// doubleWide the rectangle is 2w pixels wide on the surface and each byte
|
||||||
|
// takes the left pixel of its pair. A byte whose pixel is off the surface
|
||||||
|
// reads 0, as jlSamplePixel does.
|
||||||
|
void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
||||||
|
|
||||||
// Persist a surface to disk in target-native form: 32000 bytes of
|
// Persist a surface to disk in target-native form: 32000 bytes of
|
||||||
// pixels followed by 200 bytes of SCB followed by 512 bytes of $0RGB
|
// pixels followed by 200 bytes of SCB followed by 512 bytes of $0RGB
|
||||||
// palette table (16 palettes x 16 colors, native uint16_t). Returns
|
// palette table (16 palettes x 16 colors, native uint16_t). Returns
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# shellcheck shell=bash
|
|
||||||
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
||||||
# See LICENSE.md for license information.
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
#
|
|
||||||
# libxmp-lite 4.7.0: the MOD decoder the DOS, Atari ST, X68000, SDL3, PSP,
|
|
||||||
# web, Win16, Win32, Haiku and BeOS audio HALs compiled in until 2026-10-14,
|
|
||||||
# when JoeyLib's own module player (src/core/modPlayer.c) replaced it. No
|
|
||||||
# build uses it now; it stays a reference to compare that player against,
|
|
||||||
# installed only when asked for by name (install libxmp), so no toolchain
|
|
||||||
# fetches it.
|
|
||||||
|
|
||||||
compDescription="audio: libxmp-lite 4.7.0 source (optional; no build links it; a reference for the module player)"
|
|
||||||
compGroups="optional"
|
|
||||||
compRequires="host"
|
|
||||||
compHostDeps="tar"
|
|
||||||
compUrl="https://github.com/libxmp/libxmp/releases/download/libxmp-4.7.0/libxmp-lite-4.7.0.tar.gz"
|
|
||||||
compSha256="69967a7802d2d2f938347f6f9cd95232bf45a4623cbfcc196e37cdaefadc3974"
|
|
||||||
compPin="$compSha256"
|
|
||||||
|
|
||||||
|
|
||||||
compInstall() {
|
|
||||||
local archive
|
|
||||||
archive="$(fetchFile "$compUrl" "$compSha256")"
|
|
||||||
rm -rf "$compDir"
|
|
||||||
extractArchive "$archive" "$compDir" 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
compInstalled() {
|
|
||||||
[ -f "$compDir/include/libxmp-lite/xmp.h" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
compVerify() {
|
|
||||||
[ -d "$compDir/src" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
compEnv() {
|
|
||||||
envVar LIBXMP_DIR "$compDir"
|
|
||||||
}
|
|
||||||
|
|
@ -55,6 +55,7 @@ LIB_OBJS := \
|
||||||
$(BUILD)/obj/port/ptplayer.o \
|
$(BUILD)/obj/port/ptplayer.o \
|
||||||
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
||||||
$(BUILD)/obj/68k/planarHash.o \
|
$(BUILD)/obj/68k/planarHash.o \
|
||||||
|
$(BUILD)/obj/68k/planarIndexed.o \
|
||||||
$(BUILD)/obj/68k/planarSprite.o \
|
$(BUILD)/obj/68k/planarSprite.o \
|
||||||
$(BUILD)/obj/68k/spriteC2p.o \
|
$(BUILD)/obj/68k/spriteC2p.o \
|
||||||
$(BUILD)/obj/68k/planarTileLayer.o \
|
$(BUILD)/obj/68k/planarTileLayer.o \
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ LIB_OBJS := \
|
||||||
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S)) \
|
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S)) \
|
||||||
$(BUILD)/obj/68k/spriteEmitInterleaved68k.o \
|
$(BUILD)/obj/68k/spriteEmitInterleaved68k.o \
|
||||||
$(BUILD)/obj/68k/planarHash.o \
|
$(BUILD)/obj/68k/planarHash.o \
|
||||||
|
$(BUILD)/obj/68k/planarIndexed.o \
|
||||||
$(BUILD)/obj/68k/planarSprite.o \
|
$(BUILD)/obj/68k/planarSprite.o \
|
||||||
$(BUILD)/obj/68k/spriteC2p.o \
|
$(BUILD)/obj/68k/spriteC2p.o \
|
||||||
$(BUILD)/obj/codegen/spriteCompile.o \
|
$(BUILD)/obj/codegen/spriteCompile.o \
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
REPO_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..)
|
||||||
|
|
||||||
# Tool paths (AMIGA_CC, LLVM816_ROOT, LIBXMP_DIR, ...) come from the file
|
# Tool paths (AMIGA_CC, LLVM816_ROOT, X68K_ROOT, ...) come from the file
|
||||||
# install.sh generates; it is the only place they are defined.
|
# install.sh generates; it is the only place they are defined.
|
||||||
-include $(REPO_DIR)/toolchains/config.mk
|
-include $(REPO_DIR)/toolchains/config.mk
|
||||||
|
|
||||||
|
|
|
||||||
12
make/iigs.mk
12
make/iigs.mk
|
|
@ -10,13 +10,11 @@
|
||||||
# bank placement is handled by -ffunction-sections + link816
|
# bank placement is handled by -ffunction-sections + link816
|
||||||
# dead-stripping.
|
# dead-stripping.
|
||||||
#
|
#
|
||||||
# STATUS: the launchable-binary pipeline (multi-segment OMF emission +
|
# `iigs` (and `iigs-lib`) builds the library alone; the examples come from
|
||||||
# disk packaging + GS/OS launch) is still being brought up. `iigs-lib`
|
# `iigs-examples`, or `iigs-disk`, which also packs them on a volume.
|
||||||
# compile-checks every library + example translation unit, and
|
# `iigs-clang-smoke` (not part of the default goal: it needs MAME) checks
|
||||||
# `iigs-clang-smoke` (not part of the default goal: it needs MAME)
|
# the clang -> llvm-mc -> link816 -> MAME path end to end. Programs are
|
||||||
# validates the full clang -> llvm-mc -> link816 -> MAME path end to end. Per-example binary targets build via
|
# multi-segment OMF from clang-build.sh.
|
||||||
# clang-build.sh; large examples need the multi-segment OMF wiring still
|
|
||||||
# in progress.
|
|
||||||
#
|
#
|
||||||
# This file holds JoeyLib's library for the IIgs, programRules, the IIgs
|
# This file holds JoeyLib's library for the IIgs, programRules, the IIgs
|
||||||
# rules for one program, which make/programs.mk instantiates for every
|
# rules for one program, which make/programs.mk instantiates for every
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
#
|
#
|
||||||
# make every port the programs build for whose toolchain
|
# make every port the programs build for whose toolchain
|
||||||
# is installed (ports without one are noted, skipped)
|
# is installed (ports without one are noted, skipped)
|
||||||
# make <port> one port: iigs amiga atarist ste dos x68000 dvx sdl3 web win32 win16 psp
|
# make <port> one port: any of JOEY_PORTS (make/common.mk)
|
||||||
# make <port>-<goal> one of that port's goals: iigs-game-disk GAME=NAME,
|
# make <port>-<goal> one of that port's goals: iigs-game-disk GAME=NAME,
|
||||||
# iigs-verify-game-disk, iigs-game-disks
|
# iigs-verify-game-disk, iigs-game-disks
|
||||||
# make JOEY_PORT=<port> <target>
|
# make JOEY_PORT=<port> <target>
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,10 @@ LIB_OBJS := \
|
||||||
$(BUILD)/obj/68k/sfxMix.o \
|
$(BUILD)/obj/68k/sfxMix.o \
|
||||||
$(BUILD)/obj/68k/planarSpriteWalk.o \
|
$(BUILD)/obj/68k/planarSpriteWalk.o \
|
||||||
$(BUILD)/obj/68k/planarLayerBlit.o \
|
$(BUILD)/obj/68k/planarLayerBlit.o \
|
||||||
|
$(BUILD)/obj/68k/planarIndexedColumns.o \
|
||||||
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
||||||
$(BUILD)/obj/68k/planarHash.o \
|
$(BUILD)/obj/68k/planarHash.o \
|
||||||
|
$(BUILD)/obj/68k/planarIndexed.o \
|
||||||
$(BUILD)/obj/68k/planarSprite.o \
|
$(BUILD)/obj/68k/planarSprite.o \
|
||||||
$(BUILD)/obj/68k/spriteC2p.o \
|
$(BUILD)/obj/68k/spriteC2p.o \
|
||||||
$(BUILD)/obj/68k/planarTileLayer.o \
|
$(BUILD)/obj/68k/planarTileLayer.o \
|
||||||
|
|
|
||||||
36
scripts/check-indexed.sh
Executable file
36
scripts/check-indexed.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
# See LICENSE.md for license information.
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# check-indexed.sh - Host gate for jlSurfacePasteIndexed and
|
||||||
|
# jlSurfaceSnapIndexed: the BLANK port built with the host cc, and
|
||||||
|
# tests/host/indexedHost.c pasting and snapping edge-case and seeded random
|
||||||
|
# rectangles (odd columns, both edges, doubleWide bytes cut by an edge, rows
|
||||||
|
# off both ends, the int16_t limits), each checked against a reference made
|
||||||
|
# one pixel at a time with jlDrawPixel and jlSamplePixel; then the planar
|
||||||
|
# ports' paste and snap (src/m68k/planarIndexed.c) on host buffers in both
|
||||||
|
# planar layouts, separate and ST-interleaved.
|
||||||
|
#
|
||||||
|
# Usage: scripts/check-indexed.sh
|
||||||
|
# Exit: 0 PASS, 1 FAIL
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
cc=${CC:-cc}
|
||||||
|
work=$(mktemp -d -t joeylib-indexed.XXXXXX)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
|
cd "$repo"
|
||||||
|
"$cc" -O2 -DJOEYLIB_PLATFORM_BLANK \
|
||||||
|
-Iinclude -Iinclude/joey -Isrc/core -Isrc/codegen -Isrc/m68k -Wall -Wextra -Werror \
|
||||||
|
src/core/*.c src/generic/*.c src/blank/blank.c \
|
||||||
|
src/m68k/planarIndexed.c src/m68k/planarHash.c tests/host/indexedHost.c \
|
||||||
|
-lm -o "$work/indexedHost"
|
||||||
|
|
||||||
|
if "$work/indexedHost"; then
|
||||||
|
echo "check-indexed: PASS (indexed paste and snap match the pixel-by-pixel reference, clipped at every edge, chunky and both planar layouts)"
|
||||||
|
else
|
||||||
|
echo "check-indexed: FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
@ -10,14 +10,15 @@
|
||||||
# Kinds, in the order `all` runs them:
|
# Kinds, in the order `all` runs them:
|
||||||
# host the shared layer on the host, no emulator: the new-port
|
# host the shared layer on the host, no emulator: the new-port
|
||||||
# template (blank), file roots, the dirty-band marker, the line
|
# template (blank), file roots, the dirty-band marker, the line
|
||||||
# and circle fallbacks at the far ends of their coordinates, input,
|
# and circle fallbacks at the far ends of their coordinates, the
|
||||||
|
# indexed paste and snap against pixel-by-pixel ones, input,
|
||||||
# millis, the module player the software-mixing ports share,
|
# millis, the module player the software-mixing ports share,
|
||||||
# save, serial, sfxmix, tilebank, tile layers against their maps,
|
# save, serial, sfxmix, tilebank, tile layers against their maps,
|
||||||
# the Tiled map importer, and the VGM converter (with Furnace
|
# the Tiled map importer, and the VGM converter (with Furnace
|
||||||
# installed, on its demo songs too)
|
# installed, on its demo songs too)
|
||||||
# golden UBER on each port, its 54 per-op surface hashes against
|
# golden UBER on each port, its per-op surface hashes against
|
||||||
# tests/goldens/uber (x68000 against the IIgs's, its own gate;
|
# tests/goldens/uber (x68000 against the IIgs's, its own gate;
|
||||||
# dvx, sdl3, web, win32, win16, psp, haiku, haiku32 and beos
|
# dvx, sdl3, web, win32, win16, psp, haiku, haiku32, and beos
|
||||||
# against the DOS port's, whose chunky stage they share; psp under
|
# against the DOS port's, whose chunky stage they share; psp under
|
||||||
# PPSSPP with every memory access checked as a PSP checks it, so
|
# PPSSPP with every memory access checked as a PSP checks it, so
|
||||||
# an unaligned one fails; haiku, haiku32 and beos in their VMs
|
# an unaligned one fails; haiku, haiku32 and beos in their VMs
|
||||||
|
|
@ -232,6 +233,7 @@ host host blank scripts/check-blank.sh
|
||||||
host host roots scripts/check-fileroots.sh
|
host host roots scripts/check-fileroots.sh
|
||||||
host host marks scripts/check-dirtymark.sh
|
host host marks scripts/check-dirtymark.sh
|
||||||
host host draw scripts/check-draw.sh
|
host host draw scripts/check-draw.sh
|
||||||
|
host host indexed scripts/check-indexed.sh
|
||||||
host host input scripts/check-input.sh
|
host host input scripts/check-input.sh
|
||||||
host host millis scripts/check-millis.sh
|
host host millis scripts/check-millis.sh
|
||||||
host host mod scripts/check-modplayer.sh
|
host host mod scripts/check-modplayer.sh
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
# Manager takes the focus, the desktop comes back and UBER presents into
|
# Manager takes the focus, the desktop comes back and UBER presents into
|
||||||
# its window) and resumed (the Task Manager's Switch To): the
|
# its window) and resumed (the Task Manager's Switch To): the
|
||||||
# screenshots must be the 320x200 mode, the desktop, then 320x200 again;
|
# screenshots must be the 320x200 mode, the desktop, then 320x200 again;
|
||||||
# DVX.LOG must show the mode set twice; and UBER's 54 hashes must equal
|
# DVX.LOG must show the mode set twice; and UBER's hashes must equal
|
||||||
# the DOS port's (tests/goldens/uber/dos.txt): the stage is DOS's
|
# the DOS port's (tests/goldens/uber/dos.txt): the stage is DOS's
|
||||||
# wherever it is shown.
|
# wherever it is shown.
|
||||||
# 2. KEYS on the whole screen: the typed string must reach jlInputGetChar
|
# 2. KEYS on the whole screen: the typed string must reach jlInputGetChar
|
||||||
|
|
@ -227,4 +227,4 @@ if [ -n "$fails" ]; then
|
||||||
echo "verify-dvx-fullscreen: FAIL - $fails"
|
echo "verify-dvx-fullscreen: FAIL - $fails"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "verify-dvx-fullscreen: PASS (UBER 54/54 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)"
|
echo "verify-dvx-fullscreen: PASS (UBER's hashes 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)"
|
||||||
|
|
|
||||||
|
|
@ -1885,7 +1885,7 @@ uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||||
const AmigaPlanarT *pd;
|
const AmigaPlanarT *pd;
|
||||||
|
|
||||||
pd = (const AmigaPlanarT *)s->portData;
|
pd = (const AmigaPlanarT *)s->portData;
|
||||||
return jlPlanarSurfaceHash(s, (const uint8_t *const *)pd->planes, AMIGA_BYTES_PER_ROW, PLANAR_HASH_SEPARATE_PAIR_STEP);
|
return jlPlanarSurfaceHash(s, (const uint8_t *const *)pd->planes, AMIGA_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1908,6 +1908,12 @@ bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar paste (src/m68k/planarIndexed.c), over separate planes.
|
||||||
|
void jlpSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
jlPlanarPasteIndexed(((AmigaPlanarT *)dst->portData)->planes, AMIGA_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP, x, y, w, h, src, srcStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
AmigaPlanarT *pd;
|
AmigaPlanarT *pd;
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
@ -1925,6 +1931,12 @@ bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar snap (src/m68k/planarIndexed.c), over separate planes.
|
||||||
|
void jlpSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
jlPlanarSnapIndexed((const uint8_t *const *)((const AmigaPlanarT *)src->portData)->planes, AMIGA_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP, x, y, w, h, dst, dstStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t *jlpStageAllocPixels(void) {
|
uint8_t *jlpStageAllocPixels(void) {
|
||||||
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
||||||
* stays NULL; cross-platform code reads pixels via jlpSamplePixel
|
* stays NULL; cross-platform code reads pixels via jlpSamplePixel
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ static uint16_t quantizeColorToSt(uint16_t orgb);
|
||||||
static void quantizePalettes(ScreenTablesT *tables, const jlSurfaceT *src, uint16_t mask);
|
static void quantizePalettes(ScreenTablesT *tables, const jlSurfaceT *src, uint16_t mask);
|
||||||
static void setShifterVideoBase(const uint8_t *page);
|
static void setShifterVideoBase(const uint8_t *page);
|
||||||
static uint16_t stFirstMarkedPair(void);
|
static uint16_t stFirstMarkedPair(void);
|
||||||
|
static void stPlaneStarts(const jlSurfaceT *s, uint8_t *planes[]);
|
||||||
static uint8_t tablesToWrite(void);
|
static uint8_t tablesToWrite(void);
|
||||||
extern void jlStLeaveSuper(void *savedSsp);
|
extern void jlStLeaveSuper(void *savedSsp);
|
||||||
|
|
||||||
|
|
@ -694,6 +695,20 @@ static uint16_t stFirstMarkedPair(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Where plane p starts, as the shared planar helpers (src/m68k) address it:
|
||||||
|
// its words sit at 2 * p in each group, a group (two plane-byte columns)
|
||||||
|
// every ST_BYTES_PER_GROUP bytes.
|
||||||
|
static void stPlaneStarts(const jlSurfaceT *s, uint8_t *planes[]) {
|
||||||
|
uint8_t *base;
|
||||||
|
uint16_t p;
|
||||||
|
|
||||||
|
base = ((const StPlanarT *)s->portData)->base;
|
||||||
|
for (p = 0u; p < ST_PLANES; p++) {
|
||||||
|
planes[p] = base + p * sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static long writePrevPaletteRegs(void) {
|
static long writePrevPaletteRegs(void) {
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
for (i = 0; i < JL_SURFACE_COLORS_PER_PALETTE; i++) {
|
||||||
|
|
@ -1457,18 +1472,12 @@ bool jlpFloodScanRowPlanes(const jlSurfaceT *s, int16_t leftX, int16_t rightX, i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The shared planar hash (src/m68k/planarHash.c): plane p's words sit at
|
// The shared planar hash (src/m68k/planarHash.c).
|
||||||
// 2 * p in each group, a group (two plane-byte columns) every 8 bytes.
|
|
||||||
uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||||
const StPlanarT *pd;
|
uint8_t *planes[ST_PLANES];
|
||||||
const uint8_t *planes[ST_PLANES];
|
|
||||||
uint16_t p;
|
|
||||||
|
|
||||||
pd = (const StPlanarT *)s->portData;
|
stPlaneStarts(s, planes);
|
||||||
for (p = 0u; p < ST_PLANES; p++) {
|
return jlPlanarSurfaceHash(s, (const uint8_t *const *)planes, ST_BYTES_PER_ROW, ST_BYTES_PER_GROUP);
|
||||||
planes[p] = pd->base + p * sizeof(uint16_t);
|
|
||||||
}
|
|
||||||
return jlPlanarSurfaceHash(s, planes, ST_BYTES_PER_ROW, ST_BYTES_PER_GROUP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1485,6 +1494,15 @@ bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar paste (src/m68k/planarIndexed.c).
|
||||||
|
void jlpSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
uint8_t *planes[ST_PLANES];
|
||||||
|
|
||||||
|
stPlaneStarts(dst, planes);
|
||||||
|
jlPlanarPasteIndexed(planes, ST_BYTES_PER_ROW, ST_BYTES_PER_GROUP, x, y, w, h, src, srcStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
StPlanarT *pd;
|
StPlanarT *pd;
|
||||||
|
|
||||||
|
|
@ -1496,6 +1514,15 @@ bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar snap (src/m68k/planarIndexed.c).
|
||||||
|
void jlpSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
uint8_t *planes[ST_PLANES];
|
||||||
|
|
||||||
|
stPlaneStarts(src, planes);
|
||||||
|
jlPlanarSnapIndexed((const uint8_t *const *)planes, ST_BYTES_PER_ROW, ST_BYTES_PER_GROUP, x, y, w, h, dst, dstStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Phase 9: no chunky storage on the ST. Cross-platform code treats
|
// Phase 9: no chunky storage on the ST. Cross-platform code treats
|
||||||
// NULL as "port has no chunky shadow" (same contract Amiga uses).
|
// NULL as "port has no chunky shadow" (same contract Amiga uses).
|
||||||
uint8_t *jlpSurfaceAllocPixels(void) {
|
uint8_t *jlpSurfaceAllocPixels(void) {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,9 @@
|
||||||
// 0xF8..0xFB Relative mouse packet (3 bytes total). The header's
|
// 0xF8..0xFB Relative mouse packet (3 bytes total). The header's
|
||||||
// low two bits are the button state (bit 1 = left,
|
// low two bits are the button state (bit 1 = left,
|
||||||
// bit 0 = right). Next two bytes are signed dx, dy.
|
// bit 0 = right). Next two bytes are signed dx, dy.
|
||||||
// Other Discarded (joystick / clock / status etc.) -- we just
|
// 0xFD..0xFF Joystick packets: both sticks (2 bytes) or one (1).
|
||||||
// consume the documented byte count to stay in sync.
|
// Other Discarded (clock / status etc.) -- we just consume the
|
||||||
|
// documented byte count to stay in sync.
|
||||||
//
|
//
|
||||||
// The ISR writes into private gIsrState (keyboard) and gIsrMouse*
|
// The ISR writes into private gIsrState (keyboard) and gIsrMouse*
|
||||||
// buffers; jlpInputPoll copies them into the public globals.
|
// buffers; jlpInputPoll copies them into the public globals.
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ void jlAudioTone(uint16_t freqHz) {
|
||||||
|
|
||||||
void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
void jlAudioVoice(uint8_t voice, uint16_t freqHz, uint8_t atten) {
|
||||||
// Deliberately NOT gated on gAudioReady -- the engine is for the
|
// Deliberately NOT gated on gAudioReady -- the engine is for the
|
||||||
// PCM/SFX paths that need DMA + libxmp wiring. The voices are direct
|
// PCM/SFX paths that need DMA and module-player wiring. The voices are direct
|
||||||
// chip-register programming and work whether that came up or not.
|
// chip-register programming and work whether that came up or not.
|
||||||
// Range-check voice here so the per-port HAL can assume
|
// Range-check voice here so the per-port HAL can assume
|
||||||
// voice < JL_AUDIO_VOICES.
|
// voice < JL_AUDIO_VOICES.
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@
|
||||||
|
|
||||||
// interp()/mix arithmetic (delta * fracHalf up to ~8.4M, and the
|
// interp()/mix arithmetic (delta * fracHalf up to ~8.4M, and the
|
||||||
// >> 15 on a possibly-negative value) requires a >= 32-bit int with
|
// >> 15 on a possibly-negative value) requires a >= 32-bit int with
|
||||||
// arithmetic right shift. DOS (DJGPP) and ST (m68k gcc) both satisfy
|
// arithmetic right shift. Every port that builds it satisfies this; the
|
||||||
// this; the file is excluded from the IIgs build (16-bit int) by
|
// IIgs (16-bit int), the Amiga, and DVX build without it
|
||||||
// make/iigs.mk. Fail loudly if anyone ever shares this mixer with a
|
// (CORE_C_SRCS_NO_MIX, make/common.mk). Fail loudly if anyone ever shares this mixer with a
|
||||||
// 16-bit-int target instead of producing silent garbage SFX.
|
// 16-bit-int target instead of producing silent garbage SFX.
|
||||||
#if INT_MAX < 0x7FFFFFFF
|
#if INT_MAX < 0x7FFFFFFF
|
||||||
#error "audioSfxMix requires >= 32-bit int (DOS/ST only); IIgs must use NTPstreamsound"
|
#error "audioSfxMix requires >= 32-bit int (DOS/ST only); IIgs must use NTPstreamsound"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
// See LICENSE.md for license information.
|
// See LICENSE.md for license information.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
//
|
//
|
||||||
// Shared SFX overlay mixer used by HALs whose engine outputs MOD-only
|
// Shared SFX overlay mixer used by the HALs that mix on the CPU (DOS, the
|
||||||
// (libxmp-lite on DOS + ST). The mixer overlays one-shot signed 8-bit
|
// ST, the X68000, and the hosted ports, around modPlayer.c). The mixer overlays one-shot signed 8-bit
|
||||||
// PCM samples on top of the engine's already-rendered unsigned 8-bit
|
// PCM samples on top of the engine's already-rendered unsigned 8-bit
|
||||||
// MOD output, with a 16.16 fixed-point step so the SFX rate can
|
// MOD output, with a 16.16 fixed-point step so the SFX rate can
|
||||||
// differ from the output rate without needing an up-front resampling
|
// differ from the output rate without needing an up-front resampling
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@
|
||||||
// Portable chip-tracker music sequencer. One authored song (baked to
|
// Portable chip-tracker music sequencer. One authored song (baked to
|
||||||
// the JYM1 event stream by tools/songbake) plays on every port through
|
// the JYM1 event stream by tools/songbake) plays on every port through
|
||||||
// the jlAudioVoice 3-voice tone layer plus the jlAudioNoise percussion
|
// the jlAudioVoice 3-voice tone layer plus the jlAudioNoise percussion
|
||||||
// generator: YM2149 on the ST, Paula on the Amiga, OPL2 on DOS, DOC
|
// generator: YM2149 on the ST, Paula on the Amiga, OPL2 (and Sound
|
||||||
// oscillators on the IIgs. Per tick the work is a handful of event
|
// Blaster noise) on DOS, DOC oscillators on the IIgs, the YM2151 on the
|
||||||
// bytes and at most four register-level calls, so sustained playback
|
// X68000, and psgSynth.c in software on DVX and the hosted ports. Per
|
||||||
// costs register-write time on every port -- no CPU mixing anywhere.
|
// tick the work is a handful of event bytes and at most four
|
||||||
|
// register-level calls, so sustained playback costs register-write time
|
||||||
|
// wherever a chip makes the sound.
|
||||||
//
|
//
|
||||||
// Time base is jlMillisElapsed(), not the frame counter, so one song
|
// Time base is jlMillisElapsed(), not the frame counter, so one song
|
||||||
// keeps its tempo across 50/60/70 Hz displays. jlAudioFrameTick pumps
|
// keeps its tempo across 50/60/70 Hz displays. jlAudioFrameTick pumps
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ extern void jlIigsTileMapPasteMark (uint16_t bx, uint16_t by, uint16_t wTiles, u
|
||||||
extern void jlIigsTilePasteMonoInner(uint8_t *dstRow0, const uint8_t *monoTile, uint16_t fgColor, uint16_t bgColor);
|
extern void jlIigsTilePasteMonoInner(uint8_t *dstRow0, const uint8_t *monoTile, uint16_t fgColor, uint16_t bgColor);
|
||||||
extern void jlIigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
extern void jlIigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||||
extern void jlIigsCopyRectRows (uint8_t *dst, uint16_t srcLo, uint16_t srcBank, uint16_t rowBytes, uint16_t rows);
|
extern void jlIigsCopyRectRows (uint8_t *dst, uint16_t srcLo, uint16_t srcBank, uint16_t rowBytes, uint16_t rows);
|
||||||
|
extern void jlIigsPasteIndexedRows (uint8_t *dst, const uint8_t *src, uint16_t srcStride, uint16_t bytes, uint16_t rows, uint16_t mode);
|
||||||
|
extern void jlIigsSnapIndexedRows (const uint8_t *src, uint8_t *dst, uint16_t dstStride, uint16_t bytes, uint16_t rows, uint16_t mode);
|
||||||
extern void jlIigsFillRectMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
extern void jlIigsFillRectMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||||
extern void jlIigsFillRectNoMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
extern void jlIigsFillRectNoMark (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||||
extern void jlIigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
extern void jlIigsFloodWalkAndScansInner(uint8_t *pixels, uint16_t x, uint16_t y, uint16_t matchColor, uint16_t newColor, uint16_t matchEqual, int16_t *stackX, int16_t *stackY, uint16_t *spInOut, uint16_t maxSp);
|
||||||
|
|
@ -711,6 +713,26 @@ void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Indexed-color rectangles (jlSurfacePasteIndexed / jlSurfaceSnapIndexed
|
||||||
|
// payload): w bytes a row, h rows, the rectangle wholly on the surface
|
||||||
|
// (core clips, plots the edge halves, and marks).
|
||||||
|
void jlpGenericSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
||||||
|
#if !defined(jlpSurfacePasteIndexed)
|
||||||
|
#if defined(JL_HAS_SURFACE_PASTE_INDEXED)
|
||||||
|
void jlpSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
||||||
|
#else
|
||||||
|
#define jlpSurfacePasteIndexed(_d, _x, _y, _w, _h, _s, _st, _dw) jlpGenericSurfacePasteIndexed((_d), (_x), (_y), (_w), (_h), (_s), (_st), (_dw))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
void jlpGenericSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
||||||
|
#if !defined(jlpSurfaceSnapIndexed)
|
||||||
|
#if defined(JL_HAS_SURFACE_SNAP_INDEXED)
|
||||||
|
void jlpSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
||||||
|
#else
|
||||||
|
#define jlpSurfaceSnapIndexed(_s, _x, _y, _w, _h, _d, _st, _dw) jlpGenericSurfaceSnapIndexed((_s), (_x), (_y), (_w), (_h), (_d), (_st), (_dw))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
void jlpGenericSurfaceFreePixels(uint8_t *pixels);
|
void jlpGenericSurfaceFreePixels(uint8_t *pixels);
|
||||||
#if !defined(jlpSurfaceFreePixels)
|
#if !defined(jlpSurfaceFreePixels)
|
||||||
#if defined(JL_HAS_SURFACE_FREE_PIXELS)
|
#if defined(JL_HAS_SURFACE_FREE_PIXELS)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
// 8x8 tile geometry constants come from joey/tile.h -- one source of
|
// 8x8 tile geometry constants come from joey/tile.h -- one source of
|
||||||
// truth for the 4bpp packed layout (32 bytes/tile, 4 bytes/row, 8 px/side).
|
// truth for the 4bpp packed layout (32 bytes/tile, 4 bytes/row, 8 px/side).
|
||||||
|
|
||||||
// Color 0 is always transparent for sprites (DESIGN.md contract);
|
// Color 0 is always transparent for sprites (a library contract);
|
||||||
// TRANSPARENT_NIBBLE comes from spriteInternal.h, shared with every
|
// TRANSPARENT_NIBBLE comes from spriteInternal.h, shared with every
|
||||||
// per-CPU emitter.
|
// per-CPU emitter.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ typedef char stageBandsGroupMinRows[(STAGE_MARK_GROUP_MIN_ROWS == 9u) ? 1 : -1];
|
||||||
|
|
||||||
// (public API declared in joey/surface.h)
|
// (public API declared in joey/surface.h)
|
||||||
// (internal prototypes declared in surfaceInternal.h)
|
// (internal prototypes declared in surfaceInternal.h)
|
||||||
|
static bool indexedOnSurface(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t shift);
|
||||||
|
static bool indexedRows(int16_t y, uint16_t h, uint16_t *first, uint16_t *end);
|
||||||
#if !defined(JOEYLIB_PLATFORM_IIGS) && !defined(JL_CPU_68000)
|
#if !defined(JOEYLIB_PLATFORM_IIGS) && !defined(JL_CPU_68000)
|
||||||
static inline void markGroup(uint8_t *minPtr, uint8_t *maxPtr, uint8_t minWord, uint8_t maxWord);
|
static inline void markGroup(uint8_t *minPtr, uint8_t *maxPtr, uint8_t minWord, uint8_t maxWord);
|
||||||
static void markGroups(uint16_t y, uint16_t yEnd, uint8_t minWord, uint8_t maxWord);
|
static void markGroups(uint16_t y, uint16_t yEnd, uint8_t minWord, uint8_t maxWord);
|
||||||
|
|
@ -81,6 +83,34 @@ uint16_t jlgStagePaletteDirtyMask = 0xFFFFu;
|
||||||
|
|
||||||
// ----- Internal helpers (alphabetical) -----
|
// ----- Internal helpers (alphabetical) -----
|
||||||
|
|
||||||
|
// An indexed rectangle (w bytes of 1 << shift pixels, h rows) wholly on the
|
||||||
|
// surface, in 16-bit sums (every operand under 640): the common case, which
|
||||||
|
// needs no clipping.
|
||||||
|
static bool indexedOnSurface(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t shift) {
|
||||||
|
return x >= 0 && y >= 0 && w <= JL_SURFACE_WIDTH && h <= JL_SURFACE_HEIGHT && (uint16_t)((uint16_t)x + (uint16_t)(w << shift)) <= JL_SURFACE_WIDTH && (uint16_t)((uint16_t)y + h) <= JL_SURFACE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The rows [first, end) of an h-row rectangle at y that are on the surface;
|
||||||
|
// false when none are.
|
||||||
|
static bool indexedRows(int16_t y, uint16_t h, uint16_t *first, uint16_t *end) {
|
||||||
|
int32_t top;
|
||||||
|
int32_t bottom;
|
||||||
|
|
||||||
|
top = y < 0 ? -(int32_t)y : 0;
|
||||||
|
bottom = (int32_t)JL_SURFACE_HEIGHT - y;
|
||||||
|
if (bottom > (int32_t)h) {
|
||||||
|
bottom = h;
|
||||||
|
}
|
||||||
|
if (top >= bottom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*first = (uint16_t)top;
|
||||||
|
*end = (uint16_t)bottom;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#if !defined(JOEYLIB_PLATFORM_IIGS) && !defined(JL_CPU_68000)
|
#if !defined(JOEYLIB_PLATFORM_IIGS) && !defined(JL_CPU_68000)
|
||||||
// Widen the STAGE_DIRTY_GROUP4_ROWS rows at minPtr / maxPtr. (Spelling the
|
// Widen the STAGE_DIRTY_GROUP4_ROWS rows at minPtr / maxPtr. (Spelling the
|
||||||
// four rows out benched slower on DOS than this loop.)
|
// four rows out benched slower on DOS than this loop.)
|
||||||
|
|
@ -372,6 +402,71 @@ bool jlSurfaceLoadFile(jlSurfaceT *dst, const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The bytes wholly on the surface go to the port in one call (whatever it
|
||||||
|
// stores pixels as); with doubleWide a byte cut by an edge shows one
|
||||||
|
// pixel there (a head at column 0, a tail at 319), plotted on its own.
|
||||||
|
void jlSurfacePasteIndexed(jlSurfaceT *dst, int16_t x, int16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
const uint8_t *row;
|
||||||
|
int32_t first;
|
||||||
|
int32_t end;
|
||||||
|
int32_t left;
|
||||||
|
int32_t right;
|
||||||
|
uint16_t rowFirst;
|
||||||
|
uint16_t rowEnd;
|
||||||
|
uint16_t r;
|
||||||
|
uint8_t shift;
|
||||||
|
bool head;
|
||||||
|
bool tail;
|
||||||
|
|
||||||
|
if (dst == NULL || src == NULL || w == 0u || h == 0u || x >= JL_SURFACE_WIDTH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shift = doubleWide ? 1u : 0u;
|
||||||
|
if (indexedOnSurface(x, y, w, h, shift)) {
|
||||||
|
jlpSurfacePasteIndexed(dst, (uint16_t)x, (uint16_t)y, w, h, src, srcStride, doubleWide);
|
||||||
|
surfaceMarkDirtyRect(dst, x, y, (int16_t)(w << shift), (int16_t)h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!indexedRows(y, h, &rowFirst, &rowEnd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
left = x;
|
||||||
|
right = left + ((int32_t)w << shift);
|
||||||
|
if (right <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Byte i covers columns x + (i << shift) .. x + ((i + 1) << shift) - 1.
|
||||||
|
first = left < 0 ? ((-left + (1 << shift) - 1) >> shift) : 0;
|
||||||
|
end = ((int32_t)JL_SURFACE_WIDTH - left) >> shift;
|
||||||
|
if (end > (int32_t)w) {
|
||||||
|
end = w;
|
||||||
|
}
|
||||||
|
head = doubleWide && left < 0 && ((-left) & 1) != 0;
|
||||||
|
tail = doubleWide && end < (int32_t)w && (((int32_t)JL_SURFACE_WIDTH - left) & 1) != 0;
|
||||||
|
if (first < end) {
|
||||||
|
jlpSurfacePasteIndexed(dst, (uint16_t)(left + (first << shift)), (uint16_t)(y + rowFirst), (uint16_t)(end - first), (uint16_t)(rowEnd - rowFirst), src + (uint32_t)rowFirst * srcStride + (uint32_t)first, srcStride, doubleWide);
|
||||||
|
}
|
||||||
|
if (head || tail) {
|
||||||
|
for (r = rowFirst; r < rowEnd; r++) {
|
||||||
|
row = src + (uint32_t)r * srcStride;
|
||||||
|
if (head) {
|
||||||
|
jlpDrawPixel(dst, 0u, (uint16_t)(y + r), (uint8_t)(row[first - 1] & 0x0Fu));
|
||||||
|
}
|
||||||
|
if (tail) {
|
||||||
|
jlpDrawPixel(dst, (uint16_t)(JL_SURFACE_WIDTH - 1), (uint16_t)(y + r), (uint8_t)(row[end] & 0x0Fu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (left < 0) {
|
||||||
|
left = 0;
|
||||||
|
}
|
||||||
|
if (right > JL_SURFACE_WIDTH) {
|
||||||
|
right = JL_SURFACE_WIDTH;
|
||||||
|
}
|
||||||
|
surfaceMarkDirtyRect(dst, (int16_t)left, (int16_t)(y + rowFirst), (int16_t)(right - left), (int16_t)(rowEnd - rowFirst));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path) {
|
bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
|
|
@ -399,6 +494,58 @@ bool jlSurfaceSaveFile(const jlSurfaceT *src, const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The bytes whose pixels are on the surface come from the port in one call;
|
||||||
|
// the rest read 0.
|
||||||
|
void jlSurfaceSnapIndexed(const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
uint8_t *row;
|
||||||
|
int32_t first;
|
||||||
|
int32_t end;
|
||||||
|
int32_t left;
|
||||||
|
uint16_t rowFirst;
|
||||||
|
uint16_t rowEnd;
|
||||||
|
uint16_t r;
|
||||||
|
uint8_t shift;
|
||||||
|
|
||||||
|
if (src == NULL || dst == NULL || w == 0u || h == 0u) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shift = doubleWide ? 1u : 0u;
|
||||||
|
if (indexedOnSurface(x, y, w, h, shift)) {
|
||||||
|
jlpSurfaceSnapIndexed(src, (uint16_t)x, (uint16_t)y, w, h, dst, dstStride, doubleWide);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
left = x;
|
||||||
|
// Byte i reads column x + (i << shift).
|
||||||
|
first = left < 0 ? ((-left + (1 << shift) - 1) >> shift) : 0;
|
||||||
|
end = left < JL_SURFACE_WIDTH ? (((int32_t)JL_SURFACE_WIDTH - 1 - left) >> shift) + 1 : 0;
|
||||||
|
if (first > (int32_t)w) {
|
||||||
|
first = w;
|
||||||
|
}
|
||||||
|
if (end > (int32_t)w) {
|
||||||
|
end = w;
|
||||||
|
}
|
||||||
|
if (end < first) {
|
||||||
|
end = first;
|
||||||
|
}
|
||||||
|
if (!indexedRows(y, h, &rowFirst, &rowEnd)) {
|
||||||
|
rowFirst = h;
|
||||||
|
rowEnd = h;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
row = dst + (uint32_t)r * dstStride;
|
||||||
|
if (r < rowFirst || r >= rowEnd || first == end) {
|
||||||
|
memset(row, 0, w);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
memset(row, 0, (size_t)first);
|
||||||
|
memset(row + end, 0, (size_t)((int32_t)w - end));
|
||||||
|
}
|
||||||
|
if (rowFirst < rowEnd && first < end) {
|
||||||
|
jlpSurfaceSnapIndexed(src, (uint16_t)(left + (first << shift)), (uint16_t)(y + rowFirst), (uint16_t)(end - first), (uint16_t)(rowEnd - rowFirst), dst + (uint32_t)rowFirst * dstStride + (uint32_t)first, dstStride, doubleWide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----- Internal (alphabetical) -----
|
// ----- Internal (alphabetical) -----
|
||||||
|
|
||||||
bool jlStageAlloc(void) {
|
bool jlStageAlloc(void) {
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,19 @@ extern jlSurfaceT *jlgStage;
|
||||||
// (src/m68k/planarHash.c) is the planar ports' whole hash.
|
// (src/m68k/planarHash.c) is the planar ports' whole hash.
|
||||||
#define SURFACE_HASH_SEED_LO 0xACE1u
|
#define SURFACE_HASH_SEED_LO 0xACE1u
|
||||||
#define SURFACE_HASH_SEED_HI 0x1357u
|
#define SURFACE_HASH_SEED_HI 0x1357u
|
||||||
// jlPlanarSurfaceHash's pairStep for separate planes: two columns, two bytes.
|
// The planar helpers' pairStep for separate planes: two columns, two bytes.
|
||||||
#define PLANAR_HASH_SEPARATE_PAIR_STEP 2u
|
#define PLANAR_SEPARATE_PAIR_STEP 2u
|
||||||
|
|
||||||
uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep);
|
uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep);
|
||||||
|
// A plane byte's 8 bits at the low bit of its 8 pixels' nibbles, the left
|
||||||
|
// pixel's highest: four lookups or'd at shifts 0 to 3 are the 8 pixels' 4
|
||||||
|
// chunky bytes (src/m68k/planarHash.c).
|
||||||
|
#define PLANAR_SPREAD_VALUES 256u
|
||||||
|
extern const uint32_t jlgPlanarSpread[PLANAR_SPREAD_VALUES];
|
||||||
|
// The planar ports' jlpSurfacePasteIndexed / jlpSurfaceSnapIndexed
|
||||||
|
// (src/m68k/planarIndexed.c), planes addressed as jlPlanarSurfaceHash's.
|
||||||
|
void jlPlanarPasteIndexed(uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide);
|
||||||
|
void jlPlanarSnapIndexed(const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide);
|
||||||
uint32_t jlSurfaceHashFinish(uint16_t lo, uint16_t hi, const jlSurfaceT *s);
|
uint32_t jlSurfaceHashFinish(uint16_t lo, uint16_t hi, const jlSurfaceT *s);
|
||||||
|
|
||||||
// Stage SCB / palette dirty flags. jlScbSet* and jlPaletteSet set them
|
// Stage SCB / palette dirty flags. jlScbSet* and jlPaletteSet set them
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@
|
||||||
#include "surfaceInternal.h" // full jlSurfaceT (s->pixels) + JL_SURFACE_PIXELS_SIZE
|
#include "surfaceInternal.h" // full jlSurfaceT (s->pixels) + JL_SURFACE_PIXELS_SIZE
|
||||||
#include "port.h" // jlpGenericSurfaceClear prototype
|
#include "port.h" // jlpGenericSurfaceClear prototype
|
||||||
|
|
||||||
|
// Four bytes' low nibbles: the indexed paste and snap take four bytes a
|
||||||
|
// step, each byte's nibble moved within it, so byte order never matters.
|
||||||
|
#define NIBBLES_32 0x0F0F0F0Fu
|
||||||
|
|
||||||
// ----- Prototypes -----
|
// ----- Prototypes -----
|
||||||
|
|
||||||
static inline void plotNibble(uint8_t *row, uint16_t x, uint8_t nibble);
|
static inline void plotNibble(uint8_t *row, uint16_t x, uint8_t nibble);
|
||||||
|
|
@ -206,3 +210,124 @@ void jlpGenericDrawPixel(jlSurfaceT *s, uint16_t x, uint16_t y, uint8_t colorInd
|
||||||
}
|
}
|
||||||
plotNibble(&s->pixels[SURFACE_ROW_OFFSET(y)], x, (uint8_t)(colorIndex & 0x0F));
|
plotNibble(&s->pixels[SURFACE_ROW_OFFSET(y)], x, (uint8_t)(colorIndex & 0x0F));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Indexed bytes onto a chunky surface, the rectangle wholly on it: a
|
||||||
|
// doubled pixel pair from an even column is one whole byte (c * 0x11),
|
||||||
|
// four at a time, two single pixels from an even column are one byte, and
|
||||||
|
// an odd column starts and ends nibble by nibble. A surface with no chunky
|
||||||
|
// pixels (a planar port's, with no paste of its own) is plotted pixel by
|
||||||
|
// pixel.
|
||||||
|
void jlpGenericSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
const uint8_t *s;
|
||||||
|
uint8_t *row;
|
||||||
|
uint8_t *d;
|
||||||
|
uint32_t v;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t px;
|
||||||
|
uint8_t c;
|
||||||
|
|
||||||
|
if (dst->pixels == NULL) {
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
s = src + (uint32_t)r * srcStride;
|
||||||
|
for (i = 0; i < w; i++) {
|
||||||
|
c = (uint8_t)(s[i] & 0x0Fu);
|
||||||
|
if (doubleWide) {
|
||||||
|
jlpDrawPixel(dst, (uint16_t)(x + 2u * i), (uint16_t)(y + r), c);
|
||||||
|
jlpDrawPixel(dst, (uint16_t)(x + 2u * i + 1u), (uint16_t)(y + r), c);
|
||||||
|
} else {
|
||||||
|
jlpDrawPixel(dst, (uint16_t)(x + i), (uint16_t)(y + r), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
s = src + (uint32_t)r * srcStride;
|
||||||
|
row = &dst->pixels[SURFACE_ROW_OFFSET(y + r)];
|
||||||
|
if (doubleWide && (x & 1u) == 0u) {
|
||||||
|
d = row + (x >> 1);
|
||||||
|
for (i = 0; (uint16_t)(i + 4u) <= w; i = (uint16_t)(i + 4u)) {
|
||||||
|
memcpy(&v, s + i, sizeof(v));
|
||||||
|
v &= NIBBLES_32;
|
||||||
|
v |= v << 4;
|
||||||
|
memcpy(d + i, &v, sizeof(v));
|
||||||
|
}
|
||||||
|
for (; i < w; i++) {
|
||||||
|
d[i] = (uint8_t)((s[i] & 0x0Fu) * 0x11u);
|
||||||
|
}
|
||||||
|
} else if (doubleWide) {
|
||||||
|
px = x;
|
||||||
|
for (i = 0; i < w; i++) {
|
||||||
|
c = (uint8_t)(s[i] & 0x0Fu);
|
||||||
|
plotNibble(row, px, c);
|
||||||
|
plotNibble(row, (uint16_t)(px + 1u), c);
|
||||||
|
px = (uint16_t)(px + 2u);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = 0;
|
||||||
|
px = x;
|
||||||
|
if ((px & 1u) != 0u) {
|
||||||
|
plotNibble(row, px, (uint8_t)(s[0] & 0x0Fu));
|
||||||
|
px++;
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
d = row + (px >> 1);
|
||||||
|
for (; (uint16_t)(i + 1u) < w; i = (uint16_t)(i + 2u)) {
|
||||||
|
*d++ = (uint8_t)(((s[i] & 0x0Fu) << 4) | (s[i + 1u] & 0x0Fu));
|
||||||
|
}
|
||||||
|
if (i < w) {
|
||||||
|
plotNibble(row, (uint16_t)(x + i), (uint8_t)(s[i] & 0x0Fu));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A chunky surface's pixels back to indexed bytes, the rectangle wholly on
|
||||||
|
// it: byte i is column x + (i << doubleWide), its nibble (even columns the
|
||||||
|
// high one), doubled ones four at a time. A surface with no chunky pixels
|
||||||
|
// is sampled pixel by pixel.
|
||||||
|
void jlpGenericSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
const uint8_t *row;
|
||||||
|
uint8_t *d;
|
||||||
|
uint32_t v;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t px;
|
||||||
|
uint8_t shift;
|
||||||
|
|
||||||
|
shift = doubleWide ? 1u : 0u;
|
||||||
|
if (src->pixels == NULL) {
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
d = dst + (uint32_t)r * dstStride;
|
||||||
|
for (i = 0; i < w; i++) {
|
||||||
|
d[i] = jlpSamplePixel(src, (int16_t)(x + (i << shift)), (int16_t)(y + r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
d = dst + (uint32_t)r * dstStride;
|
||||||
|
row = &src->pixels[SURFACE_ROW_OFFSET(y + r)];
|
||||||
|
if (doubleWide) {
|
||||||
|
// Column x + 2i is byte i's high nibble from an even x, its
|
||||||
|
// low one from an odd x.
|
||||||
|
row += x >> 1;
|
||||||
|
for (i = 0; (uint16_t)(i + 4u) <= w; i = (uint16_t)(i + 4u)) {
|
||||||
|
memcpy(&v, row + i, sizeof(v));
|
||||||
|
v = ((x & 1u) == 0u ? v >> 4 : v) & NIBBLES_32;
|
||||||
|
memcpy(d + i, &v, sizeof(v));
|
||||||
|
}
|
||||||
|
for (; i < w; i++) {
|
||||||
|
d[i] = (uint8_t)((x & 1u) == 0u ? row[i] >> 4 : row[i] & 0x0Fu);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (i = 0; i < w; i++) {
|
||||||
|
px = (uint16_t)(x + i);
|
||||||
|
d[i] = (px & 1u) != 0u ? (uint8_t)(row[px >> 1] & 0x0Fu) : (uint8_t)(row[px >> 1] >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
// 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, HOSTED_FRAME_HZ, kept by the backend's microsecond clock from jlInit
|
// one, HOSTED_FRAME_HZ, kept by the backend's microsecond clock from jlInit
|
||||||
// on. jlWaitVBL sleeps to the clock's next frame boundary. The frame reaches
|
// on. jlWaitVBL sleeps to the clock's next frame boundary. The frame reaches
|
||||||
// the window at most once a virtual frame: when a present lands in a frame
|
// the window at most once a virtual frame: a present in a frame not shown
|
||||||
// that has not been shown yet, else at the next frame wait or input poll.
|
// yet shows at once, and one in a frame already shown waits for the next.
|
||||||
// A program that presents many times a frame (UBER's present rows) pays for
|
// A program that presents many times a frame (UBER's present rows) pays for
|
||||||
// expanding its rows, not for the window. Each show hands the backend the
|
// expanding its rows, not for the window. Each show hands the backend the
|
||||||
// rectangles expanded since the last one -- one a run of changed rows, as
|
// rectangles expanded since the last one -- one a run of changed rows, as
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,15 @@ extern void jlIigsFillCircleInner(uint8_t *pixels, uint16_t cx, uint16_t cy, uin
|
||||||
#define LINE_MS_SHIFT 4u
|
#define LINE_MS_SHIFT 4u
|
||||||
#define LINE_MS_STEPS (((BEAM_LINES_PAL - 1u) >> LINE_MS_SHIFT) + 2u)
|
#define LINE_MS_STEPS (((BEAM_LINES_PAL - 1u) >> LINE_MS_SHIFT) + 2u)
|
||||||
|
|
||||||
|
// The indexed rows' modes (iigsIndexed.s's PIM_* and SIM_*, which say
|
||||||
|
// what each makes of a byte).
|
||||||
|
#define IIGS_PASTE_DOUBLE 0u
|
||||||
|
#define IIGS_PASTE_JOIN1 1u
|
||||||
|
#define IIGS_PASTE_JOIN2 2u
|
||||||
|
#define IIGS_SNAP_HIGH 0u
|
||||||
|
#define IIGS_SNAP_LOW 1u
|
||||||
|
#define IIGS_SNAP_BOTH 2u
|
||||||
|
|
||||||
// ----- Module state -----
|
// ----- Module state -----
|
||||||
|
|
||||||
// The beam's lines a frame, and the line the heartbeat's tick steps on,
|
// The beam's lines a frame, and the line the heartbeat's tick steps on,
|
||||||
|
|
@ -652,6 +661,93 @@ void jlpSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The indexed paste: jlIigsPasteIndexedRows does every row's whole surface
|
||||||
|
// bytes (by IIGS_PASTE_*: a doubled pixel pair from one source byte, or a
|
||||||
|
// byte joining two), and a pixel alone in a row's first byte's low nibble
|
||||||
|
// (an odd x) or its last byte's high one is plotted here. Doubled pixels
|
||||||
|
// from an odd x always leave both: the bytes between join neighbours.
|
||||||
|
void jlpSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
uint8_t *row;
|
||||||
|
uint16_t head;
|
||||||
|
uint16_t pairs;
|
||||||
|
uint16_t tailByte;
|
||||||
|
uint16_t r;
|
||||||
|
bool tail;
|
||||||
|
|
||||||
|
row = dst->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
|
||||||
|
head = (uint16_t)(x & 1u);
|
||||||
|
if (doubleWide) {
|
||||||
|
if (head == 0u) {
|
||||||
|
jlIigsPasteIndexedRows(row, src, srcStride, w, h, IIGS_PASTE_DOUBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (w > 1u) {
|
||||||
|
jlIigsPasteIndexedRows(row + 1, src, srcStride, (uint16_t)(w - 1u), h, IIGS_PASTE_JOIN1);
|
||||||
|
}
|
||||||
|
tail = true;
|
||||||
|
tailByte = w;
|
||||||
|
} else {
|
||||||
|
pairs = (uint16_t)((w - head) >> 1);
|
||||||
|
if (pairs != 0u) {
|
||||||
|
jlIigsPasteIndexedRows(row + head, src + head, srcStride, pairs, h, IIGS_PASTE_JOIN2);
|
||||||
|
}
|
||||||
|
tail = ((w - head) & 1u) != 0u;
|
||||||
|
tailByte = (uint16_t)(head + pairs);
|
||||||
|
}
|
||||||
|
if (head == 0u && !tail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
if (head != 0u) {
|
||||||
|
row[0] = (uint8_t)((row[0] & 0xF0u) | (src[0] & 0x0Fu));
|
||||||
|
}
|
||||||
|
if (tail) {
|
||||||
|
row[tailByte] = (uint8_t)((row[tailByte] & 0x0Fu) | (uint8_t)(src[w - 1u] << 4));
|
||||||
|
}
|
||||||
|
row += JL_SURFACE_BYTES_PER_ROW;
|
||||||
|
src += srcStride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The indexed snap: jlIigsSnapIndexedRows does every row's whole surface
|
||||||
|
// bytes (by IIGS_SNAP_*: doubled pixels are one nibble a byte, the high
|
||||||
|
// one from an even x and the low from an odd; single ones both), and a
|
||||||
|
// single pixel alone in a byte at either end of a row is read here.
|
||||||
|
void jlpSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
const uint8_t *row;
|
||||||
|
uint16_t head;
|
||||||
|
uint16_t pairs;
|
||||||
|
uint16_t r;
|
||||||
|
bool tail;
|
||||||
|
|
||||||
|
row = src->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
|
||||||
|
head = (uint16_t)(x & 1u);
|
||||||
|
if (doubleWide) {
|
||||||
|
jlIigsSnapIndexedRows(row, dst, dstStride, w, h, head != 0u ? IIGS_SNAP_LOW : IIGS_SNAP_HIGH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pairs = (uint16_t)((w - head) >> 1);
|
||||||
|
if (pairs != 0u) {
|
||||||
|
jlIigsSnapIndexedRows(row + head, dst + head, dstStride, (uint16_t)(pairs << 1), h, IIGS_SNAP_BOTH);
|
||||||
|
}
|
||||||
|
tail = ((w - head) & 1u) != 0u;
|
||||||
|
if (head == 0u && !tail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
if (head != 0u) {
|
||||||
|
dst[0] = (uint8_t)(row[0] & 0x0Fu);
|
||||||
|
}
|
||||||
|
if (tail) {
|
||||||
|
dst[w - 1u] = (uint8_t)(row[head + pairs] >> 4);
|
||||||
|
}
|
||||||
|
row += JL_SURFACE_BYTES_PER_ROW;
|
||||||
|
dst += dstStride;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// IIgs is chunky-native: portData is unused. The chunky `pixels`
|
// IIgs is chunky-native: portData is unused. The chunky `pixels`
|
||||||
// buffer at $01:2000 is the stage's pixel storage and the source for
|
// buffer at $01:2000 is the stage's pixel storage and the source for
|
||||||
// jlStagePresent's PEI-slam to $E1.
|
// jlStagePresent's PEI-slam to $E1.
|
||||||
|
|
|
||||||
465
src/iigs/iigsIndexed.s
Normal file
465
src/iigs/iigsIndexed.s
Normal file
|
|
@ -0,0 +1,465 @@
|
||||||
|
; Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
; See LICENSE.md for license information.
|
||||||
|
; SPDX-License-Identifier: MIT
|
||||||
|
;
|
||||||
|
; iigsIndexed.s - the IIgs indexed paste and snap (jlSurfacePasteIndexed /
|
||||||
|
; jlSurfaceSnapIndexed): whole surface bytes of rows; hal.c works out the
|
||||||
|
; arguments and plots a lone nibble at either end of a row.
|
||||||
|
;
|
||||||
|
; GAS syntax for the llvm-mos w65816 toolchain (ABI notes: joeyDraw.s).
|
||||||
|
;
|
||||||
|
; A surface row never crosses a bank (the stage is $01:2000, and every
|
||||||
|
; other surface's pixels are attrNoCross), so the surface side runs with
|
||||||
|
; DBR on its bank and a 16-bit address: (dp),y or |0,x (the | keeps it
|
||||||
|
; absolute: a bare 0,x is direct page, bank 0). The caller's bytes
|
||||||
|
; can be anywhere, so that side is [dp],y, and a row step carries into the
|
||||||
|
; bank byte. The work registers are the runtime direct page's IMG0 ..
|
||||||
|
; IMG7 at $D0 (llvm816: a call clobbers them; the Loader puts the page on
|
||||||
|
; a page boundary, so no dp access pays the unaligned-D cycle); the
|
||||||
|
; arguments are read from the stack. Pixel
|
||||||
|
; bytes go two at a time through 16-bit A: masked to $0F0F, a source word
|
||||||
|
; is two pixels.
|
||||||
|
;
|
||||||
|
; ====================================================================
|
||||||
|
; void jlIigsPasteIndexedRows(uint8_t *dst, const uint8_t *src,
|
||||||
|
; uint16_t srcStride, uint16_t bytes,
|
||||||
|
; uint16_t rows, uint16_t mode)
|
||||||
|
;
|
||||||
|
; dst is the first row's first surface byte, rows of 160; bytes (>= 1) a
|
||||||
|
; row's surface bytes. Surface byte k of a row is, by mode:
|
||||||
|
; PIM_DOUBLE src[k] doubled, (src[k] & 15) * 17: a pair of doubled
|
||||||
|
; pixels from an even column
|
||||||
|
; PIM_JOIN1 (src[k] << 4) | (src[k + 1] & 15): doubled pixels from an
|
||||||
|
; odd column, every byte straddling two source bytes
|
||||||
|
; PIM_JOIN2 (src[2k] << 4) | (src[2k + 1] & 15): single pixels
|
||||||
|
; A joined byte from the word s = src[j] | src[j + 1] << 8 is the low byte
|
||||||
|
; of (m << 4) | (m >> 8), m = s & $0F0F; two of them make a stored word.
|
||||||
|
;
|
||||||
|
; ABI: arg0 dst in A:X; src @4,s, srcStride @8,s, bytes @10,s, rows @12,s,
|
||||||
|
; mode @14,s on JSL entry (+2 after php, phb).
|
||||||
|
; ====================================================================
|
||||||
|
|
||||||
|
.set PIM_DOUBLE, 0
|
||||||
|
.set PIM_JOIN1, 1
|
||||||
|
.set PIM_JOIN2, 2
|
||||||
|
.set IX_ROW_BYTES, 160
|
||||||
|
|
||||||
|
.set IX_SRC, 0xD0 ; caller bytes, far (4)
|
||||||
|
.set IX_TMP, 0xD4
|
||||||
|
.set IX_ACC, 0xD6
|
||||||
|
.set IX_SURF, 0xD8 ; surface row address
|
||||||
|
.set IX_END, 0xDA ; a row's surface end (join)
|
||||||
|
.set IX_ROWS, 0xDC ; rows left
|
||||||
|
.set IX_ODD, 0xDE ; a row's odd last byte (1)
|
||||||
|
.set IX_RUNS, IX_ACC ; double: a row's runs of 4 words
|
||||||
|
.set IX_WORDS, IX_END ; double: then its words (0-3)
|
||||||
|
|
||||||
|
.set IP_SRC, 6 ; ,s after php + phb
|
||||||
|
.set IP_SSTRIDE, 10
|
||||||
|
.set IP_BYTES, 12
|
||||||
|
.set IP_ROWS, 14
|
||||||
|
.set IP_MODE, 16
|
||||||
|
|
||||||
|
.section .text.jlIigsPasteIndexedRows,"ax"
|
||||||
|
|
||||||
|
; A joined byte of the source word at [IX_SRC],y into A's low byte (the
|
||||||
|
; high byte is left over).
|
||||||
|
.macro IX_JOIN
|
||||||
|
lda [IX_SRC],y
|
||||||
|
and #0x0F0F
|
||||||
|
sta IX_TMP
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
ora IX_TMP+1
|
||||||
|
.endm
|
||||||
|
|
||||||
|
; A word of two doubled pixel pairs: two source bytes at [IX_SRC],y, each
|
||||||
|
; nibble times 17, to (IX_SURF),y.
|
||||||
|
.macro IX_DOUBLE
|
||||||
|
lda [IX_SRC],y
|
||||||
|
and #0x0F0F
|
||||||
|
sta IX_TMP
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
ora IX_TMP
|
||||||
|
sta (IX_SURF),y
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
.endm
|
||||||
|
|
||||||
|
; The next row: the source on by srcStride (a carry into its bank byte
|
||||||
|
; only when there is one), the surface on by 160; then back to \row while
|
||||||
|
; rows are left. (Its labels are 8 and 9, so a caller's 1b-7b pass over it.)
|
||||||
|
.macro IX_NEXT_ROW row
|
||||||
|
lda IX_SRC
|
||||||
|
clc
|
||||||
|
adc IP_SSTRIDE,s
|
||||||
|
sta IX_SRC
|
||||||
|
bcc 8f
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
inc IX_SRC+2
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
8:
|
||||||
|
lda IX_SURF
|
||||||
|
clc
|
||||||
|
adc #IX_ROW_BYTES
|
||||||
|
sta IX_SURF
|
||||||
|
dec IX_ROWS
|
||||||
|
beq 9f
|
||||||
|
brl \row
|
||||||
|
9:
|
||||||
|
.endm
|
||||||
|
|
||||||
|
; Every row of joined bytes: Y walks the source (step per byte), X the
|
||||||
|
; surface, two bytes a store up to IX_END, an odd last one (IX_ODD) stored
|
||||||
|
; alone.
|
||||||
|
.macro IX_JOIN_ROWS step
|
||||||
|
1:
|
||||||
|
ldy #0
|
||||||
|
ldx IX_SURF
|
||||||
|
lda IP_BYTES,s
|
||||||
|
and #0xFFFE
|
||||||
|
clc
|
||||||
|
adc IX_SURF
|
||||||
|
sta IX_END
|
||||||
|
cpx IX_END
|
||||||
|
bcs 3f
|
||||||
|
2:
|
||||||
|
IX_JOIN
|
||||||
|
and #0x00FF
|
||||||
|
sta IX_ACC
|
||||||
|
.rept \step
|
||||||
|
iny
|
||||||
|
.endr
|
||||||
|
IX_JOIN
|
||||||
|
xba
|
||||||
|
and #0xFF00
|
||||||
|
ora IX_ACC
|
||||||
|
sta |0,x
|
||||||
|
.rept \step
|
||||||
|
iny
|
||||||
|
.endr
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
cpx IX_END
|
||||||
|
bcc 2b
|
||||||
|
3:
|
||||||
|
lda IX_ODD
|
||||||
|
beq 4f
|
||||||
|
IX_JOIN
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
sta |0,x
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
4:
|
||||||
|
IX_NEXT_ROW 1b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.globl jlIigsPasteIndexedRows
|
||||||
|
jlIigsPasteIndexedRows:
|
||||||
|
php
|
||||||
|
phb
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
sta IX_SURF
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
plb ; DBR = the surface's bank
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda IP_SRC,s
|
||||||
|
sta IX_SRC
|
||||||
|
lda IP_SRC+2,s
|
||||||
|
sta IX_SRC+2
|
||||||
|
lda IP_ROWS,s
|
||||||
|
sta IX_ROWS
|
||||||
|
; a row's shape, the same in every row
|
||||||
|
lda IP_BYTES,s
|
||||||
|
and #1
|
||||||
|
sta IX_ODD
|
||||||
|
lda IP_MODE,s
|
||||||
|
beq pxDouble
|
||||||
|
cmp #PIM_JOIN1
|
||||||
|
beq pxJoin1
|
||||||
|
brl pxJoin2
|
||||||
|
pxJoin1:
|
||||||
|
IX_JOIN_ROWS 1
|
||||||
|
brl pxDone
|
||||||
|
pxJoin2:
|
||||||
|
IX_JOIN_ROWS 2
|
||||||
|
brl pxDone
|
||||||
|
pxDouble:
|
||||||
|
; Y walks both sides: runs of 4 words (X counts them), the
|
||||||
|
; 0-3 words left one at a time, an odd last byte alone
|
||||||
|
lda IP_BYTES,s
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta IX_RUNS
|
||||||
|
lda IP_BYTES,s
|
||||||
|
and #6
|
||||||
|
lsr a
|
||||||
|
sta IX_WORDS
|
||||||
|
pxDoubleRow:
|
||||||
|
ldy #0
|
||||||
|
ldx IX_RUNS
|
||||||
|
beq pxDoubleWords
|
||||||
|
pxDoubleRun:
|
||||||
|
.rept 4
|
||||||
|
IX_DOUBLE
|
||||||
|
.endr
|
||||||
|
dex
|
||||||
|
bne pxDoubleRun
|
||||||
|
pxDoubleWords:
|
||||||
|
ldx IX_WORDS
|
||||||
|
beq pxDoubleOdd
|
||||||
|
pxDoubleWord:
|
||||||
|
IX_DOUBLE
|
||||||
|
dex
|
||||||
|
bne pxDoubleWord
|
||||||
|
pxDoubleOdd:
|
||||||
|
lda IX_ODD
|
||||||
|
beq pxDoubleNext
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
lda [IX_SRC],y
|
||||||
|
and #0x0F
|
||||||
|
sta IX_TMP
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
ora IX_TMP
|
||||||
|
sta (IX_SURF),y
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
pxDoubleNext:
|
||||||
|
IX_NEXT_ROW pxDoubleRow
|
||||||
|
pxDone:
|
||||||
|
plb
|
||||||
|
plp
|
||||||
|
rtl
|
||||||
|
|
||||||
|
|
||||||
|
; ====================================================================
|
||||||
|
; void jlIigsSnapIndexedRows(const uint8_t *src, uint8_t *dst,
|
||||||
|
; uint16_t dstStride, uint16_t bytes,
|
||||||
|
; uint16_t rows, uint16_t mode)
|
||||||
|
;
|
||||||
|
; src is the first row's first surface byte, rows of 160; bytes (>= 1) a
|
||||||
|
; row's output bytes. Output byte k of a row is, by mode:
|
||||||
|
; SIM_HIGH surface byte k >> 4: doubled pixels from an even column
|
||||||
|
; SIM_LOW surface byte k & 15: doubled pixels from an odd column
|
||||||
|
; SIM_BOTH surface byte k / 2's high nibble when k is even, its low one
|
||||||
|
; when odd: single pixels (bytes even)
|
||||||
|
; SIM_BOTH turns a surface word b0 | b1 << 8 into H = its high nibbles and
|
||||||
|
; L = its low ones, then stores H.lo | L.lo << 8 and H.hi | L.hi << 8.
|
||||||
|
; A 16-bit surface read may take one byte past a row; it is never stored.
|
||||||
|
;
|
||||||
|
; ABI: arg0 src in A:X; dst @4,s, dstStride @8,s, bytes @10,s, rows @12,s,
|
||||||
|
; mode @14,s on JSL entry (+2 after php, phb).
|
||||||
|
; ====================================================================
|
||||||
|
|
||||||
|
.set SIM_HIGH, 0
|
||||||
|
.set SIM_LOW, 1
|
||||||
|
.set SIM_BOTH, 2
|
||||||
|
|
||||||
|
.set SX_TMP2, 0xD0
|
||||||
|
.set SX_ROWS, 0xD2 ; rows left
|
||||||
|
.set SX_DST, 0xDC ; caller bytes, far (4)
|
||||||
|
|
||||||
|
.set IS_DST, 6 ; ,s after php + phb
|
||||||
|
.set IS_DSTRIDE, 10
|
||||||
|
.set IS_BYTES, 12
|
||||||
|
.set IS_ROWS, 14
|
||||||
|
.set IS_MODE, 16
|
||||||
|
|
||||||
|
.section .text.jlIigsSnapIndexedRows,"ax"
|
||||||
|
|
||||||
|
; A word of SIM_HIGH (\shift 1) or SIM_LOW (0) output: two surface bytes
|
||||||
|
; at (IX_SURF),y to [SX_DST],y.
|
||||||
|
.macro IS_NIBBLE_WORD shift
|
||||||
|
lda (IX_SURF),y
|
||||||
|
.if \shift
|
||||||
|
and #0xF0F0
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
.else
|
||||||
|
and #0x0F0F
|
||||||
|
.endif
|
||||||
|
sta [SX_DST],y
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
.endm
|
||||||
|
|
||||||
|
; One row of SIM_HIGH or SIM_LOW: Y walks both sides, two words a loop.
|
||||||
|
.macro IS_NIBBLE_ROW shift
|
||||||
|
ldy #0
|
||||||
|
lda IS_BYTES,s
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
tax
|
||||||
|
beq 4f
|
||||||
|
1:
|
||||||
|
IS_NIBBLE_WORD \shift
|
||||||
|
IS_NIBBLE_WORD \shift
|
||||||
|
dex
|
||||||
|
bne 1b
|
||||||
|
4:
|
||||||
|
lda IS_BYTES,s
|
||||||
|
and #2
|
||||||
|
beq 2f
|
||||||
|
IS_NIBBLE_WORD \shift
|
||||||
|
2:
|
||||||
|
lda IS_BYTES,s
|
||||||
|
lsr a
|
||||||
|
bcc 3f
|
||||||
|
lda (IX_SURF),y
|
||||||
|
.if \shift
|
||||||
|
and #0x00F0
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
.else
|
||||||
|
and #0x000F
|
||||||
|
.endif
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
sta [SX_DST],y
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
3:
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.globl jlIigsSnapIndexedRows
|
||||||
|
jlIigsSnapIndexedRows:
|
||||||
|
php
|
||||||
|
phb
|
||||||
|
rep #0x30
|
||||||
|
.a16
|
||||||
|
.i16
|
||||||
|
sta IX_SURF
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
plb ; DBR = the surface's bank
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda IS_DST,s
|
||||||
|
sta SX_DST
|
||||||
|
lda IS_DST+2,s
|
||||||
|
sta SX_DST+2
|
||||||
|
lda IS_ROWS,s
|
||||||
|
sta SX_ROWS
|
||||||
|
sxRow:
|
||||||
|
lda IS_MODE,s
|
||||||
|
bne sxNotHigh
|
||||||
|
IS_NIBBLE_ROW 1
|
||||||
|
brl sxNext
|
||||||
|
sxNotHigh:
|
||||||
|
cmp #SIM_LOW
|
||||||
|
bne sxBoth
|
||||||
|
IS_NIBBLE_ROW 0
|
||||||
|
brl sxNext
|
||||||
|
sxBoth:
|
||||||
|
; X walks the surface, Y the output, four bytes from a word
|
||||||
|
ldy #0
|
||||||
|
ldx IX_SURF
|
||||||
|
lda IS_BYTES,s
|
||||||
|
and #0xFFFC
|
||||||
|
lsr a
|
||||||
|
clc
|
||||||
|
adc IX_SURF
|
||||||
|
sta IX_END
|
||||||
|
cpx IX_END
|
||||||
|
bcs sxBothTail
|
||||||
|
sxBothWord:
|
||||||
|
lda |0,x
|
||||||
|
sta IX_TMP ; b0 | b1 << 8
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
and #0x0F0F
|
||||||
|
sta IX_ACC ; H
|
||||||
|
lda IX_TMP
|
||||||
|
and #0x0F0F
|
||||||
|
sta IX_TMP ; L
|
||||||
|
xba
|
||||||
|
and #0xFF00
|
||||||
|
sta SX_TMP2 ; L.lo << 8
|
||||||
|
lda IX_ACC
|
||||||
|
and #0x00FF
|
||||||
|
ora SX_TMP2
|
||||||
|
sta [SX_DST],y ; H.lo | L.lo << 8
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
lda IX_ACC
|
||||||
|
xba
|
||||||
|
and #0x00FF
|
||||||
|
sta SX_TMP2
|
||||||
|
lda IX_TMP
|
||||||
|
and #0xFF00
|
||||||
|
ora SX_TMP2
|
||||||
|
sta [SX_DST],y ; H.hi | L.hi << 8
|
||||||
|
iny
|
||||||
|
iny
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
cpx IX_END
|
||||||
|
bcc sxBothWord
|
||||||
|
sxBothTail:
|
||||||
|
lda IS_BYTES,s
|
||||||
|
and #2
|
||||||
|
beq sxNext
|
||||||
|
lda |0,x
|
||||||
|
and #0x00FF
|
||||||
|
sta IX_TMP ; b0
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta IX_ACC ; b0 >> 4
|
||||||
|
lda IX_TMP
|
||||||
|
and #0x000F
|
||||||
|
xba
|
||||||
|
ora IX_ACC
|
||||||
|
sta [SX_DST],y ; b0 >> 4 | (b0 & 15) << 8
|
||||||
|
sxNext:
|
||||||
|
; the output's carry rides into its bank byte
|
||||||
|
lda SX_DST
|
||||||
|
clc
|
||||||
|
adc IS_DSTRIDE,s
|
||||||
|
sta SX_DST
|
||||||
|
sep #0x20
|
||||||
|
.a8
|
||||||
|
lda SX_DST+2
|
||||||
|
adc #0
|
||||||
|
sta SX_DST+2
|
||||||
|
rep #0x20
|
||||||
|
.a16
|
||||||
|
lda IX_SURF
|
||||||
|
clc
|
||||||
|
adc #IX_ROW_BYTES
|
||||||
|
sta IX_SURF
|
||||||
|
dec SX_ROWS
|
||||||
|
beq sxDone
|
||||||
|
brl sxRow
|
||||||
|
sxDone:
|
||||||
|
plb
|
||||||
|
plp
|
||||||
|
rtl
|
||||||
|
|
@ -5,31 +5,37 @@
|
||||||
// The planar ports' jlpSurfaceHash (Amiga, X68000, Atari ST): the pixels
|
// The planar ports' jlpSurfaceHash (Amiga, X68000, Atari ST): the pixels
|
||||||
// hashed as the chunky bytes they would be, so a planar port's hash matches
|
// hashed as the chunky bytes they would be, so a planar port's hash matches
|
||||||
// the chunky ports' for the same picture. A plane byte holds one plane's bit
|
// the chunky ports' for the same picture. A plane byte holds one plane's bit
|
||||||
// of 8 pixels; gSpread[b] puts byte b's bits at the low bit of those pixels'
|
// of 8 pixels; jlgPlanarSpread[b] puts byte b's bits at the low bit of those
|
||||||
// nibbles, left pixel highest, so four lookups or'd at shifts 0 to 3 are the
|
// pixels' nibbles, left pixel highest, so four lookups or'd at shifts 0 to 3
|
||||||
// 4 chunky bytes of the 8 pixels. Decoding a pixel at a time cost four bit
|
// are the 4 chunky bytes of the 8 pixels (planarIndexed.c's snap reads them
|
||||||
// tests a pixel, and was most of an UBER run.
|
// the same way). Decoding a pixel at a time cost four bit tests a pixel, and
|
||||||
|
// was most of an UBER run.
|
||||||
//
|
//
|
||||||
// Both layouts reach plane p's byte of column c (0..39) in a row at
|
// Both layouts reach plane p's byte of column c (0..39) in a row at
|
||||||
// planes[p] + row * rowBytes + (c >> 1) * pairStep + (c & 1): separate
|
// planes[p] + row * rowBytes + (c >> 1) * pairStep + (c & 1): separate
|
||||||
// planes are 40 bytes a row with pairStep 2, and the ST's interleaved
|
// planes are 40 bytes a row with pairStep 2, and the ST's interleaved
|
||||||
// groups put plane p's word at 2 * p, a group (two columns) every 8 bytes.
|
// groups put plane p's word at 2 * p, a group (two columns) every 8 bytes.
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "surfaceInternal.h"
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
#define SPREAD_VALUES 256u
|
|
||||||
#define PIXELS_PER_PLANE_BYTE 8u
|
|
||||||
#define NIBBLE_BITS 4u
|
|
||||||
#define COLUMN_PAIRS (JL_SURFACE_WIDTH / 16)
|
#define COLUMN_PAIRS (JL_SURFACE_WIDTH / 16)
|
||||||
|
|
||||||
static inline __attribute__((always_inline)) void mixPlaneBytes(uint16_t *lo, uint16_t *hi, uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3);
|
// Bit j of a plane byte (pixel 7 - j) at bit 4 * j.
|
||||||
static void spreadBuild(void);
|
#define SPREAD(_b) ((uint32_t)((((uint32_t)(_b) & 0x01u)) | (((uint32_t)(_b) & 0x02u) << 3) | (((uint32_t)(_b) & 0x04u) << 6) | (((uint32_t)(_b) & 0x08u) << 9) | (((uint32_t)(_b) & 0x10u) << 12) | (((uint32_t)(_b) & 0x20u) << 15) | (((uint32_t)(_b) & 0x40u) << 18) | (((uint32_t)(_b) & 0x80u) << 21)))
|
||||||
|
#define SPREAD_ROW(_h) \
|
||||||
|
SPREAD((_h) * 16u + 0u), SPREAD((_h) * 16u + 1u), SPREAD((_h) * 16u + 2u), SPREAD((_h) * 16u + 3u), \
|
||||||
|
SPREAD((_h) * 16u + 4u), SPREAD((_h) * 16u + 5u), SPREAD((_h) * 16u + 6u), SPREAD((_h) * 16u + 7u), \
|
||||||
|
SPREAD((_h) * 16u + 8u), SPREAD((_h) * 16u + 9u), SPREAD((_h) * 16u + 10u), SPREAD((_h) * 16u + 11u), \
|
||||||
|
SPREAD((_h) * 16u + 12u), SPREAD((_h) * 16u + 13u), SPREAD((_h) * 16u + 14u), SPREAD((_h) * 16u + 15u)
|
||||||
|
|
||||||
static uint32_t gSpread[SPREAD_VALUES];
|
static inline __attribute__((always_inline)) void mixPlaneBytes(uint16_t *lo, uint16_t *hi, uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3);
|
||||||
static bool gSpreadBuilt = false;
|
|
||||||
|
const uint32_t jlgPlanarSpread[PLANAR_SPREAD_VALUES] = {
|
||||||
|
SPREAD_ROW(0u), SPREAD_ROW(1u), SPREAD_ROW(2u), SPREAD_ROW(3u), SPREAD_ROW(4u), SPREAD_ROW(5u), SPREAD_ROW(6u), SPREAD_ROW(7u),
|
||||||
|
SPREAD_ROW(8u), SPREAD_ROW(9u), SPREAD_ROW(10u), SPREAD_ROW(11u), SPREAD_ROW(12u), SPREAD_ROW(13u), SPREAD_ROW(14u), SPREAD_ROW(15u)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep) {
|
uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep) {
|
||||||
|
|
@ -43,9 +49,6 @@ uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[],
|
||||||
uint16_t row;
|
uint16_t row;
|
||||||
uint16_t pair;
|
uint16_t pair;
|
||||||
|
|
||||||
if (!gSpreadBuilt) {
|
|
||||||
spreadBuild();
|
|
||||||
}
|
|
||||||
lo = SURFACE_HASH_SEED_LO;
|
lo = SURFACE_HASH_SEED_LO;
|
||||||
hi = SURFACE_HASH_SEED_HI;
|
hi = SURFACE_HASH_SEED_HI;
|
||||||
for (row = 0u; row < JL_SURFACE_HEIGHT; row++) {
|
for (row = 0u; row < JL_SURFACE_HEIGHT; row++) {
|
||||||
|
|
@ -75,7 +78,7 @@ static inline __attribute__((always_inline)) void mixPlaneBytes(uint16_t *lo, ui
|
||||||
uint16_t h;
|
uint16_t h;
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
|
|
||||||
chunky = gSpread[b0] | (gSpread[b1] << 1) | (gSpread[b2] << 2) | (gSpread[b3] << 3);
|
chunky = jlgPlanarSpread[b0] | (jlgPlanarSpread[b1] << 1) | (jlgPlanarSpread[b2] << 2) | (jlgPlanarSpread[b3] << 3);
|
||||||
l = *lo;
|
l = *lo;
|
||||||
h = *hi;
|
h = *hi;
|
||||||
b = (uint8_t)(chunky >> 24);
|
b = (uint8_t)(chunky >> 24);
|
||||||
|
|
@ -90,20 +93,3 @@ static inline __attribute__((always_inline)) void mixPlaneBytes(uint16_t *lo, ui
|
||||||
*hi = h;
|
*hi = h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void spreadBuild(void) {
|
|
||||||
uint32_t value;
|
|
||||||
uint16_t b;
|
|
||||||
uint16_t k;
|
|
||||||
|
|
||||||
for (b = 0u; b < SPREAD_VALUES; b++) {
|
|
||||||
value = 0u;
|
|
||||||
for (k = 0u; k < PIXELS_PER_PLANE_BYTE; k++) {
|
|
||||||
if ((b & (0x80u >> k)) != 0u) {
|
|
||||||
value |= (uint32_t)1u << ((PIXELS_PER_PLANE_BYTE - 1u - k) * NIBBLE_BITS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gSpread[b] = value;
|
|
||||||
}
|
|
||||||
gSpreadBuilt = true;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
339
src/m68k/planarIndexed.c
Normal file
339
src/m68k/planarIndexed.c
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
// See LICENSE.md for license information.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// The planar ports' indexed paste and snap (jlpSurfacePasteIndexed /
|
||||||
|
// jlpSurfaceSnapIndexed on the Amiga, X68000, and Atari ST), a column -- 8
|
||||||
|
// pixels, one byte of each plane -- at a time. Plane p's byte of column c in
|
||||||
|
// a row is at planes[p] + row * rowBytes + (c >> 1) * pairStep + (c & 1), as
|
||||||
|
// in planarHash.c. The core has clipped the rectangle to the surface.
|
||||||
|
//
|
||||||
|
// Paste: a column's plane bytes are two pairs of kPasteTable lookups,
|
||||||
|
// planarIndexedColumns.s's method (its header has the table's layout): each
|
||||||
|
// lookup takes two source nibbles a and b and gives their pixels' plane
|
||||||
|
// bits. A row's first and last columns, which the rectangle may only partly
|
||||||
|
// cover, are merged under masks of the pixels it does. On the 68k ports the
|
||||||
|
// asm does every row but those of doubled pixels from an odd column, whose
|
||||||
|
// pairs straddle source bytes; pasteRow here does them, and on the host all
|
||||||
|
// of them, which is where scripts/check-indexed.sh proves the table and the
|
||||||
|
// method. (The asm reads a first or last column's whole source window, up
|
||||||
|
// to 7 bytes outside a source row; pasteRow reads only the row, the rest of
|
||||||
|
// its window colour 15.)
|
||||||
|
//
|
||||||
|
// Snap: a column's 4 plane bytes are its 4 chunky bytes through
|
||||||
|
// jlgPlanarSpread, as the hash reads them, whose nibbles are the
|
||||||
|
// rectangle's bytes: whole columns at fixed nibbles (the asm on the 68k
|
||||||
|
// ports), the bytes before and after them one at a time.
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
#define PLANES 4u
|
||||||
|
#define COLUMN_PIXELS 8u
|
||||||
|
#define COLUMN_SHIFT 3u
|
||||||
|
#define COLUMN_MASK 7u
|
||||||
|
#define NIBBLE_MASK 0x0Fu
|
||||||
|
#define NIBBLE_BITS 4u
|
||||||
|
#define NO_COLUMN 0xFFFFu
|
||||||
|
|
||||||
|
// kPasteTable: block a (16 of them) holds four 16-entry tables of b, each
|
||||||
|
// entry the plane bytes (plane 0 the top byte) of nibble a at the pixels of
|
||||||
|
// mask _ma and nibble b at those of _mb.
|
||||||
|
#define PASTE_SINGLE_A 0u // a at pixel 0, b at 2
|
||||||
|
#define PASTE_SINGLE_B 1u // a at pixel 1, b at 3
|
||||||
|
#define PASTE_DOUBLE_A 2u // a at pixels 0-1, b at 4-5
|
||||||
|
#define PASTE_DOUBLE_B 3u // a at pixels 2-3, b at 6-7
|
||||||
|
#define PASTE_TABLES 4u
|
||||||
|
#define PASTE_ENTRIES (16u * PASTE_TABLES * 16u)
|
||||||
|
#define PASTE_DOUBLE_OFFSET (PASTE_DOUBLE_A * 16u)
|
||||||
|
#define PLANE_BITS(_c, _m) ((((_c) & 1u) != 0u ? (uint32_t)(_m) << 24 : 0u) | (((_c) & 2u) != 0u ? (uint32_t)(_m) << 16 : 0u) | (((_c) & 4u) != 0u ? (uint32_t)(_m) << 8 : 0u) | (((_c) & 8u) != 0u ? (uint32_t)(_m) : 0u))
|
||||||
|
#define PASTE_ENTRY(_a, _b, _ma, _mb) (PLANE_BITS((_a), (_ma)) | PLANE_BITS((_b), (_mb)))
|
||||||
|
#define PASTE_ROW(_a, _ma, _mb) \
|
||||||
|
PASTE_ENTRY((_a), 0u, (_ma), (_mb)), PASTE_ENTRY((_a), 1u, (_ma), (_mb)), PASTE_ENTRY((_a), 2u, (_ma), (_mb)), PASTE_ENTRY((_a), 3u, (_ma), (_mb)), \
|
||||||
|
PASTE_ENTRY((_a), 4u, (_ma), (_mb)), PASTE_ENTRY((_a), 5u, (_ma), (_mb)), PASTE_ENTRY((_a), 6u, (_ma), (_mb)), PASTE_ENTRY((_a), 7u, (_ma), (_mb)), \
|
||||||
|
PASTE_ENTRY((_a), 8u, (_ma), (_mb)), PASTE_ENTRY((_a), 9u, (_ma), (_mb)), PASTE_ENTRY((_a), 10u, (_ma), (_mb)), PASTE_ENTRY((_a), 11u, (_ma), (_mb)), \
|
||||||
|
PASTE_ENTRY((_a), 12u, (_ma), (_mb)), PASTE_ENTRY((_a), 13u, (_ma), (_mb)), PASTE_ENTRY((_a), 14u, (_ma), (_mb)), PASTE_ENTRY((_a), 15u, (_ma), (_mb))
|
||||||
|
#define PASTE_BLOCK(_a) \
|
||||||
|
PASTE_ROW((_a), 0x80u, 0x20u), PASTE_ROW((_a), 0x40u, 0x10u), PASTE_ROW((_a), 0xC0u, 0x0Cu), PASTE_ROW((_a), 0x30u, 0x03u)
|
||||||
|
// The lookup of table _t at nibbles _a and _b (their high nibbles ignored).
|
||||||
|
#define PASTE(_t, _a, _b) kPasteTable[(((uint16_t)(_a) & NIBBLE_MASK) << 6) | ((_t) << 4) | ((uint16_t)(_b) & NIBBLE_MASK)]
|
||||||
|
|
||||||
|
static const uint32_t kPasteTable[PASTE_ENTRIES] = {
|
||||||
|
PASTE_BLOCK(0u), PASTE_BLOCK(1u), PASTE_BLOCK(2u), PASTE_BLOCK(3u), PASTE_BLOCK(4u), PASTE_BLOCK(5u), PASTE_BLOCK(6u), PASTE_BLOCK(7u),
|
||||||
|
PASTE_BLOCK(8u), PASTE_BLOCK(9u), PASTE_BLOCK(10u), PASTE_BLOCK(11u), PASTE_BLOCK(12u), PASTE_BLOCK(13u), PASTE_BLOCK(14u), PASTE_BLOCK(15u)
|
||||||
|
};
|
||||||
|
|
||||||
|
// What a column's source bytes are: single pixels (8 bytes), doubled ones
|
||||||
|
// (4), or doubled ones from an odd column (5, every pair but the end
|
||||||
|
// pixels straddling two).
|
||||||
|
#define KIND_SINGLE 0u
|
||||||
|
#define KIND_DOUBLE 1u
|
||||||
|
#define KIND_STRADDLE 2u
|
||||||
|
#define WINDOW_BYTES COLUMN_PIXELS
|
||||||
|
#define WINDOW_OUTSIDE 0x0Fu
|
||||||
|
#define FULL_MASK 0xFFu
|
||||||
|
|
||||||
|
// A whole column's snap: all 8 pixels, or doubled from pixels 0, 2, 4, 6
|
||||||
|
// or 1, 3, 5, 7 (planarIndexedColumns.s's modes).
|
||||||
|
#define SNAP_SINGLE 0u
|
||||||
|
#define SNAP_EVEN 1u
|
||||||
|
#define SNAP_ODD 2u
|
||||||
|
#define SNAP_NIBBLES 0x0F0F0F0Fu
|
||||||
|
#define CHUNKY_BYTES (COLUMN_PIXELS / 2u)
|
||||||
|
|
||||||
|
#if defined(JL_CPU_68000)
|
||||||
|
// planarIndexedColumns.s.
|
||||||
|
void jlPlanarPasteColumnsInterleaved(const uint8_t *src, uint16_t srcStride, uint8_t *base, uint16_t dstOff, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *table, bool doubleWide, uint8_t headMask, uint8_t tailMask);
|
||||||
|
void jlPlanarPasteColumnsSeparate(const uint8_t *src, uint16_t srcStride, uint8_t *const *planes, uint16_t dstOff, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *table, bool doubleWide, uint8_t headMask, uint8_t tailMask);
|
||||||
|
void jlPlanarSnapColumnsInterleaved(const uint8_t *base, uint16_t srcOff, uint16_t srcStride, uint8_t *dst, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *spread, uint8_t mode);
|
||||||
|
void jlPlanarSnapColumnsSeparate(const uint8_t *const *planes, uint16_t srcOff, uint16_t srcStride, uint8_t *dst, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *spread, uint8_t mode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint16_t columnOffset(uint16_t c, uint16_t pairStep);
|
||||||
|
static uint32_t columnPlanes(const uint8_t *sp, uint8_t kind);
|
||||||
|
static void mergeColumn(uint8_t *const rows[], uint16_t off, uint32_t planes, uint8_t mask);
|
||||||
|
static void pasteRow(uint8_t *const rows[], uint16_t pairStep, uint16_t c0, uint16_t columns, const uint8_t *s, uint16_t lead, uint16_t w, uint8_t kind, uint8_t headMask, uint8_t tailMask);
|
||||||
|
static void snapBytes(const uint8_t *const rows[], uint16_t pairStep, uint16_t px, uint16_t n, uint8_t *d, uint8_t shift);
|
||||||
|
#if !defined(JL_CPU_68000)
|
||||||
|
static void snapColumns(const uint8_t *const rows[], uint16_t pairStep, uint16_t c, uint16_t n, uint8_t *d, uint8_t mode);
|
||||||
|
#endif
|
||||||
|
static inline uint32_t snapDecode(const uint8_t *const rows[], uint16_t off);
|
||||||
|
static inline void storeColumn(uint8_t *const rows[], uint16_t off, uint32_t planes);
|
||||||
|
|
||||||
|
|
||||||
|
static inline uint16_t columnOffset(uint16_t c, uint16_t pairStep) {
|
||||||
|
return (uint16_t)((c >> 1) * pairStep + (c & 1u));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A column's plane bytes from its source bytes sp[0..]. Single pixels 0-7
|
||||||
|
// are sp[0..7]; doubled ones from an even column sp[0..3] twice each; from
|
||||||
|
// an odd column sp[0], sp[1] twice, sp[2] twice, sp[3] twice, sp[4].
|
||||||
|
static uint32_t columnPlanes(const uint8_t *sp, uint8_t kind) {
|
||||||
|
if (kind == KIND_DOUBLE) {
|
||||||
|
return PASTE(PASTE_DOUBLE_A, sp[0], sp[2]) | PASTE(PASTE_DOUBLE_B, sp[1], sp[3]);
|
||||||
|
}
|
||||||
|
if (kind == KIND_STRADDLE) {
|
||||||
|
return PASTE(PASTE_SINGLE_A, sp[0], sp[1]) | PASTE(PASTE_SINGLE_B, sp[1], sp[2]) | ((PASTE(PASTE_SINGLE_A, sp[2], sp[3]) | PASTE(PASTE_SINGLE_B, sp[3], sp[4])) >> NIBBLE_BITS);
|
||||||
|
}
|
||||||
|
return PASTE(PASTE_SINGLE_A, sp[0], sp[2]) | PASTE(PASTE_SINGLE_B, sp[1], sp[3]) | ((PASTE(PASTE_SINGLE_A, sp[4], sp[6]) | PASTE(PASTE_SINGLE_B, sp[5], sp[7])) >> NIBBLE_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlPlanarPasteIndexed(uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
uint8_t *rows[PLANES];
|
||||||
|
uint32_t rowOff;
|
||||||
|
uint16_t last;
|
||||||
|
uint16_t c0;
|
||||||
|
uint16_t columns;
|
||||||
|
uint16_t lead;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t p;
|
||||||
|
uint8_t shift;
|
||||||
|
uint8_t kind;
|
||||||
|
uint8_t headMask;
|
||||||
|
uint8_t tailMask;
|
||||||
|
|
||||||
|
// The columns c0 .. c0 + columns - 1 the pixels x .. last touch; the
|
||||||
|
// first's and the last's pixels in the rectangle (one column: both).
|
||||||
|
// lead: the source bytes the first column's window starts before the
|
||||||
|
// row (a doubled pixel from an odd x pairs with the byte before).
|
||||||
|
shift = doubleWide ? 1u : 0u;
|
||||||
|
kind = !doubleWide ? KIND_SINGLE : ((x & 1u) == 0u ? KIND_DOUBLE : KIND_STRADDLE);
|
||||||
|
last = (uint16_t)(x + (w << shift) - 1u);
|
||||||
|
c0 = (uint16_t)(x >> COLUMN_SHIFT);
|
||||||
|
columns = (uint16_t)((last >> COLUMN_SHIFT) - c0 + 1u);
|
||||||
|
headMask = (uint8_t)(FULL_MASK >> (x & COLUMN_MASK));
|
||||||
|
tailMask = (uint8_t)(FULL_MASK << (COLUMN_MASK - (last & COLUMN_MASK)));
|
||||||
|
if (columns == 1u) {
|
||||||
|
headMask = (uint8_t)(headMask & tailMask);
|
||||||
|
}
|
||||||
|
lead = (uint16_t)(((x & COLUMN_MASK) + (kind == KIND_STRADDLE ? 1u : 0u)) >> shift);
|
||||||
|
rowOff = (uint32_t)y * rowBytes;
|
||||||
|
#if defined(JL_CPU_68000)
|
||||||
|
if (kind != KIND_STRADDLE) {
|
||||||
|
if (pairStep == PLANAR_SEPARATE_PAIR_STEP) {
|
||||||
|
jlPlanarPasteColumnsSeparate(src - lead, srcStride, planes, (uint16_t)(rowOff + columnOffset(c0, pairStep)), rowBytes, columns, h, &kPasteTable[shift != 0u ? PASTE_DOUBLE_OFFSET : 0u], doubleWide, headMask, tailMask);
|
||||||
|
} else {
|
||||||
|
jlPlanarPasteColumnsInterleaved(src - lead, srcStride, planes[0], (uint16_t)(rowOff + columnOffset(c0, pairStep)), rowBytes, columns, h, &kPasteTable[shift != 0u ? PASTE_DOUBLE_OFFSET : 0u], doubleWide, headMask, tailMask);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (p = 0; p < PLANES; p++) {
|
||||||
|
rows[p] = planes[p] + rowOff;
|
||||||
|
}
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
pasteRow(rows, pairStep, c0, columns, src, lead, w, kind, headMask, tailMask);
|
||||||
|
src += srcStride;
|
||||||
|
for (p = 0; p < PLANES; p++) {
|
||||||
|
rows[p] += rowBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jlPlanarSnapIndexed(const uint8_t *const planes[], uint16_t rowBytes, uint16_t pairStep, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
const uint8_t *rows[PLANES];
|
||||||
|
uint32_t rowOff;
|
||||||
|
uint16_t lead;
|
||||||
|
uint16_t full;
|
||||||
|
uint16_t trail;
|
||||||
|
uint16_t xFull;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t p;
|
||||||
|
uint8_t shift;
|
||||||
|
uint8_t first;
|
||||||
|
uint8_t mode;
|
||||||
|
|
||||||
|
// A doubled snap takes pixel 0 or 1 of each pair (x's parity): a whole
|
||||||
|
// column is 8 bytes single and 4 doubled. The lead bytes come before
|
||||||
|
// the first whole column, the trail ones after the last.
|
||||||
|
shift = doubleWide ? 1u : 0u;
|
||||||
|
first = (uint8_t)(x & shift);
|
||||||
|
lead = (uint16_t)((((uint16_t)(COLUMN_PIXELS + first) - (x & COLUMN_MASK)) & COLUMN_MASK) >> shift);
|
||||||
|
if (lead > w) {
|
||||||
|
lead = w;
|
||||||
|
}
|
||||||
|
full = (uint16_t)((w - lead) >> (COLUMN_SHIFT - shift));
|
||||||
|
trail = (uint16_t)(w - lead - (full << (COLUMN_SHIFT - shift)));
|
||||||
|
xFull = (uint16_t)(x + (lead << shift));
|
||||||
|
mode = shift == 0u ? SNAP_SINGLE : (first == 0u ? SNAP_EVEN : SNAP_ODD);
|
||||||
|
rowOff = (uint32_t)y * rowBytes;
|
||||||
|
for (p = 0; p < PLANES; p++) {
|
||||||
|
rows[p] = planes[p] + rowOff;
|
||||||
|
}
|
||||||
|
#if defined(JL_CPU_68000)
|
||||||
|
if (full != 0u) {
|
||||||
|
if (pairStep == PLANAR_SEPARATE_PAIR_STEP) {
|
||||||
|
jlPlanarSnapColumnsSeparate(planes, (uint16_t)(rowOff + columnOffset((uint16_t)(xFull >> COLUMN_SHIFT), pairStep)), rowBytes, dst + lead, dstStride, full, h, jlgPlanarSpread, mode);
|
||||||
|
} else {
|
||||||
|
jlPlanarSnapColumnsInterleaved(planes[0], (uint16_t)(rowOff + columnOffset((uint16_t)(xFull >> COLUMN_SHIFT), pairStep)), rowBytes, dst + lead, dstStride, full, h, jlgPlanarSpread, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (r = 0; r < h; r++) {
|
||||||
|
snapBytes(rows, pairStep, x, lead, dst, shift);
|
||||||
|
#if !defined(JL_CPU_68000)
|
||||||
|
snapColumns(rows, pairStep, (uint16_t)(xFull >> COLUMN_SHIFT), full, dst + lead, mode);
|
||||||
|
#endif
|
||||||
|
snapBytes(rows, pairStep, (uint16_t)(xFull + (full << COLUMN_SHIFT)), trail, dst + w - trail, shift);
|
||||||
|
dst += dstStride;
|
||||||
|
for (p = 0; p < PLANES; p++) {
|
||||||
|
rows[p] += rowBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A column's plane bytes where mask has a pixel, the old ones elsewhere.
|
||||||
|
static void mergeColumn(uint8_t *const rows[], uint16_t off, uint32_t planes, uint8_t mask) {
|
||||||
|
uint16_t p;
|
||||||
|
uint8_t keep;
|
||||||
|
|
||||||
|
keep = (uint8_t)~mask;
|
||||||
|
for (p = 0; p < PLANES; p++) {
|
||||||
|
rows[p][off] = (uint8_t)((rows[p][off] & keep) | ((uint8_t)(planes >> ((PLANES - 1u - p) * 8u)) & mask));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One row's columns c0 .. c0 + columns - 1, as planarIndexedColumns.s does
|
||||||
|
// them: the first and the last merged under their masks, from windows of
|
||||||
|
// the source, the ones between whole, straight from the row. The first
|
||||||
|
// column's window starts lead bytes before the row's first. A window byte
|
||||||
|
// outside the row's w reads WINDOW_OUTSIDE, every plane bit set, standing
|
||||||
|
// for whatever the asm reads there: the masks have to drop it.
|
||||||
|
static void pasteRow(uint8_t *const rows[], uint16_t pairStep, uint16_t c0, uint16_t columns, const uint8_t *s, uint16_t lead, uint16_t w, uint8_t kind, uint8_t headMask, uint8_t tailMask) {
|
||||||
|
uint8_t window[WINDOW_BYTES];
|
||||||
|
int32_t first;
|
||||||
|
int32_t index;
|
||||||
|
uint16_t c;
|
||||||
|
uint16_t k;
|
||||||
|
uint16_t step;
|
||||||
|
|
||||||
|
step = kind == KIND_SINGLE ? COLUMN_PIXELS : CHUNKY_BYTES;
|
||||||
|
first = -(int32_t)lead;
|
||||||
|
for (c = 0; c < columns; c++) {
|
||||||
|
if (c == 0u || c == columns - 1u) {
|
||||||
|
for (k = 0; k < WINDOW_BYTES; k++) {
|
||||||
|
index = first + (int32_t)k;
|
||||||
|
window[k] = (index >= 0 && index < (int32_t)w) ? s[index] : WINDOW_OUTSIDE;
|
||||||
|
}
|
||||||
|
mergeColumn(rows, columnOffset((uint16_t)(c0 + c), pairStep), columnPlanes(window, kind), c == 0u ? headMask : tailMask);
|
||||||
|
} else {
|
||||||
|
storeColumn(rows, columnOffset((uint16_t)(c0 + c), pairStep), columnPlanes(s + first, kind));
|
||||||
|
}
|
||||||
|
first += step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// n bytes of a row from columns px, px + (1 << shift), ..., one at a time
|
||||||
|
// (a column decoded once).
|
||||||
|
static void snapBytes(const uint8_t *const rows[], uint16_t pairStep, uint16_t px, uint16_t n, uint8_t *d, uint8_t shift) {
|
||||||
|
uint32_t chunky;
|
||||||
|
uint16_t last;
|
||||||
|
uint16_t c;
|
||||||
|
uint16_t i;
|
||||||
|
|
||||||
|
last = NO_COLUMN;
|
||||||
|
chunky = 0u;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
c = (uint16_t)(px >> COLUMN_SHIFT);
|
||||||
|
if (c != last) {
|
||||||
|
chunky = snapDecode(rows, columnOffset(c, pairStep));
|
||||||
|
last = c;
|
||||||
|
}
|
||||||
|
d[i] = (uint8_t)((chunky >> ((COLUMN_MASK - (px & COLUMN_MASK)) * NIBBLE_BITS)) & NIBBLE_MASK);
|
||||||
|
px = (uint16_t)(px + (1u << shift));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if !defined(JL_CPU_68000)
|
||||||
|
// n whole columns of a row from column c, as planarIndexedColumns.s does
|
||||||
|
// them: single, all 8 pixels, the chunky bytes' high nibbles (pixels 0, 2,
|
||||||
|
// 4, 6) to the even bytes and the low to the odd; doubled, 4 bytes, the
|
||||||
|
// high nibbles (SNAP_EVEN) or the low.
|
||||||
|
static void snapColumns(const uint8_t *const rows[], uint16_t pairStep, uint16_t c, uint16_t n, uint8_t *d, uint8_t mode) {
|
||||||
|
uint32_t chunky;
|
||||||
|
uint32_t wanted;
|
||||||
|
uint16_t k;
|
||||||
|
|
||||||
|
for (; n > 0u; n--) {
|
||||||
|
chunky = snapDecode(rows, columnOffset(c, pairStep));
|
||||||
|
c++;
|
||||||
|
wanted = (mode == SNAP_ODD ? chunky : chunky >> NIBBLE_BITS) & SNAP_NIBBLES;
|
||||||
|
for (k = 0; k < CHUNKY_BYTES; k++) {
|
||||||
|
d[k << (mode == SNAP_SINGLE ? 1u : 0u)] = (uint8_t)(wanted >> ((CHUNKY_BYTES - 1u - k) * 8u));
|
||||||
|
}
|
||||||
|
if (mode == SNAP_SINGLE) {
|
||||||
|
wanted = chunky & SNAP_NIBBLES;
|
||||||
|
for (k = 0; k < CHUNKY_BYTES; k++) {
|
||||||
|
d[(k << 1) + 1u] = (uint8_t)(wanted >> ((CHUNKY_BYTES - 1u - k) * 8u));
|
||||||
|
}
|
||||||
|
d += COLUMN_PIXELS;
|
||||||
|
} else {
|
||||||
|
d += CHUNKY_BYTES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// The chunky long of the column at off: its 8 pixels' nibbles, pixel 0's
|
||||||
|
// highest.
|
||||||
|
static inline uint32_t snapDecode(const uint8_t *const rows[], uint16_t off) {
|
||||||
|
return jlgPlanarSpread[rows[0][off]] | (jlgPlanarSpread[rows[1][off]] << 1) | (jlgPlanarSpread[rows[2][off]] << 2) | (jlgPlanarSpread[rows[3][off]] << 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline void storeColumn(uint8_t *const rows[], uint16_t off, uint32_t planes) {
|
||||||
|
rows[0][off] = (uint8_t)(planes >> 24);
|
||||||
|
rows[1][off] = (uint8_t)(planes >> 16);
|
||||||
|
rows[2][off] = (uint8_t)(planes >> 8);
|
||||||
|
rows[3][off] = (uint8_t)planes;
|
||||||
|
}
|
||||||
690
src/m68k/planarIndexedColumns.s
Normal file
690
src/m68k/planarIndexedColumns.s
Normal file
|
|
@ -0,0 +1,690 @@
|
||||||
|
| Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
| See LICENSE.md for license information.
|
||||||
|
| SPDX-License-Identifier: MIT
|
||||||
|
|
|
||||||
|
| The planar ports' indexed paste and snap, a column (8 pixels, one byte of
|
||||||
|
| each plane) at a time: the paste every column of its rows, a first or
|
||||||
|
| last one the rectangle cuts merged under its mask; the snap whole columns
|
||||||
|
| (planarIndexed.c does the bytes before and after them, and pasted doubled
|
||||||
|
| pixels from an odd column).
|
||||||
|
|
|
||||||
|
| void jlPlanarPasteColumnsInterleaved(const uint8_t *src, uint16_t srcStride, uint8_t *base, uint16_t dstOff, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *table, bool doubleWide, uint8_t headMask, uint8_t tailMask);
|
||||||
|
| void jlPlanarPasteColumnsSeparate(const uint8_t *src, uint16_t srcStride, uint8_t *const *planes, uint16_t dstOff, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *table, bool doubleWide, uint8_t headMask, uint8_t tailMask);
|
||||||
|
| void jlPlanarSnapColumnsInterleaved(const uint8_t *base, uint16_t srcOff, uint16_t srcStride, uint8_t *dst, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *spread, uint8_t mode);
|
||||||
|
| void jlPlanarSnapColumnsSeparate(const uint8_t *const *planes, uint16_t srcOff, uint16_t srcStride, uint8_t *dst, uint16_t dstStride, uint16_t columns, uint16_t rows, const uint32_t *spread, uint8_t mode);
|
||||||
|
|
|
||||||
|
| Paste:
|
||||||
|
|
|
||||||
|
| src is the first column's first source byte (before the row's when the
|
||||||
|
| rectangle starts inside the column), dstOff its plane-0 byte's offset (in
|
||||||
|
| base, the ST's interleaved buffer, or in each of the four separate
|
||||||
|
| planes), columns (>= 1) every column a row touches. headMask and tailMask
|
||||||
|
| are the first and last columns' pixels in the rectangle (bit 7 the left
|
||||||
|
| one; one column: headMask alone, already both). table is planarIndexed.c's
|
||||||
|
| kPasteTable, + 128 bytes for doubleWide. A first or last column reads its
|
||||||
|
| whole source window, up to 7 bytes outside a source row (RAM on these
|
||||||
|
| machines), and the mask drops what those bytes make.
|
||||||
|
|
|
||||||
|
| movep.l 0(src) reads source bytes 0, 2, 4, 6 and movep.l 1(src) bytes 1,
|
||||||
|
| 3, 5, 7, at any alignment. Masked to their nibbles a and b, the low word
|
||||||
|
| of either is (a << 8) | b, and with b times 4 it is a byte offset
|
||||||
|
| into kPasteTable: 16 blocks of 256 bytes, block a holding four 16-entry
|
||||||
|
| tables of b, each entry the plane bytes (plane 0 the top byte) of a and
|
||||||
|
| b at fixed pixels. Two lookups or'd are four pixels' bits:
|
||||||
|
| single +0: a at pixel 0, b at 2 +64: a at 1, b at 3
|
||||||
|
| so bytes 0-3 are one lookup pair and 4-7 another, shifted
|
||||||
|
| right 4 (8 source bytes a column)
|
||||||
|
| doubled +128: a at pixels 0-1, b at 4-5 +192: a at 2-3, b at 6-7
|
||||||
|
| (4 source bytes a column, so one movep.l pair is two columns)
|
||||||
|
| A column's four plane bytes go out with one movep.l on the ST (plane p's
|
||||||
|
| byte of a column sits at 2 * p) and through an 8-byte scratch on the
|
||||||
|
| stack on the separate-plane ports: two columns movep'd into it at 0 and 1
|
||||||
|
| are each plane's word for the pair. A masked column is (old & ~mask) |
|
||||||
|
| (new & mask), the mask's byte four times over.
|
||||||
|
|
|
||||||
|
| Registers: a0 the source, a1 the table, a2 the destination (a2-a5 the
|
||||||
|
| four planes when separate; on the ST a3 the row's start and a4 the row
|
||||||
|
| stride), a6 the source's step to the next row, d0/d1 the indices, d2/d3
|
||||||
|
| the columns, d4 the pair count, d5 the nibble mask, d6 the rows left
|
||||||
|
| (separate: the planes' row step in its high word), d7 scratch.
|
||||||
|
|
|
||||||
|
| Snap: srcOff is the first column's plane-0 byte; spread is
|
||||||
|
| jlgPlanarSpread, whose lookups of a column's four plane bytes, plane p's
|
||||||
|
| shifted left p, are its 4 chunky bytes (planarHash.c). mode 0 takes all
|
||||||
|
| 8 pixels -- the high nibbles are pixels 0, 2, 4, 6 and the low ones 1, 3,
|
||||||
|
| 5, 7, so two movep.l at 0 and 1 interleave them into the row -- mode 1
|
||||||
|
| (doubled, from even pixels) the high nibbles and mode 2 (doubled, from
|
||||||
|
| odd ones) the low, 4 consecutive bytes stored as a long, or byte, word,
|
||||||
|
| byte at an odd address. Registers: a0 the destination, a1 the table, a2
|
||||||
|
| the column (a2-a5 the planes when separate), d0 the chunky long, d1 the
|
||||||
|
| index, d2 scratch, d3 mode 1's shift (4, else 0), d4 the pair or column
|
||||||
|
| count, d5 the nibble mask, d6 the rows left, d7 the index mask.
|
||||||
|
|
|
||||||
|
| movep is a 68000-68040 instruction; a 68060 emulates it in software.
|
||||||
|
|
|
||||||
|
| ABI: cdecl, d2-d7/a2-a6 saved.
|
||||||
|
|
|
||||||
|
| GAS m68k syntax; every entry has both spellings (a.out/hunk "_" and ELF).
|
||||||
|
|
||||||
|
.equ SAVED, 44 | d2-d7/a2-a6
|
||||||
|
.equ LOCALS, 24
|
||||||
|
.equ L_SCRATCH, 0 | 8 bytes, even
|
||||||
|
.equ L_PAIRS, 8 | the middle's pairs - 1 (-1: none)
|
||||||
|
.equ L_HEAD, 10 | the middle starts at an odd column
|
||||||
|
.equ L_TAIL, 11 | a middle column after its pairs
|
||||||
|
.equ L_HEADMASK, 12 | the first column's mask, x4
|
||||||
|
.equ L_TAILMASK, 16 | the last column's
|
||||||
|
.equ L_FIRSTSTEP, 20 | ST: the first column to the next
|
||||||
|
.equ L_LAST, 22 | a last column the rectangle cuts
|
||||||
|
.equ L_FIRST, 23 | a first column the rectangle cuts
|
||||||
|
.equ ARGS, (LOCALS + SAVED + 4)
|
||||||
|
.equ A_SRC, ARGS + 0
|
||||||
|
.equ A_SRCSTRIDE, ARGS + 4 + 2
|
||||||
|
.equ A_DST, ARGS + 8
|
||||||
|
.equ A_DSTOFF, ARGS + 12 + 2
|
||||||
|
.equ A_DSTSTRIDE, ARGS + 16 + 2
|
||||||
|
.equ A_COLUMNS, ARGS + 20 + 2
|
||||||
|
.equ A_ROWS, ARGS + 24 + 2
|
||||||
|
.equ A_TABLE, ARGS + 28
|
||||||
|
.equ A_DOUBLE, ARGS + 32 + 3
|
||||||
|
.equ A_HEADMASK, ARGS + 36 + 3
|
||||||
|
.equ A_TAILMASK, ARGS + 40 + 3
|
||||||
|
|
||||||
|
.equ S_LOCALS, 28
|
||||||
|
.equ S_ROWSRC, 0 | the row's first column offset
|
||||||
|
.equ S_ROWDST, 4
|
||||||
|
.equ S_PLANES, 8 | four plane bases
|
||||||
|
.equ S_PAIRS, 24 | pairs - 1 (-1: none)
|
||||||
|
.equ S_HEAD, 26 | an odd first column
|
||||||
|
.equ S_TAIL, 27 | a last column after the pairs
|
||||||
|
.equ S_ARGS, (S_LOCALS + SAVED + 4)
|
||||||
|
.equ S_SRC, S_ARGS + 0
|
||||||
|
.equ S_SRCOFF, S_ARGS + 4 + 2
|
||||||
|
.equ S_SRCSTRIDE, S_ARGS + 8 + 2
|
||||||
|
.equ S_DST, S_ARGS + 12
|
||||||
|
.equ S_DSTSTRIDE, S_ARGS + 16 + 2
|
||||||
|
.equ S_COLUMNS, S_ARGS + 20 + 2
|
||||||
|
.equ S_ROWS, S_ARGS + 24 + 2
|
||||||
|
.equ S_SPREAD, S_ARGS + 28
|
||||||
|
.equ S_MODE, S_ARGS + 32 + 3
|
||||||
|
.equ MODE_SINGLE, 0
|
||||||
|
.equ MODE_EVEN, 1
|
||||||
|
.equ INDEX_MASK, 0x03FC | a byte times 4
|
||||||
|
.equ NIBBLE_SHIFT, 4
|
||||||
|
|
||||||
|
.equ PAIR_TABLE, 64 | the second lookup of a pair
|
||||||
|
.equ NIBBLES, 0x0F0F0F0F
|
||||||
|
.equ ST_PAIR_NEXT, 8 | the ST's group: two columns
|
||||||
|
.equ ST_ODD_NEXT, 7 | an odd column to the next group
|
||||||
|
.equ ST_EVEN_NEXT, 1 | an even column to the odd one
|
||||||
|
|
||||||
|
|
||||||
|
| The two lookups of the index pair in d0/d1's low words, into \out (b
|
||||||
|
| times 4 by two adds: 8 cycles to lsl.b #2's 10).
|
||||||
|
.macro LOOKUP out
|
||||||
|
add.b %d0,%d0
|
||||||
|
add.b %d0,%d0
|
||||||
|
add.b %d1,%d1
|
||||||
|
add.b %d1,%d1
|
||||||
|
move.l 0(%a1,%d0.w),\out
|
||||||
|
or.l PAIR_TABLE(%a1,%d1.w),\out
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| One doubled column: 4 source bytes.
|
||||||
|
.macro DOUBLE_ONE out
|
||||||
|
movep.w 0(%a0),%d0
|
||||||
|
movep.w 1(%a0),%d1
|
||||||
|
addq.l #4,%a0
|
||||||
|
and.w %d5,%d0
|
||||||
|
and.w %d5,%d1
|
||||||
|
LOOKUP \out
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Two doubled columns: 8 source bytes, the right column's from the low
|
||||||
|
| words, the left's from the high.
|
||||||
|
.macro DOUBLE_PAIR left, right
|
||||||
|
movep.l 0(%a0),%d0
|
||||||
|
movep.l 1(%a0),%d1
|
||||||
|
addq.l #8,%a0
|
||||||
|
and.l %d5,%d0
|
||||||
|
and.l %d5,%d1
|
||||||
|
LOOKUP \right
|
||||||
|
swap %d0
|
||||||
|
swap %d1
|
||||||
|
LOOKUP \left
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| One single column: 8 source bytes, pixels 4-7 from the low words.
|
||||||
|
.macro SINGLE_ONE out
|
||||||
|
movep.l 0(%a0),%d0
|
||||||
|
movep.l 1(%a0),%d1
|
||||||
|
addq.l #8,%a0
|
||||||
|
and.l %d5,%d0
|
||||||
|
and.l %d5,%d1
|
||||||
|
LOOKUP %d7
|
||||||
|
lsr.l #4,%d7
|
||||||
|
swap %d0
|
||||||
|
swap %d1
|
||||||
|
LOOKUP \out
|
||||||
|
or.l %d7,\out
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
.macro SINGLE_PAIR left, right
|
||||||
|
SINGLE_ONE \left
|
||||||
|
SINGLE_ONE \right
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Stores. ST: a column's plane bytes at 0, 2, 4, 6 from a2. A head column
|
||||||
|
| is odd, so the next group starts 7 on; a tail column is even, so the next
|
||||||
|
| column is 1 on.
|
||||||
|
.macro ST_TAIL col
|
||||||
|
movep.l \col,0(%a2)
|
||||||
|
addq.l #ST_EVEN_NEXT,%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro ST_HEAD col
|
||||||
|
movep.l \col,0(%a2)
|
||||||
|
addq.l #ST_ODD_NEXT,%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro ST_PAIR left, right
|
||||||
|
movep.l \left,0(%a2)
|
||||||
|
movep.l \right,1(%a2)
|
||||||
|
addq.l #ST_PAIR_NEXT,%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Separate planes: a byte into each, or a word of two columns (the pairs
|
||||||
|
| start at an even column).
|
||||||
|
.macro SEP_ONE col
|
||||||
|
movep.l \col,L_SCRATCH(%sp)
|
||||||
|
move.b L_SCRATCH(%sp),(%a2)+
|
||||||
|
move.b L_SCRATCH+2(%sp),(%a3)+
|
||||||
|
move.b L_SCRATCH+4(%sp),(%a4)+
|
||||||
|
move.b L_SCRATCH+6(%sp),(%a5)+
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_HEAD col
|
||||||
|
SEP_ONE \col
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_TAIL col
|
||||||
|
SEP_ONE \col
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_PAIR left, right
|
||||||
|
movep.l \left,L_SCRATCH(%sp)
|
||||||
|
movep.l \right,L_SCRATCH+1(%sp)
|
||||||
|
move.w L_SCRATCH(%sp),(%a2)+
|
||||||
|
move.w L_SCRATCH+2(%sp),(%a3)+
|
||||||
|
move.w L_SCRATCH+4(%sp),(%a4)+
|
||||||
|
move.w L_SCRATCH+6(%sp),(%a5)+
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Masked stores: \col where the mask at \mask(sp) has a pixel, the old
|
||||||
|
| plane bytes elsewhere. The ST leaves a2 on the column (ST_FIRST_NEXT
|
||||||
|
| steps it); separate planes step every plane on.
|
||||||
|
.macro ST_MASKED col, mask
|
||||||
|
movep.l 0(%a2),%d0
|
||||||
|
move.l \mask(%sp),%d1
|
||||||
|
and.l %d1,\col
|
||||||
|
not.l %d1
|
||||||
|
and.l %d1,%d0
|
||||||
|
or.l \col,%d0
|
||||||
|
movep.l %d0,0(%a2)
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_MASKED col, mask
|
||||||
|
move.l \mask(%sp),%d1
|
||||||
|
and.l %d1,\col
|
||||||
|
movep.l \col,L_SCRATCH(%sp)
|
||||||
|
not.b %d1
|
||||||
|
move.b (%a2),%d0
|
||||||
|
and.b %d1,%d0
|
||||||
|
or.b L_SCRATCH(%sp),%d0
|
||||||
|
move.b %d0,(%a2)+
|
||||||
|
move.b (%a3),%d0
|
||||||
|
and.b %d1,%d0
|
||||||
|
or.b L_SCRATCH+2(%sp),%d0
|
||||||
|
move.b %d0,(%a3)+
|
||||||
|
move.b (%a4),%d0
|
||||||
|
and.b %d1,%d0
|
||||||
|
or.b L_SCRATCH+4(%sp),%d0
|
||||||
|
move.b %d0,(%a4)+
|
||||||
|
move.b (%a5),%d0
|
||||||
|
and.b %d1,%d0
|
||||||
|
or.b L_SCRATCH+6(%sp),%d0
|
||||||
|
move.b %d0,(%a5)+
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro ST_FIRST_NEXT
|
||||||
|
adda.w L_FIRSTSTEP(%sp),%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_FIRST_NEXT
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Row starts and ends. The pointers stay in registers from row to row: the
|
||||||
|
| source (a0) steps on by a6, srcStride less the bytes a row's columns
|
||||||
|
| read. The ST starts each row's a2 at a3 and steps a3 on by a4 (the row
|
||||||
|
| stride); separate planes step each plane on by the stride less the
|
||||||
|
| columns, kept in d6's high word.
|
||||||
|
.macro ST_ROW
|
||||||
|
movea.l %a3,%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro ST_ROW_END
|
||||||
|
adda.l %a4,%a3
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_ROW
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_ROW_END
|
||||||
|
swap %d6
|
||||||
|
adda.w %d6,%a2
|
||||||
|
adda.w %d6,%a3
|
||||||
|
adda.w %d6,%a4
|
||||||
|
adda.w %d6,%a5
|
||||||
|
swap %d6
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Every row: a first column cut by the rectangle, masked; the middle (a
|
||||||
|
| head column, the pairs, a tail column); a last column cut by it, masked;
|
||||||
|
| then the next row.
|
||||||
|
.macro ROWS layout, mode
|
||||||
|
1:
|
||||||
|
\layout\()_ROW
|
||||||
|
tst.b L_FIRST(%sp)
|
||||||
|
beq.s 7f
|
||||||
|
\mode\()_ONE %d2
|
||||||
|
\layout\()_MASKED %d2, L_HEADMASK
|
||||||
|
\layout\()_FIRST_NEXT
|
||||||
|
7:
|
||||||
|
tst.b L_HEAD(%sp)
|
||||||
|
beq.s 2f
|
||||||
|
\mode\()_ONE %d2
|
||||||
|
\layout\()_HEAD %d2
|
||||||
|
2:
|
||||||
|
move.w L_PAIRS(%sp),%d4
|
||||||
|
bmi.w 4f
|
||||||
|
3:
|
||||||
|
\mode\()_PAIR %d2, %d3
|
||||||
|
\layout\()_PAIR %d2, %d3
|
||||||
|
dbra %d4,3b
|
||||||
|
4:
|
||||||
|
tst.b L_TAIL(%sp)
|
||||||
|
beq.s 5f
|
||||||
|
\mode\()_ONE %d2
|
||||||
|
\layout\()_TAIL %d2
|
||||||
|
5:
|
||||||
|
tst.b L_LAST(%sp)
|
||||||
|
beq.s 6f
|
||||||
|
\mode\()_ONE %d2
|
||||||
|
\layout\()_MASKED %d2, L_TAILMASK
|
||||||
|
6:
|
||||||
|
adda.l %a6,%a0
|
||||||
|
\layout\()_ROW_END
|
||||||
|
subq.w #1,%d6
|
||||||
|
bne.w 1b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| a6 = srcStride - columns * (1 << \shift): a row's source bytes read
|
||||||
|
| (4 a doubled column, 8 a single one) to the next row's first.
|
||||||
|
.macro SRC_SKIP shift
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w A_SRCSTRIDE(%sp),%d0
|
||||||
|
moveq #0,%d1
|
||||||
|
move.w A_COLUMNS(%sp),%d1
|
||||||
|
lsl.l #\shift,%d1
|
||||||
|
sub.l %d1,%d0
|
||||||
|
movea.l %d0,%a6
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| A mask argument's byte four times over, into a local.
|
||||||
|
.macro REPLICATE arg, local
|
||||||
|
moveq #0,%d0
|
||||||
|
move.b \arg(%sp),%d0
|
||||||
|
move.w %d0,%d1
|
||||||
|
lsl.w #8,%d1
|
||||||
|
or.w %d1,%d0
|
||||||
|
move.w %d0,%d1
|
||||||
|
swap %d0
|
||||||
|
move.w %d1,%d0
|
||||||
|
move.l %d0,\local(%sp)
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| The shared setup: locals, the source, the table, the nibble mask, the
|
||||||
|
| rows, the masks, and the split of the columns: a first one the rectangle
|
||||||
|
| cuts (its mask not 0xFF), a last one it cuts (of two or more), and the
|
||||||
|
| middle -- the rest: a head column when it starts odd (dstOff's parity,
|
||||||
|
| flipped after a cut first column), the pairs, and a tail column.
|
||||||
|
.macro SETUP
|
||||||
|
movem.l %d2-%d7/%a2-%a6,-(%sp)
|
||||||
|
lea -LOCALS(%sp),%sp
|
||||||
|
movea.l A_SRC(%sp),%a0
|
||||||
|
movea.l A_TABLE(%sp),%a1
|
||||||
|
move.l #NIBBLES,%d5
|
||||||
|
move.w A_ROWS(%sp),%d6
|
||||||
|
REPLICATE A_HEADMASK, L_HEADMASK
|
||||||
|
REPLICATE A_TAILMASK, L_TAILMASK
|
||||||
|
move.w A_DSTOFF(%sp),%d0
|
||||||
|
andi.w #1,%d0 | the first column odd
|
||||||
|
move.w #ST_EVEN_NEXT,L_FIRSTSTEP(%sp)
|
||||||
|
tst.w %d0
|
||||||
|
beq.s 1f
|
||||||
|
move.w #ST_ODD_NEXT,L_FIRSTSTEP(%sp)
|
||||||
|
1:
|
||||||
|
move.w A_COLUMNS(%sp),%d1
|
||||||
|
clr.b L_FIRST(%sp)
|
||||||
|
cmpi.b #0xFF,A_HEADMASK(%sp)
|
||||||
|
beq.s 2f
|
||||||
|
st L_FIRST(%sp)
|
||||||
|
subq.w #1,%d1
|
||||||
|
eori.w #1,%d0 | the middle starts after it
|
||||||
|
2:
|
||||||
|
clr.b L_LAST(%sp)
|
||||||
|
cmpi.w #2,A_COLUMNS(%sp)
|
||||||
|
bcs.s 3f
|
||||||
|
cmpi.b #0xFF,A_TAILMASK(%sp)
|
||||||
|
beq.s 3f
|
||||||
|
st L_LAST(%sp)
|
||||||
|
subq.w #1,%d1
|
||||||
|
3:
|
||||||
|
clr.b L_HEAD(%sp)
|
||||||
|
clr.b L_TAIL(%sp)
|
||||||
|
move.w #-1,L_PAIRS(%sp)
|
||||||
|
tst.w %d1 | the middle's columns
|
||||||
|
beq.s 4f
|
||||||
|
move.b %d0,L_HEAD(%sp) | the middle starts odd
|
||||||
|
sub.w %d0,%d1
|
||||||
|
move.w %d1,%d0
|
||||||
|
andi.w #1,%d0
|
||||||
|
move.b %d0,L_TAIL(%sp)
|
||||||
|
lsr.w #1,%d1
|
||||||
|
subq.w #1,%d1
|
||||||
|
move.w %d1,L_PAIRS(%sp)
|
||||||
|
4:
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro FINISH
|
||||||
|
lea LOCALS(%sp),%sp
|
||||||
|
movem.l (%sp)+,%d2-%d7/%a2-%a6
|
||||||
|
rts
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| The mode's rows: a6 for its source bytes, then the rows. (Label 9: ROWS
|
||||||
|
| has 1 to 7.)
|
||||||
|
.macro MODES layout
|
||||||
|
tst.b A_DOUBLE(%sp)
|
||||||
|
beq.w 9f
|
||||||
|
SRC_SKIP 2
|
||||||
|
ROWS \layout, DOUBLE
|
||||||
|
FINISH
|
||||||
|
9:
|
||||||
|
SRC_SKIP 3
|
||||||
|
ROWS \layout, SINGLE
|
||||||
|
FINISH
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
.globl jlPlanarPasteColumnsInterleaved
|
||||||
|
.globl _jlPlanarPasteColumnsInterleaved
|
||||||
|
.globl jlPlanarPasteColumnsSeparate
|
||||||
|
.globl _jlPlanarPasteColumnsSeparate
|
||||||
|
|
||||||
|
jlPlanarPasteColumnsInterleaved:
|
||||||
|
_jlPlanarPasteColumnsInterleaved:
|
||||||
|
SETUP
|
||||||
|
movea.l A_DST(%sp),%a3
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w A_DSTOFF(%sp),%d0
|
||||||
|
adda.l %d0,%a3
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w A_DSTSTRIDE(%sp),%d0
|
||||||
|
movea.l %d0,%a4
|
||||||
|
MODES ST
|
||||||
|
|
||||||
|
|
||||||
|
jlPlanarPasteColumnsSeparate:
|
||||||
|
_jlPlanarPasteColumnsSeparate:
|
||||||
|
SETUP
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w A_DSTOFF(%sp),%d0
|
||||||
|
movea.l A_DST(%sp),%a5
|
||||||
|
movea.l (%a5)+,%a2
|
||||||
|
adda.l %d0,%a2
|
||||||
|
movea.l (%a5)+,%a3
|
||||||
|
adda.l %d0,%a3
|
||||||
|
movea.l (%a5)+,%a4
|
||||||
|
adda.l %d0,%a4
|
||||||
|
movea.l (%a5),%a5
|
||||||
|
adda.l %d0,%a5
|
||||||
|
move.w A_DSTSTRIDE(%sp),%d0
|
||||||
|
sub.w A_COLUMNS(%sp),%d0 | the planes' skip
|
||||||
|
swap %d6
|
||||||
|
move.w %d0,%d6
|
||||||
|
swap %d6
|
||||||
|
MODES SEP
|
||||||
|
|
||||||
|
|
||||||
|
| ----- Snap -----
|
||||||
|
|
||||||
|
| One plane byte's lookup into d0, the first (\first: move) or or'd onto
|
||||||
|
| d0 shifted left one.
|
||||||
|
.macro SPREAD src, first
|
||||||
|
move.b \src,%d1
|
||||||
|
add.w %d1,%d1
|
||||||
|
add.w %d1,%d1
|
||||||
|
and.w %d7,%d1
|
||||||
|
.ifc \first,1
|
||||||
|
move.l 0(%a1,%d1.w),%d0
|
||||||
|
.else
|
||||||
|
add.l %d0,%d0
|
||||||
|
or.l 0(%a1,%d1.w),%d0
|
||||||
|
.endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| A column's chunky long from its plane bytes, plane 3 first: on the ST at
|
||||||
|
| \at + 6, 4, 2, 0 from a2, separate from each plane's cursor.
|
||||||
|
.macro ST_DECODE at
|
||||||
|
SPREAD 6+\at(%a2), 1
|
||||||
|
SPREAD 4+\at(%a2), 0
|
||||||
|
SPREAD 2+\at(%a2), 0
|
||||||
|
SPREAD 0+\at(%a2), 0
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_DECODE
|
||||||
|
SPREAD (%a5)+, 1
|
||||||
|
SPREAD (%a4)+, 0
|
||||||
|
SPREAD (%a3)+, 0
|
||||||
|
SPREAD (%a2)+, 0
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| A column's bytes out. Single: the high nibbles to the even bytes and the
|
||||||
|
| low to the odd. Doubled: the wanted nibbles to 4 consecutive bytes (an
|
||||||
|
| even or odd destination: \odd).
|
||||||
|
.macro OUT_SINGLE odd
|
||||||
|
move.l %d0,%d2
|
||||||
|
lsr.l #NIBBLE_SHIFT,%d2
|
||||||
|
and.l %d5,%d2
|
||||||
|
and.l %d5,%d0
|
||||||
|
movep.l %d2,0(%a0)
|
||||||
|
movep.l %d0,1(%a0)
|
||||||
|
addq.l #8,%a0
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro OUT_DOUBLE odd
|
||||||
|
lsr.l %d3,%d0
|
||||||
|
and.l %d5,%d0
|
||||||
|
.ifc \odd,0
|
||||||
|
move.l %d0,(%a0)+
|
||||||
|
.else
|
||||||
|
rol.l #8,%d0
|
||||||
|
move.b %d0,(%a0)+
|
||||||
|
swap %d0
|
||||||
|
move.w %d0,(%a0)+
|
||||||
|
swap %d0
|
||||||
|
lsr.l #8,%d0
|
||||||
|
move.b %d0,(%a0)+
|
||||||
|
.endif
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Row starts. ST: a2 = base + row offset; separate: each plane's.
|
||||||
|
.macro ST_SROW
|
||||||
|
movea.l S_SRC(%sp),%a2
|
||||||
|
adda.l S_ROWSRC(%sp),%a2
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_SROW
|
||||||
|
move.l S_ROWSRC(%sp),%d0
|
||||||
|
movea.l S_PLANES(%sp),%a2
|
||||||
|
adda.l %d0,%a2
|
||||||
|
movea.l S_PLANES+4(%sp),%a3
|
||||||
|
adda.l %d0,%a3
|
||||||
|
movea.l S_PLANES+8(%sp),%a4
|
||||||
|
adda.l %d0,%a4
|
||||||
|
movea.l S_PLANES+12(%sp),%a5
|
||||||
|
adda.l %d0,%a5
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| One row's columns. The ST walks a head column, pairs, and a tail (its
|
||||||
|
| columns alternate 1 and 7 apart); separate planes step every cursor one.
|
||||||
|
.macro ST_SCOLUMNS out, odd
|
||||||
|
tst.b S_HEAD(%sp)
|
||||||
|
beq.s 2f
|
||||||
|
ST_DECODE 0
|
||||||
|
\out \odd
|
||||||
|
addq.l #ST_ODD_NEXT,%a2
|
||||||
|
2:
|
||||||
|
move.w S_PAIRS(%sp),%d4
|
||||||
|
bmi.w 4f
|
||||||
|
3:
|
||||||
|
ST_DECODE 0
|
||||||
|
\out \odd
|
||||||
|
ST_DECODE 1
|
||||||
|
\out \odd
|
||||||
|
addq.l #ST_PAIR_NEXT,%a2
|
||||||
|
dbra %d4,3b
|
||||||
|
4:
|
||||||
|
tst.b S_TAIL(%sp)
|
||||||
|
beq.s 5f
|
||||||
|
ST_DECODE 0
|
||||||
|
\out \odd
|
||||||
|
5:
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SEP_SCOLUMNS out, odd
|
||||||
|
move.w S_COLUMNS(%sp),%d4
|
||||||
|
subq.w #1,%d4
|
||||||
|
3:
|
||||||
|
SEP_DECODE
|
||||||
|
\out \odd
|
||||||
|
dbra %d4,3b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
| Every row; a doubled snap picks the even- or odd-address column body by
|
||||||
|
| the row's destination.
|
||||||
|
.macro SROWS layout
|
||||||
|
1:
|
||||||
|
movea.l S_ROWDST(%sp),%a0
|
||||||
|
\layout\()_SROW
|
||||||
|
tst.b S_MODE(%sp)
|
||||||
|
bne.w 6f
|
||||||
|
\layout\()_SCOLUMNS OUT_SINGLE, 0
|
||||||
|
bra.w 8f
|
||||||
|
6:
|
||||||
|
move.l %a0,%d0
|
||||||
|
btst #0,%d0
|
||||||
|
bne.w 7f
|
||||||
|
\layout\()_SCOLUMNS OUT_DOUBLE, 0
|
||||||
|
bra.w 8f
|
||||||
|
7:
|
||||||
|
\layout\()_SCOLUMNS OUT_DOUBLE, 1
|
||||||
|
8:
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w S_SRCSTRIDE(%sp),%d0
|
||||||
|
add.l %d0,S_ROWSRC(%sp)
|
||||||
|
move.w S_DSTSTRIDE(%sp),%d0
|
||||||
|
add.l %d0,S_ROWDST(%sp)
|
||||||
|
subq.w #1,%d6
|
||||||
|
bne.w 1b
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
.macro SSETUP
|
||||||
|
movem.l %d2-%d7/%a2-%a6,-(%sp)
|
||||||
|
lea -S_LOCALS(%sp),%sp
|
||||||
|
moveq #0,%d0
|
||||||
|
move.w S_SRCOFF(%sp),%d0
|
||||||
|
move.l %d0,S_ROWSRC(%sp)
|
||||||
|
move.l S_DST(%sp),S_ROWDST(%sp)
|
||||||
|
movea.l S_SPREAD(%sp),%a1
|
||||||
|
move.l #NIBBLES,%d5
|
||||||
|
move.w #INDEX_MASK,%d7
|
||||||
|
move.w S_ROWS(%sp),%d6
|
||||||
|
moveq #0,%d3
|
||||||
|
cmpi.b #MODE_EVEN,S_MODE(%sp)
|
||||||
|
bne.s 1f
|
||||||
|
moveq #NIBBLE_SHIFT,%d3
|
||||||
|
1:
|
||||||
|
move.w S_COLUMNS(%sp),%d1
|
||||||
|
andi.w #1,%d0
|
||||||
|
move.b %d0,S_HEAD(%sp)
|
||||||
|
sub.w %d0,%d1
|
||||||
|
move.w %d1,%d0
|
||||||
|
andi.w #1,%d0
|
||||||
|
move.b %d0,S_TAIL(%sp)
|
||||||
|
lsr.w #1,%d1
|
||||||
|
subq.w #1,%d1
|
||||||
|
move.w %d1,S_PAIRS(%sp)
|
||||||
|
.endm
|
||||||
|
|
||||||
|
.macro SFINISH
|
||||||
|
lea S_LOCALS(%sp),%sp
|
||||||
|
movem.l (%sp)+,%d2-%d7/%a2-%a6
|
||||||
|
rts
|
||||||
|
.endm
|
||||||
|
|
||||||
|
|
||||||
|
.globl jlPlanarSnapColumnsInterleaved
|
||||||
|
.globl _jlPlanarSnapColumnsInterleaved
|
||||||
|
.globl jlPlanarSnapColumnsSeparate
|
||||||
|
.globl _jlPlanarSnapColumnsSeparate
|
||||||
|
|
||||||
|
jlPlanarSnapColumnsInterleaved:
|
||||||
|
_jlPlanarSnapColumnsInterleaved:
|
||||||
|
SSETUP
|
||||||
|
SROWS ST
|
||||||
|
SFINISH
|
||||||
|
|
||||||
|
|
||||||
|
jlPlanarSnapColumnsSeparate:
|
||||||
|
_jlPlanarSnapColumnsSeparate:
|
||||||
|
SSETUP
|
||||||
|
movea.l S_SRC(%sp),%a0
|
||||||
|
move.l (%a0)+,S_PLANES(%sp)
|
||||||
|
move.l (%a0)+,S_PLANES+4(%sp)
|
||||||
|
move.l (%a0)+,S_PLANES+8(%sp)
|
||||||
|
move.l (%a0),S_PLANES+12(%sp)
|
||||||
|
SROWS SEP
|
||||||
|
SFINISH
|
||||||
|
|
@ -22,7 +22,8 @@
|
||||||
#include "m68kIrq.h"
|
#include "m68kIrq.h"
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
||||||
// The sample side lives in audioPcm.c (libxmp-lite + MSM6258 ADPCM). It is
|
// The sample side lives in audioPcm.c (modPlayer.c and the SFX mixer,
|
||||||
|
// encoded to MSM6258 ADPCM). It is
|
||||||
// driven from this file's Init/Shutdown/FrameTick so there is one owner of the
|
// driven from this file's Init/Shutdown/FrameTick so there is one owner of the
|
||||||
// audio lifecycle, matching how the other ports are laid out.
|
// audio lifecycle, matching how the other ports are laid out.
|
||||||
bool jlpAudioInitPcm(void);
|
bool jlpAudioInitPcm(void);
|
||||||
|
|
|
||||||
|
|
@ -381,7 +381,7 @@ uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||||
const X68kPlanarT *pd;
|
const X68kPlanarT *pd;
|
||||||
|
|
||||||
pd = (const X68kPlanarT *)s->portData;
|
pd = (const X68kPlanarT *)s->portData;
|
||||||
return jlPlanarSurfaceHash(s, (const uint8_t *const *)pd->planes, X68K_BYTES_PER_ROW, PLANAR_HASH_SEPARATE_PAIR_STEP);
|
return jlPlanarSurfaceHash(s, (const uint8_t *const *)pd->planes, X68K_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -404,6 +404,12 @@ bool jlpSurfaceLoadFile(jlSurfaceT *dst, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar paste (src/m68k/planarIndexed.c), over separate planes.
|
||||||
|
void jlpSurfacePasteIndexed(jlSurfaceT *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *src, uint16_t srcStride, bool doubleWide) {
|
||||||
|
jlPlanarPasteIndexed(((X68kPlanarT *)dst->portData)->planes, X68K_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP, x, y, w, h, src, srcStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
X68kPlanarT *pd;
|
X68kPlanarT *pd;
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
|
|
@ -421,6 +427,12 @@ bool jlpSurfaceSaveFile(const jlSurfaceT *src, FILE *fp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shared planar snap (src/m68k/planarIndexed.c), over separate planes.
|
||||||
|
void jlpSurfaceSnapIndexed(const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *dst, uint16_t dstStride, bool doubleWide) {
|
||||||
|
jlPlanarSnapIndexed((const uint8_t *const *)((const X68kPlanarT *)src->portData)->planes, X68K_BYTES_PER_ROW, PLANAR_SEPARATE_PAIR_STEP, x, y, w, h, dst, dstStride, doubleWide);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t *jlpStageAllocPixels(void) {
|
uint8_t *jlpStageAllocPixels(void) {
|
||||||
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
||||||
* stays NULL; cross-platform code reads pixels via jlpSamplePixel
|
* stays NULL; cross-platform code reads pixels via jlpSamplePixel
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,10 @@ UBER: jlTileMapPaste 10x5: 49 iters / 16 frames = 153 ops/sec | hash=52B9646B
|
||||||
UBER: gameFrameClean composite: 11 iters / 16 frames = 34 ops/sec | hash=C51FE171
|
UBER: gameFrameClean composite: 11 iters / 16 frames = 34 ops/sec | hash=C51FE171
|
||||||
UBER: jlTileLayerDraw scroll: 22 iters / 132 frames = 8 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll: 22 iters / 132 frames = 8 ops/sec | hash=2DDFC909
|
||||||
UBER: jlTileLayerDraw scroll + present: 18 iters / 131 frames = 6 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll + present: 18 iters / 131 frames = 6 ops/sec | hash=2DDFC909
|
||||||
|
UBER: jlSurfacePasteIndexed 160x168 wide: 2 iters / 17 frames = 5 ops/sec | hash=E5A3D565
|
||||||
|
UBER: jlSurfaceSnapIndexed 160x168 wide: 2 iters / 28 frames = 3 ops/sec | hash=E5A3D565
|
||||||
UBER-CHK: tileLayer moves: hash=59A121BE
|
UBER-CHK: tileLayer moves: hash=59A121BE
|
||||||
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
||||||
UBER-CHK: fillRectRows: hash=FA0501E4
|
UBER-CHK: fillRectRows: hash=FA0501E4
|
||||||
|
UBER-CHK: indexed: hash=9956D67C
|
||||||
|
UBER-CHK: indexed sweep: hash=00000000
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,10 @@ UBER: jlTileMapPaste 10x5: 49 iters / 18 frames = 163 ops/sec | hash=52B9646B
|
||||||
UBER: gameFrameClean composite: 13 iters / 16 frames = 48 ops/sec | hash=C51FE171
|
UBER: gameFrameClean composite: 13 iters / 16 frames = 48 ops/sec | hash=C51FE171
|
||||||
UBER: jlTileLayerDraw scroll: 23 iters / 132 frames = 10 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll: 23 iters / 132 frames = 10 ops/sec | hash=2DDFC909
|
||||||
UBER: jlTileLayerDraw scroll + present: 18 iters / 131 frames = 8 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll + present: 18 iters / 131 frames = 8 ops/sec | hash=2DDFC909
|
||||||
|
UBER: jlSurfacePasteIndexed 160x168 wide: 3 iters / 23 frames = 7 ops/sec | hash=E5A3D565
|
||||||
|
UBER: jlSurfaceSnapIndexed 160x168 wide: 2 iters / 30 frames = 3 ops/sec | hash=E5A3D565
|
||||||
UBER-CHK: tileLayer moves: hash=59A121BE
|
UBER-CHK: tileLayer moves: hash=59A121BE
|
||||||
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
||||||
UBER-CHK: fillRectRows: hash=FA0501E4
|
UBER-CHK: fillRectRows: hash=FA0501E4
|
||||||
|
UBER-CHK: indexed: hash=9956D67C
|
||||||
|
UBER-CHK: indexed sweep: hash=00000000
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,10 @@ UBER: jlTileMapPaste 10x5: 113 iters / 18 frames = 450 ops/sec | hash=52B9646B
|
||||||
UBER: gameFrameClean composite: 43 iters / 16 frames = 195 ops/sec | hash=C51FE171
|
UBER: gameFrameClean composite: 43 iters / 16 frames = 195 ops/sec | hash=C51FE171
|
||||||
UBER: jlTileLayerDraw scroll: 93 iters / 128 frames = 50 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll: 93 iters / 128 frames = 50 ops/sec | hash=2DDFC909
|
||||||
UBER: jlTileLayerDraw scroll + present: 13 iters / 135 frames = 6 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll + present: 13 iters / 135 frames = 6 ops/sec | hash=2DDFC909
|
||||||
|
UBER: jlSurfacePasteIndexed 160x168 wide: 6 iters / 17 frames = 25 ops/sec | hash=E5A3D565
|
||||||
|
UBER: jlSurfaceSnapIndexed 160x168 wide: 6 iters / 17 frames = 24 ops/sec | hash=E5A3D565
|
||||||
UBER-CHK: tileLayer moves: hash=59A121BE
|
UBER-CHK: tileLayer moves: hash=59A121BE
|
||||||
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
||||||
UBER-CHK: fillRectRows: hash=FA0501E4
|
UBER-CHK: fillRectRows: hash=FA0501E4
|
||||||
|
UBER-CHK: indexed: hash=9956D67C
|
||||||
|
UBER-CHK: indexed sweep: hash=00000000
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,10 @@ UBER: jlTileMapPaste 10x5: 15 iters / 16 frames = 56 ops/sec | hash=52B9646B
|
||||||
UBER: gameFrameClean composite: 5 iters / 16 frames = 18 ops/sec | hash=C51FE171
|
UBER: gameFrameClean composite: 5 iters / 16 frames = 18 ops/sec | hash=C51FE171
|
||||||
UBER: jlTileLayerDraw scroll: 20 iters / 128 frames = 9 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll: 20 iters / 128 frames = 9 ops/sec | hash=2DDFC909
|
||||||
UBER: jlTileLayerDraw scroll + present: 10 iters / 128 frames = 4 ops/sec | hash=2DDFC909
|
UBER: jlTileLayerDraw scroll + present: 10 iters / 128 frames = 4 ops/sec | hash=2DDFC909
|
||||||
|
UBER: jlSurfacePasteIndexed 160x168 wide: 2 iters / 23 frames = 5 ops/sec | hash=E5A3D565
|
||||||
|
UBER: jlSurfaceSnapIndexed 160x168 wide: 2 iters / 22 frames = 5 ops/sec | hash=E5A3D565
|
||||||
UBER-CHK: tileLayer moves: hash=59A121BE
|
UBER-CHK: tileLayer moves: hash=59A121BE
|
||||||
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
UBER-CHK: tilePasteGlyph: hash=CA66C63E
|
||||||
UBER-CHK: fillRectRows: hash=FA0501E4
|
UBER-CHK: fillRectRows: hash=FA0501E4
|
||||||
|
UBER-CHK: indexed: hash=9956D67C
|
||||||
|
UBER-CHK: indexed sweep: hash=00000000
|
||||||
|
|
|
||||||
511
tests/host/indexedHost.c
Normal file
511
tests/host/indexedHost.c
Normal file
|
|
@ -0,0 +1,511 @@
|
||||||
|
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
||||||
|
// See LICENSE.md for license information.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
//
|
||||||
|
// indexedHost.c - Host-cc harness for jlSurfacePasteIndexed and
|
||||||
|
// jlSurfaceSnapIndexed (src/core/surface.c clipping, the port's rectangle
|
||||||
|
// writer and reader behind it). Each case is checked against a reference
|
||||||
|
// built one pixel at a time with jlDrawPixel and jlSamplePixel:
|
||||||
|
// paste the surface after a paste equals a copy of the same background
|
||||||
|
// with every on-surface pixel of the rectangle plotted by hand
|
||||||
|
// (high nibbles in the source ignored, doubleWide pairs, a
|
||||||
|
// doubleWide byte cut by the left or right edge showing its one
|
||||||
|
// on-surface pixel)
|
||||||
|
// snap every byte of the rectangle equals jlSamplePixel of its column
|
||||||
|
// (0 off the surface), and the bytes between w and the stride are
|
||||||
|
// left alone
|
||||||
|
// Edge cases (odd columns, both edges, rows past both ends, w 65535, the
|
||||||
|
// int16_t limits) run first, then seeded random rectangles.
|
||||||
|
//
|
||||||
|
// The planar ports' paste and snap (src/m68k/planarIndexed.c, plain C) run
|
||||||
|
// here too, on host buffers in both of their layouts -- four separate
|
||||||
|
// planes (Amiga, X68000) and the ST's interleaved plane words -- given
|
||||||
|
// on-surface rectangles as the core hands them over, and checked against
|
||||||
|
// the same kind of pixel-by-pixel reference.
|
||||||
|
//
|
||||||
|
// Built by scripts/check-indexed.sh with -DJOEYLIB_PLATFORM_BLANK.
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "joey/joey.h"
|
||||||
|
#include "surfaceInternal.h"
|
||||||
|
|
||||||
|
#define SRC_BYTES 81920u
|
||||||
|
#define RANDOM_CASES 600u
|
||||||
|
#define RANDOM_SEED 0x1D2C3B4Au
|
||||||
|
#define RANDOM_X_SPAN 700
|
||||||
|
#define RANDOM_X_LOW (-360)
|
||||||
|
#define RANDOM_Y_SPAN 440
|
||||||
|
#define RANDOM_Y_LOW (-220)
|
||||||
|
#define RANDOM_W_MAX 200u
|
||||||
|
#define RANDOM_H_MAX 220u
|
||||||
|
#define RANDOM_PAD_MAX 5u
|
||||||
|
#define CANARY 0xA5u
|
||||||
|
#define BACK_X_MUL 7
|
||||||
|
#define BACK_Y_MUL 3
|
||||||
|
#define PLANAR_PLANES 4u
|
||||||
|
#define PLANAR_ROW_BYTES (JL_SURFACE_WIDTH / 8)
|
||||||
|
#define PLANAR_CASES 500u
|
||||||
|
#define PLANAR_H_MAX 24u
|
||||||
|
#define ST_ROW_BYTES 160u
|
||||||
|
#define ST_GROUP_BYTES 8u
|
||||||
|
|
||||||
|
typedef struct LayoutT {
|
||||||
|
const char *name;
|
||||||
|
uint8_t *planes[PLANAR_PLANES];
|
||||||
|
uint16_t rowBytes;
|
||||||
|
uint16_t pairStep;
|
||||||
|
} LayoutT;
|
||||||
|
|
||||||
|
typedef struct CaseT {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t w;
|
||||||
|
uint16_t h;
|
||||||
|
uint16_t pad;
|
||||||
|
bool doubleWide;
|
||||||
|
} CaseT;
|
||||||
|
|
||||||
|
static const CaseT kEdgeCases[] = {
|
||||||
|
{ 0, 0, 160u, 200u, 0u, true },
|
||||||
|
{ 0, 0, 320u, 200u, 0u, false },
|
||||||
|
{ 1, 3, 17u, 5u, 2u, false },
|
||||||
|
{ 1, 3, 16u, 5u, 0u, false },
|
||||||
|
{ 1, 3, 17u, 5u, 1u, true },
|
||||||
|
{ -1, 10, 20u, 4u, 0u, true },
|
||||||
|
{ -1, 10, 20u, 4u, 3u, false },
|
||||||
|
{ -2, 10, 20u, 4u, 0u, true },
|
||||||
|
{ -3, 10, 2u, 4u, 0u, true },
|
||||||
|
{ -3, 10, 1u, 4u, 0u, true },
|
||||||
|
{ 319, 50, 8u, 3u, 0u, true },
|
||||||
|
{ 318, 50, 8u, 3u, 0u, true },
|
||||||
|
{ 317, 50, 8u, 3u, 0u, true },
|
||||||
|
{ 319, 50, 8u, 3u, 0u, false },
|
||||||
|
{ 320, 50, 8u, 3u, 0u, true },
|
||||||
|
{ -9, -5, 170u, 210u, 4u, true },
|
||||||
|
{ -9, -5, 340u, 210u, 4u, false },
|
||||||
|
{ 40, 198, 10u, 10u, 0u, false },
|
||||||
|
{ 40, -10, 10u, 10u, 0u, true },
|
||||||
|
{ 40, -11, 10u, 10u, 0u, true },
|
||||||
|
{ 40, 200, 10u, 10u, 0u, true },
|
||||||
|
{ 5, 5, 0u, 10u, 2u, true },
|
||||||
|
{ 5, 5, 10u, 0u, 0u, true },
|
||||||
|
{ -301, 7, 65535u, 1u, 0u, true },
|
||||||
|
{ -301, 7, 65535u, 1u, 0u, false },
|
||||||
|
{-32768, -32768, 9u, 2u, 0u, true },
|
||||||
|
{ 32767, 32767, 9u, 2u, 0u, false }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EDGE_CASE_COUNT ((uint16_t)(sizeof(kEdgeCases) / sizeof(kEdgeCases[0])))
|
||||||
|
|
||||||
|
static uint8_t gSrc[SRC_BYTES];
|
||||||
|
static uint8_t gSnap[SRC_BYTES];
|
||||||
|
static uint32_t gRandom = RANDOM_SEED;
|
||||||
|
static uint8_t gSeparate[PLANAR_PLANES][PLANAR_ROW_BYTES * JL_SURFACE_HEIGHT];
|
||||||
|
static uint8_t gInterleaved[ST_ROW_BYTES * JL_SURFACE_HEIGHT];
|
||||||
|
static uint8_t gPlanarBack[PLANAR_PLANES][ST_ROW_BYTES * JL_SURFACE_HEIGHT];
|
||||||
|
static uint8_t gRef[JL_SURFACE_HEIGHT][JL_SURFACE_WIDTH];
|
||||||
|
|
||||||
|
static bool check(const char *name, bool ok, const char *detail);
|
||||||
|
static bool comparePaste(const jlSurfaceT *got, const jlSurfaceT *want, const CaseT *c, char *detail, size_t size);
|
||||||
|
static bool compareSnap(const jlSurfaceT *back, const CaseT *c, char *detail, size_t size);
|
||||||
|
static void fillBackground(jlSurfaceT *s);
|
||||||
|
static uint32_t nextRandom(void);
|
||||||
|
static void pasteReference(jlSurfaceT *s, const CaseT *c, uint16_t stride);
|
||||||
|
static uint8_t planarGet(const LayoutT *l, uint16_t x, uint16_t y);
|
||||||
|
static uint8_t *planarByte(const LayoutT *l, uint16_t p, uint16_t x, uint16_t y);
|
||||||
|
static bool planarCase(LayoutT *l, const CaseT *c, char *detail, size_t size);
|
||||||
|
static void planarSet(const LayoutT *l, uint16_t x, uint16_t y, uint8_t color);
|
||||||
|
static void planarStore(const LayoutT *l, bool save);
|
||||||
|
static bool runCase(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, const CaseT *c, char *detail, size_t size);
|
||||||
|
static bool runCases(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, bool random);
|
||||||
|
static bool runPlanar(LayoutT *l);
|
||||||
|
static uint8_t sampleWide(const jlSurfaceT *s, int32_t x, int32_t y);
|
||||||
|
|
||||||
|
|
||||||
|
static bool check(const char *name, bool ok, const char *detail) {
|
||||||
|
printf(" %s: %s%s%s\n", name, ok ? "PASS" : "FAIL", detail[0] != '\0' ? " -- " : "", detail);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool comparePaste(const jlSurfaceT *got, const jlSurfaceT *want, const CaseT *c, char *detail, size_t size) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint8_t a;
|
||||||
|
uint8_t b;
|
||||||
|
|
||||||
|
if (jlSurfaceHash(got) == jlSurfaceHash(want)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
|
a = jlSamplePixel(got, x, y);
|
||||||
|
b = jlSamplePixel(want, x, y);
|
||||||
|
if (a != b) {
|
||||||
|
snprintf(detail, size, "paste at (%d,%d) w %u h %u%s: pixel (%d,%d) is %u, want %u", c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", x, y, a, b);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
snprintf(detail, size, "paste at (%d,%d) w %u h %u: hashes differ, pixels equal", c->x, c->y, c->w, c->h);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool compareSnap(const jlSurfaceT *back, const CaseT *c, char *detail, size_t size) {
|
||||||
|
const uint8_t *row;
|
||||||
|
uint32_t stride;
|
||||||
|
uint32_t r;
|
||||||
|
uint32_t i;
|
||||||
|
uint8_t want;
|
||||||
|
|
||||||
|
stride = (uint32_t)c->w + c->pad;
|
||||||
|
for (r = 0; r < c->h; r++) {
|
||||||
|
row = gSnap + r * stride;
|
||||||
|
for (i = 0; i < c->w; i++) {
|
||||||
|
want = sampleWide(back, (int32_t)c->x + (int32_t)(i << (c->doubleWide ? 1u : 0u)), (int32_t)c->y + (int32_t)r);
|
||||||
|
if (row[i] != want) {
|
||||||
|
snprintf(detail, size, "snap at (%d,%d) w %u h %u%s: byte %lu of row %lu is %u, want %u", c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", (unsigned long)i, (unsigned long)r, row[i], want);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < stride; i++) {
|
||||||
|
if (row[i] != CANARY) {
|
||||||
|
snprintf(detail, size, "snap at (%d,%d) w %u h %u: wrote past w at byte %lu of row %lu", c->x, c->y, c->w, c->h, (unsigned long)i, (unsigned long)r);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void fillBackground(jlSurfaceT *s) {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
|
jlDrawPixel(s, x, y, (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t nextRandom(void) {
|
||||||
|
gRandom ^= gRandom << 13;
|
||||||
|
gRandom ^= gRandom >> 17;
|
||||||
|
gRandom ^= gRandom << 5;
|
||||||
|
return gRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void pasteReference(jlSurfaceT *s, const CaseT *c, uint16_t stride) {
|
||||||
|
uint32_t r;
|
||||||
|
uint32_t i;
|
||||||
|
uint32_t k;
|
||||||
|
uint32_t span;
|
||||||
|
int32_t px;
|
||||||
|
int32_t py;
|
||||||
|
uint8_t color;
|
||||||
|
|
||||||
|
span = c->doubleWide ? 2u : 1u;
|
||||||
|
for (r = 0; r < c->h; r++) {
|
||||||
|
py = (int32_t)c->y + (int32_t)r;
|
||||||
|
if (py < 0 || py >= JL_SURFACE_HEIGHT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (i = 0; i < c->w; i++) {
|
||||||
|
color = (uint8_t)(gSrc[r * stride + i] & 0x0Fu);
|
||||||
|
for (k = 0; k < span; k++) {
|
||||||
|
px = (int32_t)c->x + (int32_t)(i * span + k);
|
||||||
|
if (px >= 0 && px < JL_SURFACE_WIDTH) {
|
||||||
|
jlDrawPixel(s, (int16_t)px, (int16_t)py, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t planarGet(const LayoutT *l, uint16_t x, uint16_t y) {
|
||||||
|
uint16_t p;
|
||||||
|
uint8_t bit;
|
||||||
|
uint8_t color;
|
||||||
|
|
||||||
|
bit = (uint8_t)(0x80u >> (x & 7u));
|
||||||
|
color = 0u;
|
||||||
|
for (p = 0; p < PLANAR_PLANES; p++) {
|
||||||
|
if ((*planarByte(l, p, x, y) & bit) != 0u) {
|
||||||
|
color = (uint8_t)(color | (1u << p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t *planarByte(const LayoutT *l, uint16_t p, uint16_t x, uint16_t y) {
|
||||||
|
uint16_t c;
|
||||||
|
|
||||||
|
c = (uint16_t)(x >> 3);
|
||||||
|
return l->planes[p] + (uint32_t)y * l->rowBytes + (uint32_t)(c >> 1) * l->pairStep + (c & 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One on-surface rectangle: pasted on the planes and, a pixel at a time, on
|
||||||
|
// gRef, then compared everywhere; snapped back from the planes and compared
|
||||||
|
// with gRef's pixels at the rectangle's columns.
|
||||||
|
static bool planarCase(LayoutT *l, const CaseT *c, char *detail, size_t size) {
|
||||||
|
uint32_t bytes;
|
||||||
|
uint32_t i;
|
||||||
|
uint16_t stride;
|
||||||
|
uint16_t r;
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
uint16_t span;
|
||||||
|
uint16_t k;
|
||||||
|
uint8_t got;
|
||||||
|
|
||||||
|
stride = (uint16_t)(c->w + c->pad);
|
||||||
|
bytes = (uint32_t)stride * c->h;
|
||||||
|
span = c->doubleWide ? 2u : 1u;
|
||||||
|
for (i = 0; i < bytes; i++) {
|
||||||
|
gSrc[i] = (uint8_t)nextRandom();
|
||||||
|
}
|
||||||
|
planarStore(l, false);
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
|
gRef[y][x] = (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jlPlanarPasteIndexed(l->planes, l->rowBytes, l->pairStep, (uint16_t)c->x, (uint16_t)c->y, c->w, c->h, gSrc, stride, c->doubleWide);
|
||||||
|
for (r = 0; r < c->h; r++) {
|
||||||
|
for (i = 0; i < c->w; i++) {
|
||||||
|
for (k = 0; k < span; k++) {
|
||||||
|
gRef[c->y + r][c->x + i * span + k] = (uint8_t)(gSrc[r * stride + i] & 0x0Fu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
|
got = planarGet(l, x, y);
|
||||||
|
if (got != gRef[y][x]) {
|
||||||
|
snprintf(detail, size, "%s paste at (%d,%d) w %u h %u%s: pixel (%u,%u) is %u, want %u", l->name, c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", x, y, got, gRef[y][x]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(gSnap, CANARY, bytes);
|
||||||
|
jlPlanarSnapIndexed((const uint8_t *const *)l->planes, l->rowBytes, l->pairStep, (uint16_t)c->x, (uint16_t)c->y, c->w, c->h, gSnap, stride, c->doubleWide);
|
||||||
|
for (r = 0; r < c->h; r++) {
|
||||||
|
for (i = 0; i < stride; i++) {
|
||||||
|
got = gSnap[r * stride + i];
|
||||||
|
if (i < c->w ? got != gRef[c->y + r][c->x + i * span] : got != CANARY) {
|
||||||
|
snprintf(detail, size, "%s snap at (%d,%d) w %u h %u%s: byte %lu of row %u is %u", l->name, c->x, c->y, c->w, c->h, c->doubleWide ? " doubleWide" : "", (unsigned long)i, r, got);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void planarSet(const LayoutT *l, uint16_t x, uint16_t y, uint8_t color) {
|
||||||
|
uint8_t *b;
|
||||||
|
uint16_t p;
|
||||||
|
uint8_t bit;
|
||||||
|
|
||||||
|
bit = (uint8_t)(0x80u >> (x & 7u));
|
||||||
|
for (p = 0; p < PLANAR_PLANES; p++) {
|
||||||
|
b = planarByte(l, p, x, y);
|
||||||
|
if (((color >> p) & 1u) != 0u) {
|
||||||
|
*b = (uint8_t)(*b | bit);
|
||||||
|
} else {
|
||||||
|
*b = (uint8_t)(*b & ~bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Save the layout's background to gPlanarBack, or put it back.
|
||||||
|
static void planarStore(const LayoutT *l, bool save) {
|
||||||
|
uint8_t *buffer;
|
||||||
|
size_t bytes;
|
||||||
|
uint16_t p;
|
||||||
|
|
||||||
|
if (l->pairStep == ST_GROUP_BYTES) {
|
||||||
|
buffer = gInterleaved;
|
||||||
|
bytes = sizeof(gInterleaved);
|
||||||
|
if (save) {
|
||||||
|
memcpy(gPlanarBack[0], buffer, bytes);
|
||||||
|
} else {
|
||||||
|
memcpy(buffer, gPlanarBack[0], bytes);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (p = 0; p < PLANAR_PLANES; p++) {
|
||||||
|
bytes = sizeof(gSeparate[p]);
|
||||||
|
if (save) {
|
||||||
|
memcpy(gPlanarBack[p], gSeparate[p], bytes);
|
||||||
|
} else {
|
||||||
|
memcpy(gSeparate[p], gPlanarBack[p], bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool runCase(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, const CaseT *c, char *detail, size_t size) {
|
||||||
|
uint32_t bytes;
|
||||||
|
uint32_t i;
|
||||||
|
uint16_t stride;
|
||||||
|
|
||||||
|
stride = (uint16_t)(c->w + c->pad);
|
||||||
|
bytes = (uint32_t)stride * c->h;
|
||||||
|
for (i = 0; i < bytes; i++) {
|
||||||
|
gSrc[i] = (uint8_t)nextRandom();
|
||||||
|
}
|
||||||
|
jlSurfaceCopy(stage, back);
|
||||||
|
jlSurfaceCopy(ref, back);
|
||||||
|
jlSurfacePasteIndexed(stage, c->x, c->y, c->w, c->h, gSrc, stride, c->doubleWide);
|
||||||
|
pasteReference(ref, c, stride);
|
||||||
|
if (!comparePaste(stage, ref, c, detail, size)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(gSnap, CANARY, bytes);
|
||||||
|
jlSurfaceSnapIndexed(back, c->x, c->y, c->w, c->h, gSnap, stride, c->doubleWide);
|
||||||
|
return compareSnap(back, c, detail, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool runCases(jlSurfaceT *stage, jlSurfaceT *ref, const jlSurfaceT *back, bool random) {
|
||||||
|
CaseT c;
|
||||||
|
char detail[200];
|
||||||
|
uint16_t count;
|
||||||
|
uint16_t n;
|
||||||
|
|
||||||
|
detail[0] = '\0';
|
||||||
|
count = random ? (uint16_t)RANDOM_CASES : EDGE_CASE_COUNT;
|
||||||
|
for (n = 0; n < count; n++) {
|
||||||
|
if (random) {
|
||||||
|
c.x = (int16_t)(RANDOM_X_LOW + (int32_t)(nextRandom() % RANDOM_X_SPAN));
|
||||||
|
c.y = (int16_t)(RANDOM_Y_LOW + (int32_t)(nextRandom() % RANDOM_Y_SPAN));
|
||||||
|
c.w = (uint16_t)(nextRandom() % (RANDOM_W_MAX + 1u));
|
||||||
|
c.h = (uint16_t)(nextRandom() % (RANDOM_H_MAX + 1u));
|
||||||
|
c.pad = (uint16_t)(nextRandom() % (RANDOM_PAD_MAX + 1u));
|
||||||
|
c.doubleWide = (nextRandom() & 1u) != 0u;
|
||||||
|
} else {
|
||||||
|
c = kEdgeCases[n];
|
||||||
|
}
|
||||||
|
if (!runCase(stage, ref, back, &c, detail, sizeof(detail))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == count) {
|
||||||
|
snprintf(detail, sizeof(detail), "%u rectangles", count);
|
||||||
|
}
|
||||||
|
return check(random ? "random rectangles" : "edge rectangles", n == count, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool runPlanar(LayoutT *l) {
|
||||||
|
CaseT c;
|
||||||
|
char detail[200];
|
||||||
|
char name[48];
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
uint16_t n;
|
||||||
|
uint16_t maxW;
|
||||||
|
|
||||||
|
detail[0] = '\0';
|
||||||
|
for (y = 0; y < JL_SURFACE_HEIGHT; y++) {
|
||||||
|
for (x = 0; x < JL_SURFACE_WIDTH; x++) {
|
||||||
|
planarSet(l, x, y, (uint8_t)((x * BACK_X_MUL + y * BACK_Y_MUL) & 0x0F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
planarStore(l, true);
|
||||||
|
for (n = 0; n < PLANAR_CASES; n++) {
|
||||||
|
c.doubleWide = (nextRandom() & 1u) != 0u;
|
||||||
|
c.x = (int16_t)(nextRandom() % JL_SURFACE_WIDTH);
|
||||||
|
if (c.doubleWide && c.x == JL_SURFACE_WIDTH - 1) {
|
||||||
|
c.x--;
|
||||||
|
}
|
||||||
|
maxW = (uint16_t)((JL_SURFACE_WIDTH - c.x) >> (c.doubleWide ? 1u : 0u));
|
||||||
|
// Mostly narrow (the edge columns), now and then the whole span left.
|
||||||
|
c.w = (uint16_t)(1u + nextRandom() % ((n & 3u) == 0u ? maxW : (maxW < 20u ? maxW : 20u)));
|
||||||
|
c.y = (int16_t)(nextRandom() % JL_SURFACE_HEIGHT);
|
||||||
|
c.h = (uint16_t)(1u + nextRandom() % PLANAR_H_MAX);
|
||||||
|
if (c.y + c.h > JL_SURFACE_HEIGHT) {
|
||||||
|
c.h = (uint16_t)(JL_SURFACE_HEIGHT - c.y);
|
||||||
|
}
|
||||||
|
c.pad = (uint16_t)(nextRandom() % (RANDOM_PAD_MAX + 1u));
|
||||||
|
if (!planarCase(l, &c, detail, sizeof(detail))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == PLANAR_CASES) {
|
||||||
|
snprintf(detail, sizeof(detail), "%u rectangles", n);
|
||||||
|
}
|
||||||
|
snprintf(name, sizeof(name), "planar %s", l->name);
|
||||||
|
return check(name, n == PLANAR_CASES, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t sampleWide(const jlSurfaceT *s, int32_t x, int32_t y) {
|
||||||
|
if (x < 0 || x >= JL_SURFACE_WIDTH || y < 0 || y >= JL_SURFACE_HEIGHT) {
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
return jlSamplePixel(s, (int16_t)x, (int16_t)y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
LayoutT separate;
|
||||||
|
LayoutT interleaved;
|
||||||
|
jlConfigT config;
|
||||||
|
jlSurfaceT *stage;
|
||||||
|
jlSurfaceT *ref;
|
||||||
|
jlSurfaceT *back;
|
||||||
|
uint16_t p;
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
config.codegenBytes = 0u;
|
||||||
|
config.noAudio = true;
|
||||||
|
if (!jlInit(&config)) {
|
||||||
|
printf("indexedHost: FAIL -- jlInit: %s\n", jlLastError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
stage = jlStageGet();
|
||||||
|
ref = jlSurfaceCreate();
|
||||||
|
back = jlSurfaceCreate();
|
||||||
|
if (ref == NULL || back == NULL) {
|
||||||
|
printf("indexedHost: FAIL -- jlSurfaceCreate: %s\n", jlLastError());
|
||||||
|
jlShutdown();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fillBackground(back);
|
||||||
|
printf("indexedHost: indexed paste and snap, chunky and planar, against pixel-by-pixel references\n");
|
||||||
|
ok = true;
|
||||||
|
ok &= runCases(stage, ref, back, false);
|
||||||
|
ok &= runCases(stage, ref, back, true);
|
||||||
|
separate.name = "separate planes";
|
||||||
|
separate.rowBytes = PLANAR_ROW_BYTES;
|
||||||
|
separate.pairStep = PLANAR_SEPARATE_PAIR_STEP;
|
||||||
|
interleaved.name = "interleaved planes";
|
||||||
|
interleaved.rowBytes = ST_ROW_BYTES;
|
||||||
|
interleaved.pairStep = ST_GROUP_BYTES;
|
||||||
|
for (p = 0; p < PLANAR_PLANES; p++) {
|
||||||
|
separate.planes[p] = gSeparate[p];
|
||||||
|
interleaved.planes[p] = gInterleaved + p * sizeof(uint16_t);
|
||||||
|
}
|
||||||
|
ok &= runPlanar(&separate);
|
||||||
|
ok &= runPlanar(&interleaved);
|
||||||
|
jlSurfaceDestroy(back);
|
||||||
|
jlSurfaceDestroy(ref);
|
||||||
|
jlShutdown();
|
||||||
|
printf("indexedHost: %s\n", ok ? "PASS" : "FAIL");
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue