More performance work.
This commit is contained in:
parent
3b4effb95a
commit
07f0e4f503
19 changed files with 1062 additions and 32 deletions
|
|
@ -317,6 +317,90 @@ perf-directive memory with the outcome.
|
|||
scratch, 13-20% of the op; + optional CopyMaskedMark fusion), R10
|
||||
fused drawPixel+mark asm entry (~820 -> ~300 cyc), optional R7
|
||||
saveAndDraw consolidation, optional R12 IIgs C-residue reshapes.
|
||||
- [ ] Phase 3
|
||||
- [ ] Phase 4
|
||||
- [x] Phase 3 LANDED 2026-07-07 (goldens refrozen 49 -> 50 per the
|
||||
Phase-0 procedure; final gate 50/50 x4). jlTileMapPaste in
|
||||
include/joey/tile.h + tile.c: tileset + row-major uint8_t index map,
|
||||
TILE_MAP_SKIP=0xFF, one validation + one dirty union (68k/DOS);
|
||||
IIgs loop specialized (hoisted stage test, base + (idx<<5) tileset
|
||||
indexing with a sizeof proof -- tiles[idx] cost a 32-bit multiply
|
||||
per cell on the 65816 and initially made the batch SLOWER than the
|
||||
loop). New golden'd row `jlTileMapPaste 10x5` (cross-identity
|
||||
52B9646B x4). RESULTS tiles/s batch vs loop: ST 8,150 (+106%,
|
||||
gap 1.6-1.9x) / Amiga 7,650 (+104%, gap 1.40x = INSIDE the 1.5x
|
||||
target) / DOS 22,500 (+74%) / IIgs 3,000 (+2%; per-tile cost is
|
||||
~93% inner JSL post-Phase-2 -- whole-map asm walker is Phase 4's
|
||||
lever there). SPRITE BATCHES DESCOPED: gate measures only the
|
||||
tilemap row and post-R5 sprite dispatch overhead is ~3-6%; re-add
|
||||
only against a measured need + Scott's API sign-off. API shape
|
||||
SIGNED OFF by Scott 2026-07-07 (tileset + row-major uint8_t map,
|
||||
TILE_MAP_SKIP=0xFF, caller-owned index validity) -- the public
|
||||
header contract in include/joey/tile.h is final.
|
||||
- [x] Phase 4 COMPLETE 2026-07-07 (all four arms resolved; ST landed
|
||||
below). IIgs WHOLE-MAP WALKER LANDED
|
||||
(gate 50/50 + iigs-verify): iigsTileMapPasteMark in joeyDraw.s --
|
||||
one JSL walks the index map with an incrementally-advanced dst
|
||||
offset (ONE gRowOffsetLut read; +4/column, +1280/tile-row), a
|
||||
22-byte stacked D-frame for the register-starved inner (map ptr,
|
||||
tile-src ptr, bases, counters -- X survives the 16-store body and
|
||||
carries the dst offset; Y is clobbered so the map index lives in
|
||||
the frame), bank-carry-safe idx<<5 tileset add, brl on all three
|
||||
wrapping branches, ONE band union tail. RESULT: IIgs jlTileMapPaste
|
||||
10x5 60 -> 140 maps/s = 7,000 tiles/s (+133%), ~400 cyc/tile --
|
||||
BELOW the 480-cyc single-tile hand-coded ceiling (the walker
|
||||
amortizes what per-tile calls still pay). Batch row across ports:
|
||||
ST 8,150 / Amiga 7,650 / IIgs 7,000 / DOS 22,500 tiles/s.
|
||||
AMIGA BLITTER COOKIE-CUT SPRITE: recon complete, DEFERRED with the
|
||||
design recorded -- the source side is net-new (sprite data is
|
||||
fast-RAM chunky + CPU immediates; a chip-RAM pre-shift subsystem
|
||||
is required), the synchronous per-call WaitBlit contract forfeits
|
||||
the parallelism win below ~32x32 (all current UBER sprite rows are
|
||||
16x16 = at/below the crossover), and the honest experiment needs a
|
||||
new >=32x32 golden'd row measured with AND without the fence. Full
|
||||
register program (BLTCON0 0x0F00|shift|0xCA cookie-cut, per-plane B,
|
||||
shared A mask) + risk list in the recon (session 2026-07-07).
|
||||
ST ARM LANDED 2026-07-07 (4-agent recon + adversary, then C-first
|
||||
per the binding feedback): the "movem row engine" hypothesis is
|
||||
DEAD -- the tile dst is a byte scatter movem cannot address, and
|
||||
byte-pair word fusion loses on a barrel-shifterless 68000 (lsl.w #8
|
||||
= 22 cyc > the move.b it replaces). The C incremental pair walker
|
||||
(stTileMapPaste, stTile.h) measured +17% (adversary predicted the
|
||||
+15-23% cap: gcc already hoists the row mulu; the 632-cyc copy body
|
||||
dominates) -- that measured C ceiling justified asm, and the real
|
||||
weapon is MOVEP: stTileMapPasteMovep (src/atarist/tileMap.s) does
|
||||
move.l (a0)+ / movep.l d0,d16(a1) = one tile row's 4 plane bytes in
|
||||
36 cyc. ST batch row 163 -> 346 ops/s = 17,300 tiles/s (2.12x, 462
|
||||
cyc/tile), ABOVE the Phase-0 12,700-15,500 ceiling (it assumed
|
||||
byte-move bodies). C walker stays as the odd-tileset-pointer
|
||||
fallback (move.l needs an even src; jlTileT is align-1). Gate: ST
|
||||
50/50 (52B9646B held) + 12-case on-emulator TILETEST proof of the
|
||||
head/tail/w1/h1/skip/odd-ptr paths the golden never exercises, each
|
||||
with border-ring containment. RESTORE-FROM-CLEAN-BUFFER: EXPERIMENT
|
||||
RUN AND PASSED 2026-07-07 (Scott approved same day; sign-off also
|
||||
ratified the jlTileMapPaste shape). jlSurfaceCopyRect is PUBLIC API
|
||||
(include/joey/surface.h): same-position rect copy, clip + outward
|
||||
16-px group snap UNIFORM on every port (uniform snap is what keeps
|
||||
cross-port hashes identical), pixels only, dirty-marked via
|
||||
surfaceMarkDirtyRect; generic chunky row-memcpy engine (DOS + both
|
||||
IIgs flavors -- the IIgs stage IS plain-C writable through its
|
||||
bank-$01 pixels pointer, adversary-verified), ST = strided
|
||||
interleaved row memcpys, Amiga = per-plane CPU row copies (blitter
|
||||
modulo variant recorded as its upgrade; CopyMemQuick unusable at
|
||||
16-px grain). Documented caller hazards: snapped erase can touch
|
||||
content up to 15 px outside the rect and invalidates overlapping
|
||||
live sprite backups -- erase-all-then-draw-all, one mechanism per
|
||||
region. STRATEGY row `gameFrameClean composite` golden'd (contract
|
||||
50 -> 51, cross-identity C51FE171 x4): vs gameFrame ST 48/22 =
|
||||
2.18x (bar was 1.4x) / DOS 195/19 = 10.3x / IIgs 18/10 = 1.8x /
|
||||
Amiga 34/25 = 1.36x (least, as the adversary predicted: blitter
|
||||
fillRect meant less repaint to delete + chip-chip copy vs fast-RAM
|
||||
backup restore). A library-transparent variant stays REJECTED (the
|
||||
ST present source is the composited shadow, sprites corrupt it
|
||||
pre-present; a clean source means a third 32KB buffer + routing
|
||||
API by another name). IIGS
|
||||
PEI-SLAM: evaluated and REJECTED -- models ~+28% on the map walker
|
||||
but holds SEI for the whole map (~5.6 ms at 10x5; audio ISR jitter)
|
||||
and the VBL-driven bench clock cannot honestly measure SEI ops
|
||||
(SEI-inflation artifact); the landed walker already beats the
|
||||
480-cyc per-tile ceiling. Batch row after Phase 4: DOS 22,500 / ST
|
||||
17,300 / Amiga 7,650 / IIgs 7,000 tiles/s.
|
||||
- [ ] Phase 5
|
||||
|
|
|
|||
164
PERF.md
164
PERF.md
|
|
@ -102,6 +102,110 @@ half-buffer IRQ), IIgs 0% (no handler installed until PlayMod). ALL
|
|||
pre-2026-07-07 ST comparisons -- including the Phase-0 ceiling gaps
|
||||
and the Phase-1 relative results -- understated the ST by ~2x.
|
||||
|
||||
**2026-07-07, NATIVE-PERF PHASE 4 (IIgs arm) LANDED -- whole-map asm
|
||||
walker:** iigsTileMapPasteMark (joeyDraw.s) replaces the IIgs
|
||||
stage-arm per-tile loop of jlTileMapPaste with ONE JSL that walks the
|
||||
index map: dst offset advances incrementally (+4/column, +1280/tile
|
||||
row) from a single gRowOffsetLut read, the register-starved inner
|
||||
lives in a 22-byte stacked D-frame (X survives the 16-store body and
|
||||
carries the dst offset; the map index sits in the frame because Y is
|
||||
clobbered), the idx << 5 tileset add carries into the bank byte, all
|
||||
three wrapping branches are brl, and ONE band union closes the call.
|
||||
Gated 50/50 + make iigs-verify. RESULT: IIgs batch row 60 -> 140
|
||||
maps/s = 7,000 tiles/s (+133%), ~400 cyc/tile -- BELOW the 480-cyc
|
||||
single-tile hand-coded ceiling, because the walker amortizes the
|
||||
JSL/derive/mark that even a hand-coded per-tile call pays. Batch row
|
||||
across all ports: ST 8,150 / Amiga 7,650 / IIgs 7,000 / DOS 22,500
|
||||
tiles/sec (the morning's per-call loop numbers were 1,600 / 2,300 /
|
||||
1,750 / 10,000). The Amiga blitter cookie-cut sprite was recon'd and
|
||||
DEFERRED with a full design on record: the synchronous WaitBlit the
|
||||
hash contract requires erases the parallelism win at <= 16x16 (every
|
||||
current sprite row), and the chip-RAM pre-shifted source data is a
|
||||
net-new subsystem -- the honest experiment is a >= 32x32 golden'd row
|
||||
measured with and without the fence.
|
||||
|
||||
**2026-07-07, NATIVE-PERF PHASE 4 CLOSED -- clean-buffer experiment
|
||||
PASSED, jlSurfaceCopyRect is public API:** Scott signed off the
|
||||
jlTileMapPaste shape and approved the clean-buffer experiment; both
|
||||
landed same-day. jlSurfaceCopyRect(dst, src, x, y, w, h) is a
|
||||
SAME-POSITION rect copy (clip, then snap outward to the 16-px group
|
||||
grid on every port -- uniform snap keeps cross-port hashes
|
||||
byte-identical): core wrapper + dirty mark in surface.c, generic
|
||||
chunky row-memcpy engine (serves DOS and both IIgs surface flavors --
|
||||
the IIgs stage pixels pointer reaches bank $01 through ordinary
|
||||
large-model stores, recon-verified against the sprite-restore
|
||||
precedent), ST override = strided interleaved row memcpys (the
|
||||
raw-span restore recipe), Amiga override = per-plane CPU row copies
|
||||
(CopyMemQuick is unusable at 16-px grain: word alignment only;
|
||||
the modulo-driven blitter variant is the recorded production
|
||||
upgrade). The STRATEGY row `gameFrameClean composite` (erase 3
|
||||
sprite windows from a pristine clean surface + 3 draws + present; NO
|
||||
per-frame background repaint, NO save-under -- the static-background
|
||||
pattern, not an op-for-op gameFrame speedup) measures, vs gameFrame:
|
||||
**ST 48 vs 22 = 2.18x (acceptance bar was 1.4x), DOS 195 vs 19 =
|
||||
10.3x, IIgs 18 vs 10 = 1.8x, Amiga 34 vs 25 = 1.36x** (Amiga gains
|
||||
least, exactly as the adversarial recon predicted: its fillRect was
|
||||
already blitter-fast, and clean->stage is a chip-to-chip CPU copy
|
||||
against display DMA -- the blitter rect variant is its lever).
|
||||
GOLDEN CONTRACT now **51 lines**: regression gate exactly 50 match +
|
||||
1 extra per port, 4-port cross-identity of the new row (hash
|
||||
C51FE171 x4), then freeze. Found along the way: `make all` does NOT
|
||||
regenerate joey.2mg, so bench-iigs.sh silently benched the previous
|
||||
UBER (clean 50/50, new row absent) until `make iigs-disk` -- recorded
|
||||
in the stale-binary memory.
|
||||
|
||||
**2026-07-07, NATIVE-PERF PHASE 4 (ST arm) LANDED -- movep.l map
|
||||
engine:** the ST jlTileMapPaste arm now dispatches to
|
||||
stTileMapPasteMovep (src/atarist/tileMap.s): per tile row, one
|
||||
`move.l (a0)+` + `movep.l d0,d16(a1)` writes all 4 interleaved plane
|
||||
bytes in 36 cycles vs ~80 for the four compiled move.b -- movep IS
|
||||
the half-group byte-scatter pattern (the classic ST c2p trick), where
|
||||
movem is useless (byte-granular dst) and byte-pair word fusion loses
|
||||
(lsl.w #8 costs 22 cyc on a barrel-shifterless 68000, priced during
|
||||
recon). Cells walk in even/odd pairs sharing one group pointer
|
||||
(+8/pair, all row offsets compile-time d16); odd bx = head cell, odd
|
||||
width = tail cell. The C pair walker (stTileMapPaste in stTile.h,
|
||||
+17% alone -- adversarial recon correctly predicted the pure-C cap at
|
||||
+15-23% because gcc already hoists the mulu and the 632-cyc copy body
|
||||
dominates) stays as the odd-tileset-pointer fallback: `move.l (a0)+`
|
||||
needs an even source, jlTileT is align-1, and the guard is one AND
|
||||
per call. RESULT: ST batch row 163 -> 346 ops/sec = **17,300
|
||||
tiles/s** (2.12x, 462 cyc/tile) -- ABOVE the Phase-0 12,700-15,500
|
||||
"hand-coded ceiling", which under-counted by assuming byte-move
|
||||
bodies. Gate: ST 50/50 bit-identical (hash 52B9646B held through
|
||||
both engines) + a 12-case on-emulator proof (TILETEST, session
|
||||
scratchpad) covering odd-bx head, odd-width tail, w=1 columns, h=1,
|
||||
checkerboard/end/all skips, and both odd-pointer fallback paths,
|
||||
each with a border-ring containment check. Batch row across ports:
|
||||
DOS 22,500 / ST 17,300 / Amiga 7,650 / IIgs 7,000 tiles/s. IIgs
|
||||
PEI-slam map variant was EVALUATED and REJECTED: models ~+28%
|
||||
(~312 cyc/tile) but requires SEI for the whole map (~5.6 ms
|
||||
interrupts-off at 10x5, audio ISR jitter) AND the VBL-driven bench
|
||||
clock cannot honestly measure SEI ops (PERF artifact: SEI inflation)
|
||||
-- the walker already beats the 480-cyc per-tile ceiling without it.
|
||||
|
||||
**2026-07-07, NATIVE-PERF PHASE 3 LANDED -- jlTileMapPaste batch API:**
|
||||
one public entry pastes a wTiles x hTiles rectangle from a tileset +
|
||||
row-major uint8_t index map (TILE_MAP_SKIP = 0xFF leaves a cell
|
||||
untouched): one validation, one dirty union (68k/DOS; the IIgs
|
||||
stage-fused entries mark per pasted tile, and its loop hoists the
|
||||
stage test + does tileset indexing as base + (idx << 5) -- the
|
||||
natural tiles[idx] spelling cost a 32-bit indexed multiply per cell
|
||||
on the 65816 and erased the batch win until specialized). GOLDEN
|
||||
CONTRACT now **50 lines**: refreeze followed the Phase-0 procedure
|
||||
(regression exactly 49 match + 1 extra per port, then 4-port
|
||||
cross-identity of the new `jlTileMapPaste 10x5` row -- all four hash
|
||||
52B9646B -- then freeze). RESULTS, tiles/sec batch vs per-call loop:
|
||||
ST 8,150 vs 4,000 (+106%, gap-to-ceiling 1.6-1.9x); Amiga 7,650 vs
|
||||
3,750 (+104%, gap 1.40x -- INSIDE the ~1.5x program target); DOS
|
||||
22,500 vs 12,900 (+74%, 2.5x); IIgs 3,000 vs 2,950 (+2% -- its
|
||||
Phase-2 fused entries already deleted the per-tile wrapper, so the
|
||||
remaining cost is ~93% inner JSL; a whole-map IIgs asm walker is the
|
||||
Phase-4 lever). Sprite batch APIs were DESCOPED from Phase 3: the
|
||||
phase gate measures only the tilemap row, and post-R5 sprite dispatch
|
||||
overhead is ~3-6% -- not worth new public surface without a measured
|
||||
need.
|
||||
|
||||
**2026-07-07, NATIVE-PERF PHASE 2 COMPLETE -- Steps D3 (R6 + R10)
|
||||
close the phase:** (R6) the IIgs masked-copy inner's 4 x 8 jsr/rts
|
||||
round-trips to tmaskByte were inlined via the TMASK_BYTE assembler
|
||||
|
|
@ -244,49 +348,53 @@ meet or beat it -- project directive). Non-IIgs cells show absolute
|
|||
ops/sec plus percentage of the IIgs number; sub-100% cells are bolded
|
||||
as below-floor. `tools/uber-perf-table` emits this table directly;
|
||||
regenerate after any re-capture (Amiga needs TIMEOUT=600 -- the
|
||||
default cuts off the final checks). Capture provenance: ST 2026-07-07
|
||||
(post-audio-fix); IIgs/Amiga/DOS 2026-07-06 (recovery-gate rerun,
|
||||
unaffected by the ST fix).
|
||||
default cuts off the final checks). Capture provenance: all four
|
||||
ports 2026-07-07, the 51-line golden freeze gate (jlSurfaceCopyRect +
|
||||
gameFrameClean experiment; ST column includes the movep.l map engine;
|
||||
Amiga run with TIMEOUT=600; IIgs from a fresh `make iigs-disk` image
|
||||
-- `make all` alone leaves joey.2mg stale).
|
||||
|
||||
| Op | IIgs (ops/sec) | Amiga (ops/sec, % of IIGS) | Atari ST (ops/sec, % of IIGS) | DOS (ops/sec, % of IIGS) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| jlSurfaceClear | 33 | 75 (227%) | 88 (267%) | 66 (200%) |
|
||||
| jlPaletteSet | 1561 | 11403 (730%) | 10388 (665%) | 29809 (1910%) |
|
||||
| jlScbSetRange | 163 | 3953 (2425%) | 4833 (2965%) | 8495 (5212%) |
|
||||
| jlPaletteSet | 1561 | 11503 (737%) | 10388 (665%) | 29809 (1910%) |
|
||||
| jlScbSetRange | 163 | 4003 (2456%) | 4833 (2965%) | 8495 (5212%) |
|
||||
| jlDrawPixel | 5576 | 5853 (105%) | 6522 (117%) | 23687 (425%) |
|
||||
| jlDrawLine H | 1142 | 1753 (154%) | 2670 (234%) | 5708 (500%) |
|
||||
| jlDrawLine V | 99 | 303 (306%) | 261 (264%) | 468 (473%) |
|
||||
| jlDrawLine diag | 42 | **40 (95%)** | 88 (210%) | 109 (260%) |
|
||||
| jlDrawRect 100x100 | 73 | 285 (390%) | 279 (382%) | 454 (622%) |
|
||||
| jlDrawCircle r=16 | 304 | **285 (94%)** | 366 (120%) | 447 (147%) |
|
||||
| jlDrawRect 100x100 | 73 | 285 (390%) | 279 (382%) | 470 (644%) |
|
||||
| jlDrawCircle r=16 | 304 | **285 (94%)** | 359 (118%) | 447 (147%) |
|
||||
| jlDrawCircle r=80 | 70 | **63 (90%)** | 83 (119%) | 98 (140%) |
|
||||
| jlFillRect 16x16 | 602 | 1153 (192%) | 1203 (200%) | 3013 (500%) |
|
||||
| jlFillRect 80x80 | 110 | 285 (259%) | 240 (218%) | 414 (376%) |
|
||||
| jlFillRect 16x16 | 602 | 1153 (192%) | 1225 (203%) | 3027 (503%) |
|
||||
| jlFillRect 80x80 | 110 | 285 (259%) | 236 (215%) | 414 (376%) |
|
||||
| jlFillRect 320x200 | 21 | 68 (324%) | 77 (367%) | 58 (276%) |
|
||||
| jlFillCircle r=40 | 42 | **31 (74%)** | 155 (369%) | 156 (371%) |
|
||||
| jlSamplePixel | 4797 | 7253 (151%) | 9603 (200%) | 46397 (967%) |
|
||||
| jlTileFill | 3251 | 3753 (115%) | 3855 (119%) | 11840 (364%) |
|
||||
| jlTileCopy | 1988 | 2903 (146%) | 3445 (173%) | 10913 (549%) |
|
||||
| jlSamplePixel | 4797 | 7253 (151%) | 9603 (200%) | 46251 (964%) |
|
||||
| jlTileFill | 3251 | 3703 (114%) | 3855 (119%) | 11840 (364%) |
|
||||
| jlTileCopy | 1988 | 2953 (149%) | 3445 (173%) | 10913 (549%) |
|
||||
| jlTileCopyMasked | 681 | 1003 (147%) | 1632 (240%) | 3391 (498%) |
|
||||
| jlTilePaste | 2710 | 3803 (140%) | 4033 (149%) | 12545 (463%) |
|
||||
| jlTilePaste | 2710 | 3803 (140%) | 4033 (149%) | 12601 (465%) |
|
||||
| jlTileSnap | 2530 | 6003 (237%) | 6403 (253%) | 19247 (761%) |
|
||||
| jlSpriteSaveUnder | 602 | 2103 (349%) | 2611 (434%) | 10743 (1785%) |
|
||||
| jlSpriteDraw | 966 | 1103 (114%) | 1283 (133%) | 6390 (661%) |
|
||||
| jlSpriteRestoreUnder | 545 | 1453 (267%) | 1781 (327%) | 6246 (1146%) |
|
||||
| jlSpriteSaveAndDraw | 423 | 753 (178%) | 892 (211%) | 4656 (1101%) |
|
||||
| jlStagePresent full | 67 | 540 (806%) | 467 (697%) | 1132 (1690%) |
|
||||
| jlInputPoll | 228 | 4003 (1756%) | 2841 (1246%) | 3148 (1381%) |
|
||||
| jlKeyDown | 10191 | 39753 (390%) | 44448 (436%) | 77738 (763%) |
|
||||
| jlKeyPressed | 11389 | 38003 (334%) | 44448 (390%) | 74907 (658%) |
|
||||
| jlMouseX | 20214 | 97703 (483%) | 117920 (583%) | 155685 (770%) |
|
||||
| joeyJoyConnected | 10191 | 38903 (382%) | 44260 (434%) | 77748 (763%) |
|
||||
| jlAudioFrameTick | 27509 | 57003 (207%) | 39885 (145%) | 59372 (216%) |
|
||||
| jlAudioIsPlayingMod | 11071 | 56403 (509%) | 60441 (546%) | 128214 (1158%) |
|
||||
| surfaceMarkDirtyRect (via jlFillRect 32x32) | 323 | 853 (264%) | 788 (244%) | 1476 (457%) |
|
||||
| jlSpriteDraw unaligned | 906 | **903 (100%)** | 1070 (118%) | 3837 (424%) |
|
||||
| jlSpriteDraw | 966 | 1103 (114%) | 1283 (133%) | 6404 (663%) |
|
||||
| jlSpriteRestoreUnder | 545 | 1453 (267%) | 1781 (327%) | 6218 (1141%) |
|
||||
| jlSpriteSaveAndDraw | 423 | 803 (190%) | 892 (211%) | 4668 (1104%) |
|
||||
| jlStagePresent full | 67 | 534 (797%) | 467 (697%) | 1137 (1697%) |
|
||||
| jlInputPoll | 228 | 4053 (1778%) | 2788 (1223%) | 3148 (1381%) |
|
||||
| jlKeyDown | 10191 | 39353 (386%) | 45286 (444%) | 77526 (761%) |
|
||||
| jlKeyPressed | 11389 | 38353 (337%) | 45286 (398%) | 75119 (660%) |
|
||||
| jlMouseX | 20214 | 96753 (479%) | 117981 (584%) | 155281 (768%) |
|
||||
| joeyJoyConnected | 10191 | 38903 (382%) | 44260 (434%) | 77537 (761%) |
|
||||
| jlAudioFrameTick | 27509 | 56353 (205%) | 40637 (148%) | 59211 (215%) |
|
||||
| jlAudioIsPlayingMod | 11071 | 56453 (510%) | 60441 (546%) | 128004 (1156%) |
|
||||
| surfaceMarkDirtyRect (via jlFillRect 32x32) | 323 | 853 (264%) | 774 (240%) | 1476 (457%) |
|
||||
| jlSpriteDraw unaligned | 906 | **903 (100%)** | 1070 (118%) | 5215 (576%) |
|
||||
| jlSpriteDraw sweep16 | 60 | **56 (93%)** | 68 (113%) | 362 (603%) |
|
||||
| jlTilePaste map 10x5 | 59 | 78 (132%) | 80 (136%) | 259 (439%) |
|
||||
| gameFrame composite | 10 | 25 (250%) | 21 (210%) | 19 (190%) |
|
||||
| jlTilePaste map 10x5 | 59 | 78 (132%) | 80 (136%) | 258 (437%) |
|
||||
| gameFrame composite | 10 | 25 (250%) | 22 (220%) | 19 (190%) |
|
||||
| jlTileMapPaste 10x5 | 140 | 153 (109%) | 346 (247%) | 450 (321%) |
|
||||
| gameFrameClean composite | 18 | 34 (189%) | 48 (267%) | 195 (1083%) |
|
||||
|
||||
> **IIgs full-screen ceiling (~29 ops/sec).** A full-screen op moves
|
||||
> 32000 bytes = 16000 word-stores x 6 cyc (STA long,X / PEI, both
|
||||
|
|
|
|||
|
|
@ -354,6 +354,19 @@ static void op_spriteDrawSweep(void) {
|
|||
#define UBER_TILEMAP_W 10
|
||||
#define UBER_TILEMAP_H 5
|
||||
|
||||
// Batch twin of op_tileMapPaste: the SAME 10x5 alternating map in ONE
|
||||
// jlTileMapPaste call (NATIVE-PERF Phase 3) at a disjoint spot two
|
||||
// block-rows below, so both rows keep independent pixel signatures.
|
||||
// gTileSet[0/1] are struct copies of gTileMapA/B made at setup.
|
||||
#define UBER_TILEMAP_B_Y0 (UBER_TILEMAP_Y0 + UBER_TILEMAP_H + 1)
|
||||
static jlTileT gTileSet[2];
|
||||
static uint8_t gTileMapIndices[UBER_TILEMAP_W * UBER_TILEMAP_H];
|
||||
|
||||
static void op_tileMapPasteBatch(void) {
|
||||
jlTileMapPaste(gStage, UBER_TILEMAP_X0, UBER_TILEMAP_B_Y0, UBER_TILEMAP_W, UBER_TILEMAP_H, gTileSet, gTileMapIndices);
|
||||
}
|
||||
|
||||
|
||||
static void op_tileMapPaste(void) {
|
||||
uint8_t tx;
|
||||
uint8_t ty;
|
||||
|
|
@ -392,8 +405,10 @@ static void op_tileMapPaste(void) {
|
|||
static const int16_t gFrameSpriteX[UBER_FRAME_SPRITES] = { 48, 144, 240 };
|
||||
static const int16_t gFrameSpriteY[UBER_FRAME_SPRITES] = { 80, 96, 112 };
|
||||
|
||||
static void op_gameFrame(void) {
|
||||
int16_t i;
|
||||
// The frame's static content (play-region fill + HUD tile row) --
|
||||
// shared between op_gameFrame's per-call repaint and the one-time
|
||||
// clean-surface paint of op_gameFrameClean.
|
||||
static void paintFrameStatic(void) {
|
||||
uint8_t t;
|
||||
|
||||
jlFillRect(gStage, UBER_FRAME_RX, UBER_FRAME_RY, UBER_FRAME_RW, UBER_FRAME_RH, UBER_FRAME_BG);
|
||||
|
|
@ -404,6 +419,13 @@ static void op_gameFrame(void) {
|
|||
jlTilePaste(gStage, (uint8_t)(UBER_FRAME_TILE_TX + t), UBER_FRAME_TILE_TY, &gTileMapB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void op_gameFrame(void) {
|
||||
int16_t i;
|
||||
|
||||
paintFrameStatic();
|
||||
for (i = 0; i < UBER_FRAME_SPRITES; i++) {
|
||||
jlSpriteSaveAndDraw(gStage, gSprite, gFrameSpriteX[i], gFrameSpriteY[i], &gFrameBackups[i]);
|
||||
}
|
||||
|
|
@ -414,6 +436,31 @@ static void op_gameFrame(void) {
|
|||
}
|
||||
|
||||
|
||||
// Clean-buffer twin of op_gameFrame (NATIVE-PERF Phase 4 STRATEGY
|
||||
// row -- it measures the clean-buffer pattern for static-background
|
||||
// games, not an op-for-op speedup of gameFrame). The background +
|
||||
// HUD are painted ONCE into gCleanSurface before the timed window,
|
||||
// so the per-frame body is 3 window erases from the clean copy + 3
|
||||
// sprite draws + present: the per-frame background repaint and the
|
||||
// per-sprite save-under disappear. Erase-all-then-draw-all per the
|
||||
// jlSurfaceCopyRect contract. End state every call = sprites drawn
|
||||
// over clean background, stable from call 1, so the post-window
|
||||
// hash is iteration-count independent.
|
||||
static jlSurfaceT *gCleanSurface = NULL;
|
||||
|
||||
static void op_gameFrameClean(void) {
|
||||
int16_t i;
|
||||
|
||||
for (i = 0; i < UBER_FRAME_SPRITES; i++) {
|
||||
jlSurfaceCopyRect(gStage, gCleanSurface, gFrameSpriteX[i], gFrameSpriteY[i], 16, 16);
|
||||
}
|
||||
for (i = 0; i < UBER_FRAME_SPRITES; i++) {
|
||||
jlSpriteDraw(gStage, gSprite, gFrameSpriteX[i], gFrameSpriteY[i]);
|
||||
}
|
||||
jlStagePresent();
|
||||
}
|
||||
|
||||
|
||||
// ----- Build the ball sprite procedurally -----
|
||||
|
||||
#define BALL_TILES_X 2
|
||||
|
|
@ -1026,12 +1073,40 @@ static void __attribute__((noinline)) runAllTests(void) {
|
|||
jlFillRect(gStage, 8, 72, 8, 8, 9);
|
||||
jlDrawLine(gStage, 8, 72, 15, 79, 1);
|
||||
jlTileSnap(gStage, 1, 9, &gTileMapB);
|
||||
gTileSet[0] = gTileMapA;
|
||||
gTileSet[1] = gTileMapB;
|
||||
{
|
||||
uint8_t tx;
|
||||
uint8_t ty;
|
||||
for (ty = 0; ty < UBER_TILEMAP_H; ty++) {
|
||||
for (tx = 0; tx < UBER_TILEMAP_W; tx++) {
|
||||
gTileMapIndices[ty * UBER_TILEMAP_W + tx] = (uint8_t)((tx + ty) & 1u);
|
||||
}
|
||||
}
|
||||
}
|
||||
gFrameBackups[0].bytes = gFrameBackupBytes[0];
|
||||
gFrameBackups[1].bytes = gFrameBackupBytes[1];
|
||||
gFrameBackups[2].bytes = gFrameBackupBytes[2];
|
||||
|
||||
timeOp("jlTilePaste map 10x5", op_tileMapPaste);
|
||||
timeOp("gameFrame composite", op_gameFrame);
|
||||
timeOp("jlTileMapPaste 10x5", op_tileMapPasteBatch);
|
||||
|
||||
// Clean-buffer strategy row (appended last per the cumulative-
|
||||
// hash safety rule): paint the frame's static content, snapshot
|
||||
// the WHOLE stage into the clean surface (clean == stage
|
||||
// everywhere makes the snapped erase windows hash-safe by
|
||||
// construction), then time erase+draw+present.
|
||||
gCleanSurface = jlSurfaceCreate();
|
||||
if (gCleanSurface != NULL) {
|
||||
paintFrameStatic();
|
||||
jlSurfaceCopy(gCleanSurface, gStage);
|
||||
timeOp("gameFrameClean composite", op_gameFrameClean);
|
||||
jlSurfaceDestroy(gCleanSurface);
|
||||
gCleanSurface = NULL;
|
||||
} else {
|
||||
jlLogF("UBER: gameFrameClean composite: SKIPPED (surface alloc failed)\n");
|
||||
}
|
||||
|
||||
jlLogF("UBER: ----- end -----\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@
|
|||
#define JL_HAS_FLOOD_WALK_PLANES // amiga planar flood walk-out, function
|
||||
#define JL_HAS_FLOOD_SCAN_ROW_PLANES // amiga planar flood scan-row, function
|
||||
#define JL_HAS_SURFACE_COPY_PLANES // amiga blitter plane copy, function
|
||||
#define JL_HAS_SURFACE_COPY_RECT // amiga per-plane rect row copy, function
|
||||
#define JL_HAS_SAMPLE_PIXEL // amiga planar nibble decode, function
|
||||
#define JL_HAS_SURFACE_HASH // amiga planar hash, function
|
||||
#define JL_HAS_SURFACE_LOAD_FILE // amiga planar load, function
|
||||
|
|
@ -206,6 +207,7 @@
|
|||
#define JL_HAS_FLOOD_WALK_PLANES // st planar flood walk-out, function
|
||||
#define JL_HAS_FLOOD_SCAN_ROW_PLANES // st planar flood scan-row, function
|
||||
#define JL_HAS_SURFACE_COPY_PLANES // st planar buffer memcpy, function
|
||||
#define JL_HAS_SURFACE_COPY_RECT // st interleaved rect row memcpy, function
|
||||
#define JL_HAS_SAMPLE_PIXEL // st planar nibble decode, function
|
||||
#define JL_HAS_SURFACE_HASH // st planar hash, function
|
||||
#define JL_HAS_SURFACE_LOAD_FILE // st planar load, function
|
||||
|
|
|
|||
|
|
@ -50,6 +50,23 @@ jlSurfaceT *jlStageGet(void);
|
|||
// surfaces.
|
||||
void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src);
|
||||
|
||||
// Copy a pixel rectangle from src into dst at the SAME position (no
|
||||
// repositioning, so planar ports never shift bits). The rect is
|
||||
// clipped to the surface, then widened OUTWARD to the 16-pixel group
|
||||
// grid ON EVERY PORT -- the copied window is x rounded down and
|
||||
// x + w rounded up to multiples of 16 (same window rule as
|
||||
// jlSpriteSaveUnder), so up to 15 extra pixels on each side are
|
||||
// copied. Two consequences the caller owns: a snapped erase can
|
||||
// clobber on-screen content up to 15 px outside the requested rect,
|
||||
// and it silently invalidates any live jlSpriteBackupT whose
|
||||
// recorded window it touches. Copies pixels only: SCBs and palettes
|
||||
// are untouched. The intended use is restoring background
|
||||
// rectangles from a pristine copy of the scene (the clean-buffer
|
||||
// pattern) instead of per-sprite save/restore -- erase ALL windows,
|
||||
// then redraw ALL sprites; do not mix with backup-restore in the
|
||||
// same region.
|
||||
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h);
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -132,4 +132,23 @@ void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by,
|
|||
const jlSurfaceT *fontSurface, const uint16_t *asciiMap,
|
||||
const char *str);
|
||||
|
||||
// Map entry value that leaves a cell untouched (nothing pasted, the
|
||||
// destination shows through). Mirrors TILE_NO_GLYPH's role in
|
||||
// jlDrawText.
|
||||
#define TILE_MAP_SKIP ((uint8_t)0xFFu)
|
||||
|
||||
// Paste a wTiles x hTiles rectangle of tiles in ONE call: the
|
||||
// validation, dispatch, and dirty union are paid once for the whole
|
||||
// map instead of per tile (the jlDrawText batching pattern
|
||||
// generalized, NATIVE-PERF Phase 3). `tiles` is the tileset; `map`
|
||||
// holds wTiles * hTiles tileset indices in row-major order
|
||||
// (map[row * wTiles + col]). An index of TILE_MAP_SKIP leaves that
|
||||
// cell untouched; any other out-of-tileset index is the CALLER'S
|
||||
// contract to avoid (the batch does not know the tileset length and
|
||||
// does not range-check it). The whole rectangle must fit on the
|
||||
// surface -- a map whose edge crosses TILE_BLOCKS_PER_ROW/COL is
|
||||
// rejected as a no-op, matching the per-tile ops' silent-reject
|
||||
// convention. wTiles or hTiles of 0 is a no-op.
|
||||
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1454,6 +1454,45 @@ void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
// Same-position group-snapped rect copy (NATIVE-PERF Phase 4
|
||||
// clean-buffer op). The core wrapper clipped and 16-px-snapped the
|
||||
// rect; per plane each row is a short contiguous span at the same
|
||||
// byte offset in both buffers. CPU row copies, not the blitter:
|
||||
// amigaBlitterCopyPlanes hardcodes modulo 0 (whole contiguous rows),
|
||||
// a 16-px snap gives only word alignment (CopyMemQuick needs longs),
|
||||
// and typical spans (a 16x16 sprite window = 2 bytes/row/plane) are
|
||||
// below any setup-cost crossover. A modulo-driven blitter variant is
|
||||
// the production upgrade if this op ever moves big rects.
|
||||
void jlpSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
AmigaPlanarT *dstPd;
|
||||
AmigaPlanarT *srcPd;
|
||||
uint8_t *d;
|
||||
const uint8_t *s;
|
||||
uint16_t off;
|
||||
uint16_t rowBytes;
|
||||
uint16_t row;
|
||||
uint8_t plane;
|
||||
|
||||
dstPd = (AmigaPlanarT *)dst->portData;
|
||||
srcPd = (AmigaPlanarT *)src->portData;
|
||||
if (dstPd == NULL || srcPd == NULL) {
|
||||
jlpGenericSurfaceCopyRect(dst, src, x, y, w, h);
|
||||
return;
|
||||
}
|
||||
off = (uint16_t)(y * AMIGA_BYTES_PER_ROW + (x >> 3));
|
||||
rowBytes = (uint16_t)(w >> 3);
|
||||
for (plane = 0; plane < 4u; plane++) {
|
||||
d = dstPd->planes[plane] + off;
|
||||
s = srcPd->planes[plane] + off;
|
||||
for (row = 0; row < h; row++) {
|
||||
memcpy(d, s, rowBytes);
|
||||
d += AMIGA_BYTES_PER_ROW;
|
||||
s += AMIGA_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Phase 4 planar dual-write helper: set one pixel's bit in each of
|
||||
// the four shadow planes. Caller (the per-primitive walker below) has
|
||||
// already validated (x, y) is on-surface. byteOff is computed once
|
||||
|
|
|
|||
|
|
@ -1311,6 +1311,39 @@ void jlpSurfaceCopyPlanes(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
// Same-position group-snapped rect copy (NATIVE-PERF Phase 4
|
||||
// clean-buffer op). The core wrapper clipped and 16-px-snapped the
|
||||
// rect, so each row's span is contiguous interleaved bytes at the
|
||||
// same offset in both buffers -- the raw-span sprite-restore recipe
|
||||
// with a strided source. Offsets are multiples of 8 from long-aligned
|
||||
// bases, so memcpy's long path is alignment-safe.
|
||||
void jlpSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
StPlanarT *dstPd;
|
||||
StPlanarT *srcPd;
|
||||
uint8_t *d;
|
||||
const uint8_t *s;
|
||||
uint16_t off;
|
||||
uint16_t rowBytes;
|
||||
uint16_t row;
|
||||
|
||||
dstPd = (StPlanarT *)dst->portData;
|
||||
srcPd = (StPlanarT *)src->portData;
|
||||
if (dstPd == NULL || srcPd == NULL) {
|
||||
jlpGenericSurfaceCopyRect(dst, src, x, y, w, h);
|
||||
return;
|
||||
}
|
||||
off = (uint16_t)(y * ST_BYTES_PER_ROW + (x >> 4) * ST_BYTES_PER_GROUP);
|
||||
d = dstPd->base + off;
|
||||
s = srcPd->base + off;
|
||||
rowBytes = (uint16_t)((w >> 4) * ST_BYTES_PER_GROUP);
|
||||
for (row = 0; row < h; row++) {
|
||||
memcpy(d, s, rowBytes);
|
||||
d += ST_BYTES_PER_ROW;
|
||||
s += ST_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- Phases 4-7: per-pixel / tile / sprite / blit planar primitives -----
|
||||
//
|
||||
// These implementations dual-write the planar shadow alongside the
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@
|
|||
// all 4 planes. Declared here because stTileFill is its only caller.
|
||||
extern void surface68kStTileFill8x8(uint8_t *firstGroupPtr, uint16_t mask, uint8_t color);
|
||||
|
||||
// 68k asm entry (tileMap.s): whole-map paste with a movep.l row body
|
||||
// (36 cyc/tile-row vs ~80 for compiled move.b). Requires an EVEN
|
||||
// tileset pointer (move.l source alignment); stTileMapPaste guards
|
||||
// and keeps the C pair walker as the odd-pointer fallback. Declared
|
||||
// here because stTileMapPaste is its only caller.
|
||||
extern void stTileMapPasteMovep(uint8_t *rowBase, const uint8_t *tiles, const uint8_t *map, uint16_t wTiles, uint16_t hTiles, uint16_t headOdd);
|
||||
|
||||
// Phase 5 tile ops. 8x8 tiles at byte position (bx, by) start at
|
||||
// pixel (bx*8, by*8). 8 pixels wide always covers exactly half a
|
||||
// 16-pixel group: high half (bits 15..8) when bx is even, low half
|
||||
|
|
@ -93,6 +100,104 @@ static inline __attribute__((always_inline)) void stTilePaste(jlSurfaceT *dst, u
|
|||
}
|
||||
|
||||
|
||||
// Whole-map paste engine (NATIVE-PERF Phase 4). jlTileMapPaste's ST
|
||||
// arm calls this ONCE per map: the portData check, the row base
|
||||
// derivation, and the group walk are hoisted out of the per-cell
|
||||
// loop -- the generic loop re-derives base + by*1280 + (bx>>1)*8 +
|
||||
// (bx&1) and reloads portData per cell, ~250 of its ~980 cyc/tile.
|
||||
// Cells advance in even/odd PAIRS sharing one group pointer (+8 per
|
||||
// pair, constant displacements select the half-group), so an odd bx
|
||||
// contributes a head cell and an odd width a tail cell. Pixel output
|
||||
// is byte-identical to per-cell stTilePaste.
|
||||
#define ST_MAP_CELL_ROW(_o, _r) \
|
||||
do { \
|
||||
gp[(_o) + (_r) * ST_BYTES_PER_ROW + 0] = src[(_r) * TILE_BYTES_PER_ROW + 0]; \
|
||||
gp[(_o) + (_r) * ST_BYTES_PER_ROW + 2] = src[(_r) * TILE_BYTES_PER_ROW + 1]; \
|
||||
gp[(_o) + (_r) * ST_BYTES_PER_ROW + 4] = src[(_r) * TILE_BYTES_PER_ROW + 2]; \
|
||||
gp[(_o) + (_r) * ST_BYTES_PER_ROW + 6] = src[(_r) * TILE_BYTES_PER_ROW + 3]; \
|
||||
} while (0)
|
||||
|
||||
#define ST_MAP_CELL(_o) \
|
||||
do { \
|
||||
src = tileBytes + ((uint16_t)idx << 5); \
|
||||
ST_MAP_CELL_ROW(_o, 0); \
|
||||
ST_MAP_CELL_ROW(_o, 1); \
|
||||
ST_MAP_CELL_ROW(_o, 2); \
|
||||
ST_MAP_CELL_ROW(_o, 3); \
|
||||
ST_MAP_CELL_ROW(_o, 4); \
|
||||
ST_MAP_CELL_ROW(_o, 5); \
|
||||
ST_MAP_CELL_ROW(_o, 6); \
|
||||
ST_MAP_CELL_ROW(_o, 7); \
|
||||
} while (0)
|
||||
|
||||
static inline __attribute__((always_inline)) void stTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
||||
StPlanarT *pd;
|
||||
const uint8_t *tileBytes;
|
||||
const uint8_t *src;
|
||||
uint8_t *rowBase;
|
||||
uint8_t *gp;
|
||||
uint8_t idx;
|
||||
uint8_t ty;
|
||||
uint8_t tx;
|
||||
uint8_t pairs;
|
||||
uint8_t headOdd;
|
||||
uint8_t tail;
|
||||
|
||||
tileBytes = (const uint8_t *)tiles;
|
||||
pd = (StPlanarT *)dst->portData;
|
||||
if (pd == NULL) {
|
||||
for (ty = 0; ty < hTiles; ty++) {
|
||||
for (tx = 0; tx < wTiles; tx++) {
|
||||
idx = map[tx];
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
jlpGenericTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), tileBytes + ((uint16_t)idx << 5));
|
||||
}
|
||||
}
|
||||
map += wTiles;
|
||||
}
|
||||
return;
|
||||
}
|
||||
rowBase = pd->base + (uint16_t)by * 8u * ST_BYTES_PER_ROW + ((uint16_t)bx >> 1) * ST_BYTES_PER_GROUP;
|
||||
headOdd = (uint8_t)(bx & 1u);
|
||||
if (((uintptr_t)tileBytes & 1u) == 0u) {
|
||||
stTileMapPasteMovep(rowBase, tileBytes, map, wTiles, hTiles, headOdd);
|
||||
return;
|
||||
}
|
||||
pairs = (uint8_t)((uint8_t)(wTiles - headOdd) >> 1);
|
||||
tail = (uint8_t)((uint8_t)(wTiles - headOdd) & 1u);
|
||||
for (ty = 0; ty < hTiles; ty++) {
|
||||
gp = rowBase;
|
||||
if (headOdd != 0u) {
|
||||
idx = *map++;
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
ST_MAP_CELL(1);
|
||||
}
|
||||
gp += ST_BYTES_PER_GROUP;
|
||||
}
|
||||
for (tx = pairs; tx != 0u; tx--) {
|
||||
idx = *map++;
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
ST_MAP_CELL(0);
|
||||
}
|
||||
idx = *map++;
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
ST_MAP_CELL(1);
|
||||
}
|
||||
gp += ST_BYTES_PER_GROUP;
|
||||
}
|
||||
if (tail != 0u) {
|
||||
idx = *map++;
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
ST_MAP_CELL(0);
|
||||
}
|
||||
}
|
||||
rowBase += 8u * ST_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
#undef ST_MAP_CELL
|
||||
#undef ST_MAP_CELL_ROW
|
||||
|
||||
|
||||
// Planar monochrome paste. monoTile follows the cross-port mono
|
||||
// contract (include/joey/tile.h + jlpGenericTilePasteMono): 32 chunky
|
||||
// nibble-pair bytes, row-major, TILE_BYTES_PER_ROW bytes per row, two
|
||||
|
|
|
|||
127
src/atarist/tileMap.s
Normal file
127
src/atarist/tileMap.s
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
| ST whole-map tile-paste engine, movep.l row body (NATIVE-PERF
|
||||
| Phase 4).
|
||||
|
|
||||
| One call pastes a wTiles x hTiles map of 8x8 tiles. The copy body
|
||||
| uses the 68000's byte-lane scatter instruction:
|
||||
|
|
||||
| move.l (%a0)+,%d0 | one tile row: 4 plane bytes, 12 cyc
|
||||
| movep.l %d0,d16(%a1) | writes d16, d16+2, d16+4, d16+6, 24 cyc
|
||||
|
|
||||
| = 36 cyc per tile row vs ~80 for the four move.b the compiler emits
|
||||
| (movep writes MSB first, matching the plane-major tile layout and
|
||||
| the interleaved d16 0/2/4/6 dst pattern of stTilePaste). movem is
|
||||
| useless here -- the dst is a byte scatter -- but movep IS this
|
||||
| access pattern. Cells advance in even/odd pairs sharing one group
|
||||
| pointer (+8/pair); an odd bx adds a head cell (displacements +1),
|
||||
| an odd width a tail cell. Row displacements are compile-time
|
||||
| constants (row r at r*160), so the group pointer only moves once
|
||||
| per pair.
|
||||
|
|
||||
| void stTileMapPasteMovep(uint8_t *rowBase, const uint8_t *tiles,
|
||||
| const uint8_t *map, uint16_t wTiles,
|
||||
| uint16_t hTiles, uint16_t headOdd);
|
||||
|
|
||||
| rowBase = planar base + by*1280 + (bx>>1)*8 (group-aligned; the
|
||||
| caller keeps the bx&1 head flag). CONTRACT: tiles must be EVEN
|
||||
| (move.l source alignment on 68000); the C caller guards and falls
|
||||
| back to the byte-copy engine for odd tileset pointers. Map bytes of
|
||||
| 0xFF (TILE_MAP_SKIP) leave the cell untouched but still advance the
|
||||
| walk. wTiles/hTiles >= 1 (jlTileMapPaste validated).
|
||||
|
|
||||
| cdecl args land in 4-byte slots (uint16_t value at slot+2), same
|
||||
| convention as surface68kStTileFill8x8 (lineSpan.s). d2-d7/a2-a6 are
|
||||
| callee-save; register lists (not hand masks) keep the predec movem
|
||||
| mask correct.
|
||||
|
|
||||
| Register map: a0 tile src (scratch), a1 group walker, a2 map
|
||||
| cursor, a3 tileset base, a4 rowBase (+1280/map row); d0 movep data,
|
||||
| d1 map byte, d2 pairs/row, d3 tail flag, d4 rows left, d5 head
|
||||
| flag, d6 pair countdown.
|
||||
|
|
||||
| GAS m68k syntax, assembled by m68k-atari-mint-as. ST-only: this
|
||||
| file must stay in src/atarist/ (src/m68k/*.s is wildcarded into the
|
||||
| Amiga build too).
|
||||
|
||||
.equ MP_SAVED, 32 | d2-d6/a2-a4 = 8 longs
|
||||
.equ MP_OFF, (MP_SAVED + 4)
|
||||
.equ MP_BASE, MP_OFF + 0
|
||||
.equ MP_TILES, MP_OFF + 4
|
||||
.equ MP_MAP, MP_OFF + 8
|
||||
.equ MP_WTILES, MP_OFF + 12 + 2
|
||||
.equ MP_HTILES, MP_OFF + 16 + 2
|
||||
.equ MP_HEAD, MP_OFF + 20 + 2
|
||||
|
||||
| One tile cell: derive src from the map byte in d1, then 8 rows of
|
||||
| move.l + movep.l at half-group displacement \off (0 = even cell,
|
||||
| 1 = odd cell). Falls straight through on TILE_MAP_SKIP.
|
||||
.macro MP_CELL off, skip
|
||||
moveq #0,%d1
|
||||
move.b (%a2)+,%d1 | map byte
|
||||
cmp.b #-1,%d1 | TILE_MAP_SKIP?
|
||||
beq.s \skip
|
||||
lsl.w #5,%d1 | idx * sizeof(jlTileT)
|
||||
lea (%a3,%d1.w),%a0 | tile src (even by contract)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+0(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+160(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+320(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+480(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+640(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+800(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+960(%a1)
|
||||
move.l (%a0)+,%d0
|
||||
movep.l %d0,\off+1120(%a1)
|
||||
\skip:
|
||||
.endm
|
||||
|
||||
.text
|
||||
|
||||
.globl _stTileMapPasteMovep
|
||||
|
||||
_stTileMapPasteMovep:
|
||||
movem.l %d2-%d6/%a2-%a4,-(%sp)
|
||||
|
||||
movea.l MP_BASE(%sp),%a4 | a4 = rowBase
|
||||
movea.l MP_TILES(%sp),%a3 | a3 = tileset base
|
||||
movea.l MP_MAP(%sp),%a2 | a2 = map cursor
|
||||
move.w MP_WTILES(%sp),%d3
|
||||
move.w MP_HTILES(%sp),%d4 | d4 = rows left
|
||||
move.w MP_HEAD(%sp),%d5 | d5 = 1 if bx odd
|
||||
sub.w %d5,%d3
|
||||
move.w %d3,%d2
|
||||
lsr.w #1,%d2 | d2 = pairs per row
|
||||
and.w #1,%d3 | d3 = tail cell flag
|
||||
|
||||
.LmpRow:
|
||||
movea.l %a4,%a1 | a1 = this row's group ptr
|
||||
|
||||
tst.w %d5
|
||||
beq.s .LmpNoHead
|
||||
MP_CELL 1, .LmpHeadSkip
|
||||
addq.l #8,%a1
|
||||
.LmpNoHead:
|
||||
move.w %d2,%d6 | pair countdown
|
||||
beq.w .LmpNoPairs
|
||||
subq.w #1,%d6
|
||||
.LmpPair:
|
||||
MP_CELL 0, .LmpEvenSkip
|
||||
MP_CELL 1, .LmpOddSkip
|
||||
addq.l #8,%a1
|
||||
dbra %d6,.LmpPair
|
||||
.LmpNoPairs:
|
||||
tst.w %d3
|
||||
beq.s .LmpNoTail
|
||||
MP_CELL 0, .LmpTailSkip
|
||||
.LmpNoTail:
|
||||
lea 1280(%a4),%a4 | next tile row of the map
|
||||
subq.w #1,%d4
|
||||
bne.w .LmpRow
|
||||
|
||||
movem.l (%sp)+,%d2-%d6/%a2-%a4
|
||||
rts
|
||||
|
|
@ -67,6 +67,7 @@ extern void iigsTileCopyMark (const uint8_t *srcRow0, uint16_t dstBx, uint16
|
|||
extern void iigsTileCopyMaskedInner(uint8_t *dstRow0, const uint8_t *srcRow0, uint16_t transparent);
|
||||
extern void iigsTilePasteInner (uint8_t *dstRow0, const uint8_t *srcTilePixels);
|
||||
extern void iigsTilePasteMark (const uint8_t *srcTilePixels, uint16_t bx, uint16_t by);
|
||||
extern void iigsTileMapPasteMark (uint16_t bx, uint16_t by, uint16_t wTiles, uint16_t hTiles, const uint8_t *tiles, const uint8_t *map);
|
||||
extern void iigsTilePasteMonoInner(uint8_t *dstRow0, const uint8_t *monoTile, uint16_t fgColor, uint16_t bgColor);
|
||||
extern void iigsTileSnapInner (uint8_t *dstTilePixels, const uint8_t *srcRow0);
|
||||
extern void iigsFillRectInner (uint8_t *pixels, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t nibble);
|
||||
|
|
@ -542,6 +543,19 @@ void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
// Same-position group-snapped rect copy (jlSurfaceCopyRect payload).
|
||||
// The generic body serves the chunky ports (incl. the IIgs stage --
|
||||
// its pixels pointer reaches bank $01 through ordinary large-model
|
||||
// stores); the planar ports override with their plane-layout walks.
|
||||
void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
#if !defined(jlpSurfaceCopyRect)
|
||||
#if defined(JL_HAS_SURFACE_COPY_RECT)
|
||||
void jlpSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
#else
|
||||
#define jlpSurfaceCopyRect(_d, _s, _x, _y, _w, _h) jlpGenericSurfaceCopyRect((_d), (_s), (_x), (_y), (_w), (_h))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void jlpGenericSurfaceFreePixels(uint8_t *pixels);
|
||||
#if !defined(jlpSurfaceFreePixels)
|
||||
#if defined(JL_HAS_SURFACE_FREE_PIXELS)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,49 @@ void jlSurfaceCopy(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
void jlSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, int16_t x, int16_t y, uint16_t w, uint16_t h) {
|
||||
int32_t left;
|
||||
int32_t top;
|
||||
int32_t right;
|
||||
int32_t bottom;
|
||||
|
||||
if (dst == NULL || src == NULL || dst == src) {
|
||||
return;
|
||||
}
|
||||
if (w == 0u || h == 0u) {
|
||||
return;
|
||||
}
|
||||
// Clip in int32 (jlFillRect precedent), then widen OUTWARD to the
|
||||
// 16-px group grid per the header contract. SURFACE_WIDTH is a
|
||||
// multiple of 16, so the widened right edge never re-crosses it.
|
||||
left = x;
|
||||
top = y;
|
||||
right = (int32_t)x + w;
|
||||
bottom = (int32_t)y + h;
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
}
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
if (right > SURFACE_WIDTH) {
|
||||
right = SURFACE_WIDTH;
|
||||
}
|
||||
if (bottom > SURFACE_HEIGHT) {
|
||||
bottom = SURFACE_HEIGHT;
|
||||
}
|
||||
if (left >= right || top >= bottom) {
|
||||
return;
|
||||
}
|
||||
left = left & ~(int32_t)15;
|
||||
right = (right + 15) & ~(int32_t)15;
|
||||
jlpSurfaceCopyRect(dst, src, (uint16_t)left, (uint16_t)top, (uint16_t)(right - left), (uint16_t)(bottom - top));
|
||||
// Pixels-only op: the SCB/palette dirty flags are jlSurfaceCopy's
|
||||
// concern, not this one's.
|
||||
surfaceMarkDirtyRect(dst, (int16_t)left, (int16_t)top, (int16_t)(right - left), (int16_t)(bottom - top));
|
||||
}
|
||||
|
||||
|
||||
jlSurfaceT *jlSurfaceCreate(void) {
|
||||
jlSurfaceT *s;
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@
|
|||
// the tile side or the 4-px-per-word grain ever changes):
|
||||
typedef char tileMarkBandProof[(SURFACE_WORD_INDEX(TILE_PIXELS_PER_SIDE) == 2) ? 1 : -1];
|
||||
|
||||
// jlTileMapPaste indexes its tileset as base + (idx << 5); pin the
|
||||
// struct size so that shift can never drift from sizeof(jlTileT).
|
||||
typedef char tileMapStrideProof[(sizeof(jlTileT) == 32) ? 1 : -1];
|
||||
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
#define tileMarkDirty(_pixelY, _bx) iigsMarkDirtyRowsInner((uint16_t)(_pixelY), (uint16_t)((_pixelY) + TILE_PIXELS_PER_SIDE), (uint16_t)((uint16_t)(_bx) << 1), (uint16_t)(((uint16_t)(_bx) << 1) + 1u))
|
||||
// Fill/copy/paste FUSE the mark into their stage-arm asm entries on
|
||||
|
|
@ -242,6 +246,87 @@ void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex) {
|
|||
}
|
||||
|
||||
|
||||
void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map) {
|
||||
uint8_t tx;
|
||||
uint8_t ty;
|
||||
uint8_t idx;
|
||||
const uint8_t *row;
|
||||
|
||||
if (dst == NULL || tiles == NULL || map == NULL) {
|
||||
return;
|
||||
}
|
||||
if (wTiles == 0u || hTiles == 0u) {
|
||||
return;
|
||||
}
|
||||
if ((uint16_t)bx + wTiles > TILE_BLOCKS_PER_ROW || (uint16_t)by + hTiles > TILE_BLOCKS_PER_COL) {
|
||||
return;
|
||||
}
|
||||
// One validation above, one dirty union below: the per-cell loop
|
||||
// pays only the jlp dispatch (inline on the 68k ports since Phase
|
||||
// 2 R4a; the fused stage entry on IIgs). Tileset indexing is a
|
||||
// 16-bit shift folded into the far base ONCE per cell -- the
|
||||
// natural tiles[idx] spelling made the 65816 do a 32-bit indexed
|
||||
// multiply per cell, which alone erased the batch win there.
|
||||
row = map;
|
||||
#ifdef JOEYLIB_PLATFORM_IIGS
|
||||
// The stage test is hoisted OUT of the loop on the IIgs: the
|
||||
// jlpTilePaste macro's per-cell (dst == gStage) ternary
|
||||
// materializes a boolean (~65 cycles/cell) that a 10x5 map pays
|
||||
// 50 times. The fused stage entry marks each pasted tile itself,
|
||||
// so the stage arm needs no union; non-stage surfaces carry no
|
||||
// dirty state.
|
||||
if (dst == gStage) {
|
||||
// ONE JSL for the whole map (NATIVE-PERF Phase 4): the asm
|
||||
// walker amortizes the per-tile JSL + arg staging + derive +
|
||||
// 8-row mark across the rectangle and issues one band union.
|
||||
iigsTileMapPasteMark(bx, by, wTiles, hTiles, (const uint8_t *)tiles, map);
|
||||
return;
|
||||
}
|
||||
for (ty = 0; ty < hTiles; ty++) {
|
||||
for (tx = 0; tx < wTiles; tx++) {
|
||||
idx = row[tx];
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
iigsTilePasteInner(&dst->pixels[SURFACE_ROW_OFFSET((uint16_t)(by + ty) << 3) + ((uint16_t)(bx + tx) << 2)], (const uint8_t *)tiles + ((uint16_t)idx << 5));
|
||||
}
|
||||
}
|
||||
row += wTiles;
|
||||
}
|
||||
#elif defined(JOEYLIB_PLATFORM_ATARIST)
|
||||
// Whole-map engine (NATIVE-PERF Phase 4, stTile.h): one portData
|
||||
// check, one row-base derive, and a paired group-pointer walk for
|
||||
// the whole rectangle instead of the per-cell re-derivation the
|
||||
// generic loop pays.
|
||||
(void)tx;
|
||||
(void)ty;
|
||||
(void)idx;
|
||||
(void)row;
|
||||
stTileMapPaste(dst, bx, by, wTiles, hTiles, tiles, map);
|
||||
// One rectangle-union mark for the whole map. Over-marks
|
||||
// TILE_MAP_SKIP cells (safe: present copies extra CLEAN content,
|
||||
// pixels unchanged).
|
||||
if (dst == gStage) {
|
||||
surfaceMarkDirtyRows((uint16_t)((uint16_t)by << 3), (uint16_t)(((uint16_t)by + hTiles) << 3), (uint8_t)((uint16_t)bx << 1), (uint8_t)((((uint16_t)bx + wTiles) << 1) - 1u));
|
||||
}
|
||||
#else
|
||||
for (ty = 0; ty < hTiles; ty++) {
|
||||
for (tx = 0; tx < wTiles; tx++) {
|
||||
idx = row[tx];
|
||||
if (idx != TILE_MAP_SKIP) {
|
||||
jlpTilePaste(dst, (uint8_t)(bx + tx), (uint8_t)(by + ty), (const uint8_t *)tiles + ((uint16_t)idx << 5));
|
||||
}
|
||||
}
|
||||
row += wTiles;
|
||||
}
|
||||
// One rectangle-union mark for the whole map. Over-marks
|
||||
// TILE_MAP_SKIP cells (safe: present copies extra CLEAN content,
|
||||
// pixels unchanged).
|
||||
if (dst == gStage) {
|
||||
surfaceMarkDirtyRows((uint16_t)((uint16_t)by << 3), (uint16_t)(((uint16_t)by + hTiles) << 3), (uint8_t)((uint16_t)bx << 1), (uint8_t)((((uint16_t)bx + wTiles) << 1) - 1u));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in) {
|
||||
if (dst == NULL || in == NULL) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,33 @@ void jlpGenericSurfaceCopyChunky(jlSurfaceT *dst, const jlSurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
// jlSurfaceCopyRect's chunky payload: same-position row segments. The
|
||||
// core wrapper already clipped and group-snapped the rect, so x and w
|
||||
// are multiples of 16 and the rect is on-surface. Guarded to a no-op
|
||||
// on planar ports (pixels == NULL), whose JL_HAS_SURFACE_COPY_RECT
|
||||
// override owns the plane work. The row offset is seeded once and
|
||||
// walked by stride: SURFACE_ROW_OFFSET is a LUT read on the IIgs and
|
||||
// a multiply elsewhere, and neither belongs in a row loop.
|
||||
void jlpGenericSurfaceCopyRect(jlSurfaceT *dst, const jlSurfaceT *src, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
uint8_t *d;
|
||||
const uint8_t *s;
|
||||
uint16_t rowBytes;
|
||||
uint16_t row;
|
||||
|
||||
if (dst->pixels == NULL || src->pixels == NULL) {
|
||||
return;
|
||||
}
|
||||
d = dst->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
|
||||
s = src->pixels + SURFACE_ROW_OFFSET(y) + (x >> 1);
|
||||
rowBytes = (uint16_t)(w >> 1);
|
||||
for (row = 0; row < h; row++) {
|
||||
memcpy(d, s, rowBytes);
|
||||
d += SURFACE_BYTES_PER_ROW;
|
||||
s += SURFACE_BYTES_PER_ROW;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Release a per-surface chunky pixel buffer. Must mirror
|
||||
// jlpGenericSurfaceAllocPixels' allocator exactly: handing a
|
||||
// jlpBigAlloc pointer to free() corrupts the IIgs heap
|
||||
|
|
|
|||
|
|
@ -1731,6 +1731,250 @@ iigsTileCopyMark:
|
|||
plp
|
||||
rtl
|
||||
|
||||
; ====================================================================
|
||||
; iigsTileMapPasteMark(uint16_t bx, uint16_t by, uint16_t wTiles,
|
||||
; uint16_t hTiles, const jlTileT *tiles,
|
||||
; const uint8_t *map)
|
||||
;
|
||||
; NATIVE-PERF Phase 4: whole-map tile walker. ONE JSL walks the
|
||||
; wTiles x hTiles row-major uint8_t index map and pastes every
|
||||
; non-0xFF tile to the stage, then widens the dirty band once for the
|
||||
; whole rectangle -- amortizing the per-tile JSL, arg staging, derive,
|
||||
; and 8-row mark that iigsTilePasteMark pays per tile. The dst offset
|
||||
; advances incrementally (+4 per tile column, +1280 per tile row =
|
||||
; 8 SHR rows) from ONE gRowOffsetLut read at entry.
|
||||
;
|
||||
; ABI: bx in A; by @4,s, wTiles @6,s, hTiles @8,s, tiles @10,s/12,s,
|
||||
; map @14,s/16,s on JSL entry. Frame: php+phb+phd (+4), parked
|
||||
; bx (+2), then a 22-byte reserved D-frame (+22) = +28 total; D
|
||||
; = SP+1 so frame slots are D-relative and args sit at D+31..43.
|
||||
; X survives the 16-pair copy body (sta long,x never writes X),
|
||||
; so it carries the walking dst offset; Y is clobbered per cell
|
||||
; and the map index lives in the frame instead. The idx << 5
|
||||
; tileset add carries into the bank byte (tmwSrc+2), so a
|
||||
; tileset crossing a bank boundary still resolves.
|
||||
;
|
||||
; No internal jsr (the band union is inline), so the section is
|
||||
; self-contained -- no cross-section 16-bit-jsr hazard. The three
|
||||
; wrapping branches (skip-cell forward, column back, row back) span
|
||||
; the ~150-byte copy body and use the bne-over-brl idiom.
|
||||
; ====================================================================
|
||||
.section .text.iigsTileMapPasteMark,"ax"
|
||||
.globl iigsTileMapPasteMark
|
||||
iigsTileMapPasteMark:
|
||||
tmwSrc = 0 ; current tile src far ptr ([0],y)
|
||||
tmwMap = 4 ; map far ptr ([4],y)
|
||||
tmwRowBase = 8 ; dst offset of current tile row
|
||||
tmwTilesLo = 10 ; tileset base low16
|
||||
tmwTilesHi = 12 ; tileset base high16 (bank)
|
||||
tmwMapIdx = 14 ; running map index (reused: yEnd for the mark)
|
||||
tmwTx = 16 ; columns left this row (reused: minWord)
|
||||
tmwTy = 18 ; rows left (reused: maxWord)
|
||||
tmwWt = 20 ; wTiles reload value
|
||||
tmwBx = 22 ; parked bx
|
||||
tmwByArg = 31 ; by (orig 4,s + 28 pushed, D-relative)
|
||||
tmwWtArg = 33 ; wTiles (orig 6,s)
|
||||
tmwHtArg = 35 ; hTiles (orig 8,s)
|
||||
tmwTLoArg = 37 ; tiles low16 (orig 10,s)
|
||||
tmwTHiArg = 39 ; tiles high16 (orig 12,s)
|
||||
tmwMLoArg = 41 ; map low16 (orig 14,s)
|
||||
tmwMHiArg = 43 ; map high16 (orig 16,s)
|
||||
|
||||
php
|
||||
phb
|
||||
phd
|
||||
rep #0x30
|
||||
.a16
|
||||
.i16
|
||||
pha ; park bx -> D+22
|
||||
tsc
|
||||
sec
|
||||
sbc #22
|
||||
tcs ; reserve the 22-byte frame
|
||||
inc a
|
||||
tcd ; D = frame base (bank-0 stack)
|
||||
|
||||
lda tmwMLoArg
|
||||
sta tmwMap+0
|
||||
lda tmwMHiArg
|
||||
sta tmwMap+2
|
||||
lda tmwTLoArg
|
||||
sta tmwTilesLo
|
||||
lda tmwTHiArg
|
||||
sta tmwTilesHi
|
||||
lda tmwWtArg
|
||||
sta tmwWt
|
||||
lda tmwHtArg
|
||||
sta tmwTy
|
||||
stz tmwMapIdx
|
||||
; row-0 dst offset = gRowOffsetLut[by*8] + bx*4 (ONCE)
|
||||
lda tmwByArg
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
tax
|
||||
lda tmwBx
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc gRowOffsetLut,x
|
||||
sta tmwRowBase
|
||||
|
||||
tmwRowTop:
|
||||
lda tmwWt
|
||||
sta tmwTx
|
||||
lda tmwRowBase
|
||||
tax ; X = dst offset, walks +4/column
|
||||
tmwColTop:
|
||||
ldy tmwMapIdx
|
||||
sep #0x20
|
||||
.a8
|
||||
lda [tmwMap], y ; map byte
|
||||
cmp #0xFF
|
||||
bne tmwDoTile
|
||||
rep #0x20
|
||||
.a16
|
||||
brl tmwCellNext ; skip: past the copy body's range
|
||||
tmwDoTile:
|
||||
rep #0x20
|
||||
.a16
|
||||
and #0x00FF ; idx (high byte was stale)
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a ; idx * 32 (sizeof proof in tile.c)
|
||||
clc
|
||||
adc tmwTilesLo
|
||||
sta tmwSrc+0
|
||||
lda tmwTilesHi
|
||||
adc #0 ; bank carry
|
||||
sta tmwSrc+2
|
||||
; 8 rows: src stride 4 via [0],y; dst stride 160 folded
|
||||
; into the long,x literals (the iigsTilePasteMark body).
|
||||
ldy #0
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+0,x
|
||||
ldy #2
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+2,x
|
||||
ldy #4
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+160,x
|
||||
ldy #6
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+162,x
|
||||
ldy #8
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+320,x
|
||||
ldy #10
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+322,x
|
||||
ldy #12
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+480,x
|
||||
ldy #14
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+482,x
|
||||
ldy #16
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+640,x
|
||||
ldy #18
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+642,x
|
||||
ldy #20
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+800,x
|
||||
ldy #22
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+802,x
|
||||
ldy #24
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+960,x
|
||||
ldy #26
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+962,x
|
||||
ldy #28
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+1120,x
|
||||
ldy #30
|
||||
lda [tmwSrc], y
|
||||
sta 0x012000+1122,x
|
||||
tmwCellNext:
|
||||
inc tmwMapIdx ; 16-bit (M=16 on both arrival paths)
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx ; dst offset += 4 (one tile column)
|
||||
dec tmwTx
|
||||
beq tmwRowDone
|
||||
brl tmwColTop
|
||||
tmwRowDone:
|
||||
lda tmwRowBase
|
||||
clc
|
||||
adc #1280 ; 8 SHR rows x 160 = one tile row
|
||||
sta tmwRowBase
|
||||
dec tmwTy
|
||||
beq tmwMark
|
||||
brl tmwRowTop
|
||||
|
||||
tmwMark:
|
||||
; One band union for the whole rectangle (over-marks
|
||||
; TILE_MAP_SKIP cells -- safe, present just re-uploads
|
||||
; clean pixels). Frame slots are dead now; reuse them:
|
||||
; tmwMapIdx = yEnd, tmwTx = minWord, tmwTy = maxWord.
|
||||
lda tmwByArg
|
||||
clc
|
||||
adc tmwHtArg
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sta tmwMapIdx ; yEnd = (by + hTiles) * 8
|
||||
lda tmwBx
|
||||
asl a
|
||||
sta tmwTx ; minWord = bx * 2
|
||||
lda tmwBx
|
||||
clc
|
||||
adc tmwWtArg
|
||||
asl a
|
||||
dec a
|
||||
sta tmwTy ; maxWord = (bx + wTiles) * 2 - 1
|
||||
lda tmwByArg
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
tax ; X = first row = by * 8
|
||||
sep #0x20
|
||||
.a8
|
||||
tmwMarkLoop:
|
||||
lda tmwTx
|
||||
cmp gStageMinWord,x
|
||||
bcs tmwNoMin
|
||||
sta gStageMinWord,x ; widen down
|
||||
tmwNoMin:
|
||||
lda tmwTy
|
||||
cmp gStageMaxWord,x
|
||||
bcc tmwNoMax
|
||||
beq tmwNoMax
|
||||
sta gStageMaxWord,x ; widen up
|
||||
tmwNoMax:
|
||||
inx
|
||||
cpx tmwMapIdx
|
||||
bcc tmwMarkLoop
|
||||
rep #0x30
|
||||
.a16
|
||||
.i16
|
||||
tsc
|
||||
clc
|
||||
adc #24 ; drop frame (22) + parked bx (2)
|
||||
tcs
|
||||
pld
|
||||
plb
|
||||
plp
|
||||
rtl
|
||||
|
||||
|
||||
|
||||
|
||||
; --------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -202,3 +202,5 @@ UBER: total wall time: 165280 ms (8264 frames @ 50 Hz)
|
|||
|
||||
UBER: press any key to exit
|
||||
|
||||
UBER: jlTileMapPaste 10x5: 49 iters / 16 frames = 153 ops/sec | hash=52B9646B
|
||||
UBER: gameFrameClean composite: 11 iters / 16 frames = 34 ops/sec | hash=C51FE171
|
||||
|
|
|
|||
|
|
@ -198,3 +198,5 @@ UBER: total wall time: 289833 ms (17390 frames @ 60 Hz)
|
|||
|
||||
UBER: press any key to exit
|
||||
|
||||
UBER: jlTileMapPaste 10x5: 49 iters / 18 frames = 163 ops/sec | hash=52B9646B
|
||||
UBER: gameFrameClean composite: 13 iters / 16 frames = 48 ops/sec | hash=C51FE171
|
||||
|
|
|
|||
|
|
@ -198,3 +198,5 @@ UBER: total wall time: 23142 ms (1620 frames @ 70 Hz)
|
|||
|
||||
UBER: press any key to exit
|
||||
|
||||
UBER: jlTileMapPaste 10x5: 113 iters / 18 frames = 450 ops/sec | hash=52B9646B
|
||||
UBER: gameFrameClean composite: 43 iters / 16 frames = 195 ops/sec | hash=C51FE171
|
||||
|
|
|
|||
|
|
@ -98,3 +98,5 @@ UBER-CHK: floodFillBounded: hash=EF6B6F51
|
|||
UBER-CHK: ----- end -----
|
||||
UBER: total wall time: 419016 ms (25141 frames @ 60 Hz)
|
||||
UBER: press any key to exit
|
||||
UBER: jlTileMapPaste 10x5: 15 iters / 16 frames = 56 ops/sec | hash=52B9646B
|
||||
UBER: gameFrameClean composite: 5 iters / 16 frames = 18 ops/sec | hash=C51FE171
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue