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)
|
||||
|
||||
### 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.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ There are three layers, and on some platforms they share physical hardware:
|
|||
|
||||
* *Module playback* -- `jlAudioPlayMod`, four-channel Protracker `.MOD`.
|
||||
* *Chip tracker* -- `jlMusicPlay`, a portable event stream driving three tone
|
||||
voices plus noise at register-write cost, with no CPU mixing. The recommended
|
||||
music path.
|
||||
voices plus noise at register-write cost on the machines' sound chips (DVX
|
||||
and the hosted ports mix them in software). The recommended music path.
|
||||
* *Voices and effects* -- `jlAudioVoice`, `jlAudioNoise`, `jlAudioPlaySfx`, and
|
||||
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
|
||||
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
|
||||
sound library). DOS uses the OPL2
|
||||
rhythm-mode snare, leaving the three 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.
|
||||
sound library). DOS mixes the same generator into the Sound Blaster's
|
||||
stream, at about 22 kHz, so pitches 0 to 5 sound alike there; an AdLib with
|
||||
no Sound Blaster plays the OPL2 rhythm-mode snare instead, leaving the three
|
||||
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]
|
||||
====
|
||||
|
|
@ -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.
|
||||
One authored song plays on every port at register-write cost. Because there
|
||||
is no CPU mixing, this is fully viable on the ST and cheap on DOS, which
|
||||
`.MOD` playback is not.
|
||||
One authored song plays on every port, at register-write cost wherever a
|
||||
sound chip makes it (DVX and the hosted ports mix it in software, and DOS
|
||||
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
|
||||
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
|
||||
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;
|
||||
70 on VGA mode 13h and on DVX, whose window has a virtual 70 Hz (and whose full
|
||||
screen is mode 13h's, or a VBE mode's, with the same frame count); and 60 on
|
||||
every hosted port, whose window has a virtual 60 Hz. The X68000 times its
|
||||
cannot show PAL; 50 or 60 on the ST, as the machine is set (71 with a
|
||||
monochrome monitor); 55 on the X68000; 70 on DOS's VGA and on DVX, whose
|
||||
window has a virtual 70 Hz (and whose full screen is mode 13h's, or a VBE
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -139,6 +139,77 @@ Erase *all* regions, then draw *all* sprites. Never mix this with per-sprite
|
|||
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
|
||||
|
||||
[source,c]
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@
|
|||
|
||||
|`surface.h`
|
||||
|`jlSurfaceCreate`, `jlSurfaceDestroy`, `jlStageGet`, `jlSurfaceCopy`,
|
||||
`jlSurfaceCopyRect`, `jlSurfaceSaveFile`, `jlSurfaceLoadFile`,
|
||||
`jlSurfaceHash`
|
||||
`jlSurfaceCopyRect`, `jlSurfacePasteIndexed`, `jlSurfaceSnapIndexed`,
|
||||
`jlSurfaceSaveFile`, `jlSurfaceLoadFile`, `jlSurfaceHash`
|
||||
|
||||
|`present.h`
|
||||
|`jlStagePresent`
|
||||
|
|
@ -86,7 +86,8 @@
|
|||
|`tile.h`
|
||||
|`jlTileCopy`, `jlTileCopyMasked`, `jlTileFill`, `jlTileBankLoad`,
|
||||
`jlTileBankLoadToSurface`, `jlTileSnap`, `jlTilePaste`, `jlTilePasteMono`,
|
||||
`jlTilePasteGlyph`, `jlDrawText`, `jlTileMapPaste`
|
||||
`jlTilePasteGlyph`, `jlDrawText`, `jlTileMapPaste`, `jlTileLayerCreate`,
|
||||
`jlTileLayerDestroy`, `jlTileLayerDraw`, `jlTileLayerSetTile`
|
||||
|
||||
|`sprite.h`
|
||||
|`jlSpriteBankLoad`, `jlSpriteBankLoadPrecompiled`,
|
||||
|
|
|
|||
|
|
@ -243,8 +243,8 @@ one-time startup cost for a much smaller distribution.
|
|||
=== Music: the Portable Chip Tracker
|
||||
|
||||
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
|
||||
ST, where `.MOD` playback is only marginally viable.
|
||||
at register-write cost on the machines' sound chips -- which matters
|
||||
enormously on the ST, where `.MOD` playback is only marginally viable.
|
||||
|
||||
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]
|
||||
----
|
||||
$ 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 help
|
||||
----
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@ hardware:
|
|||
|
||||
|Chip tracker
|
||||
|`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
|
||||
|`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
|
||||
// NATIVE-PERF Phase 6 W0 probe rows. NOT part of the golden contract:
|
||||
// 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) {
|
||||
uberMbPhase(UBER_PHASE_CHECKS);
|
||||
jlLogF("UBER-CHK: ----- begin -----");
|
||||
|
|
@ -2068,6 +2251,8 @@ static void __attribute__((noinline)) runCorrectnessChecks(void) {
|
|||
checkTileLayer();
|
||||
checkTilePasteGlyph();
|
||||
checkFillRectRows();
|
||||
checkIndexed();
|
||||
checkIndexedSweep();
|
||||
// jlShutdown -> jlInit -> stale-sprite-destroy (#34) needs a teardown
|
||||
// of the whole library mid-run; it gets a dedicated micro-example in
|
||||
// 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)");
|
||||
}
|
||||
|
||||
// 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
|
||||
// The presents count from here (the IIgs and hosted ones only while
|
||||
// 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
|
||||
// with tone voice 2: author noise and voice-2 notes
|
||||
// apart (songbake warns on overlap).
|
||||
// DOS -- OPL2 rhythm-mode snare drum (the chip's LFSR noise
|
||||
// source) on channel 7; the three tone channels are
|
||||
// untouched. A tuned drum: its pitch spreads over the
|
||||
// snare's range instead of following the rate above.
|
||||
// DOS -- the YM2149's 17-bit generator, mixed into the Sound
|
||||
// Blaster's stream (~22 kHz, so pitches 0-5 sound
|
||||
// alike). An OPL2 without a Sound Blaster plays its
|
||||
// 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
|
||||
// (channels 0-2 are the tone voices).
|
||||
// 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
|
||||
// tools/songbake text notation) through the jlAudioVoice tone layer.
|
||||
// One authored song plays on every port at register-write cost --
|
||||
// there is no CPU mixing, so unlike jlAudioPlayMod this path is fully
|
||||
// viable on the ST and cheap on DOS. The stream is read IN PLACE:
|
||||
// One authored song plays on every port at register-write cost on the
|
||||
// retro machines' chips (DOS's noise and the DVX and hosted ports mix in
|
||||
// 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
|
||||
// pumped by jlAudioFrameTick (call it once per frame, as usual) and
|
||||
// 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
|
||||
// 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
|
||||
// under MAME), and 60 on every hosted port. The actual VBL cadence may
|
||||
// the ST as the machine is set (71 with a monochrome monitor), 70 on DOS
|
||||
// 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
|
||||
// by to convert iters-per-N-frames to ops/sec.
|
||||
uint16_t jlFrameHz(void);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
// The library forces color 0 of every palette to black; jlPaletteSet
|
||||
// silently masks that entry to $000 regardless of what the caller
|
||||
// passed, and jlSurfaceLoadFile does the same. Palette and SCB changes
|
||||
// to the stage reach the display at the next jlStagePresent. See
|
||||
// claudeDocs/DESIGN.md sections 5 and 10.
|
||||
// to the stage reach the display at the next jlStagePresent. See the
|
||||
// manual's "Palette and Scanline Control" (docs/ApiPalette.adoc).
|
||||
|
||||
#ifndef JOEYLIB_PALETTE_H
|
||||
#define JOEYLIB_PALETTE_H
|
||||
|
|
|
|||
|
|
@ -193,6 +193,8 @@
|
|||
#define JL_HAS_SURFACE_COPY_RECT // jlpSurfaceCopyRect: jlIigsCopyRectRows
|
||||
// [dp],y row copies (short rows lose to
|
||||
// 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_PUT // the generic rings, an asm put (iigsTileLayer.c)
|
||||
#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_SAMPLE_PIXEL // planarSamplePixel, inline (amigaTile.h)
|
||||
#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_SAVE_FILE // amiga planar save, 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_SAMPLE_PIXEL // stSamplePixel, inline (stTile.h)
|
||||
#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_SAVE_FILE // st planar save, 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_SAMPLE_PIXEL
|
||||
#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_SAVE_FILE
|
||||
#define JL_HAS_STAGE_ALLOC_PIXELS
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@
|
|||
// each row is 4 bytes (8 pixels at 4bpp packed; high nibble = left
|
||||
// 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.
|
||||
//
|
||||
// Performance contract: jlSpriteDraw should be at least as fast as the
|
||||
// IIgs reference (the DESIGN.md spec calls for runtime-compiled draw
|
||||
// code per CPU). The machine ports have runtime emitters (IIgs 65816,
|
||||
// IIgs reference (the design calls for runtime-compiled draw code per
|
||||
// CPU). The machine ports have runtime emitters (IIgs 65816,
|
||||
// Amiga/X68000 68k planar, ST 68k interleaved, DOS/DVX x86), and so does
|
||||
// Win16 (x86, as DOS);
|
||||
// jlSpriteCompile compiles the shift/op variants into the codegen arena
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
// does not see the storage format.
|
||||
//
|
||||
// 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
|
||||
#define JOEYLIB_SURFACE_H
|
||||
|
|
@ -72,6 +73,23 @@ void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
|||
// same region.
|
||||
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
|
||||
// pixels followed by 200 bytes of SCB followed by 512 bytes of $0RGB
|
||||
// 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/68k/spriteEmitPlanar68k.o \
|
||||
$(BUILD)/obj/68k/planarHash.o \
|
||||
$(BUILD)/obj/68k/planarIndexed.o \
|
||||
$(BUILD)/obj/68k/planarSprite.o \
|
||||
$(BUILD)/obj/68k/spriteC2p.o \
|
||||
$(BUILD)/obj/68k/planarTileLayer.o \
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ LIB_OBJS := \
|
|||
$(patsubst $(SRC_68K)/%.s,$(BUILD)/obj/68k/%.o,$(SHARED_S)) \
|
||||
$(BUILD)/obj/68k/spriteEmitInterleaved68k.o \
|
||||
$(BUILD)/obj/68k/planarHash.o \
|
||||
$(BUILD)/obj/68k/planarIndexed.o \
|
||||
$(BUILD)/obj/68k/planarSprite.o \
|
||||
$(BUILD)/obj/68k/spriteC2p.o \
|
||||
$(BUILD)/obj/codegen/spriteCompile.o \
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
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.
|
||||
-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
|
||||
# dead-stripping.
|
||||
#
|
||||
# STATUS: the launchable-binary pipeline (multi-segment OMF emission +
|
||||
# disk packaging + GS/OS launch) is still being brought up. `iigs-lib`
|
||||
# compile-checks every library + example translation unit, and
|
||||
# `iigs-clang-smoke` (not part of the default goal: it needs MAME)
|
||||
# validates the full clang -> llvm-mc -> link816 -> MAME path end to end. Per-example binary targets build via
|
||||
# clang-build.sh; large examples need the multi-segment OMF wiring still
|
||||
# in progress.
|
||||
# `iigs` (and `iigs-lib`) builds the library alone; the examples come from
|
||||
# `iigs-examples`, or `iigs-disk`, which also packs them on a volume.
|
||||
# `iigs-clang-smoke` (not part of the default goal: it needs MAME) checks
|
||||
# the clang -> llvm-mc -> link816 -> MAME path end to end. Programs are
|
||||
# multi-segment OMF from clang-build.sh.
|
||||
#
|
||||
# This file holds JoeyLib's library for the IIgs, programRules, the IIgs
|
||||
# rules for one program, which make/programs.mk instantiates for every
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#
|
||||
# make every port the programs build for whose toolchain
|
||||
# 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,
|
||||
# iigs-verify-game-disk, iigs-game-disks
|
||||
# make JOEY_PORT=<port> <target>
|
||||
|
|
|
|||
|
|
@ -62,8 +62,10 @@ LIB_OBJS := \
|
|||
$(BUILD)/obj/68k/sfxMix.o \
|
||||
$(BUILD)/obj/68k/planarSpriteWalk.o \
|
||||
$(BUILD)/obj/68k/planarLayerBlit.o \
|
||||
$(BUILD)/obj/68k/planarIndexedColumns.o \
|
||||
$(BUILD)/obj/68k/spriteEmitPlanar68k.o \
|
||||
$(BUILD)/obj/68k/planarHash.o \
|
||||
$(BUILD)/obj/68k/planarIndexed.o \
|
||||
$(BUILD)/obj/68k/planarSprite.o \
|
||||
$(BUILD)/obj/68k/spriteC2p.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:
|
||||
# host the shared layer on the host, no emulator: the new-port
|
||||
# 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,
|
||||
# save, serial, sfxmix, tilebank, tile layers against their maps,
|
||||
# the Tiled map importer, and the VGM converter (with Furnace
|
||||
# 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;
|
||||
# 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
|
||||
# PPSSPP with every memory access checked as a PSP checks it, so
|
||||
# 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 marks scripts/check-dirtymark.sh
|
||||
host host draw scripts/check-draw.sh
|
||||
host host indexed scripts/check-indexed.sh
|
||||
host host input scripts/check-input.sh
|
||||
host host millis scripts/check-millis.sh
|
||||
host host mod scripts/check-modplayer.sh
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
# Manager takes the focus, the desktop comes back and UBER presents into
|
||||
# its window) and resumed (the Task Manager's Switch To): the
|
||||
# screenshots must be the 320x200 mode, the desktop, then 320x200 again;
|
||||
# DVX.LOG must show the mode set twice; and UBER's 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
|
||||
# wherever it is shown.
|
||||
# 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"
|
||||
exit 1
|
||||
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;
|
||||
|
||||
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) {
|
||||
AmigaPlanarT *pd;
|
||||
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) {
|
||||
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
||||
* 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 setShifterVideoBase(const uint8_t *page);
|
||||
static uint16_t stFirstMarkedPair(void);
|
||||
static void stPlaneStarts(const jlSurfaceT *s, uint8_t *planes[]);
|
||||
static uint8_t tablesToWrite(void);
|
||||
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) {
|
||||
uint16_t 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
|
||||
// 2 * p in each group, a group (two plane-byte columns) every 8 bytes.
|
||||
// The shared planar hash (src/m68k/planarHash.c).
|
||||
uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
||||
const StPlanarT *pd;
|
||||
const uint8_t *planes[ST_PLANES];
|
||||
uint16_t p;
|
||||
uint8_t *planes[ST_PLANES];
|
||||
|
||||
pd = (const StPlanarT *)s->portData;
|
||||
for (p = 0u; p < ST_PLANES; p++) {
|
||||
planes[p] = pd->base + p * sizeof(uint16_t);
|
||||
}
|
||||
return jlPlanarSurfaceHash(s, planes, ST_BYTES_PER_ROW, ST_BYTES_PER_GROUP);
|
||||
stPlaneStarts(s, planes);
|
||||
return jlPlanarSurfaceHash(s, (const uint8_t *const *)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) {
|
||||
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
|
||||
// NULL as "port has no chunky shadow" (same contract Amiga uses).
|
||||
uint8_t *jlpSurfaceAllocPixels(void) {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@
|
|||
// 0xF8..0xFB Relative mouse packet (3 bytes total). The header's
|
||||
// low two bits are the button state (bit 1 = left,
|
||||
// bit 0 = right). Next two bytes are signed dx, dy.
|
||||
// Other Discarded (joystick / clock / status etc.) -- we just
|
||||
// consume the documented byte count to stay in sync.
|
||||
// 0xFD..0xFF Joystick packets: both sticks (2 bytes) or one (1).
|
||||
// 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*
|
||||
// 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) {
|
||||
// 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.
|
||||
// Range-check voice here so the per-port HAL can assume
|
||||
// voice < JL_AUDIO_VOICES.
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@
|
|||
|
||||
// interp()/mix arithmetic (delta * fracHalf up to ~8.4M, and the
|
||||
// >> 15 on a possibly-negative value) requires a >= 32-bit int with
|
||||
// arithmetic right shift. DOS (DJGPP) and ST (m68k gcc) both satisfy
|
||||
// this; the file is excluded from the IIgs build (16-bit int) by
|
||||
// make/iigs.mk. Fail loudly if anyone ever shares this mixer with a
|
||||
// arithmetic right shift. Every port that builds it satisfies this; the
|
||||
// IIgs (16-bit int), the Amiga, and DVX build without it
|
||||
// (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.
|
||||
#if INT_MAX < 0x7FFFFFFF
|
||||
#error "audioSfxMix requires >= 32-bit int (DOS/ST only); IIgs must use NTPstreamsound"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
// See LICENSE.md for license information.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Shared SFX overlay mixer used by HALs whose engine outputs MOD-only
|
||||
// (libxmp-lite on DOS + ST). The mixer overlays one-shot signed 8-bit
|
||||
// Shared SFX overlay mixer used by the HALs that mix on the CPU (DOS, the
|
||||
// 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
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@
|
|||
// Portable chip-tracker music sequencer. One authored song (baked to
|
||||
// the JYM1 event stream by tools/songbake) plays on every port through
|
||||
// the jlAudioVoice 3-voice tone layer plus the jlAudioNoise percussion
|
||||
// generator: YM2149 on the ST, Paula on the Amiga, OPL2 on DOS, DOC
|
||||
// oscillators on the IIgs. Per tick the work is a handful of event
|
||||
// bytes and at most four register-level calls, so sustained playback
|
||||
// costs register-write time on every port -- no CPU mixing anywhere.
|
||||
// generator: YM2149 on the ST, Paula on the Amiga, OPL2 (and Sound
|
||||
// Blaster noise) on DOS, DOC oscillators on the IIgs, the YM2151 on the
|
||||
// X68000, and psgSynth.c in software on DVX and the hosted ports. Per
|
||||
// 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
|
||||
// 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 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 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 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);
|
||||
|
|
@ -711,6 +713,26 @@ void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t
|
|||
#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);
|
||||
#if !defined(jlpSurfaceFreePixels)
|
||||
#if defined(JL_HAS_SURFACE_FREE_PIXELS)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
// 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).
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
// (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)
|
||||
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);
|
||||
|
|
@ -81,6 +83,34 @@ uint16_t jlgStagePaletteDirtyMask = 0xFFFFu;
|
|||
|
||||
// ----- 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)
|
||||
// Widen the STAGE_DIRTY_GROUP4_ROWS rows at minPtr / maxPtr. (Spelling the
|
||||
// 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) {
|
||||
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) -----
|
||||
|
||||
bool jlStageAlloc(void) {
|
||||
|
|
|
|||
|
|
@ -142,10 +142,19 @@ extern jlSurfaceT *jlgStage;
|
|||
// (src/m68k/planarHash.c) is the planar ports' whole hash.
|
||||
#define SURFACE_HASH_SEED_LO 0xACE1u
|
||||
#define SURFACE_HASH_SEED_HI 0x1357u
|
||||
// jlPlanarSurfaceHash's pairStep for separate planes: two columns, two bytes.
|
||||
#define PLANAR_HASH_SEPARATE_PAIR_STEP 2u
|
||||
// The planar helpers' pairStep for separate planes: two columns, two bytes.
|
||||
#define PLANAR_SEPARATE_PAIR_STEP 2u
|
||||
|
||||
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);
|
||||
|
||||
// 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 "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 -----
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// the window at most once a virtual frame: when a present lands in a frame
|
||||
// that has not been shown yet, else at the next frame wait or input poll.
|
||||
// the window at most once a virtual frame: a present in a frame not shown
|
||||
// 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
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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_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 -----
|
||||
|
||||
// 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`
|
||||
// buffer at $01:2000 is the stage's pixel storage and the source for
|
||||
// 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
|
||||
// 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
|
||||
// of 8 pixels; gSpread[b] puts byte b's bits at the low bit of those pixels'
|
||||
// nibbles, left pixel highest, so four lookups or'd at shifts 0 to 3 are the
|
||||
// 4 chunky bytes of the 8 pixels. Decoding a pixel at a time cost four bit
|
||||
// tests a pixel, and was most of an UBER run.
|
||||
// of 8 pixels; jlgPlanarSpread[b] puts byte b's bits at the low bit of those
|
||||
// pixels' nibbles, left pixel highest, so four lookups or'd at shifts 0 to 3
|
||||
// are the 4 chunky bytes of the 8 pixels (planarIndexed.c's snap reads them
|
||||
// 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
|
||||
// planes[p] + row * rowBytes + (c >> 1) * pairStep + (c & 1): separate
|
||||
// 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.
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.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)
|
||||
|
||||
// Bit j of a plane byte (pixel 7 - j) at bit 4 * j.
|
||||
#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 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 void spreadBuild(void);
|
||||
|
||||
static uint32_t gSpread[SPREAD_VALUES];
|
||||
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) {
|
||||
|
|
@ -43,9 +49,6 @@ uint32_t jlPlanarSurfaceHash(const jlSurfaceT *s, const uint8_t *const planes[],
|
|||
uint16_t row;
|
||||
uint16_t pair;
|
||||
|
||||
if (!gSpreadBuilt) {
|
||||
spreadBuild();
|
||||
}
|
||||
lo = SURFACE_HASH_SEED_LO;
|
||||
hi = SURFACE_HASH_SEED_HI;
|
||||
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;
|
||||
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;
|
||||
h = *hi;
|
||||
b = (uint8_t)(chunky >> 24);
|
||||
|
|
@ -90,20 +93,3 @@ static inline __attribute__((always_inline)) void mixPlaneBytes(uint16_t *lo, ui
|
|||
*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 "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
|
||||
// audio lifecycle, matching how the other ports are laid out.
|
||||
bool jlpAudioInitPcm(void);
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ uint32_t jlpSurfaceHash(const jlSurfaceT *s) {
|
|||
const X68kPlanarT *pd;
|
||||
|
||||
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) {
|
||||
X68kPlanarT *pd;
|
||||
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) {
|
||||
/* Phase 9: Amiga has no chunky shadow. The stage pixels pointer
|
||||
* 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: 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: 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: tilePasteGlyph: hash=CA66C63E
|
||||
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: 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: 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: tilePasteGlyph: hash=CA66C63E
|
||||
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: 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: 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: tilePasteGlyph: hash=CA66C63E
|
||||
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: 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: 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: tilePasteGlyph: hash=CA66C63E
|
||||
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