pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - Core interface
4 *
5 * Copyright (c) 2021 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 */
12
13
14#include "impl/pico9918_priv.h"
15/* pico9918_gpu_service: the register writes below are where a GPU program is armed,
16 and a host that handed the library the GPU wants it run there. */
18#include "overlay/splash.h" /* pico9918_reset re-arms the splash, as a host reset does */
19
20#include <string.h>
21
22
23/* The mode emitters stay out of pico9918_scan_line. That function is well past the Thumb-1 b.n
24 reach, so anything added to it relaxes branches and shuffles registers inside whichever emitters
25 are inlined there. Keeping them out of line costs nothing on either board. */
26#define EMITTER_NOINLINE PICO9918_NOINLINE
27
28#ifdef PICO_BUILD
29/* The DMA channel numbers are compile-time macros in the pico platform header, not
30 variables here: an extern channel number costs three instructions at every access.
31 The copy channel's two configs do live here. */
32PICO9918_COPY_STATE()
33#else
34/* Off-target the fills and the copy are plain structs. */
35pico9918_fill32_t pico9918_fill_border = {NULL, 0};
36pico9918_fill32_t pico9918_fill_masks = {NULL, 0};
37pico9918_fill32_t pico9918_fill_line = {NULL, 0};
38pico9918_copy32_t pico9918_copy = {NULL, NULL, 0};
39#endif
40
41/* not .scratch_x: that is where the fill writes */
42PICO9918_SECTION_SCRATCH_Y(buffer) static uint32_t bg;
43
44/* Where a scanline is arbitrated: the fill, the sprites, the bitmap layer and the composite all land
45 here. Its own bank, so the fill writes it from `bg`'s and a caller reads it against striped SRAM. */
46static PICO9918_SECTION_SCRATCH_X(buffer) uint8_t __aligned(4) scanlineBuffer[SCANLINE_BUFFER_BYTES];
47
48
49/* Declared on the impl surface, with the TEXT80_WIDE_ROW macro and the inline readers that go
50 with it, so the frame module does not call across the TU boundary for them every line. */
51pico9918_mode_t pico9918_cached_mode = TMS_MODE_GRAPHICS_I;
52
53/* Configured and claimed once, before the host brings up anything that shares the
54 DMA. Defined below, next to the tables it fills. */
55void initLookups(void);
56
57#if PICO9918_SINGLE_INSTANCE
58
59// VRAM is intentionally never zeroed at boot (pico9918_reset() below
60// leaves it alone - matches real TMS9918 hardware, whose VRAM content is
61// undefined at power-on); every other field is explicitly written by
62// pico9918_reset()/vdpRegisterReset() before anything reads it - so this
63// doesn't need the crt0 .bss zero-fill, and skipping it saves boot time.
64//
65// The 256-byte alignment is required: the GPU guards vram.bytes[0x8000] and
66// the palette with MPU regions that are whole 256-byte pages, and neither
67// range may cross a page boundary. A 256-aligned instance is what fixes where
68// inside its page each range lands.
69static pico9918_t __aligned(256) PICO9918_UNINITIALIZED(tms9918Inst);
70
71/* const so the instance address is a link-time constant rather than a pointer the
72 emitters have to load and keep live: every field offset then folds into its own
73 literal, which is what makes vram's offset within the struct cost nothing. */
74pico9918_t* const tms9918 = &tms9918Inst;
75
76/** \brief initialize the TMS9918 library in single-instance mode */
78void __time_critical_func(pico9918_init)(void)
79{
80 tms9918->vdpBase = PICO9918_BASE_TMS9918;
81#if PICO9918_BUILD_RUNTIME_CHIP
83#endif
84 initLookups();
86}
87
88#else
89
90#include <stdlib.h>
91
92/** \brief create a new TMS9918 */
93PICO9918_DLLEXPORT pico9918_t* pico9918_new(void)
94{
95 pico9918_t* tms9918 = (pico9918_t*)calloc(1, sizeof(pico9918_t));
96 if (tms9918 != NULL)
97 {
98 tms9918->vdpBase = PICO9918_BASE_TMS9918; /* see pico9918_init */
99#if PICO9918_BUILD_RUNTIME_CHIP
101#endif
102 initLookups();
103 pico9918_reset(tms9918);
104 }
105
106 return tms9918;
107}
108
109#endif
110
111
112static const pico9918_mode_t r1Modes[] = {TMS_MODE_GRAPHICS_I, TMS_MODE_MULTICOLOR, TMS_MODE_TEXT,
113 TMS_MODE_GRAPHICS_I};
114
115static inline pico9918_mode_t tmsMode(pico9918_t* tms9918)
116{
117 /* The pre-A part leaves M3 undecoded, so the bit selects nothing and M1/M2 still do. */
118 if ((TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_MODE_GRAPHICS_II) && PICO9918_GM2(tms9918))
119 {
120 return TMS_MODE_GRAPHICS_II;
121 }
122 /* An F18A honours M4 while still locked, so the test is the personality, not the lock. */
123 else if (PICO9918_M4(tms9918))
124 {
125 return TMS_MODE_TEXT80;
126 }
127 else
128 {
129 return r1Modes[(TMS_REGISTER(tms9918, TMS_REG_1) & (TMS_R1_MODE_MULTICOLOR | TMS_R1_MODE_TEXT)) >> 3];
130 }
131}
132
133/** \brief sprite size (8 or 16) */
134static inline uint8_t tmsSpriteSize(pico9918_t* tms9918)
135{
136 return TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_SPRITE_16 ? 16 : 8;
137}
138
139/** \brief sprite size (0 = 1x, 1 = 2x) */
140static inline bool tmsSpriteMag(pico9918_t* tms9918)
141{
142 return TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_SPRITE_MAG2;
143}
144
145/** \brief name table base address */
146static inline uint16_t tmsNameTableAddr(pico9918_t* tms9918)
147{
148 return (TMS_REGISTER(tms9918, TMS_REG_NAME_TABLE) & 0x0f) << 10;
149}
150
151/** \brief name table base address */
152static inline uint16_t tmsNameTable2Addr(pico9918_t* tms9918)
153{
154 return (TMS_REGISTER(tms9918, PICO9918_REG_NAME_TABLE2) & 0x0f) << 10;
155}
156
157/** \brief color table base address */
158static inline uint16_t tmsColorTableAddr(pico9918_t* tms9918)
159{
160 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
161
162 return (TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE) & mask) << 6;
163}
164
165/** \brief color table base address */
166static inline uint16_t tmsColorTable2Addr(pico9918_t* tms9918)
167{
168 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
169
170 return (TMS_REGISTER(tms9918, PICO9918_REG_COLOR_TABLE2) & mask) << 6;
171}
172
173/** \brief pattern table base address */
174static inline uint16_t tmsPatternTableAddr(pico9918_t* tms9918)
175{
176 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x04 : 0x07;
177
178 return (TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & mask) << 11;
179}
180
181/** \brief sprite attribute table base address */
182static inline uint16_t tmsSpriteAttrTableAddr(pico9918_t* tms9918)
183{
184 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_ATTR_TABLE) & 0x7f) << 7;
185}
186
187/** \brief sprite pattern table base address */
188static inline uint16_t tmsSpritePatternTableAddr(pico9918_t* tms9918)
189{
190 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_PATT_TABLE) & 0x07) << 11;
191}
192
193/** \brief background color */
194static inline pico9918_color_t tmsMainBgColor(pico9918_t* tms9918)
195{
196 return TMS_REGISTER(tms9918, TMS_REG_FG_BG_COLOR) & 0x0f;
197}
198
199/** \brief foreground color */
200static inline pico9918_color_t tmsMainFgColor(pico9918_t* tms9918)
201{
202 const pico9918_color_t c = (pico9918_color_t)(TMS_REGISTER(tms9918, TMS_REG_FG_BG_COLOR) >> 4);
203 return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
204}
205
206/** \brief foreground color */
207static inline pico9918_color_t tmsFgColor(pico9918_t* tms9918, uint8_t colorByte)
208{
209 const pico9918_color_t c = (pico9918_color_t)(colorByte >> 4);
210 return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
211}
212
213/** \brief background color */
214static inline pico9918_color_t tmsBgColor(pico9918_t* tms9918, uint8_t colorByte)
215{
216 const pico9918_color_t c = (pico9918_color_t)(colorByte & 0x0f);
217 return c == TMS_TRANSPARENT ? tmsMainBgColor(tms9918) : c;
218}
219
220
221// default palette 0xARGB
222static const uint16_t defaultPalette[] = {
223 //-- Palette 0, default TMS9918A palette
224 0x0000, 0xF000, 0xF2C3, 0xF5D6, 0xF54F, 0xF76F, 0xFD54, 0xF4EF, 0xFF54, 0xFF76, 0xFDC3, 0xFED6, 0xF2B2,
225 0xFC5C, 0xFCCC, 0xFFFF,
226 //-- Palette 1, ECM1 (0 index is always 000) version of palette 0
227 0x0000, 0xF2C3, 0xF000, 0xF54F, 0xF000, 0xFD54, 0xF000, 0xF4EF, 0xF000, 0xFCCC, 0xF000, 0xFDC3, 0xF000,
228 0xFC5C, 0xF000, 0xFFFF,
229 //-- Palette 2, CGA colors
230 0x0000, 0xF00A, 0xF0A0, 0xF0AA, 0xFA00, 0xFA0A, 0xFA50, 0xFAAA, 0xF555, 0xF55F, 0xF5F5, 0xF5FF, 0xFF55,
231 0xFF5F, 0xFFF5, 0xFFFF,
232 //-- Palette 3, ECM1 (0 index is always 000) version of palette 2
233 0x0000, 0xF555, 0xF000, 0xF00A, 0xF000, 0xF0A0, 0xF000, 0xF0AA, 0xF000, 0xFA00, 0xF000, 0xFA0A, 0xF000,
234 0xFA50, 0xF000, 0xFFFF};
235
236static PICO9918_NOINLINE void vdpRegisterReset(pico9918_t* tms9918)
237{
238 tms9918->isUnlocked = false;
239 tms9918->restart = 0;
240 tms9918->unlockCount = 0;
241 tms9918->lockedMask = 0x07;
242 memset(&TMS_REGISTER(tms9918, TMS_REG_0), 0, TMS_REGISTERS);
243 TMS_REGISTER(tms9918, TMS_REG_1) = 0x40;
244 TMS_REGISTER(tms9918, TMS_REG_3) = 0x10;
245 TMS_REGISTER(tms9918, TMS_REG_4) = 0x01;
246 TMS_REGISTER(tms9918, TMS_REG_5) = 0x0A;
247 TMS_REGISTER(tms9918, TMS_REG_6) = 0x02;
248 TMS_REGISTER(tms9918, TMS_REG_7) = 0xF2;
249 TMS_REGISTER(tms9918, PICO9918_REG_MAX_SCAN_SPRITES) = MAX_SPRITES - 1; // scanline sprites
250 TMS_REGISTER(tms9918, PICO9918_REG_VRAM_INC) = 1; // vram address increment register
251 TMS_REGISTER(tms9918, PICO9918_REG_MAX_SPRITES) = MAX_SPRITES; // Sprites to process
252 TMS_REGISTER(tms9918, PICO9918_REG_GPU_PC_MSB) = 0x40;
253}
254
255
256#if PICO9918_BUILD_RUNTIME_CHIP
257
258/** \brief the feature bits a personality answers to - the ladder, in one place */
259static uint8_t chipFeatures(pico9918_chip_t chip)
260{
261 switch (chip)
262 {
264 return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_CONFIG | PICO9918_FEAT_OVERLAY |
265 PICO9918_FEAT_BITMAP | PICO9918_FEAT_WIDE_T80 | PICO9918_FEAT_GPU_RAM;
267 return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_CONFIG | PICO9918_FEAT_OVERLAY |
268 PICO9918_FEAT_BITMAP | PICO9918_FEAT_GPU_RAM;
269 case PICO9918_CHIP_F18A: return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_BITMAP;
270 case PICO9918_CHIP_TMS9918A: return PICO9918_FEAT_BITMAP | PICO9918_FEAT_VRAM_4K;
271 default: return PICO9918_FEAT_VRAM_4K;
272 }
273}
274
275/** \brief select which chip this instance answers as */
277{
278 /* unsigned, so a value below the bottom of the ladder clamps here too rather than being stored */
279 if ((unsigned)chip > (unsigned)PICO9918_CHIP_MAX)
280 {
281 chip = PICO9918_CHIP_MAX;
282 }
283
284 tms9918->chip = (uint8_t)chip;
285 tms9918->features = chipFeatures(chip);
286
287#if !PICO9918_NO_SPLASH
289#endif
290
291 /* The wide line is a different palette layout, so the tier is a palette change */
292 tms9918->palDirty = 1;
293
294 /* the new personality either seeds its effects from the block or lets go of them */
295 tms9918->configDirty = true;
296 tms9918->configVdpDirty = true;
297
298 if (!PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK))
299 {
300 tms9918->isUnlocked = false;
301 tms9918->unlockCount = 0;
302 tms9918->lockedMask = 0x07;
303 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
304 }
305
306 TMS_STATUS(tms9918, PICO9918_SR_IDENT) = PICO9918_SR1_ID(tms9918);
307}
308
309/** \brief which chip this instance answers as */
314
315#endif // PICO9918_BUILD_RUNTIME_CHIP
316
317/** \brief reset the new TMS9918 */
319{
320 tms9918->regWriteStage0Value = 0;
321 tms9918->currentAddress = 0;
322 tms9918->gpuAddress = 0xFFFF; // "Odd" don't start value
323 tms9918->regWriteStage = 0;
324
325 tms9918->palWriteStage = 0;
326 tms9918->palWriteStage0Value = 0;
327 tms9918->flash = 0;
328 memset(&TMS_STATUS(tms9918, PICO9918_SR_STATUS), 0, TMS_STATUS_REGISTERS);
330 TMS_STATUS(tms9918, PICO9918_SR_IDENT) = PICO9918_SR1_ID(tms9918);
331 TMS_STATUS(tms9918, PICO9918_SR_VERSION) = 0x1A; // Version
332 tms9918->readAheadBuffer = 0;
333
334 vdpRegisterReset(tms9918);
335 TMS_REGISTER(tms9918, TMS_REG_1) = 0x00; // turn display off
336 TMS_REGISTER(tms9918, TMS_REG_7) = 0x00;
337 pico9918_cached_mode = TMS_MODE_GRAPHICS_I;
338
339 // set up default palettes (arm is little-endian, tms9900 is big-endian)
340 for (int i = 0; i < sizeof(defaultPalette) / sizeof(uint16_t); ++i)
341 {
342 tms9918->vram.map.pram[i] = __builtin_bswap16(defaultPalette[i]);
343 }
344
345 /* row-30 progressive has no border line, so nothing else invalidates the derived LUT */
346 tms9918->palDirty = 1;
347
348 pico9918_frame_reset_count_impl(PICO9918_INST_ONLY);
350
351 /* ram intentionally left in unknown state */
352}
353
354
355/**
356 * \brief destroy a TMS9918
357 *
358 * tms9918: tms9918 object to destroy / clean up
359 */
361{
362#if !PICO9918_SINGLE_INSTANCE
363 free(tms9918);
364 tms9918 = NULL;
365#endif
366}
367
368/**
369 * \brief write an address (mode = 1) to the tms9918
370 *
371 * data: the data (DB0 -> DB7) to send
372 */
373PICO9918_DLLEXPORT void __time_critical_func(pico9918_write_addr)(PICO9918_INST_ARG uint8_t data)
374{
376}
377
378/** \brief read from the status register */
383
384/** \brief read from the status register without resetting it */
389
390/**
391 * \brief write data (mode = 0) to the tms9918
392 *
393 * data: the data (DB0 -> DB7) to send
394 */
395PICO9918_DLLEXPORT void __time_critical_func(pico9918_write_data)(PICO9918_INST_ARG uint8_t data)
396{
398}
399
400
401/** \brief read data (mode = 0) from the tms9918 */
406
407/** \brief read data (mode = 0) from the tms9918 */
412
413/** \brief return true if both INT status and INT control set */
418
419/** \brief raise the INT status flag */
424
425/** \brief set status flag */
427void __time_critical_func(pico9918_set_status)(PICO9918_INST_ARG uint8_t status)
428{
430}
431
432static const uint32_t zeroWord = 0;
433
434/* Sprites and the bitmap layer are always on the 256-pixel grid, so their masks are one
435 bit per grid pixel whatever the mode. A tile layer's own line is not: 80 columns at eight bits a
436 pixel are 512 pixels and want a bit for each, which is what lets a layer be selected per pixel.
437 The two are different lengths *and* different units, and the same function must not take both. */
438typedef uint32_t BitMask[9];
439typedef uint32_t TileMask[SCANLINE_MASK_WORDS];
440
441/* A mask walked one `<<= 1` a pixel has to be unsigned: shifting a negative signed value left is
442 undefined, and a compiler may then fold the sign test away. `-fsanitize=shift-base` catches it. */
443#define MASK_NEXT_PIXEL 0x80000000u
444
445
446/* one object, so the per-scanline clear is a single transfer and the layout cannot drift */
447static PICO9918_SECTION_SCRATCH_X(lookup) struct
448{
449 BitMask rowBits; /* pixel mask */
450 BitMask rowTransparentSpriteBits; /* transparent sprite pixels */
451 BitMask rowSpriteBits; /* collision mask */
452} __aligned(4) rowMasks;
453
454/** \brief Test and update the sprite collision mask. */
455static inline uint32_t tmsTestCollisionMask(const uint32_t xPos, const uint32_t spritePixels,
456 const uint32_t spriteWidth)
457{
458 uint32_t rowSpriteBitsWord = xPos >> 5;
459 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
460
461 uint32_t validPixels =
462 (~rowMasks.rowSpriteBits[rowSpriteBitsWord]) & (spritePixels >> rowSpriteBitsWordBit);
463 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= validPixels;
464 validPixels <<= rowSpriteBitsWordBit;
465
466 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
467 if (rowSpriteBitsWordBit < spriteWidth)
468 {
469 uint32_t right = (~rowMasks.rowSpriteBits[++rowSpriteBitsWord]) & (spritePixels << rowSpriteBitsWordBit);
470 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= right;
471 validPixels |= (right >> rowSpriteBitsWordBit);
472 }
473
474 return validPixels;
475}
476
477
478/** \brief set the transparent sprite mask. */
479static inline void tmsSetTransparentSpriteMask(const uint32_t xPos, const uint32_t spritePixels,
480 const uint32_t spriteWidth)
481{
482 uint32_t rowSpriteBitsWord = xPos >> 5;
483 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
484
485 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord] |= spritePixels >> rowSpriteBitsWordBit;
486
487 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
488 if (rowSpriteBitsWordBit < spriteWidth)
489 {
490 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord + 1] |= spritePixels << rowSpriteBitsWordBit;
491 }
492}
493
494
495/** \brief Clear the row pixels bit mask. */
496static inline void tmsClearRowBitsMask(const uint32_t xPos, const uint32_t tilePixels,
497 const uint32_t tileWidth, BitMask rowBitsMask)
498{
499 uint32_t rowBitsWord = xPos >> 5;
500 uint32_t rowBitsWordBit = xPos & 0x1f;
501
502 uint32_t validPixels = tilePixels >> rowBitsWordBit;
503 rowBitsMask[rowBitsWord] &= ~validPixels;
504
505 rowBitsWordBit = 32 - rowBitsWordBit;
506 if (rowBitsWordBit < tileWidth)
507 {
508 ++rowBitsWord;
509 uint32_t right = (tilePixels << rowBitsWordBit);
510 rowBitsMask[rowBitsWord] &= ~right;
511 }
512}
513
514/** \brief Update the row pixels bit mask (aligned - no word boundary crossing). */
515static inline void tmsUpdateRowBitsMaskAligned(const uint32_t xPos, const uint32_t tilePixels,
516 BitMask rowBitsMask)
517{
518 rowBitsMask[xPos >> 5] |= tilePixels >> (xPos & 0x1f);
519}
520
521/** \brief Test against the row pixels bit mask (aligned - no word boundary crossing). */
522static inline uint32_t tmsTestRowBitsMaskAligned(const uint32_t xPos, const uint32_t tilePixels,
523 const BitMask rowBitsMask)
524{
525 return tilePixels & ~(rowBitsMask[xPos >> 5] << (xPos & 0x1f));
526}
527
528/* Out of line on purpose: two calls a line, and inlined it puts a second copy of the whole
529 straight-line word copy into the scanline body, which costs the board more than the call. */
530static PICO9918_NOINLINE void tmsCopyAlignMask(TileMask dstMask, const TileMask srcMask, int pixelShift)
531{
532 if (pixelShift == 0)
533 {
534 /* straight-line, not a loop: -O3 rewrites the loop form into a bootrom memcpy call */
535 dstMask[0] = srcMask[0];
536 dstMask[1] = srcMask[1];
537 dstMask[2] = srcMask[2];
538 dstMask[3] = srcMask[3];
539 dstMask[4] = srcMask[4];
540 dstMask[5] = srcMask[5];
541 dstMask[6] = srcMask[6];
542 dstMask[7] = srcMask[7];
543 dstMask[8] = srcMask[8];
544#if SCANLINE_MASK_WORDS > 9
545 dstMask[9] = srcMask[9];
546 dstMask[10] = srcMask[10];
547 dstMask[11] = srcMask[11];
548 dstMask[12] = srcMask[12];
549 dstMask[13] = srcMask[13];
550 dstMask[14] = srcMask[14];
551 dstMask[15] = srcMask[15];
552 dstMask[16] = srcMask[16];
553#endif
554 return;
555 }
556
557 if (pixelShift > 0)
558 {
559 // Right shift - carry flows left to right (low to high index)
560 uint32_t carry = 0;
561 for (int i = 0; i < SCANLINE_MASK_WORDS; i++) // LOW to HIGH
562 {
563 uint32_t word = srcMask[i];
564 dstMask[i] = (word >> pixelShift) | carry;
565 carry = word << (32 - pixelShift);
566 }
567 }
568 else
569 {
570 // Left shift - carry flows right to left (high to low index)
571 pixelShift = -pixelShift;
572 uint32_t carry = 0;
573 for (int i = SCANLINE_MASK_WORDS - 1; i >= 0; i--) // HIGH to LOW
574 {
575 uint32_t word = srcMask[i];
576 dstMask[i] = (word << pixelShift) | carry;
577 carry = word >> (32 - pixelShift);
578 }
579 }
580}
581
582/* The tile2 mask's trip home. Unshifted it is a copy out and straight back, and nothing
583 between the two writes either mask - the row emitters touch layerSelectionMask only on a
584 tile2 pass - so there is nothing to bring home. Under any scroll the round trip nets a
585 shift of -t2Scroll and loses the bits it carries off the end, so it must run.
586
587 The test lives here rather than at the call site because the scanline body is at the size
588 where one more branch in it re-plans the whole function. */
589static PICO9918_NOINLINE void tmsRestoreAlignMask(TileMask dstMask, const TileMask srcMask,
590 int pixelShift, int otherShift)
591{
592 if (pixelShift | otherShift) tmsCopyAlignMask(dstMask, srcMask, pixelShift);
593}
594
595
596/** \brief Test and update the row pixels bit mask. */
597static inline uint32_t tmsTestRowBitsMask(const uint32_t xPos, const uint32_t tilePixels,
598 const uint32_t tileWidth, const bool update, const bool test,
599 const bool testColl)
600{
601 uint32_t rowBitsWord = xPos >> 5;
602 uint32_t rowBitsWordBit = xPos & 0x1f;
603
604 uint32_t validPixels = tilePixels >> rowBitsWordBit;
605 if (testColl) validPixels &= ~rowMasks.rowSpriteBits[rowBitsWord];
606 if (test) validPixels &= ~rowMasks.rowBits[rowBitsWord];
607 if (update) rowMasks.rowBits[rowBitsWord] |= validPixels;
608 if (test || testColl) validPixels <<= rowBitsWordBit;
609
610 rowBitsWordBit = 32 - rowBitsWordBit;
611 if (rowBitsWordBit < tileWidth)
612 {
613 ++rowBitsWord;
614 uint32_t right = (tilePixels << rowBitsWordBit);
615
616 if (testColl) right &= ~rowMasks.rowSpriteBits[rowBitsWord];
617 if (test) right &= ~rowMasks.rowBits[rowBitsWord];
618
619 if (update) rowMasks.rowBits[rowBitsWord] |= right;
620 if (test || testColl) validPixels |= (right >> rowBitsWordBit);
621 }
622
623 return (test || testColl) ? validPixels : tilePixels;
624}
625
626
627/* lookup for combining ecm nibbles, returning 4 pixels.
628 *
629 * Deliberately a full table in striped SRAM. Plane 3 only ever lands in bit 2 of a pixel and
630 * nothing else does at any level (the palette note below), so a 256-entry two-plane table plus a
631 * 16-entry plane 3 mask would give the same words in a fraction of the space. That was built and
632 * rejected: it costs a load and an OR on every ECM3 quad, and the tile path takes far more lookups
633 * a line than sprites do. Revisit it only when something else needs the room. It cannot live in
634 * .scratch_x either way - core 1's stack has the top half of that bank.
635 */
636/* Every entry is written by ecmLookupInit() before `lookupsReady` is ever set, so it does not
637 need the crt0 .bss zero-fill either. */
638static uint32_t __aligned(8) PICO9918_UNINITIALIZED(ecmLookup)[16 * 16 * 16];
639
640static uint8_t PICO9918_IN_FLASH_FUNC(ecmByte)(bool h, bool m, bool l)
641{
642 return (h << 2) | (m << 1) | l;
643}
644
645/* lookup from bit planes: 333322221111 to merged palette values for four pixels
646 * NOTE: The left-most pixel is stored in the least significant byte of the result
647 * because it's more efficient to offload them that way
648 */
649static void PICO9918_IN_FLASH_FUNC(ecmLookupInit)(void)
650{
651 for (uint16_t i = 0; i < 16 * 16 * 16; ++i)
652 {
653 ecmLookup[i] =
654 (ecmByte(i & 0x800, i & 0x080, i & 0x008)) | (ecmByte(i & 0x400, i & 0x040, i & 0x004) << 8) |
655 (ecmByte(i & 0x200, i & 0x020, i & 0x002) << 16) | (ecmByte(i & 0x100, i & 0x010, i & 0x001) << 24);
656 }
657}
658
659/* random note about how palettes are applied:
660 * PR Address bit: 0 1 2 3 4 5
661 * --------------------------------------
662 * original mode: ps0 ps1 cs0 cs1 cs2 cs3
663 * 1-bit (ECM1) : ps0 cs0 cs1 cs2 cs3 px0
664 * 2-bit (ECM2) : cs0 cs1 cs2 cs3 px1 px0
665 * 3-bit (ECM3) : cs0 cs1 cs2 px2 px1 px0
666*/
667
668
669/*
670 * to generate the doubled pixels required when the sprite MAG flag is set,
671 * use a lookup table. generate the doubledBits lookup table when we need it
672 * using doubledBitsNibble.
673 */
674static uint8_t __aligned(4) doubledBitsNibble[16] = {0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f,
675 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff};
676
677/* lookup for doubling pixel patterns in mag mode */
678static PICO9918_SECTION_SCRATCH_X(lookup) uint16_t __aligned(4) doubledBits[256];
679static void PICO9918_IN_FLASH_FUNC(doubledBitsInit)(void)
680{
681 for (int i = 0; i < 256; ++i)
682 {
683 doubledBits[i] = (doubledBitsNibble[(i & 0xf0) >> 4] << 8) | doubledBitsNibble[i & 0x0f];
684 }
685}
686
687/* reversed bits in a byte */
688static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits[256];
689
690static uint8_t PICO9918_IN_FLASH_FUNC(reverseBits)(uint8_t byte)
691{
692 byte = (byte & 0xf0) >> 4 | (byte & 0x0f) << 4;
693 byte = (byte & 0xcc) >> 2 | (byte & 0x33) << 2;
694 return (byte & 0xaa) >> 1 | (byte & 0x55) << 1;
695}
696
697/* the same reversal a text cell wants: six bits, so the two pixels it never shows fall off the
698 bottom and the mirror lands back at bit 7. Folding the shift into the table saves a shift and a
699 truncation on each of the three planes and the mask - paid only by a flipped cell, and a row of
700 those is the most expensive row a text mode has. */
701static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits6[256];
702
703static void PICO9918_IN_FLASH_FUNC(reversedBitsInit)(void)
704{
705 for (int i = 0; i < 256; ++i)
706 {
707 reversedBits[i] = reverseBits(i);
708 reversedBits6[i] = reverseBits(i) << 2;
709 }
710}
711
712/* a 6-bit palette index applied to all four bytes of a uint32_t, which is one multiply and wants no
713 table at all.
714
715 Spelling it as a multiply is what makes that true. Left to itself GCC expands the constant
716 multiply into a run of shifts and adds, because materialising the constant that way costs nothing
717 - the right call for a cold caller and the wrong one for three hot ones. `mul` rather than
718 `muls`: GCC wraps inline asm in `.syntax divided`, where the Thumb-1 multiply takes two operands
719 and always sets the flags. The C arm keeps the host build and the init-time constant folding at
720 ecm0PaletteInit. */
721static inline uint32_t repeatedPalette(const uint32_t index)
722{
723#ifdef PICO_BUILD
724 uint32_t repeated = index;
725 __asm__("mul %0, %1" : "+l"(repeated) : "l"(0x01010101u));
726 return repeated;
727#else
728 return index * 0x01010101u;
729#endif
730}
731
732/* The same value, except that colour 0 of each sub-palette holds what a tile writes where it draws
733 nothing - so this one cannot be arithmetic. ECM0 tiles index it, and transparency then costs them
734 no test: `pal` is always a multiple of 16, so those four entries are exactly the zero colours. */
735static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t __aligned(4) ecm0Palette[64];
736
737static void PICO9918_IN_FLASH_FUNC(ecm0PaletteInit)(void)
738{
739 for (int i = 0; i < 64; ++i)
740 {
741 ecm0Palette[i] = repeatedPalette(i);
742 }
743}
744
745/* What a tile layer writes where it draws nothing. Hardware marks a zero tile colour as not-a-pixel
746 and falls through to the backdrop; our layer buffer carries no such bit, so the backdrop colour
747 goes in directly. The exception is a non-priority bitmap layer, where zero is the composite's own
748 transparency marker and letting the layer show through matters more. Decided once per scanline. */
749static uint32_t transparentPixels[2];
750
751/* a lookup from a 4-bit mask to a word of 8-bit masks (reversed byte order) */
752static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t __aligned(4) maskExpandNibbleToWordRev[16] = {
753 0x00000000, 0xff000000, 0x00ff0000, 0xffff0000, 0x0000ff00, 0xff00ff00, 0x00ffff00, 0xffffff00,
754 0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffff00ff, 0x0000ffff, 0xff00ffff, 0x00ffffff, 0xffffffff};
755
756/* A 2bpp bitmap-layer nibble as its two pixels, low byte leftmost. Two of these make one
757 source byte's four pixels into one output word, which is the whole point.
758 nibble pixels value
759 0b00_00 0, 0 0x0000
760 0b01_10 1, 2 0x0201
761 0b11_11 3, 3 0x0303 */
762static PICO9918_SECTION_SCRATCH_X(lookup) uint16_t __aligned(4) bmlExpand2bpp[16] = {
763 0x0000, 0x0100, 0x0200, 0x0300, 0x0001, 0x0101, 0x0201, 0x0301,
764 0x0002, 0x0102, 0x0202, 0x0302, 0x0003, 0x0103, 0x0203, 0x0303};
765
766bool lookupsReady = false;
767void PICO9918_IN_FLASH_FUNC(initLookups)(void)
768{
769 if (lookupsReady) return;
770
771 PICO9918_DMA_CLAIM();
772
773 ecmLookupInit();
774 doubledBitsInit();
775 reversedBitsInit();
776 ecm0PaletteInit();
777
778 /* every fill is configured here: one triggered before a lazy init reached it would run unconfigured */
779 PICO9918_FILL32_INIT(PICO9918_FILL_LINE, &bg);
780 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_LINE, TMS9918_PIXELS_X / 4);
781
782 PICO9918_FILL32_INIT(PICO9918_FILL_MASKS, &zeroWord);
783 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_MASKS, sizeof(rowMasks) / sizeof(uint32_t));
784
785 PICO9918_FILL32_INIT(PICO9918_FILL_BORDER, &pico9918_border_bg);
786
787 PICO9918_COPY_INIT(PICO9918_COPY);
788
789 lookupsReady = true;
790}
791
792/* a tile plane's byte split into its two quads: the high nibble - the cell's left four pixels - at
793 * bit 16, the low nibble at bit 0. Three of these OR together into one accumulator holding both of
794 * the cell's ecmLookup indices, `index >> 16` and `(uint16_t)index`.
795 */
796static inline uint32_t ecmSplitQuads(const uint32_t patt)
797{
798 return (patt | (patt << 12)) & 0x000f000fu;
799}
800
801/* the index into ecmLookup for four sprite pixels, from the three left-aligned plane words: each
802 * plane's top nibble is this quad's bit for that plane, `sb0` being plane 1. The tile path's `patt`
803 * numbers them the other way, plane 3 first, and indexes the same table.
804 *
805 * Not for correctness - the planes above `ecm` are zero and only ever shifted - but for shape:
806 * `ecm` is a scanline invariant, so GCC unswitches the emit loops on it and each level gets a
807 * straight-line body with the unused planes' terms dead.
808 */
809static inline uint32_t calculateEcmIndex(const uint32_t ecm, const uint32_t sb0, const uint32_t sb1,
810 const uint32_t sb2)
811{
812 uint32_t ecmIndex = 0;
813 switch (ecm)
814 {
815 case 3:
816 ecmIndex = sb2 >> 28;
817 // fallthrough
818 case 2:
819 ecmIndex = (ecmIndex << 4) | (sb1 >> 28);
820 // fallthrough
821 default: ecmIndex = (ecmIndex << 4) | (sb0 >> 28);
822 }
823 return ecmIndex;
824}
825
826static inline void loadSpriteData(const uint8_t* vram, uint32_t* spriteBits, uint32_t pattOffset,
827 uint32_t* pattMask, const uint32_t ecm, const uint32_t ecmOffset,
828 const bool flipX, const bool sprite16)
829{
830 int i = 0;
831 do // do-while since behavior for ecm=0 and ecm==1 is the same
832 {
833 uint32_t patt = vram[pattOffset];
834 if (flipX) patt = reversedBits[patt];
835 uint32_t bits = patt << ((flipX && sprite16) ? 16 : 24);
836
837 if (sprite16)
838 {
839 patt = vram[pattOffset + PATTERN_BYTES * 2];
840 if (flipX) patt = reversedBits[patt];
841 bits |= patt << (flipX ? 24 : 16);
842 }
843 spriteBits[i] = bits;
844 *pattMask |= bits;
845 pattOffset += ecmOffset;
846 } while (++i < ecm);
847}
848
849
850/* The sprites this scanline draws, in list order. Each carries its attribute with the row inside
851 the pattern in place of the y, which the drawing pass does not need - the collect pass has the
852 whole word in a register for the zero test anyway, so keeping it spares that pass three reads of
853 VRAM, which shares its bank with the DMA and the PIO. The index is only ever read to report a
854 fifth sprite, so it sits apart rather than widening the record every sprite pays for. */
855static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t spriteAttrRows[MAX_SPRITES];
856static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t spriteIndices[MAX_SPRITES];
857
858/**
859 * \brief Which sprites this scanline draws at all, and where in each pattern it starts.
860 *
861 * The y tests want the scanline, the wrap threshold and the table bounds. The drawing pass wants
862 * the ECM settings, the palette and the pattern table. Neither wants the other's, and together
863 * they are more than the eight registers hold - so the list is walked once here and the drawing
864 * pass reads a row at a time instead of carrying both sets through every sprite.
865 */
866static uint32_t __time_critical_func(collectSpriteRows)(PICO9918_INST_ARG uint16_t y)
867{
868 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
869 const uint32_t row30Mode =
870 (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ROW30) & unlockedMask;
871
872 /* the wrap threshold and the row both carry the YPOS -1 offset, so the walk stays in raw YPOS */
873 const int32_t realY = (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_Y_REAL) ? 0 : 1;
874 const int32_t maxY = (row30Mode ? 0xf0 : 0xe0) - realY;
875 const int32_t yAdj = (int32_t)y - realY;
876
877 uint32_t maxSprites = TMS_REGISTER(tms9918, PICO9918_REG_MAX_SPRITES);
878 if (maxSprites > MAX_SPRITES) maxSprites = MAX_SPRITES;
879
880 const uint8_t* spriteAttr = tms9918->vram.bytes + tmsSpriteAttrTableAddr(tms9918);
881 uint32_t count = 0;
882
883 for (uint32_t spriteIdx = 0; spriteIdx < maxSprites; ++spriteIdx, spriteAttr += SPRITE_ATTR_BYTES)
884 {
885 int32_t yPos = spriteAttr[SPRITE_ATTR_Y];
886
887 /* stop processing when yPos == LAST_SPRITE_YPOS */
888 if (yPos == LAST_SPRITE_YPOS && !row30Mode)
889 {
890 break;
891 }
892
893 /* check if sprite position is in the -31 to 0 range and move back to top */
894 if (yPos > maxY) yPos -= 256;
895
896 const int32_t pattRow = yAdj - yPos;
897 if ((uint32_t)pattRow > 31)
898 {
899 continue;
900 }
901
902 const uint32_t attr = *(const uint32_t*)spriteAttr;
903
904 if (attr == 0 && unlockedMask)
905 {
906 continue;
907 }
908
909 spriteAttrRows[count] = attr;
910 ((uint8_t*)&spriteAttrRows[count])[SPRITE_ATTR_Y] = (uint8_t)pattRow;
911 spriteIndices[count] = (uint8_t)spriteIdx;
912 ++count;
913 }
914
915 return count;
916}
917
918/** \brief the sprite ECM level, zero on a locked device - the one term a clone can pin */
919static inline uint32_t spriteEcm(PICO9918_INST_ONLY_ARG)
920{
921 return (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ECM_SPRITE) &
922 -(uint32_t)PICO9918_UNLOCKED(tms9918);
923}
924
925/** \brief Output Sprites to a scanline. ecm0 pins the ECM level to zero, which folds the
926 * plane loop to one pass, the colour shift to nothing and the whole ECM emit arm away.
927 */
928static inline uint8_t __time_critical_func(renderSprites)(PICO9918_INST_ARG const uint32_t spriteCount,
929 const bool spriteMag, const bool wide,
930 const bool ecm0,
931 uint8_t pixels[TMS9918_PIXELS_X])
932{
933 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
934 const uint8_t* const vram = tms9918->vram.bytes;
935 bool hasSprites = false;
936 const uint8_t spriteSize = tmsSpriteSize(tms9918);
937 const bool sprite16 = spriteSize == 16;
938 const uint8_t spriteIdxMask = sprite16 ? 0xfc : 0xff;
939 const uint8_t spriteColorMask = 0x8f | unlockedMask;
940 const uint8_t spriteSizePx = spriteSize << spriteMag;
941 const uint16_t spritePatternAddr = tmsSpritePatternTableAddr(tms9918);
942 uint32_t spritesShown = 0;
943
944 /* the sprite-number field reads zero unless a fifth sprite latches one in */
945 uint8_t tempStatus = 0;
946 uint32_t transparentCount = 0;
947
948 // ecm settings
949 const uint32_t ecm = ecm0 ? 0 : spriteEcm(PICO9918_INST_ONLY);
950 const uint32_t ecmColorOffset = (ecm == 3) ? 2 : ecm;
951 const uint32_t ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
952 const uint32_t ecmOffset =
953 0x800 >> ((TMS_REGISTER(tms9918, PICO9918_REG_PAGE_SIZE) & PICO9918_R29_SPRITE_STRIDE) >> 6);
954
955 uint8_t pal = (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_SELECT) & PICO9918_R24_SPRITE_PS) & unlockedMask;
956 if (ecm == 1)
957 {
958 pal &= 0x20;
959 }
960 else if (ecm)
961 {
962 pal = 0;
963 }
964
965 const uint32_t scanlineSprites = TMS_REGISTER(tms9918, PICO9918_REG_MAX_SCAN_SPRITES);
966 const uint32_t unlimited =
967 (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_REPORT_MAX) & unlockedMask;
968
969 for (uint32_t n = 0; n < spriteCount; ++n)
970 {
971 const uint8_t* spriteAttr = (const uint8_t*)&spriteAttrRows[n];
972
973 int32_t pattRow = spriteAttr[SPRITE_ATTR_Y] >> spriteMag;
974
975 uint8_t thisSpriteSize = spriteSize;
976 bool thisSprite16 = sprite16;
977 uint8_t thisSpriteIdxMask = spriteIdxMask;
978 uint8_t thisSpriteSizePx = spriteSizePx;
979 uint8_t spriteAttrColor = spriteAttr[SPRITE_ATTR_COLOR] & spriteColorMask;
980 bool opaq = false;
981
982 if (spriteAttrColor & 0x10)
983 {
984 if (sprite16)
985 {
986 // PICO9918-specific. If all sprites are 16px anyway, this bit is used to have opaque sprites
987 opaq = true;
988 }
989 else
990 {
991 thisSpriteSize = 16;
992 thisSprite16 = true;
993 thisSpriteIdxMask = 0xfc;
994 thisSpriteSizePx = thisSpriteSize << spriteMag;
995 }
996 }
997
998 /* check if sprite is visible on this line */
999 if (pattRow >= thisSpriteSize)
1000 {
1001 continue;
1002 }
1003
1004 /* have we exceeded the scanline sprite limit? */
1005 if (++spritesShown > MAX_SCANLINE_SPRITES)
1006 {
1007 if (((tempStatus & PICO9918_SR0_5S) == 0) && (!unlimited || spritesShown > scanlineSprites))
1008 {
1009 tempStatus |= PICO9918_SR0_5S | spriteIndices[n];
1010 }
1011
1012 if (spritesShown > scanlineSprites) break;
1013 }
1014
1015 const int32_t earlyClockOffset = (spriteAttrColor & 0x80) ? -32 : 0;
1016 int32_t xPos = (int32_t)(spriteAttr[SPRITE_ATTR_X]) + earlyClockOffset;
1017 if ((xPos > TMS9918_PIXELS_X) || (-xPos > thisSpriteSizePx))
1018 {
1019 continue;
1020 }
1021
1022 if (spriteAttrColor & 0x20) pattRow = thisSpriteSize - pattRow - 1; // flip Y?
1023
1024 /* sprite is visible on this line */
1025 uint8_t spriteColor = (spriteAttrColor & ecmColorMask) << ecmColorOffset;
1026 const uint8_t pattIdx = spriteAttr[SPRITE_ATTR_NAME] & thisSpriteIdxMask;
1027 uint16_t pattOffset = spritePatternAddr + pattIdx * PATTERN_BYTES + (uint16_t)pattRow;
1028
1029
1030 uint32_t pattMask = 0;
1031 uint32_t spriteBits[3] = {0};
1032 const bool flipX = spriteAttrColor & 0x40;
1033
1034 loadSpriteData(vram, spriteBits, pattOffset, &pattMask, ecm, ecmOffset, flipX, thisSprite16);
1035
1036 if (opaq) pattMask = 0xffff0000;
1037
1038 /* bail early if no bits to draw */
1039 if (!pattMask)
1040 {
1041 continue;
1042 }
1043
1044 if (spriteMag)
1045 {
1046 pattMask = ((uint32_t)doubledBits[pattMask >> 24] << 16) | doubledBits[(pattMask >> 16) & 0xff];
1047 }
1048
1049 /* perform clipping operations */
1050 if (xPos < 0)
1051 {
1052 int32_t absX = -xPos;
1053 uint32_t offset = absX >> spriteMag;
1054 spriteBits[2] <<= offset;
1055 spriteBits[1] <<= offset;
1056 spriteBits[0] <<= offset;
1057 pattMask <<= absX;
1058
1059 /* bail early if no bits to draw */
1060 if (!pattMask)
1061 {
1062 continue;
1063 }
1064
1065 thisSpriteSizePx += xPos;
1066 xPos = 0;
1067 }
1068
1069 int pixelsLeft = TMS9918_PIXELS_X - xPos;
1070 if (pixelsLeft < thisSpriteSizePx)
1071 {
1072 thisSpriteSizePx = pixelsLeft;
1073 pattMask &= ~((1u << (32 - pixelsLeft)) - 1);
1074 }
1075
1076 /* test and update the collision mask */
1077 uint32_t validPixels = tmsTestCollisionMask(xPos, pattMask, thisSpriteSizePx);
1078
1079 /* if the result is different, we collided */
1080 if (validPixels != pattMask)
1081 {
1082 tempStatus |= PICO9918_SR0_COLLISION;
1083 }
1084
1085 // Render valid pixels to the scanline
1086 if (ecm || (spriteColor != TMS_TRANSPARENT))
1087 {
1088 hasSprites = true;
1089 spriteColor |= pal;
1090 if (ecm)
1091 {
1092
1093 uint32_t quadPal = repeatedPalette(spriteColor);
1094
1095 if (spriteMag)
1096 {
1097 uint8_t* p = pixels + (wide ? xPos * 2 : xPos);
1098 const uint32_t step = wide ? 2 : 1;
1099 uint32_t bits = validPixels;
1100
1101 while (bits)
1102 {
1103 if (bits >> 24)
1104 {
1105 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1106 uint32_t quad = ecmLookup[ecmIndex] | quadPal;
1107
1108 for (int n = 0; n < 4; ++n)
1109 {
1110 const uint8_t v = (uint8_t)quad;
1111 if (bits & MASK_NEXT_PIXEL)
1112 {
1113 if (wide)
1114 *(uint16_t*)p = v | (v << 8);
1115 else
1116 p[0] = v;
1117 }
1118 bits <<= 1;
1119 if (bits & MASK_NEXT_PIXEL)
1120 {
1121 if (wide)
1122 *(uint16_t*)(p + 2) = v | (v << 8);
1123 else
1124 p[1] = v;
1125 }
1126 bits <<= 1;
1127 p += 2 * step;
1128 quad >>= 8;
1129 }
1130 }
1131 else
1132 {
1133 bits <<= 8;
1134 p += 8 * step;
1135 }
1136 spriteBits[2] <<= 4;
1137 spriteBits[1] <<= 4;
1138 spriteBits[0] <<= 4;
1139 }
1140 }
1141 else if (wide)
1142 {
1143 uint32_t x = xPos;
1144
1145 while (validPixels)
1146 {
1147 uint32_t chunkMask = validPixels >> 28;
1148 if (chunkMask)
1149 {
1150 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1151 uint32_t color = ecmLookup[ecmIndex] | quadPal;
1152 uint8_t* q = pixels + x * 2;
1153
1154 for (int n = 0; n < 4; ++n)
1155 {
1156 if (chunkMask & 0x8)
1157 {
1158 const uint8_t v = (uint8_t)color;
1159 *(uint16_t*)q = v | (v << 8);
1160 }
1161 chunkMask <<= 1;
1162 color >>= 8;
1163 q += 2;
1164 }
1165 }
1166 spriteBits[2] <<= 4;
1167 spriteBits[1] <<= 4;
1168 spriteBits[0] <<= 4;
1169 x += 4;
1170 validPixels <<= 4;
1171 }
1172 }
1173 else // regular ecm sprite (8 or 16px, non-magnified)
1174 {
1175
1176 // get him to be word aligned so we can smash out 4 pixels at a time
1177 uint32_t quadOffset = xPos >> 2;
1178 const uint32_t pixOffset = xPos & 0x3;
1179 validPixels >>= pixOffset;
1180 spriteBits[2] >>= pixOffset;
1181 spriteBits[1] >>= pixOffset;
1182 spriteBits[0] >>= pixOffset;
1183
1184 uint32_t* quadPixels = (uint32_t*)pixels;
1185
1186 while (validPixels)
1187 {
1188 uint32_t chunkMask = validPixels >> 28;
1189 if (chunkMask == 0x0f)
1190 {
1191 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1192 quadPixels[quadOffset] = ecmLookup[ecmIndex] | quadPal;
1193 }
1194 else if (chunkMask)
1195 {
1196 const uint32_t maskQuad = maskExpandNibbleToWordRev[chunkMask];
1197 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1198 const uint32_t color = ecmLookup[ecmIndex] | quadPal;
1199 quadPixels[quadOffset] = (quadPixels[quadOffset] & ~maskQuad) | (color & maskQuad);
1200 }
1201 spriteBits[2] <<= 4;
1202 spriteBits[1] <<= 4;
1203 spriteBits[0] <<= 4;
1204 ++quadOffset;
1205 validPixels <<= 4;
1206 }
1207 }
1208 }
1209 else // non-ecm single-color sprite
1210 {
1211 if (!wide && pico9918_cached_mode == TMS_MODE_TEXT80) spriteColor |= spriteColor << 4;
1212
1213 while (validPixels)
1214 {
1215 if ((int32_t)validPixels < 0)
1216 {
1217 if (wide)
1218 *(uint16_t*)(pixels + xPos * 2) = spriteColor | (spriteColor << 8);
1219 else
1220 pixels[xPos] = spriteColor;
1221 }
1222 validPixels <<= 1;
1223 ++xPos;
1224 }
1225 }
1226 }
1227 else
1228 {
1229 // keep track of the transparent sprites, we remove them from the sprite mask later
1230 tmsSetTransparentSpriteMask(xPos, validPixels, thisSpriteSizePx);
1231 ++transparentCount;
1232 }
1233 }
1234
1235 tms9918->scanlineHasSprites = hasSprites;
1236
1237 // remove the transparent sprite pixels if there are any
1238 if (transparentCount)
1239 {
1240 for (int i = 0; i < 9; ++i)
1241 {
1242 rowMasks.rowSpriteBits[i] ^= rowMasks.rowTransparentSpriteBits[i];
1243 }
1244 }
1245
1246
1247 return tempStatus;
1248}
1249
1250static EMITTER_NOINLINE uint8_t
1251__time_critical_func(pico9918_output_sprites)(PICO9918_INST_ARG uint16_t y, uint8_t pixels[TMS9918_PIXELS_X])
1252{
1253 const bool spriteMag = tmsSpriteMag(tms9918);
1254
1255 if (TMS_REGISTER(tms9918, TMS_REG_0) &
1256 TMS_R0_DOUBLE_ROWS) // double rows (high-res)? still only have low-res sprites
1257 y >>= 1;
1258
1259 const uint32_t spriteCount = collectSpriteRows(PICO9918_INST y);
1260
1261 /* LOAD-BEARING: pico9918_scan_line clears scanlineHasSprites before it dispatches, and
1262 * renderSprites would only store that same false back, so nothing is owed on this path. */
1263 if (spriteCount == 0) return 0;
1264
1265#if PICO9918_TEXT80_8BPP
1266 /* the store width is inside the emit loop, so it rides a clone parameter rather than a test */
1267 if (TEXT80_WIDE_ROW)
1268 {
1269 return spriteMag ? renderSprites(PICO9918_INST spriteCount, true, true, false, pixels)
1270 : renderSprites(PICO9918_INST spriteCount, false, true, false, pixels);
1271 }
1272#endif
1273
1274 if (spriteMag)
1275 {
1276 return renderSprites(PICO9918_INST spriteCount, true, false, false, pixels);
1277 }
1278
1279 /* every locked device and every ECM0 scene lands here, so it earns a clone of its own */
1280 if (spriteEcm(PICO9918_INST_ONLY) == 0)
1281 {
1282 return renderSprites(PICO9918_INST spriteCount, false, false, true, pixels);
1283 }
1284
1285 return renderSprites(PICO9918_INST spriteCount, false, false, false, pixels);
1286}
1287
1288/* What a tile row needs that the mode, rather than the layer, decides. The name and colour
1289 * addresses stay out of it: the row loop walks them as it crosses a page boundary.
1290 */
1291typedef struct
1292{
1293 const uint8_t* pattern; /* pattern table + this row; index with name * PATTERN_BYTES */
1294 int8_t flipY; /* what the ECM attribute's Y flip adds, or 0 where it is inert */
1295 uint8_t nameMask;
1296} TileRowAddr;
1297
1298/**
1299 * \brief the per-mode half of a tile row's addressing, once per layer per scanline.
1300 *
1301 * `y` is the scrolled raster row and `rawY` the unscrolled one. Multicolor is the only mode that
1302 * needs both, and it needs them apart: its name address uses the scrolled row while its pattern
1303 * byte comes from the raw one, so a vertical scroll changes which
1304 * tiles are fetched but not which four-line block of each is shown.
1305 */
1306static inline void tileRowAddr(PICO9918_INST_ARG const uint16_t y, const uint16_t rawY, const uint8_t colorReg,
1307 const bool gm2, const bool mcm, TileRowAddr* addr, uint16_t* colorTableAddr)
1308{
1309 uint16_t pageOffset = 0;
1310 const uint8_t pattRow = mcm ? (((rawY >> 2) & 0x01) + ((rawY >> 3) & 0x03) * 2) : (y & 0x07);
1311
1312 addr->nameMask = 0xff;
1313
1314 /* Multicolor's pattern address never reads the scrolled row, so Y flip is inert there */
1315 addr->flipY = mcm ? 0 : (7 - 2 * pattRow);
1316
1317 if (gm2)
1318 {
1319 pageOffset = (((y >> 6) & 0x03) & (TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & 0x03)) << 11;
1320 addr->nameMask = ((colorReg & 0x7f) << 3) | 0x07;
1321 *colorTableAddr += (pageOffset & ((colorReg & 0x60) << 6)) + pattRow;
1322 }
1323
1324 addr->pattern = tms9918->vram.bytes + tmsPatternTableAddr(tms9918) + pageOffset + pattRow;
1325}
1326
1327/* Which cell a scrolled text row starts on. Graphics cells are eight pixels wide so the scroll
1328 register splits by shifting; six does not divide, so hardware multiplies by the reciprocal
1329 instead, exactly floor(h/6) for every value the register holds. The
1330 pixel within that cell is what is left: h - 6 * cell. 80 columns doubles h first, its cells
1331 being half as wide, which is why its offset is only ever 0, 2 or 4 (:741). */
1332static inline uint32_t textScrollCell(const uint32_t hscrollPixels)
1333{
1334 return (hscrollPixels * 342) >> 11;
1335}
1336
1337/* 80 columns double the register before dividing, their cells being half as wide, and what is left
1338 inside the first cell is an even number of pixels - a whole byte at four bits a pixel, which is
1339 what lets that depth place the offset by moving the destination. Both depths and both
1340 column counts come through here so the emitter and the composite cannot disagree about it. */
1341/* Cell in the low half, the pixel within it in the high. One split a layer a line, handed to the
1342 emitter whole, so the nine wide bodies below do not each carry a copy of the division. */
1343#define TEXT_SCROLL_CELL(s) ((s) & 0xffffu)
1344#define TEXT_SCROLL_OFFSET(s) ((s) >> 16)
1345
1346static inline uint32_t textScrollSplit(const uint32_t hscroll, const bool wide)
1347{
1348 /* = wide ? hscroll * 2 : hscroll, less the branch a run-time `wide` would cost */
1349 const uint32_t h = hscroll << wide;
1350 const uint32_t cell = textScrollCell(h);
1351 const uint32_t offset = h - cell * 6;
1352
1353 /* LOAD-BEARING: the line is handed out at this offset and a caller may read it a word at a time,
1354 which an odd one costs the zero-copy path entirely. Two is the only offset six-pixel cells can
1355 leave that a word cannot start on, so it backs up a cell to eight - buffer slack covers it. */
1356 if (wide && offset == 2) return (cell ? cell - 1 : TEXT80_NUM_COLS - 1) | (8u << 16);
1357 return cell | (offset << 16);
1358}
1359
1360static inline uint32_t textPixelOffset(const uint32_t hscroll, const bool wide)
1361{
1362 return TEXT_SCROLL_OFFSET(textScrollSplit(hscroll, wide));
1363}
1364
1365static inline int scrollOffset(const uint32_t hscroll, const bool text, const bool wide)
1366{
1367 return text ? (int)textPixelOffset(hscroll, wide) : (int)(hscroll & 0x07);
1368}
1369
1370typedef struct
1371{
1372 uint8_t vertScrollReg;
1373 uint8_t yPageSwapMask;
1374 uint8_t paletteShift;
1375 uint8_t paletteMask;
1376 uint8_t startPattReg;
1377 uint8_t hpSizeMask;
1378 uint8_t priorityReg;
1379 uint8_t priorityMask;
1380 uint8_t colorTableReg;
1381 bool isTile2;
1382 uint16_t (*nameTableAddrFunc)(pico9918_t*);
1383 uint16_t (*colorTableAddrFunc)(pico9918_t*);
1385
1386static const TileLayerConfig T1_CONFIG = {.vertScrollReg = 0x1c,
1387 .yPageSwapMask = 0x01,
1388 .paletteShift = 4,
1389 .paletteMask = 0x03,
1390 .startPattReg = 0x1b,
1391 .hpSizeMask = 0x02,
1392 .priorityReg = 0, // T1 has no priority control
1393 .priorityMask = 0,
1394 .colorTableReg = TMS_REG_COLOR_TABLE,
1395 .isTile2 = false,
1396 .nameTableAddrFunc = tmsNameTableAddr,
1397 .colorTableAddrFunc = tmsColorTableAddr};
1398
1399static const TileLayerConfig T2_CONFIG = {.vertScrollReg = 0x1a,
1400 .yPageSwapMask = 0x10,
1401 .paletteShift = 2,
1402 .paletteMask = 0x0c,
1403 .startPattReg = 0x19,
1404 .hpSizeMask = 0x20,
1405 .priorityReg = 0x32,
1406 .priorityMask = 0x01,
1407 .colorTableReg = 11,
1408 .isTile2 = true,
1409 .nameTableAddrFunc = tmsNameTable2Addr,
1410 .colorTableAddrFunc = tmsColorTable2Addr};
1411
1412/* Where a scrolled 80-column row starts. Hardware doubles the scroll register before dividing by
1413 * six, T80 cells being half as wide, so the offset it leaves inside the first cell is only ever 0,
1414 * 2 or 4 pixels - a whole number of bytes at 4bpp, and never a nibble.
1415 *
1416 * `startCol` and `cells` are for the aligned emitter, which stores three words per four cells and
1417 * so cannot begin part-way through one: it backs up `bytes` cells and the caller moves the
1418 * destination four bytes for each, leaving the picture where it should be and the cells backed over
1419 * in the side border.
1420 */
1421typedef struct
1422{
1423 uint16_t startCell; /* the first cell the row shows */
1424 uint16_t startCol; /* the first cell it emits */
1425 uint8_t cells;
1426 uint8_t bytes; /* how far into the first cell the row starts */
1427 bool scrolled;
1428} TextScroll;
1429
1430static inline TextScroll textScroll80(PICO9918_INST_ARG const TileLayerConfig* config)
1431{
1432 const uint32_t hscroll =
1433 PICO9918_UNLOCKED(tms9918) ? TMS_REGISTER(tms9918, config->startPattReg) * 2 : 0;
1434 const uint32_t cell = textScrollCell(hscroll);
1435 const uint32_t bytes = (hscroll - cell * 6) >> 1;
1436
1437 TextScroll s;
1438 s.startCell = cell;
1439 s.startCol = (cell >= bytes) ? (cell - bytes) : (cell + TEXT80_NUM_COLS - bytes);
1440 s.cells = bytes ? TEXT80_NUM_COLS + 4 : TEXT80_NUM_COLS;
1441 s.bytes = bytes;
1442 s.scrolled = hscroll != 0;
1443 return s;
1444}
1445
1446/**
1447 * \brief where one layer reads this scanline from: the vertical scroll and its page swap, the name and
1448 * colour rows, and the mode's pattern table. Every mode and both layers come through here, which
1449 * is what stops the scroll from being written a fourth time.
1450 *
1451 * Position attributes decide the attribute row offset for every mode alike - by name when they are
1452 * off, ECM0 or not. `textMode` selects only what a text row does not have: the page bits, in
1453 * either direction.
1454 */
1455static inline void tileLayerAddr(PICO9918_INST_ARG const uint16_t rawY, const TileLayerConfig* config,
1456 const uint8_t numCols, const uint8_t nameAddrMask, const bool textMode,
1457 const bool gm2, const bool mcm, const bool attrPerPos, TileRowAddr* addr,
1458 uint16_t* namesAddr, uint16_t* colorAddr)
1459{
1460 uint16_t y = rawY;
1461 bool swapYPage = false;
1462
1463 if (TMS_REGISTER(tms9918, config->vertScrollReg))
1464 {
1465 int virtY = y + TMS_REGISTER(tms9918, config->vertScrollReg);
1466 const int maxY =
1467 ((TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ROW30) ? (8 * 30) : (8 * 24))
1468 << (bool)(TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_DOUBLE_ROWS);
1469
1470 if (virtY >= maxY)
1471 {
1472 virtY -= maxY;
1473
1474 /* a text row's address carries no page bit, so the size bit does nothing there */
1475 swapYPage = !textMode && (TMS_REGISTER(tms9918, PICO9918_REG_PAGE_SIZE) & config->yPageSwapMask);
1476 }
1477
1478 y = virtY;
1479 }
1480
1481 const uint16_t rowOffset = (y >> 3) * numCols;
1482
1483 *namesAddr = (config->nameTableAddrFunc(tms9918) & (nameAddrMask << 10)) + rowOffset;
1484 if (swapYPage) *namesAddr ^= 0x800;
1485
1486 *colorAddr = config->colorTableAddrFunc(tms9918);
1487 if (attrPerPos)
1488 {
1489 if (!textMode) *colorAddr += *namesAddr & 0xc00;
1490 *colorAddr = (*colorAddr + rowOffset) & VRAM_MASK;
1491 }
1492
1493 tileRowAddr(PICO9918_INST y, rawY, TMS_REGISTER(tms9918, config->colorTableReg), gm2, mcm, addr, colorAddr);
1494}
1495
1496#define TEXT80_COLOR_WORD(n) ((uint32_t)((n) * 0x111111u))
1497#define TEXT80_MASK_WORD(bits) \
1498 (((uint32_t)((bits) & 0x20 ? 0x0000F0u : 0x0)) | ((uint32_t)((bits) & 0x10 ? 0x00000Fu : 0x0)) | \
1499 ((uint32_t)((bits) & 0x08 ? 0x00F000u : 0x0)) | ((uint32_t)((bits) & 0x04 ? 0x000F00u : 0x0)) | \
1500 ((uint32_t)((bits) & 0x02 ? 0xF00000u : 0x0)) | ((uint32_t)((bits) & 0x01 ? 0x0F0000u : 0x0)))
1501
1502static const uint32_t text80ColorWord[16] = {
1503 TEXT80_COLOR_WORD(0x0), TEXT80_COLOR_WORD(0x1), TEXT80_COLOR_WORD(0x2), TEXT80_COLOR_WORD(0x3),
1504 TEXT80_COLOR_WORD(0x4), TEXT80_COLOR_WORD(0x5), TEXT80_COLOR_WORD(0x6), TEXT80_COLOR_WORD(0x7),
1505 TEXT80_COLOR_WORD(0x8), TEXT80_COLOR_WORD(0x9), TEXT80_COLOR_WORD(0xa), TEXT80_COLOR_WORD(0xb),
1506 TEXT80_COLOR_WORD(0xc), TEXT80_COLOR_WORD(0xd), TEXT80_COLOR_WORD(0xe), TEXT80_COLOR_WORD(0xf)};
1507
1508/* the low two pattern bits never reach the screen, so indexing by the raw pattern byte and
1509 repeating each entry four times spends table space to save a shift on every cell */
1510#define TEXT80_MASK_WORD4(bits) \
1511 TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits)
1512
1513static const uint32_t text80MaskWord[256] = {
1514 TEXT80_MASK_WORD4(0x00), TEXT80_MASK_WORD4(0x01), TEXT80_MASK_WORD4(0x02), TEXT80_MASK_WORD4(0x03),
1515 TEXT80_MASK_WORD4(0x04), TEXT80_MASK_WORD4(0x05), TEXT80_MASK_WORD4(0x06), TEXT80_MASK_WORD4(0x07),
1516 TEXT80_MASK_WORD4(0x08), TEXT80_MASK_WORD4(0x09), TEXT80_MASK_WORD4(0x0a), TEXT80_MASK_WORD4(0x0b),
1517 TEXT80_MASK_WORD4(0x0c), TEXT80_MASK_WORD4(0x0d), TEXT80_MASK_WORD4(0x0e), TEXT80_MASK_WORD4(0x0f),
1518 TEXT80_MASK_WORD4(0x10), TEXT80_MASK_WORD4(0x11), TEXT80_MASK_WORD4(0x12), TEXT80_MASK_WORD4(0x13),
1519 TEXT80_MASK_WORD4(0x14), TEXT80_MASK_WORD4(0x15), TEXT80_MASK_WORD4(0x16), TEXT80_MASK_WORD4(0x17),
1520 TEXT80_MASK_WORD4(0x18), TEXT80_MASK_WORD4(0x19), TEXT80_MASK_WORD4(0x1a), TEXT80_MASK_WORD4(0x1b),
1521 TEXT80_MASK_WORD4(0x1c), TEXT80_MASK_WORD4(0x1d), TEXT80_MASK_WORD4(0x1e), TEXT80_MASK_WORD4(0x1f),
1522 TEXT80_MASK_WORD4(0x20), TEXT80_MASK_WORD4(0x21), TEXT80_MASK_WORD4(0x22), TEXT80_MASK_WORD4(0x23),
1523 TEXT80_MASK_WORD4(0x24), TEXT80_MASK_WORD4(0x25), TEXT80_MASK_WORD4(0x26), TEXT80_MASK_WORD4(0x27),
1524 TEXT80_MASK_WORD4(0x28), TEXT80_MASK_WORD4(0x29), TEXT80_MASK_WORD4(0x2a), TEXT80_MASK_WORD4(0x2b),
1525 TEXT80_MASK_WORD4(0x2c), TEXT80_MASK_WORD4(0x2d), TEXT80_MASK_WORD4(0x2e), TEXT80_MASK_WORD4(0x2f),
1526 TEXT80_MASK_WORD4(0x30), TEXT80_MASK_WORD4(0x31), TEXT80_MASK_WORD4(0x32), TEXT80_MASK_WORD4(0x33),
1527 TEXT80_MASK_WORD4(0x34), TEXT80_MASK_WORD4(0x35), TEXT80_MASK_WORD4(0x36), TEXT80_MASK_WORD4(0x37),
1528 TEXT80_MASK_WORD4(0x38), TEXT80_MASK_WORD4(0x39), TEXT80_MASK_WORD4(0x3a), TEXT80_MASK_WORD4(0x3b),
1529 TEXT80_MASK_WORD4(0x3c), TEXT80_MASK_WORD4(0x3d), TEXT80_MASK_WORD4(0x3e), TEXT80_MASK_WORD4(0x3f)};
1530
1531
1532/**
1533 * \brief one 40- or 80-column text row, six pixels a cell at one byte each
1534 *
1535 * `colorStride` is 0 when the whole row shares one colour pair. At ECM1-3 a cell is an
1536 * ordinary ECM tile six pixels wide and the fg/bg pair goes inert.
1537 */
1538PICO9918_INLINE_HOT void
1539renderTextRow(PICO9918_INST_ARG const uint8_t* __restrict rowNames, const TileRowAddr* __restrict addr,
1540 const uint8_t* __restrict rowColors, const uint32_t colorStride, uint32_t pal,
1541 uint8_t* __restrict dest, const uint32_t scroll, const bool alwaysOnTop,
1542 const uint32_t numCols, const bool isTile2, const uint32_t ecm, const bool blend)
1543{
1544 const uint8_t* __restrict patternTable = addr->pattern;
1545 const bool wide = numCols == TEXT80_NUM_COLS;
1546 const uint32_t padding = wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX;
1547 const uint32_t startCell = TEXT_SCROLL_CELL(scroll);
1548 const uint32_t pixelOffset = TEXT_SCROLL_OFFSET(scroll);
1549 const uint32_t numCells = numCols + (pixelOffset ? 2 : 0);
1550
1551 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(dest, 4);
1552 uint32_t xPos = ecm ? (padding - pixelOffset) : padding;
1553
1554 const uint8_t* __restrict names = rowNames + startCell;
1555 const uint8_t* __restrict colors = rowColors + startCell * colorStride;
1556 const uint32_t colorWrap = numCols * colorStride;
1557 uint32_t col = startCell;
1558
1559 const uint32_t nameAttrMask = (ecm && !colorStride) ? 0xff : 0;
1560 const uint32_t spritePriMask = tms9918->scanlineHasSprites ? 0x80 : 0;
1561 const uint32_t spritePriForced = (isTile2 && alwaysOnTop) ? spritePriMask : 0;
1562
1563 /* the row masks are uint32_t too, so a store through one forces this reload unless it is held */
1564 const uint32_t clear = transparentPixels[0];
1565 const int32_t flipY = addr->flipY;
1566 uint32_t ecmOffset = 0, ecmColorMask = 0, ecmColorOffset = 0;
1567 const uint32_t* __restrict palette = ecm0Palette + pal;
1568 if (ecm)
1569 {
1570 ecmColorOffset = (ecm == 3) ? 2 : ecm;
1571 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
1572 ecmOffset = 0x800 >> ((TMS_REGISTER(tms9918, PICO9918_REG_PAGE_SIZE) & PICO9918_R29_TILE_STRIDE) >> 2);
1573 pal = (ecm == 1) ? (pal & 0x20) : 0;
1574 }
1575
1576 uint8_t lastColor = 0;
1577 uint32_t bgWord = palette[0], diffWord = 0;
1578 uint32_t coverBg = 0, coverDiff = 0;
1579 uint32_t lo = 0, hi = 0, m0 = 0, m1 = 0;
1580
1581#define TEXT40_NEXT_CELL() \
1582 const uint32_t name = *names++; \
1583 const uint8_t color = colors[name & nameAttrMask]; \
1584 colors += colorStride;
1585
1586#define TEXT40_CELL(wrapping) \
1587 { \
1588 TEXT40_NEXT_CELL() \
1589 if (wrapping) \
1590 { \
1591 /* text has no page size bits, so a start cell past the last reads on into the next row */ \
1592 if (++col == numCols) \
1593 { \
1594 col = 0; \
1595 names -= numCols; \
1596 colors -= colorWrap; \
1597 } \
1598 } \
1599 const uint32_t patt = patternTable[name * PATTERN_BYTES]; \
1600 if (color != lastColor) \
1601 { \
1602 const uint8_t bgColor = color & 0xf; \
1603 const uint8_t fgColor = color >> 4; \
1604 bgWord = palette[bgColor]; \
1605 diffWord = bgWord ^ palette[fgColor]; \
1606 lastColor = color; \
1607 if (isTile2) \
1608 { \
1609 /* = bgColor ? ~0u : 0u, and that xor the same for fgColor */ \
1610 coverBg = (uint32_t)(-(int32_t)bgColor >> 31); \
1611 coverDiff = coverBg ^ (uint32_t)(-(int32_t)fgColor >> 31); \
1612 } \
1613 } \
1614 /* 0x0c, not 0x0f: six pixels a cell, so the byte's two spare bits are dropped at the index */ \
1615 const uint32_t maskLo = maskExpandNibbleToWordRev[patt >> 4]; \
1616 const uint32_t maskHi = maskExpandNibbleToWordRev[patt & 0x0c]; \
1617 lo = bgWord ^ (diffWord & maskLo); \
1618 hi = bgWord ^ (diffWord & maskHi); \
1619 if (isTile2) \
1620 { \
1621 if (blend) \
1622 { \
1623 /* cover has the shape colour does, so it selects through the masks already in hand */ \
1624 m0 = coverBg ^ (coverDiff & maskLo); \
1625 m1 = coverBg ^ (coverDiff & maskHi); \
1626 } \
1627 else \
1628 { \
1629 /* = ((fg ? bits : 0) | (bg ? ~bits : 0)) & 0x3f, off the pair memoised above */ \
1630 const uint32_t cover = ((coverBg ^ (coverDiff & (patt >> 2))) & 0x3f) << 26; \
1631 /* rolled every cell, drawn or not, or the bit position stops tracking */ \
1632 coverAcc |= cover >> coverBit; \
1633 coverBit += 6; \
1634 if (coverBit >= 32) \
1635 { \
1636 *coverWord++ |= coverAcc; \
1637 coverBit -= 32; \
1638 coverAcc = cover << (6 - coverBit); \
1639 } \
1640 } \
1641 } \
1642 }
1643
1644#define TEXT40_ECM_CELL() \
1645 { \
1646 TEXT40_NEXT_CELL() \
1647 const uint8_t* pattData = patternTable + name * PATTERN_BYTES + ((color & 0x20) ? flipY : 0); \
1648 uint32_t pattMask = (color & 0x10) ? 0 : 0xff; \
1649 uint32_t cover = 0; \
1650 uint8_t patt[3] = {0}; \
1651 switch (ecm) \
1652 { \
1653 case 3: patt[0] = pattData[ecmOffset * 2]; pattMask |= patt[0]; \
1654 case 2: patt[1] = pattData[ecmOffset]; pattMask |= patt[1]; \
1655 default: patt[2] = *pattData; pattMask |= patt[2]; \
1656 } \
1657 if (pattMask) \
1658 { \
1659 if (color & 0x40) \
1660 { \
1661 patt[0] = reversedBits6[patt[0]]; \
1662 patt[1] = reversedBits6[patt[1]]; \
1663 patt[2] = reversedBits6[patt[2]]; \
1664 pattMask = reversedBits6[pattMask]; \
1665 } \
1666 cover = (pattMask & 0xfc) << 24; \
1667 if ((color | spritePriForced) & spritePriMask) \
1668 tmsClearRowBitsMask(xPos, cover, 6, rowMasks.rowSpriteBits); \
1669 uint32_t index = 0; \
1670 switch (ecm) \
1671 { \
1672 case 3: index = ecmSplitQuads(patt[0]) << 8; \
1673 case 2: index |= ecmSplitQuads(patt[1]) << 4; \
1674 default: index |= ecmSplitQuads(patt[2]); \
1675 } \
1676 const uint32_t cellPal = repeatedPalette(pal | ((color & ecmColorMask) << ecmColorOffset)); \
1677 lo = ecmLookup[index >> 16] | cellPal; \
1678 hi = ecmLookup[(uint16_t)index] | cellPal; \
1679 if (!isTile2 && (color & 0x10)) \
1680 { \
1681 lo = clear ^ ((lo ^ clear) & maskExpandNibbleToWordRev[pattMask >> 4]); \
1682 hi = clear ^ ((hi ^ clear) & maskExpandNibbleToWordRev[pattMask & 0x0f]); \
1683 } \
1684 } \
1685 else \
1686 { \
1687 lo = hi = clear; \
1688 } \
1689 if (isTile2) \
1690 { \
1691 /* every cell rolls the six-bit accumulator, drawn or not, or the bit position stops tracking */ \
1692 coverAcc |= cover >> coverBit; \
1693 coverBit += 6; \
1694 if (coverBit >= 32) \
1695 { \
1696 *coverWord++ |= coverAcc; \
1697 coverBit -= 32; \
1698 coverAcc = cover << (6 - coverBit); \
1699 } \
1700 } \
1701 xPos += 6; \
1702 }
1703
1704 if (ecm)
1705 {
1706 /* do not hoist out of the branch: shared, these stay live across both and the ECM row drops rows */
1707 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1708 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1709
1710 uint8_t* p = dest;
1711 uint32_t remaining = numCells;
1712 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1713
1714 while (remaining)
1715 {
1716 if (run > remaining) run = remaining;
1717 remaining -= run;
1718
1719 while (run--)
1720 {
1721 TEXT40_ECM_CELL();
1722 *(uint16_t*)(p) = lo;
1723 *(uint16_t*)(p + 2) = lo >> 16;
1724 *(uint16_t*)(p + 4) = hi;
1725 p += 6;
1726 }
1727
1728 names = rowNames;
1729 colors = rowColors;
1730 run = numCols;
1731 }
1732
1733 if (isTile2) *coverWord |= coverAcc;
1734 }
1735 else if (blend)
1736 {
1737 /* named, not used: the cell macro's other arm still has to compile here */
1738 uint32_t* coverWord = tms9918->layerSelectionMask;
1739 uint32_t coverAcc = 0, coverBit = 0;
1740
1741 uint16_t* p = (uint16_t*)dest;
1742 uint32_t remaining = numCells;
1743 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1744
1745 while (remaining)
1746 {
1747 if (run > remaining) run = remaining;
1748 remaining -= run;
1749
1750 while (run--)
1751 {
1752 TEXT40_CELL(false);
1753
1754 // a cell covering nothing merges nothing: all three of these are provably no-ops
1755 if (m0 | m1)
1756 {
1757 uint32_t d;
1758 d = p[0];
1759 p[0] = d ^ ((d ^ lo) & m0);
1760 d = p[1];
1761 p[1] = d ^ ((d ^ (lo >> 16)) & (m0 >> 16));
1762 d = p[2];
1763 p[2] = d ^ ((d ^ hi) & m1);
1764 }
1765 p += 3;
1766 }
1767
1768 names = rowNames;
1769 colors = rowColors;
1770 run = numCols;
1771 }
1772 }
1773 else
1774 {
1775 /* do not hoist out of the branch: shared, these stay live across both and the ECM row drops rows */
1776 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1777 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1778
1779 for (uint32_t tileX = 0; tileX < numCells; tileX += 2)
1780 {
1781 uint16_t* pix16 = (uint16_t*)pix32;
1782
1783 TEXT40_CELL(true);
1784 pix32[0] = lo;
1785 pix16[2] = hi;
1786 TEXT40_CELL(true);
1787 pix16[3] = lo;
1788 pix32[2] = (lo >> 16) | (hi << 16);
1789 pix32 += 3;
1790 }
1791
1792 if (isTile2) *coverWord |= coverAcc;
1793 }
1794
1795#undef TEXT40_ECM_CELL
1796#undef TEXT40_CELL
1797#undef TEXT40_NEXT_CELL
1798}
1799
1800/* One body per (layer, ecm). 80 columns arrives here on the 8bpp tier, where a text row is the same
1801 byte-per-pixel shape and only the count differs. At 4bpp it cannot use a layer buffer at all, so
1802 RP2040 keeps the blend-in-place emitter below. */
1803#define TEXT_ROW_PARAMS \
1804 PICO9918_INST_ARG const uint8_t *rowNames, const TileRowAddr *addr, const uint8_t *rowColors, \
1805 const uint32_t colorStride, const uint32_t pal, uint8_t *dest, const uint32_t scroll, \
1806 const bool alwaysOnTop
1807#define TEXT_ROW_CLONE(name, cols, t2, e) \
1808 static EMITTER_NOINLINE void __time_critical_func(name)(TEXT_ROW_PARAMS) \
1809 { \
1810 renderTextRow(PICO9918_INST rowNames, addr, rowColors, colorStride, pal, dest, scroll, alwaysOnTop, \
1811 cols, t2, e, false); \
1812 }
1813
1814TEXT_ROW_CLONE(text40RowT1, TEXT_NUM_COLS, false, 0)
1815TEXT_ROW_CLONE(text40RowT1Ecm1, TEXT_NUM_COLS, false, 1)
1816TEXT_ROW_CLONE(text40RowT1Ecm2, TEXT_NUM_COLS, false, 2)
1817TEXT_ROW_CLONE(text40RowT1Ecm3, TEXT_NUM_COLS, false, 3)
1818TEXT_ROW_CLONE(text40RowT2, TEXT_NUM_COLS, true, 0)
1819TEXT_ROW_CLONE(text40RowT2Ecm1, TEXT_NUM_COLS, true, 1)
1820TEXT_ROW_CLONE(text40RowT2Ecm2, TEXT_NUM_COLS, true, 2)
1821TEXT_ROW_CLONE(text40RowT2Ecm3, TEXT_NUM_COLS, true, 3)
1822
1823static void (*const textRowClones[2][4])(TEXT_ROW_PARAMS) = {
1824 {text40RowT1, text40RowT1Ecm1, text40RowT1Ecm2, text40RowT1Ecm3},
1825 {text40RowT2, text40RowT2Ecm1, text40RowT2Ecm2, text40RowT2Ecm3}};
1826
1827#if PICO9918_TEXT80_8BPP
1828/* 80 columns are the same body at a different count: one byte a pixel, six pixels a cell, the same
1829 word-and-halfword stores at the same alignments, and a layer buffer the composite can arbitrate
1830 because a mask bit now covers one pixel rather than two */
1831TEXT_ROW_CLONE(text80RowT1, TEXT80_NUM_COLS, false, 0)
1832TEXT_ROW_CLONE(text80RowT1Ecm1, TEXT80_NUM_COLS, false, 1)
1833TEXT_ROW_CLONE(text80RowT1Ecm2, TEXT80_NUM_COLS, false, 2)
1834TEXT_ROW_CLONE(text80RowT1Ecm3, TEXT80_NUM_COLS, false, 3)
1835TEXT_ROW_CLONE(text80RowT2, TEXT80_NUM_COLS, true, 0)
1836TEXT_ROW_CLONE(text80RowT2Ecm1, TEXT80_NUM_COLS, true, 1)
1837TEXT_ROW_CLONE(text80RowT2Ecm2, TEXT80_NUM_COLS, true, 2)
1838TEXT_ROW_CLONE(text80RowT2Ecm3, TEXT80_NUM_COLS, true, 3)
1839
1840/* Layer 2 folded into layer 1's line as it emits, instead of into a coverage mask for the
1841 composite to arbitrate. Only an ECM0 line with layer 1 present may do it - above ECM0 priority is
1842 attr(0) per tile - so it is a body of its own rather than what the layer always does. */
1843static EMITTER_NOINLINE void __time_critical_func(text80RowT2Blend)(TEXT_ROW_PARAMS)
1844{
1845 renderTextRow(PICO9918_INST rowNames, addr, rowColors, colorStride, pal, dest, scroll, alwaysOnTop,
1846 TEXT80_NUM_COLS, true, 0, true);
1847}
1848
1849static void (*const text80RowClones[2][4])(TEXT_ROW_PARAMS) = {
1850 {text80RowT1, text80RowT1Ecm1, text80RowT1Ecm2, text80RowT1Ecm3},
1851 {text80RowT2, text80RowT2Ecm1, text80RowT2Ecm2, text80RowT2Ecm3}};
1852#endif
1853
1854/**
1855 * \brief one 80-column text row, six pixels a cell at half a byte each. Four cells are twelve bytes, so a
1856 * group goes out as three words.
1857 *
1858 * `scrolled` splits it in two. Unscrolled, the colour bytes arrive four at a time as one aligned
1859 * word and the row cannot wrap, so the colour memo and the four-cell transparent skip both live in
1860 * that loop. Scrolled, the row starts on an arbitrary cell and wraps to its own first one, so
1861 * neither holds: the colour table is not word-aligned to the group and the group can straddle the
1862 * wrap. That clone reads a colour byte a cell and does without the skip.
1863 *
1864 * The fine offset is only ever 0, 2 or 4 pixels, which at 4bpp is a whole number of
1865 * bytes - so the caller backs the start cell up by that many and moves `pixels` four bytes per
1866 * byte of offset, which keeps the three-word store aligned and puts the cells it skipped over in
1867 * the side border.
1868 */
1869PICO9918_INLINE_HOT void
1870renderText80Row(PICO9918_INST_ARG const uint8_t* __restrict rowNames,
1871 const uint8_t* __restrict patternTable, const uint8_t* __restrict rowColors,
1872 const bool opaq, uint8_t* __restrict pixels, const uint32_t startCol,
1873 const uint32_t numCells, const bool scrolled)
1874{
1875 const uint8_t bgc = tmsMainBgColor(tms9918);
1876 const uint8_t* rowNamesTable = rowNames + startCol;
1877 const uint8_t* colors = rowColors + startCol;
1878 const uint32_t* colorTable32 = (const uint32_t*)PICO9918_ASSUME_ALIGNED(rowColors, 4);
1879 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
1880 uint32_t col = startCol;
1881
1882 /* the row wraps to its own first cell, with no page swap */
1883#define TEXT80_NEXT_CELL() \
1884 { \
1885 ++rowNamesTable; \
1886 if (scrolled && ++col == TEXT80_NUM_COLS) \
1887 { \
1888 col = 0; \
1889 rowNamesTable = rowNames; \
1890 colors = rowColors; \
1891 } \
1892 else if (scrolled) \
1893 ++colors; \
1894 }
1895
1896 if (opaq)
1897 {
1898 uint8_t lastColor = 0;
1899 uint32_t bgColorMask = text80ColorWord[bgc];
1900 uint32_t diffColorMask = 0;
1901
1902 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
1903 {
1904 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
1905 uint32_t word, acc;
1906
1907#define TEXT80_OPAQUE_CELL() \
1908 { \
1909 uint8_t color; \
1910 if (scrolled) \
1911 color = *colors; \
1912 else \
1913 { \
1914 color = (uint8_t)colorWord; \
1915 colorWord >>= 8; \
1916 } \
1917 if (color != lastColor) \
1918 { \
1919 const uint8_t bgColor = color & 0xf; \
1920 const uint8_t fgColor = color >> 4; \
1921 bgColorMask = text80ColorWord[bgColor ? bgColor : bgc]; \
1922 diffColorMask = bgColorMask ^ text80ColorWord[fgColor ? fgColor : bgc]; \
1923 lastColor = color; \
1924 } \
1925 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
1926 TEXT80_NEXT_CELL(); \
1927 word = bgColorMask ^ (diffColorMask & mask); \
1928 }
1929
1930 TEXT80_OPAQUE_CELL();
1931 acc = word;
1932 TEXT80_OPAQUE_CELL();
1933 *pix32++ = acc | (word << 24);
1934 acc = word >> 8;
1935 TEXT80_OPAQUE_CELL();
1936 *pix32++ = acc | (word << 16);
1937 acc = word >> 16;
1938 TEXT80_OPAQUE_CELL();
1939 *pix32++ = acc | (word << 8);
1940
1941#undef TEXT80_OPAQUE_CELL
1942 }
1943 }
1944 else
1945 {
1946 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
1947 {
1948 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
1949 uint32_t val, sel, accVal, accSel;
1950
1951 if (!scrolled && !colorWord)
1952 {
1953 rowNamesTable += 4;
1954 pix32 += 3;
1955 continue;
1956 }
1957
1958#define TEXT80_OVERLAY_CELL() \
1959 { \
1960 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
1961 uint8_t colorByte; \
1962 if (scrolled) \
1963 colorByte = *colors; \
1964 else \
1965 { \
1966 colorByte = (uint8_t)colorWord; \
1967 colorWord >>= 8; \
1968 } \
1969 TEXT80_NEXT_CELL(); \
1970 const uint32_t fgWord = text80ColorWord[colorByte >> 4]; \
1971 const uint32_t bgWord = text80ColorWord[colorByte & 0xf]; \
1972 const uint32_t fgSel = fgWord ? mask : 0; \
1973 const uint32_t bgSel = bgWord ? (~mask & 0xffffffu) : 0; \
1974 sel = fgSel | bgSel; \
1975 val = (fgWord & fgSel) | (bgWord & bgSel); \
1976 }
1977
1978#define TEXT80_OVERLAY_STORE(shift) \
1979 { \
1980 const uint32_t m = accSel | (sel << (shift)); \
1981 const uint32_t v = accVal | (val << (shift)); \
1982 const uint32_t old = *pix32; \
1983 *pix32++ = old ^ ((old ^ v) & m); \
1984 }
1985
1986 TEXT80_OVERLAY_CELL();
1987 accVal = val;
1988 accSel = sel;
1989 TEXT80_OVERLAY_CELL();
1990 TEXT80_OVERLAY_STORE(24);
1991 accVal = val >> 8;
1992 accSel = sel >> 8;
1993 TEXT80_OVERLAY_CELL();
1994 TEXT80_OVERLAY_STORE(16);
1995 accVal = val >> 16;
1996 accSel = sel >> 16;
1997 TEXT80_OVERLAY_CELL();
1998 TEXT80_OVERLAY_STORE(8);
1999
2000#undef TEXT80_OVERLAY_CELL
2001#undef TEXT80_OVERLAY_STORE
2002 }
2003 }
2004#undef TEXT80_NEXT_CELL
2005}
2006
2007/* One body per (can this row scroll). The unscrolled one keeps its cell count as a constant. */
2008#define TEXT80_ROW_CLONE(name, cells, scroll) \
2009 static EMITTER_NOINLINE void __time_critical_func(name)( \
2010 PICO9918_INST_ARG const uint8_t* rowNames, const uint8_t* patternTable, const uint8_t* rowColors, \
2011 const bool opaq, uint8_t* pixels, const uint32_t startCol, const uint32_t numCells) \
2012 { \
2013 renderText80Row(PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, cells, \
2014 scroll); \
2015 }
2016
2017TEXT80_ROW_CLONE(text80Row, TEXT80_NUM_COLS, false)
2018TEXT80_ROW_CLONE(text80RowScrolled, numCells, true)
2019
2020static inline void renderText80Layer(PICO9918_INST_ARG const uint8_t* rowNames,
2021 const uint8_t* patternTable, const uint8_t* rowColors,
2022 const bool opaq, uint8_t* pixels, const uint32_t startCol,
2023 const uint32_t numCells, const bool scrolled)
2024{
2025 if (scrolled)
2026 text80RowScrolled(PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, numCells);
2027 else
2028 text80Row(PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, 0, TEXT80_NUM_COLS);
2029}
2030
2031
2032/* one run of 80-column cells in the two colours R7 holds. Both are constant down the whole row, so
2033 this is the one text path that needs no colour memo: one lookup turns a pattern byte into all six
2034 pixels and the pair of colours is applied to it whole, the same expansion renderText80Row uses.
2035 Three byte stores a cell, so a run can start anywhere - which is what lets the row's wrap be a
2036 second call rather than a test on every cell. */
2037static inline uint8_t* text80TwoTone(const uint8_t* __restrict names, const uint8_t* __restrict patternTable,
2038 const uint32_t bgWord, const uint32_t diffWord,
2039 uint8_t* __restrict pixels, uint32_t cells)
2040{
2041 while (cells--)
2042 {
2043 const uint32_t pixelWord = bgWord ^ (diffWord & text80MaskWord[patternTable[*names++ * PATTERN_BYTES]]);
2044
2045 *pixels++ = pixelWord;
2046 *pixels++ = pixelWord >> 8;
2047 *pixels++ = pixelWord >> 16;
2048 }
2049 return pixels;
2050}
2051
2052/**
2053 * \brief generate a 40- or 80-column text mode scanline.
2054 *
2055 * Text has a path of its own, and hardware says so: it selects a different name address, a
2056 * different attribute address, a different colour source, a different flip, a different horizontal
2057 * scroll counter and a different expansion width - six pixels against eight. What it shares with
2058 * the graphics modes is the fetch schedule, which for us is the address generator above, the sprite
2059 * pass and the backdrop.
2060 *
2061 * The emitters write the finished line rather than a layer buffer, so the composite runs only where
2062 * something has to be arbitrated - a bitmap layer, or a priority second layer.
2063 */
2064static EMITTER_NOINLINE void __time_critical_func(text_scan_line)(PICO9918_INST_ARG uint16_t y,
2065 uint8_t pixels[TMS9918_PIXELS_X])
2066{
2067 const bool wide = pico9918_cached_mode == TMS_MODE_TEXT80;
2068 const uint8_t numCols = wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS;
2069 const uint8_t nameTableMask = (wide && !PICO9918_UNLOCKED(tms9918)) ? 0x0c : 0x0f;
2070
2071 const bool attrPerPos =
2072 PICO9918_UNLOCKED(tms9918) && (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_POS_ATTR);
2073
2074 TileRowAddr addr;
2075 uint16_t rowNamesAddr, colorTableAddr;
2076 tileLayerAddr(PICO9918_INST y, &T1_CONFIG, numCols, nameTableMask, true, false, false, attrPerPos, &addr,
2077 &rowNamesAddr, &colorTableAddr);
2078
2079 const uint8_t* patternTable = addr.pattern;
2080 const pico9918_color_t bgColor = tmsMainBgColor(tms9918);
2081 uint32_t* border = (uint32_t*)pixels;
2082
2083 pixels += TEXT_PADDING_PX;
2084
2085 const TextScroll t1 = textScroll80(PICO9918_INST & T1_CONFIG);
2086
2087 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
2088
2089 if (attrPerPos)
2090 {
2091 const bool tilesDisabled = TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_TILE1_OFF;
2092 if (!tilesDisabled)
2093 renderText80Layer(PICO9918_INST tms9918->vram.bytes + rowNamesAddr, patternTable,
2094 tms9918->vram.bytes + colorTableAddr, true, pixels - 4 * t1.bytes, t1.startCol,
2095 t1.cells, t1.scrolled);
2096
2097 if (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_TILE2_ENABLE)
2098 {
2099 const TextScroll t2 = textScroll80(PICO9918_INST & T2_CONFIG);
2100 tileLayerAddr(PICO9918_INST y, &T2_CONFIG, numCols, nameTableMask, true, false, false, attrPerPos, &addr,
2101 &rowNamesAddr, &colorTableAddr);
2102
2103 renderText80Layer(PICO9918_INST tms9918->vram.bytes + rowNamesAddr, addr.pattern,
2104 tms9918->vram.bytes + colorTableAddr, false, pixels - 4 * t2.bytes, t2.startCol,
2105 t2.cells, t2.scrolled);
2106 }
2107 }
2108 else // just plain old two-tone
2109 {
2110 const pico9918_color_t fgColor = tmsMainFgColor(tms9918);
2111 const uint8_t* rowNamesTable = tms9918->vram.bytes + rowNamesAddr;
2112
2113 if (wide)
2114 {
2115 const uint32_t bgWord = text80ColorWord[bgColor];
2116 const uint32_t diffWord = bgWord ^ text80ColorWord[fgColor];
2117
2118 const uint32_t cells = t1.bytes ? TEXT80_NUM_COLS + 1 : TEXT80_NUM_COLS;
2119 uint32_t run = cells;
2120 if (t1.startCell < TEXT80_NUM_COLS && t1.startCell + run > TEXT80_NUM_COLS)
2121 run = TEXT80_NUM_COLS - t1.startCell;
2122
2123 pixels =
2124 text80TwoTone(rowNamesTable + t1.startCell, patternTable, bgWord, diffWord, pixels - t1.bytes, run);
2125 if (run < cells) text80TwoTone(rowNamesTable, patternTable, bgWord, diffWord, pixels, cells - run);
2126 }
2127 else
2128 {
2129 const uint32_t bgWord = repeatedPalette(bgColor);
2130 const uint32_t diffWord = bgWord ^ repeatedPalette(fgColor);
2131 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
2132
2133 for (uint8_t tileX = 0; tileX < TEXT_NUM_COLS; tileX += 2)
2134 {
2135 uint16_t* pix16 = (uint16_t*)pix32;
2136 uint32_t patt = patternTable[*rowNamesTable++ * PATTERN_BYTES];
2137
2138 pix32[0] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt >> 4]);
2139 pix16[2] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt & 0x0f]);
2140
2141 patt = patternTable[*rowNamesTable++ * PATTERN_BYTES];
2142 const uint32_t lo = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt >> 4]);
2143 const uint32_t hi = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt & 0x0f]);
2144
2145 pix16[3] = lo;
2146 pix32[2] = (lo >> 16) | (hi << 16);
2147 pix32 += 3;
2148 }
2149 }
2150 }
2151
2152 border[0] = border[1] = bg;
2153 border[62] = border[63] = bg;
2154}
2155
2156/** \brief Write full tile to aligned buffer - 8 pixels at once */
2157static inline void writeToAlignedBuffer(uint8_t* buffer, uint32_t xPos, const uint32_t left,
2158 const uint32_t right)
2159{
2160 uint32_t* buffer_words = (uint32_t*)(buffer + xPos);
2161 buffer_words[0] = left;
2162 buffer_words[1] = right;
2163}
2164
2165/**
2166 * \brief render an ECM0 (enhanced color mode) graphics I tile. basically the same as original, but can scroll
2167 *
2168 * INLINE: so will be different versions generated, depending on hard-coded (or known at compile-time) arguments
2169 */
2170static inline void renderEcm0Tile(PICO9918_INST_ARG uint8_t* buffer, const uint32_t xPos,
2171 const uint8_t pattIdx, const uint8_t patternTable[],
2172 const uint32_t colorTableAddr, const uint32_t pal, const bool isTile2,
2173 const bool gm2Color, const bool mcm)
2174{
2175 /* is the pixel mask already full here? then nothing of this tile can show */
2176 if (!isTile2 && !tmsTestRowBitsMaskAligned(xPos, 0xffu << 24, tms9918->finalMask))
2177 {
2178 writeToAlignedBuffer(buffer, xPos, transparentPixels[0], transparentPixels[1]);
2179 return;
2180 }
2181
2182 const uint32_t pattByte = patternTable[pattIdx * PATTERN_BYTES];
2183 const uint32_t colorByte =
2184 mcm ? pattByte
2185 : tms9918->vram.bytes[colorTableAddr + (gm2Color ? pattIdx * PATTERN_BYTES : (pattIdx >> 3))];
2186 const uint32_t patt = mcm ? 0xf0 : pattByte;
2187
2188 const uint32_t bgColor = colorByte & 0x0f;
2189 const uint32_t fgColor = colorByte >> 4;
2190
2191 const uint32_t bgPalette = ecm0Palette[pal | bgColor];
2192 const uint32_t fgPalette = ecm0Palette[pal | fgColor];
2193
2194 uint32_t pattMask = 0xff;
2195 if (!bgColor) pattMask &= patt;
2196 if (!fgColor) pattMask ^= patt;
2197
2198 pattMask <<= 24;
2199 if (isTile2) tmsUpdateRowBitsMaskAligned(xPos, pattMask, tms9918->layerSelectionMask);
2200
2201 if (!pattMask)
2202 {
2203 if (!isTile2)
2204 {
2205 writeToAlignedBuffer(buffer, xPos, transparentPixels[0], transparentPixels[1]);
2206 }
2207 return;
2208 }
2209
2210 const uint32_t rightMask = maskExpandNibbleToWordRev[patt & 0xf];
2211 const uint32_t leftMask = maskExpandNibbleToWordRev[patt >> 4];
2212
2213 writeToAlignedBuffer(buffer, xPos, (fgPalette & leftMask) | (bgPalette & ~leftMask),
2214 (fgPalette & rightMask) | (bgPalette & ~rightMask));
2215}
2216
2217
2218/** \brief render one ECM tile into the layer buffer */
2219static inline void
2220renderEcmTileToAlignedBuffer(PICO9918_INST_ARG uint8_t* buffer, const uint32_t xPos, const uint32_t pixelOffset,
2221 const uint8_t pattIdx, const uint8_t patternTable[],
2222 const uint32_t colorTableAddr, const uint32_t ecm, const uint32_t ecmOffset,
2223 const uint32_t ecmColorMask, const uint32_t ecmColorOffset, const uint32_t pal,
2224 const bool attrPerPos, const int32_t flipY, const uint32_t tileIndex,
2225 uint32_t* lastEmpty, const bool isTile2, const bool alwaysOnTop)
2226{
2227 if ((*lastEmpty == pattIdx) ||
2228 (!isTile2 && !tmsTestRowBitsMaskAligned(xPos, 0xffu << 24, tms9918->finalMask)))
2229 {
2230 // T1 must still write: transparentPixels is the backdrop, or 0 under a bitmap layer
2231 if (!isTile2)
2232 {
2233 writeToAlignedBuffer(buffer, xPos, transparentPixels[0], transparentPixels[1]);
2234 }
2235 return;
2236 }
2237
2238 /* grab the attributes for this tile */
2239 uint32_t colorTableOffset = attrPerPos ? tileIndex : pattIdx;
2240 uint32_t pattOffset = pattIdx * PATTERN_BYTES;
2241
2242 const uint32_t colorByte = tms9918->vram.bytes[colorTableAddr + colorTableOffset];
2243
2244 const uint8_t* pattData = patternTable + pattOffset;
2245
2246 /* the pattern pointer already carries this row, so a Y flip only has to step to its mirror */
2247 if (colorByte & 0x20) pattData += flipY;
2248
2249 uint32_t pattMask = (colorByte & 0x10) ? 0 : 0xff; // handle transparency flag
2250 uint32_t index = 0;
2251
2252 uint8_t patt[3] = {0}; // indexes into this are reversed. ecm3 is in index 0
2253
2254 switch (ecm)
2255 {
2256 case 3: patt[0] = pattData[ecmOffset * 2]; pattMask |= patt[0];
2257 case 2: patt[1] = pattData[ecmOffset]; pattMask |= patt[1];
2258 default: patt[2] = *pattData; pattMask |= patt[2];
2259 }
2260
2261 /* have we any pixels to draw? */
2262 if (pattMask)
2263 {
2264 if (colorByte & 0x40) // flipX
2265 {
2266 patt[0] = reversedBits[patt[0]];
2267 patt[1] = reversedBits[patt[1]];
2268 patt[2] = reversedBits[patt[2]];
2269 pattMask = reversedBits[pattMask];
2270 }
2271
2272 const uint32_t priority = alwaysOnTop || (colorByte & 0x80);
2273 pattMask <<= 24;
2274
2275 if (isTile2) tmsUpdateRowBitsMaskAligned(xPos, pattMask, tms9918->layerSelectionMask);
2276 if (priority && tms9918->scanlineHasSprites)
2277 {
2278 const uint32_t offScreen = xPos ? 0 : pixelOffset;
2279 tmsClearRowBitsMask(xPos - pixelOffset + offScreen, pattMask << offScreen, 8, rowMasks.rowSpriteBits);
2280 }
2281
2282 switch (ecm)
2283 {
2284 case 3: index = ecmSplitQuads(patt[0]) << 8;
2285 case 2: index |= ecmSplitQuads(patt[1]) << 4;
2286 default: index |= ecmSplitQuads(patt[2]);
2287 }
2288
2289 const uint32_t palette = repeatedPalette(pal | ((colorByte & ecmColorMask) << ecmColorOffset));
2290 const uint32_t left = ecmLookup[index >> 16] | palette;
2291 const uint32_t right = ecmLookup[(uint16_t)index] | palette;
2292
2293 if (!isTile2 && (colorByte & 0x10))
2294 {
2295 const uint32_t clear = transparentPixels[0];
2296 writeToAlignedBuffer(buffer, xPos, clear ^ ((left ^ clear) & maskExpandNibbleToWordRev[pattMask >> 28]),
2297 clear ^ ((right ^ clear) & maskExpandNibbleToWordRev[(pattMask >> 24) & 0x0f]));
2298 }
2299 else
2300 {
2301 // Write to aligned buffer instead of doing expensive bit shifting
2302 writeToAlignedBuffer(buffer, xPos, left, right);
2303 }
2304 }
2305 else
2306 {
2307 // T1 must still write even when the tile is empty, for the same reason
2308 if (!isTile2)
2309 {
2310 writeToAlignedBuffer(buffer, xPos, transparentPixels[0], transparentPixels[1]);
2311 }
2312 *lastEmpty = pattIdx;
2313 }
2314}
2315
2316/* A tile's colour pair as the two words the nibble expansion wants: the background repeated, and
2317 what to flip in it where a pattern bit is set. Four pixels come out of one lookup and one xor,
2318 and it is the same expansion the F18A tile and text paths use rather than a second way of drawing
2319 the same thing. */
2320static inline void lockedFgBg(PICO9918_INST_ARG uint32_t fgbg[2], const uint8_t pal, const uint32_t colorByte)
2321{
2322 fgbg[0] = repeatedPalette(pal | tmsBgColor(tms9918, colorByte));
2323 fgbg[1] = fgbg[0] ^ repeatedPalette(pal | tmsFgColor(tms9918, colorByte));
2324}
2325
2326/** \brief generate a locked (plain TMS9918) tile row, straight into the scanline */
2327PICO9918_INLINE_HOT void
2328renderTileRowLocked(PICO9918_INST_ARG uint16_t rowNamesAddr, uint16_t colorTableAddr, uint8_t tileIndex,
2329 uint8_t pal, uint8_t pixels[TMS9918_PIXELS_X], const TileRowAddr* addr, const bool gm2,
2330 const bool mcm)
2331{
2332 const uint8_t* pattTableRow = addr->pattern;
2333 const uint8_t nameMask = addr->nameMask;
2334
2335 /* locked mode has no horizontal scroll, so 32 tiles cover the screen exactly */
2336 uint32_t numTiles = GRAPHICS_NUM_COLS;
2337
2338 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
2339 uint8_t* pattPtr = tms9918->vram.bytes + rowNamesAddr + tileIndex;
2340 uint32_t pattOffset = 0;
2341
2342 uint8_t lastPattIdx = 0;
2343 uint32_t pattByte = mcm ? 0xf0 : (uint8_t)pattTableRow[0];
2344 uint32_t lastColorByte = mcm ? (uint8_t)pattTableRow[0] : tms9918->vram.bytes[colorTableAddr];
2345 uint32_t fgbg[2];
2346 lockedFgBg(PICO9918_INST fgbg, pal, lastColorByte);
2347
2348 while (numTiles--)
2349 {
2350 uint8_t pattIdx = *pattPtr++;
2351 if (gm2) pattIdx &= nameMask;
2352
2353 if (lastPattIdx != pattIdx)
2354 {
2355 lastPattIdx = pattIdx;
2356 pattOffset = lastPattIdx * PATTERN_BYTES;
2357 const uint32_t colorByte =
2358 mcm ? pattTableRow[pattOffset]
2359 : tms9918->vram.bytes[colorTableAddr + (gm2 ? pattOffset : (pattIdx >> 3))];
2360 if (!mcm) pattByte = (uint8_t)pattTableRow[pattOffset];
2361 if (lastColorByte != colorByte)
2362 {
2363 lastColorByte = colorByte;
2364 lockedFgBg(PICO9918_INST fgbg, pal, colorByte);
2365 }
2366 }
2367
2368 pix32[0] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte >> 4]);
2369 pix32[1] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte & 0x0f]);
2370 pix32 += 2;
2371 }
2372}
2373
2374#define LOCKED_ROW_CLONE(name, g, m) \
2375 static void __time_critical_func(name)(PICO9918_INST_ARG uint16_t rowNamesAddr, uint16_t colorTableAddr, \
2376 uint8_t tileIndex, uint8_t pal, uint8_t pixels[TMS9918_PIXELS_X], \
2377 const TileRowAddr* addr) \
2378 { \
2379 renderTileRowLocked(PICO9918_INST rowNamesAddr, colorTableAddr, tileIndex, pal, pixels, addr, g, m); \
2380 }
2381
2382LOCKED_ROW_CLONE(rowLockedGm1, false, false)
2383LOCKED_ROW_CLONE(rowLockedGm2, true, false)
2384LOCKED_ROW_CLONE(rowLockedMcm, false, true)
2385
2386/* one F18A tile row into the layer buffer.
2387 *
2388 * isTile2, ecm and gm2 arrive as literals from the wrappers below, so both per-tile switch chains
2389 * and every layer test inside the tile path fold away at compile time, and each wrapper gets
2390 * its own body. The attribute is belt and braces: Priv.h already redefines inline as
2391 * __force_inline for Pico builds.
2392 *
2393 * Everything mode-specific reaches this loop through `addr`, which the caller fills once per layer
2394 * per scanline. Only two things do not fold into it and so ride the clone instead: the name mask,
2395 * which Graphics II applies and Graphics I does not, and Graphics II's colour address, which
2396 * indexes by tile row where Graphics I indexes by a group of eight names.
2397 */
2398#define TILE_ROW_PARAMS \
2399 PICO9918_INST_ARG const bool hpSize, uint16_t rowNamesAddr, uint16_t colorTableAddr, uint8_t tileIndex, \
2400 uint8_t startPattBit, const bool attrPerPos, uint8_t pal, const bool alwaysOnTop, \
2401 const TileRowAddr *addr
2402#define TILE_ROW_ARGS \
2403 PICO9918_INST hpSize, rowNamesAddr, colorTableAddr, tileIndex, startPattBit, attrPerPos, pal, alwaysOnTop, \
2404 addr
2405
2406PICO9918_INLINE_HOT void renderTileRow(TILE_ROW_PARAMS, const bool isTile2,
2407 const uint32_t ecm, const bool gm2, const bool mcm)
2408{
2409 uint32_t xPos = 0;
2410 uint32_t lastEmpty = -1;
2411
2412 const int32_t flipY = addr->flipY;
2413 const uint8_t* patternTable = addr->pattern;
2414 const uint8_t nameMask = addr->nameMask;
2415 uint8_t* targetBuffer = isTile2 ? tms9918->tileLayer2Buffer : tms9918->tileLayer1Buffer;
2416
2417 uint32_t numTiles = GRAPHICS_NUM_COLS + (startPattBit != 0);
2418
2419 uint32_t ecmColorOffset = 0, ecmColorMask = 0, ecmOffset = 0;
2420 if (ecm)
2421 {
2422 ecmColorOffset = (ecm == 3) ? 2 : ecm;
2423 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
2424 ecmOffset = 0x800 >> ((TMS_REGISTER(tms9918, PICO9918_REG_PAGE_SIZE) & PICO9918_R29_TILE_STRIDE) >> 2);
2425 pal = (ecm == 1) ? (pal & 0x20) : 0;
2426 }
2427
2428 while (numTiles)
2429 {
2430 uint32_t run = GRAPHICS_NUM_COLS - tileIndex;
2431 if (run > numTiles) run = numTiles;
2432 numTiles -= run;
2433
2434 const uint8_t* names = tms9918->vram.bytes + rowNamesAddr + tileIndex;
2435
2436 while (run--)
2437 {
2438 uint8_t pattIdx = *names++;
2439 if (gm2 || ecm) pattIdx &= nameMask;
2440 if (ecm)
2441 {
2442 renderEcmTileToAlignedBuffer(PICO9918_INST targetBuffer, xPos, startPattBit, pattIdx, patternTable,
2443 colorTableAddr, ecm, ecmOffset, ecmColorMask, ecmColorOffset, pal,
2444 attrPerPos, flipY, tileIndex, &lastEmpty, isTile2, alwaysOnTop);
2445 }
2446 else
2447 {
2448 renderEcm0Tile(PICO9918_INST targetBuffer, xPos, pattIdx, patternTable, colorTableAddr, pal,
2449 isTile2, gm2, mcm);
2450 }
2451 ++tileIndex;
2452 xPos += 8;
2453 }
2454
2455 if (hpSize)
2456 {
2457 /* the attribute address gained the page bit by addition, so it has to lose it the same way */
2458 rowNamesAddr ^= 0x400;
2459 if (attrPerPos) colorTableAddr = (colorTableAddr + ((rowNamesAddr & 0x400) << 1) - 0x400) & VRAM_MASK;
2460 }
2461 tileIndex = 0;
2462 }
2463}
2464
2465#define TILE_ROW_CLONE(name, t2, e, g, m) \
2466 static void __time_critical_func(name)(TILE_ROW_PARAMS) \
2467 { \
2468 renderTileRow(TILE_ROW_ARGS, t2, e, g, m); \
2469 }
2470
2471TILE_ROW_CLONE(rowT1Ecm0, false, 0, false, false)
2472TILE_ROW_CLONE(rowT1Ecm1, false, 1, false, false)
2473TILE_ROW_CLONE(rowT1Ecm2, false, 2, false, false)
2474TILE_ROW_CLONE(rowT1Ecm3, false, 3, false, false)
2475TILE_ROW_CLONE(rowT1Gm2, false, 0, true, false)
2476TILE_ROW_CLONE(rowT1Mcm, false, 0, false, true)
2477TILE_ROW_CLONE(rowT2Ecm0, true, 0, false, false)
2478TILE_ROW_CLONE(rowT2Ecm1, true, 1, false, false)
2479TILE_ROW_CLONE(rowT2Ecm2, true, 2, false, false)
2480TILE_ROW_CLONE(rowT2Ecm3, true, 3, false, false)
2481TILE_ROW_CLONE(rowT2Gm2, true, 0, true, false)
2482TILE_ROW_CLONE(rowT2Mcm, true, 0, false, true)
2483
2484/* [layer][ecm], with slot 4 the Graphics II ECM0 body: ECM composes with mode through the plane 1
2485 address alone, so ECM1-3 needs no mode of its own */
2486#define TILE_ROW_GM2 4
2487#define TILE_ROW_MCM 5
2488static void (*const tileRowClones[2][6])(TILE_ROW_PARAMS) = {
2489 {rowT1Ecm0, rowT1Ecm1, rowT1Ecm2, rowT1Ecm3, rowT1Gm2, rowT1Mcm},
2490 {rowT2Ecm0, rowT2Ecm1, rowT2Ecm2, rowT2Ecm3, rowT2Gm2, rowT2Mcm}};
2491
2492/**
2493 * \brief generate a tile mode scanline for either T1 or T2 layer
2494 *
2495 * Inlined into both callers on purpose, not by the compiler's judgement: only there does `config`
2496 * fold to a constant, and every field it reads is otherwise a load on a hot line. It sits near
2497 * the size gcc stops at, so a statement added here has twice silently cost the board a layer.
2498 */
2499PICO9918_INLINE_HOT void f18a_tile_layer_scan_line(PICO9918_INST_ARG uint16_t y,
2500 const TileLayerConfig* config, const bool blend)
2501{
2502 const uint32_t ecm = (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ECM_TILE) >> 4;
2503 const bool gm2 = pico9918_cached_mode == TMS_MODE_GRAPHICS_II;
2504 const bool mcm = pico9918_cached_mode == TMS_MODE_MULTICOLOR;
2505 const bool wide = TEXT80_WIDE_ROW;
2506 const bool text = wide || pico9918_cached_mode == TMS_MODE_TEXT;
2507 const uint8_t textCols = wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS;
2508
2509 /* text takes its colour per cell at ECM0 too; a graphics mode there does not (D6) */
2510 const bool attrPerPos =
2511 (text || ecm) && (TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_POS_ATTR);
2512
2513 TileRowAddr addr;
2514 uint16_t rowNamesAddr, colorTableAddr;
2515 tileLayerAddr(PICO9918_INST y, config, text ? textCols : GRAPHICS_NUM_COLS, 0x0f, text, gm2, mcm, attrPerPos,
2516 &addr, &rowNamesAddr, &colorTableAddr);
2517
2518 const uint8_t pal = (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_SELECT) & config->paletteMask)
2519 << config->paletteShift;
2520
2521 const bool alwaysOnTop =
2522 config->priorityReg ? !(TMS_REGISTER(tms9918, config->priorityReg) & config->priorityMask) : false;
2523
2524 if (text)
2525 {
2526 const uint8_t fixed = (tmsMainFgColor(tms9918) << 4) | tmsMainBgColor(tms9918);
2527
2528 /* ECM1-3 takes the attribute table by name; only ECM0 has an fg/bg pair to fall back on */
2529 const uint8_t* colors = (attrPerPos || ecm) ? tms9918->vram.bytes + colorTableAddr : &fixed;
2530 uint8_t* dest = (config->isTile2 ? tms9918->tileLayer2Buffer : tms9918->tileLayer1Buffer) +
2531 (wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX);
2532 const uint32_t scroll = textScrollSplit(TMS_REGISTER(tms9918, config->startPattReg), wide);
2533
2534#if PICO9918_TEXT80_8BPP
2535 if (blend)
2536 {
2537 /* into layer 1's line, at the offset layer 2's own scroll puts it there */
2538 dest = tms9918->tileLayer1Buffer + TEXT80_PADDING_PX +
2539 textPixelOffset(TMS_REGISTER(tms9918, PICO9918_REG_T1_HSCROLL), true) -
2540 TEXT_SCROLL_OFFSET(scroll);
2541 text80RowT2Blend(PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors, attrPerPos, pal,
2542 dest, scroll, alwaysOnTop);
2543 return;
2544 }
2545#endif
2546
2547#if PICO9918_TEXT80_8BPP
2548 if (wide)
2549 {
2550 text80RowClones[config->isTile2][ecm](PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr,
2551 colors, attrPerPos, pal, dest, scroll, alwaysOnTop);
2552 return;
2553 }
2554#endif
2555 textRowClones[config->isTile2][ecm](PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors,
2556 attrPerPos, pal, dest, scroll, alwaysOnTop);
2557 return;
2558 }
2559
2560 const uint8_t startPattBit = TMS_REGISTER(tms9918, config->startPattReg) & 0x07;
2561 const uint8_t tileIndex = (TMS_REGISTER(tms9918, config->startPattReg) >> 3);
2562 const bool hpSize = TMS_REGISTER(tms9918, PICO9918_REG_PAGE_SIZE) & config->hpSizeMask;
2563
2564 uint32_t slot = ecm;
2565 if (!ecm) slot = gm2 ? TILE_ROW_GM2 : (mcm ? TILE_ROW_MCM : 0);
2566
2567 /* two indirect calls per scanline buys a body per (isTile2, ecm, mode) with no per-tile dispatch */
2568 tileRowClones[config->isTile2][slot](PICO9918_INST hpSize, rowNamesAddr, colorTableAddr, tileIndex,
2569 startPattBit, attrPerPos, pal, alwaysOnTop, &addr);
2570}
2571
2572/** \brief generate a Graphics I mode scanline for the T1 layer */
2573static void __time_critical_func(f18a_tile1_scan_line)(PICO9918_INST_ARG uint16_t y)
2574{
2575 f18a_tile_layer_scan_line(PICO9918_INST y, &T1_CONFIG, false);
2576}
2577
2578/** \brief generate a Graphics I mode scanline for the T2 layer */
2579static void __time_critical_func(f18a_tile2_scan_line)(PICO9918_INST_ARG uint16_t y, const bool blend)
2580{
2581 f18a_tile_layer_scan_line(PICO9918_INST y, &T2_CONFIG, blend);
2582}
2583
2584static bool underLayer = false;
2585
2586/* Which buffer holds the finished line. Normally the one the caller passed, which the composite
2587 merges into. Where there is nothing to arbitrate it is tile layer 1's own buffer, and the merged
2588 line is then neither written nor read back - so the caller must ask rather than assume, which
2589 `pico9918_line_source` is for. */
2590const uint8_t* pico9918_cached_line_source = 0;
2591/**
2592 * \brief generate an F18A bitmap layer scanline
2593 *
2594 * INLINE: so will be different versions generated, depending on hard-coded (or known at compile-time) arguments
2595 */
2596PICO9918_INLINE_HOT bool renderBitmapLayerBody(PICO9918_INST_ARG uint16_t y, bool opaque, const uint8_t width,
2597 const uint16_t addr, const uint8_t bmlCtl,
2598 uint8_t pixels[TMS9918_PIXELS_X], const bool wide,
2599 const bool intoTile1)
2600{
2601 // written over T1's own buffer the layer is already above it, so the row mask arbitrates nothing
2602 bool writeMask = (bmlCtl & 0x40) && !intoTile1;
2603 underLayer = !(bmlCtl & 0x40);
2604
2605 bool returnVal = true;
2606
2607 if (writeMask && opaque && (width == 64))
2608 {
2609 for (int i = 0; i < TMS9918_PIXELS_X / 32; ++i) rowMasks.rowBits[i] = -1;
2610 writeMask = false;
2611 returnVal = false;
2612 }
2613
2614 uint32_t currentMask = 0;
2615 uint8_t xPos = TMS_REGISTER(tms9918, PICO9918_REG_BML_X);
2616
2617 if (bmlCtl & 0x10) // fat 4bpp pixels?
2618 {
2619 const uint8_t colorMask = 0xf0;
2620 const uint8_t colorOffset = 4;
2621 const uint8_t colorCount = 2;
2622 const uint8_t colorSize = 4;
2623 uint32_t maskPixelMask = 0x3u << 30;
2624 uint32_t maskX = xPos;
2625
2626 uint8_t pal = (bmlCtl & 0xc) << 2;
2627
2628 for (int xOff = 0; xOff < width; ++xOff)
2629 {
2630 uint8_t data = tms9918->vram.bytes[addr + xOff];
2631 for (int sp = 0; sp < colorCount; ++sp)
2632 {
2633 uint8_t color = (data & colorMask);
2634 if (opaque || color)
2635 {
2636 uint8_t finalColour = pal | (color >> colorOffset);
2637 if (wide)
2638 {
2639 const uint16_t pair = (uint16_t)(finalColour | (finalColour << 8));
2640 *(uint16_t*)(pixels + xPos * 2) = pair;
2641 *(uint16_t*)(pixels + xPos * 2 + 2) = pair;
2642 }
2643 else
2644 {
2645 pixels[xPos] = finalColour;
2646 pixels[xPos + 1] = finalColour;
2647 }
2648 currentMask |= maskPixelMask;
2649 }
2650 xPos += 2;
2651 data <<= colorSize;
2652 maskPixelMask >>= 2;
2653 }
2654 if (writeMask && !maskPixelMask && currentMask)
2655 {
2656 tmsTestRowBitsMask(maskX, currentMask, 32, true, false, false);
2657 maskX = xPos;
2658 maskPixelMask = 0x3u << 30;
2659 currentMask = 0;
2660 }
2661 }
2662 if (writeMask && currentMask)
2663 {
2664 tmsTestRowBitsMask(maskX, currentMask, xPos - maskX, true, false, false);
2665 }
2666 }
2667 else // regular 2bpp pixels
2668 {
2669 const uint8_t colorMask = 0xc0;
2670 const uint8_t colorOffset = 6;
2671 const uint8_t colorCount = 4;
2672 const uint8_t colorSize = 2;
2673 uint32_t maskPixelMask = 0x1u << 31;
2674 uint32_t maskX = xPos;
2675
2676 uint8_t pal = (bmlCtl & 0xf) << 2;
2677
2678 if (opaque && !wide && ((xPos & 3) == 0))
2679 {
2680 /* LOAD-BEARING: an aligned start stays aligned because the layer advances four pixels a
2681 * byte and the row is 256 wide, so no word store ever straddles xPos wrapping. That is
2682 * what removes the head/tail an unaligned block expansion would need on Cortex-M0+. */
2683 const uint32_t palQuad = repeatedPalette(pal);
2684 uint32_t* const quadPixels = (uint32_t*)pixels;
2685 uint32_t quadOffset = xPos >> 2;
2686
2687 for (int xOff = 0; xOff < width; ++xOff)
2688 {
2689 const uint8_t data = tms9918->vram.bytes[addr + xOff];
2690 quadPixels[quadOffset & (TMS9918_PIXELS_X / 4 - 1)] =
2691 (bmlExpand2bpp[data >> 4] | ((uint32_t)bmlExpand2bpp[data & 0x0f] << 16)) | palQuad;
2692 ++quadOffset;
2693 }
2694
2695 if (writeMask)
2696 {
2697 /* every pixel is opaque, so a whole group is a full mask and only the tail is partial */
2698 uint32_t left = (uint32_t)width * colorCount;
2699 while (left >= 32)
2700 {
2701 tmsTestRowBitsMask(maskX, 0xffffffffu, 32, true, false, false);
2702 maskX = (maskX + 32) & 0xff;
2703 left -= 32;
2704 }
2705 if (left) tmsTestRowBitsMask(maskX, ~0u << (32 - left), left, true, false, false);
2706 }
2707 return returnVal;
2708 }
2709
2710 for (int xOff = 0; xOff < width; ++xOff)
2711 {
2712 uint8_t data = tms9918->vram.bytes[addr + xOff];
2713 for (int sp = 0; sp < colorCount; ++sp)
2714 {
2715 uint8_t color = (data & colorMask);
2716 if (opaque || color)
2717 {
2718 const uint8_t v = pal | (color >> colorOffset);
2719 if (wide)
2720 *(uint16_t*)(pixels + xPos * 2) = (uint16_t)(v | (v << 8));
2721 else
2722 pixels[xPos] = v;
2723 currentMask |= maskPixelMask;
2724 }
2725 ++xPos;
2726 data <<= colorSize;
2727 maskPixelMask >>= 1;
2728 }
2729
2730 if (writeMask && !maskPixelMask && currentMask)
2731 {
2732 tmsTestRowBitsMask(maskX, currentMask, 32, true, false, false);
2733 maskX = xPos;
2734 maskPixelMask = 0x1u << 31;
2735 currentMask = 0;
2736 }
2737 }
2738 if (writeMask && currentMask)
2739 {
2740 tmsTestRowBitsMask(maskX, currentMask, xPos - maskX, true, false, false);
2741 }
2742 }
2743 return returnVal;
2744}
2745
2746/* One body per line width, for the reason spriteGridWord gives: the layer is on the 256-pixel grid
2747 whatever the mode, so a wide row draws each of its pixels twice. The F18A does the same - see the
2748 text2 case in f18a_tiles.vhd, which halves the layer's pixel clock rather than repeating it. */
2749#if PICO9918_TEXT80_8BPP
2750static EMITTER_NOINLINE bool __time_critical_func(renderBitmapLayer80)(
2751 PICO9918_INST_ARG uint16_t y, bool opaque, const uint8_t width, const uint16_t addr, const uint8_t bmlCtl,
2752 uint8_t pixels[TMS9918_PIXELS_X], const bool intoTile1)
2753{
2754 return renderBitmapLayerBody(PICO9918_INST y, opaque, width, addr, bmlCtl, pixels, true, intoTile1);
2755}
2756#endif
2757
2758static inline bool __time_critical_func(renderBitmapLayer40)(PICO9918_INST_ARG uint16_t y, bool opaque,
2759 const uint8_t width, const uint16_t addr,
2760 const uint8_t bmlCtl,
2761 uint8_t pixels[TMS9918_PIXELS_X])
2762{
2763 return renderBitmapLayerBody(PICO9918_INST y, opaque, width, addr, bmlCtl, pixels, false, false);
2764}
2765
2766/** \brief generate an F18A bitmap layer scanline */
2767static bool __time_critical_func(bitmap_layer_scan_line)(PICO9918_INST_ARG uint16_t y,
2768 uint8_t pixels[TMS9918_PIXELS_X],
2769 const bool intoTile1)
2770{
2771 /* bml enabled? */
2772 const uint8_t bmlCtl = TMS_REGISTER(tms9918, PICO9918_REG_BML_CONTROL);
2773 if (!(bmlCtl & 0x80)) return true;
2774
2775 /* bml on this scanline? */
2776 const uint8_t top = TMS_REGISTER(tms9918, PICO9918_REG_BML_TOP_ROW);
2777 if (top > y) return true;
2778
2779 y -= top;
2780 if (y >= TMS_REGISTER(tms9918, PICO9918_REG_BML_HEIGHT)) return true;
2781
2782 /* row stride in bytes, four pixels each, rounded up so every row starts on a byte */
2783 const uint8_t bmlWidth = TMS_REGISTER(tms9918, PICO9918_REG_BML_WIDTH);
2784 const uint8_t width = bmlWidth ? ((bmlWidth + 3) >> 2) : 64;
2785 const uint16_t addr = (TMS_REGISTER(tms9918, PICO9918_REG_BML_BASE) << 6) + (y * width);
2786
2787 const bool opaque = !(bmlCtl & 0x20);
2788
2789#if PICO9918_TEXT80_8BPP
2790 if (TEXT80_WIDE_ROW)
2791 return renderBitmapLayer80(PICO9918_INST y, opaque, width, addr, bmlCtl, pixels, intoTile1);
2792#endif
2793 return renderBitmapLayer40(PICO9918_INST y, opaque, width, addr, bmlCtl, pixels);
2794}
2795
2796/* One 32-pixel chunk of the composite, four pixels at a time. Selecting a layer per pixel is a byte
2797 * mask over two words, and the nibble-to-word lookup the tile emitters use is exactly that mask - so
2798 * a chunk is eight selects rather than thirty-two.
2799 *
2800 * `hasSprites` arrives as a literal, so a chunk no sprite touches compiles to a plain store and one
2801 * that a sprite crosses to a merge, with no test in either. The caller can only use this where both
2802 * layer pointers are word-aligned, which a scroll that is not a multiple of four denies.
2803 */
2804static inline void compositeChunkWide(uint32_t* __restrict pix32, const uint32_t* __restrict l1,
2805 const uint32_t* __restrict l2, uint32_t sel, uint32_t open,
2806 const bool hasSprites)
2807{
2808 for (int i = 0; i < 8; ++i)
2809 {
2810 const uint32_t layers = maskExpandNibbleToWordRev[sel >> 28];
2811 uint32_t merged = l1[i] ^ ((l1[i] ^ l2[i]) & layers);
2812
2813 if (hasSprites)
2814 {
2815 const uint32_t old = pix32[i];
2816 merged = old ^ ((old ^ merged) & maskExpandNibbleToWordRev[open >> 28]);
2817 open <<= 4;
2818 }
2819
2820 pix32[i] = merged;
2821 sel <<= 4;
2822 }
2823}
2824
2825/* the same chunk with a bitmap layer under the tiles, which is the one case that has to stay per
2826 * pixel: zero means transparent here, so every byte needs its own test and there is no word to
2827 * select whole. `hasSprites` is the same literal the other two chunks take, and it is worth more
2828 * here than anywhere - without it every pixel of every chunk tests and shifts a mask that is zero.
2829 */
2830static inline void compositeChunkUnder(uint8_t* __restrict pixels, const uint8_t* __restrict layer1,
2831 const uint8_t* __restrict layer2, uint32_t mask, uint32_t spriteMask,
2832 const bool hasSprites)
2833{
2834 for (int i = 0; i < 8; ++i)
2835 {
2836#define UNDER_PIXEL(n) \
2837 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2838 { \
2839 const uint8_t pixel = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2840 if (pixel) pixels[n] = pixel; \
2841 } \
2842 mask <<= 1; \
2843 if (hasSprites) spriteMask <<= 1;
2844
2845 UNDER_PIXEL(0)
2846 UNDER_PIXEL(1)
2847 UNDER_PIXEL(2)
2848 UNDER_PIXEL(3)
2849#undef UNDER_PIXEL
2850
2851 pixels += 4;
2852 layer1 += 4;
2853 layer2 += 4;
2854 }
2855}
2856
2857/* the same chunk a byte at a time, for a scroll that leaves a layer unaligned */
2858static inline void compositeChunkBytes(uint8_t* __restrict pixels, const uint8_t* __restrict layer1,
2859 const uint8_t* __restrict layer2, uint32_t mask, uint32_t spriteMask,
2860 const bool hasSprites)
2861{
2862 for (int i = 0; i < 8; ++i)
2863 {
2864#define MIXED_PIXEL(n) \
2865 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2866 { \
2867 pixels[n] = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2868 } \
2869 mask <<= 1; \
2870 if (hasSprites) spriteMask <<= 1;
2871
2872 MIXED_PIXEL(0)
2873 MIXED_PIXEL(1)
2874 MIXED_PIXEL(2)
2875 MIXED_PIXEL(3)
2876#undef MIXED_PIXEL
2877
2878 pixels += 4;
2879 layer1 += 4;
2880 layer2 += 4;
2881 }
2882}
2883
2884/* Sprites and the bitmap layer are on the 256-pixel grid whatever the mode, so a wide
2885 row's chunk of 32 tile pixels is only 16 of theirs: half a mask word, doubled bit by bit through
2886 the table the magnified sprite emitter already uses. `wide` is a clone parameter rather than a
2887 count, because that read is inside the chunk loop. */
2888static inline uint32_t spriteGridWord(const uint32_t maskWord, const BitMask mask, const bool wide)
2889{
2890 if (!wide) return mask[maskWord];
2891
2892 const uint32_t half = (maskWord & 1) ? (mask[maskWord >> 1] & 0xffff) : (mask[maskWord >> 1] >> 16);
2893 return ((uint32_t)doubledBits[half >> 8] << 16) | doubledBits[half & 0xff];
2894}
2895
2896/* A line the zero-copy path would have handed out whole, but for the sprites drawn into pixels[].
2897 * Rather than copy the tile line over them and merge the sprites back, put the sprites into the
2898 * tile line and hand that out: a word no sprite touches then costs nothing at all, and there is
2899 * no second layer or selection mask to read for the ones that do. Every other zero-copy condition
2900 * already holds here, so nothing else in pixels[] has to survive.
2901 */
2902static inline void overlaySpritesOnTile1(uint32_t* __restrict dst, const uint32_t* __restrict src,
2903 const bool wide)
2904{
2905 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X) / 32;
2906
2907 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord, dst += 8, src += 8)
2908 {
2909 uint32_t open = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
2910
2911 for (int i = 0; open; ++i, open <<= 4)
2912 {
2913 const uint32_t nibble = open >> 28;
2914 if (nibble)
2915 {
2916 const uint32_t take = maskExpandNibbleToWordRev[nibble];
2917 dst[i] = dst[i] ^ ((dst[i] ^ src[i]) & take);
2918 }
2919 }
2920 }
2921}
2922
2923PICO9918_INLINE_HOT void
2924compositeAlignedBody(PICO9918_INST_ARG uint8_t pixels[TMS9918_PIXELS_X], const int t1Scroll, const int t2Scroll,
2925 const bool wide)
2926{
2927 uint8_t* layer1 = tms9918->tileLayer1Buffer + t1Scroll;
2928 uint8_t* layer2 = tms9918->tileLayer2Buffer + t2Scroll;
2929 uint32_t* selectionMask = tms9918->layerSelectionMask;
2930 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X) / 32;
2931
2932
2933 const bool wordAligned = ((t1Scroll | t2Scroll) & 3) == 0;
2934 const uint32_t chunkCount = wordAligned ? 32 / sizeof(uint32_t) : 32;
2935 PICO9918_COPY_SET_WIDTH(PICO9918_COPY, wordAligned);
2936
2937 // Process in 32-pixel chunks (1 mask word at a time)
2938 for (uint32_t maskWord = 0; maskWord < maskWords; maskWord++)
2939 {
2940 uint32_t mask = selectionMask[maskWord];
2941
2942 /* a priority bitmap layer wins over T1 only - T2 still draws over it */
2943 uint32_t spriteMask = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide) |
2944 (spriteGridWord(maskWord, rowMasks.rowBits, wide) & ~mask);
2945
2946 if (spriteMask == 0xffffffffu)
2947 {
2948 layer1 += 32;
2949 layer2 += 32;
2950 pixels += 32;
2951 continue;
2952 }
2953
2954 if (!underLayer && !spriteMask)
2955 {
2956 if (mask == 0)
2957 {
2958 // All T1 pixels - use DMA copy for speed
2959 PICO9918_COPY_WAIT(PICO9918_COPY);
2960 PICO9918_COPY_SET_SRC(PICO9918_COPY, layer1);
2961 PICO9918_COPY_SET_DST(PICO9918_COPY, pixels);
2962 PICO9918_COPY_TRIGGER(PICO9918_COPY, chunkCount);
2963 pixels += 32;
2964 layer1 += 32;
2965 layer2 += 32;
2966 continue;
2967 }
2968 else if (mask == 0xffffffffu)
2969 {
2970 // All T2 pixels - use DMA copy for speed
2971 PICO9918_COPY_WAIT(PICO9918_COPY);
2972 PICO9918_COPY_SET_SRC(PICO9918_COPY, layer2);
2973 PICO9918_COPY_SET_DST(PICO9918_COPY, pixels);
2974 PICO9918_COPY_TRIGGER(PICO9918_COPY, chunkCount);
2975 pixels += 32;
2976 layer1 += 32;
2977 layer2 += 32;
2978 continue;
2979 }
2980 }
2981
2982
2983 // mixed - process 4 pixels at a time with individual byte access
2984 if (underLayer)
2985 {
2986 if (spriteMask)
2987 compositeChunkUnder(pixels, layer1, layer2, mask, spriteMask, true);
2988 else
2989 compositeChunkUnder(pixels, layer1, layer2, mask, 0, false);
2990
2991 pixels += 32;
2992 layer1 += 32;
2993 layer2 += 32;
2994 }
2995 else if (wordAligned)
2996 {
2997 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
2998 const uint32_t* l1 = (const uint32_t*)PICO9918_ASSUME_ALIGNED(layer1, 4);
2999 const uint32_t* l2 = (const uint32_t*)PICO9918_ASSUME_ALIGNED(layer2, 4);
3000
3001 if (spriteMask)
3002 compositeChunkWide(pix32, l1, l2, mask, ~(uint32_t)spriteMask, true);
3003 else
3004 compositeChunkWide(pix32, l1, l2, mask, 0, false);
3005
3006 pixels += 32;
3007 layer1 += 32;
3008 layer2 += 32;
3009 }
3010 else
3011 {
3012 if (spriteMask)
3013 compositeChunkBytes(pixels, layer1, layer2, mask, spriteMask, true);
3014 else
3015 compositeChunkBytes(pixels, layer1, layer2, mask, 0, false);
3016
3017 pixels += 32;
3018 layer1 += 32;
3019 layer2 += 32;
3020 }
3021 }
3022}
3023
3024
3025/* One body per line width. The chunk loop reads the sprite grid on every pass, so the rate it reads
3026 it at has to be a literal there rather than a value carried in - which is also what keeps the
3027 256-pixel modes paying nothing for the tier. */
3028#if PICO9918_TEXT80_8BPP
3029static EMITTER_NOINLINE
3030#else
3031/* one width, one caller, so it inlines: standing it out of line costs RP2040 two-layer lines dearly */
3032static inline
3033#endif
3034 void __time_critical_func(compositeAligned40)(PICO9918_INST_ARG uint8_t pixels[TMS9918_PIXELS_X],
3035 const int t1Scroll, const int t2Scroll)
3036{
3037 compositeAlignedBody(PICO9918_INST pixels, t1Scroll, t2Scroll, false);
3038}
3039
3040#if PICO9918_TEXT80_8BPP
3041static EMITTER_NOINLINE void
3042__time_critical_func(compositeAligned80)(PICO9918_INST_ARG uint8_t pixels[TMS9918_PIXELS_X], const int t1Scroll,
3043 const int t2Scroll)
3044{
3045 compositeAlignedBody(PICO9918_INST pixels, t1Scroll, t2Scroll, true);
3046}
3047#endif
3048
3049static inline void compositeAlignedTileBuffers(PICO9918_INST_ARG uint8_t pixels[TMS9918_PIXELS_X],
3050 const int t1Scroll, const int t2Scroll, const bool wide)
3051{
3052#if PICO9918_TEXT80_8BPP
3053 if (wide)
3054 {
3055 compositeAligned80(PICO9918_INST pixels, t1Scroll, t2Scroll);
3056 return;
3057 }
3058#endif
3059 compositeAligned40(PICO9918_INST pixels, t1Scroll, t2Scroll);
3060}
3061
3062/**
3063 * \brief Composite with tile layer 1 disabled (reg 0x32 bit 4). Layer 1 contributes no pixel at all, so
3064 * every position the selection mask leaves to it must keep whatever the backdrop, bitmap layer and
3065 * sprites already put there - the colour stage falls through to the backdrop for a transparent
3066 * merged pixel. Writing layer 1's buffer, or a zero buffer, would paint palette entry 0 instead.
3067 */
3068static PICO9918_NOINLINE void
3070 const int t2Scroll, const bool wide)
3071{
3072 const uint8_t* layer2 = tms9918->tileLayer2Buffer + t2Scroll;
3073 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X) / 32;
3074
3075 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord)
3076 {
3077 uint32_t mask =
3078 tms9918->layerSelectionMask[maskWord] & ~spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
3079
3080 if (!mask) /* nothing of layer 2 reaches the screen here */
3081 {
3082 layer2 += 32;
3083 pixels += 32;
3084 continue;
3085 }
3086
3087 for (int i = 0; i < 8; ++i)
3088 {
3089 if (mask & MASK_NEXT_PIXEL) pixels[0] = layer2[0];
3090 mask <<= 1;
3091
3092 if (mask & MASK_NEXT_PIXEL) pixels[1] = layer2[1];
3093 mask <<= 1;
3094
3095 if (mask & MASK_NEXT_PIXEL) pixels[2] = layer2[2];
3096 mask <<= 1;
3097
3098 if (mask & MASK_NEXT_PIXEL) pixels[3] = layer2[3];
3099 mask <<= 1;
3100
3101 pixels += 4;
3102 layer2 += 4;
3103 }
3104 }
3105}
3106
3107/**
3108 * \brief A text row draws 240 of the 256 pixels and the composite copies all of them, so layer 1's buffer
3109 * has to carry the side borders itself. The row is emitted on cell boundaries and read back
3110 * `t1Scroll` pixels along, so the border moves with the scroll - and the cells that fall outside it
3111 * either side are overwritten here rather than never written.
3112 */
3113static void __time_critical_func(textRowBorder)(PICO9918_INST_ARG const int t1Scroll, const uint32_t padding,
3114 const uint32_t width)
3115{
3116 uint8_t* left = tms9918->tileLayer1Buffer + t1Scroll;
3117 uint8_t* right = left + width - padding;
3118
3119 for (uint32_t i = 0; i < padding; ++i)
3120 {
3121 left[i] = bg;
3122 right[i] = bg;
3123 }
3124}
3125
3126/** \brief generate a Graphics I or Graphics II mode scanline */
3127static uint8_t __time_critical_func(graphics_i_scan_line)(PICO9918_INST_ARG uint16_t y,
3128 uint8_t pixels[TMS9918_PIXELS_X])
3129{
3130 uint8_t tempStatus = 0;
3131
3132 /* locked or unlocked is decided once here, not re-tested per row */
3133 if (PICO9918_UNLOCKED(tms9918))
3134 {
3135 /* the background fill owns pixels[] until it completes */
3136 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3137
3138 const uint8_t bmlCtlReg = TMS_REGISTER(tms9918, PICO9918_REG_BML_CONTROL);
3139
3140 /* LOAD-BEARING: drawn over tile layer 1's buffer a priority layer is above T1 by construction,
3141 which is what lets the blend and the zero-copy line survive it. Each condition breaks that:
3142 an UNDER layer needs per-pixel arbitration, tile 1 off means that buffer is never read, and
3143 only a wide row doubles. Relax any of them and the layer is lost or lands under T1. */
3144 const bool bmlInTile1 = TEXT80_WIDE_ROW && (bmlCtlReg & PICO9918_R31_BML_ENABLE) &&
3145 (bmlCtlReg & 0x40) &&
3146 !(TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_TILE1_OFF);
3147
3148 bool writeMask = true;
3149 if (bmlInTile1)
3150 underLayer = false;
3151 else
3152 writeMask = bitmap_layer_scan_line(PICO9918_INST y, pixels, false);
3153
3154 const uint32_t transparent = underLayer ? 0 : bg;
3155 transparentPixels[0] = transparentPixels[1] = transparent;
3156 ecm0Palette[0x00] = ecm0Palette[0x10] = ecm0Palette[0x20] = ecm0Palette[0x30] = transparent;
3157
3158 tempStatus = pico9918_output_sprites(PICO9918_INST y, pixels);
3159
3160 if (writeMask) // bitmap layer completely masked it?
3161 {
3162 const bool wide = TEXT80_WIDE_ROW;
3163 const bool textRow = wide || pico9918_cached_mode == TMS_MODE_TEXT;
3164 const int t1Scroll = scrollOffset(TMS_REGISTER(tms9918, PICO9918_REG_T1_HSCROLL), textRow, wide);
3165 const int t2Scroll = scrollOffset(TMS_REGISTER(tms9918, PICO9918_REG_T2_HSCROLL), textRow, wide);
3166 const bool tile2Enabled = TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_TILE2_ENABLE;
3167 const bool tile1Enabled = !(TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED2) & PICO9918_R50_TILE1_OFF);
3168
3169 const bool blend = wide && tile1Enabled && tile2Enabled &&
3170 !((TMS_REGISTER(tms9918, PICO9918_REG_ENHANCED1) & PICO9918_R49_ECM_TILE) >> 4) &&
3171 (bmlInTile1 || !(bmlCtlReg & PICO9918_R31_BML_ENABLE));
3172
3173 if (tile2Enabled && !blend)
3174 {
3176 tmsCopyAlignMask(tms9918->finalMask, tms9918->layerSelectionMask, t1Scroll - t2Scroll);
3177 }
3178
3179 if (tile1Enabled)
3180 {
3182 if (bmlInTile1)
3183 bitmap_layer_scan_line(PICO9918_INST y, tms9918->tileLayer1Buffer + t1Scroll, true);
3184 if (blend) f18a_tile2_scan_line(PICO9918_INST y, true);
3185 if (textRow)
3186 textRowBorder(PICO9918_INST t1Scroll, wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX,
3187 wide ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X);
3188 }
3189
3190 if (tile2Enabled && !blend)
3191 tmsRestoreAlignMask(tms9918->layerSelectionMask, tms9918->finalMask, -t1Scroll, t2Scroll);
3192
3193 if (textRow && !blend)
3194 {
3195 const uint32_t pad = wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX;
3196 const uint32_t end = pad + (wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS) * TEXT_CHAR_WIDTH;
3197 tms9918->layerSelectionMask[0] &= 0xffffffffu >> pad;
3198 tms9918->layerSelectionMask[(end - 1) >> 5] &= ~(0xffffffffu >> (end & 0x1f));
3199 }
3200
3201 /* WARNING: the scroll test keeps the handed-out line word-aligned. A caller may read it a
3202 word at a time, and on Cortex-M0+ an unaligned word load HardFaults rather than running slow */
3203 if (tile1Enabled && (!tile2Enabled || blend) && !underLayer && !(t1Scroll & 3) &&
3204 (bmlInTile1 || !(bmlCtlReg & PICO9918_R31_BML_ENABLE)))
3205 {
3206 uint8_t* line = tms9918->tileLayer1Buffer + t1Scroll;
3207
3208 if (tms9918->scanlineHasSprites)
3209 overlaySpritesOnTile1((uint32_t*)PICO9918_ASSUME_ALIGNED(line, 4),
3210 (const uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4), wide);
3211
3212 pico9918_cached_line_source = line;
3213 }
3214 else if (tile1Enabled)
3215 {
3216 compositeAlignedTileBuffers(PICO9918_INST pixels, t1Scroll, t2Scroll, wide);
3217 }
3218 else if (tile2Enabled)
3219 {
3220 compositeTile2OnlyBuffer(PICO9918_INST pixels, t2Scroll, wide);
3221 }
3222 /* both layers off: the backdrop, bitmap layer and sprites are already in pixels[] */
3223 }
3224 }
3225 else
3226 {
3227 const uint8_t tileY = y >> 3; /* which name table row (0 - 23)... or 29 */
3228
3229 /* address in name table at the start of this row */
3230 const uint16_t rowOffset = tileY * GRAPHICS_NUM_COLS;
3231 uint16_t rowNamesAddr = tmsNameTableAddr(tms9918) + rowOffset;
3232 uint16_t colorTableAddr = tmsColorTableAddr(tms9918);
3233
3234 const bool gm2 = pico9918_cached_mode == TMS_MODE_GRAPHICS_II;
3235 const bool mcm = pico9918_cached_mode == TMS_MODE_MULTICOLOR;
3236 TileRowAddr addr;
3237 tileRowAddr(PICO9918_INST y, y, TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE), gm2, mcm, &addr,
3238 &colorTableAddr);
3239
3240 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3241
3242 if (gm2)
3243 rowLockedGm2(PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3244 else if (mcm)
3245 rowLockedMcm(PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3246 else
3247 rowLockedGm1(PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3248
3249 tempStatus = pico9918_output_sprites(PICO9918_INST y, pixels);
3250 }
3251
3252 return tempStatus;
3253}
3254
3255/** \brief generate a scanline */
3256PICO9918_DLLEXPORT uint8_t __time_critical_func(pico9918_scan_line)(PICO9918_INST_ARG uint16_t y)
3257{
3258 uint8_t* const pixels = scanlineBuffer;
3259 uint8_t tempStatus = 0;
3260
3261 if (!lookupsReady) initLookups();
3262
3263 pico9918_mode_t currentCachedMode = tmsMode(tms9918);
3264 if (currentCachedMode != pico9918_cached_mode)
3265 {
3266 pico9918_cached_mode = currentCachedMode;
3267 tms9918->palDirty = 1;
3268 }
3269
3270 const uint8_t bgc = tmsMainBgColor(tms9918);
3271 const bool packedNibbles = pico9918_cached_mode == TMS_MODE_TEXT80 && !TEXT80_WIDE_ROW;
3272 bg = repeatedPalette(
3273 bgc |
3274 (packedNibbles ? bgc << 4
3275 : (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_SELECT) & PICO9918_R24_TILE1_PS) << 4));
3276#if PICO9918_TEXT80_8BPP
3277 /* a wide row is twice the line to fill, and the count is per mode rather than per build */
3278 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_LINE, pico9918_line_bytes(PICO9918_INST_ONLY) / 4);
3279#endif
3280 PICO9918_FILL32_TRIGGER(PICO9918_FILL_LINE, pixels);
3281 pico9918_cached_line_source = pixels;
3282 underLayer = false;
3283
3284 bool dispActive = (TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_DISP_ACTIVE);
3285
3286 if (dispActive)
3287 {
3288 /* the three row masks go out as one transfer; the instance masks below cover its latency */
3289 PICO9918_FILL32_TRIGGER(PICO9918_FILL_MASKS, &rowMasks);
3290
3291 for (int i = 0; i < SCANLINE_MASK_WORDS; ++i)
3292 {
3293 tms9918->layerSelectionMask[i] = 0; // Default to all T1 pixels
3294 tms9918->finalMask[i] = 0;
3295 }
3296 tms9918->scanlineHasSprites = false;
3297
3298 PICO9918_FILL32_WAIT(PICO9918_FILL_MASKS);
3299
3300 switch (pico9918_cached_mode)
3301 {
3302 case TMS_MODE_GRAPHICS_I:
3303 case TMS_MODE_GRAPHICS_II:
3304 case TMS_MODE_MULTICOLOR: tempStatus = graphics_i_scan_line(PICO9918_INST y, pixels); break;
3305
3306 case TMS_MODE_TEXT:
3307 case TMS_MODE_TEXT80:
3308 if (PICO9918_UNLOCKED(tms9918) && (pico9918_cached_mode == TMS_MODE_TEXT || TEXT80_WIDE_ROW))
3309 {
3310 tempStatus = graphics_i_scan_line(PICO9918_INST y, pixels);
3311 break;
3312 }
3313
3314 text_scan_line(PICO9918_INST y, pixels);
3315 if (PICO9918_UNLOCKED(tms9918)) tempStatus = pico9918_output_sprites(PICO9918_INST y, pixels);
3316 break;
3317 }
3318 }
3319
3320 /* pixels[] must be complete, and owned by nobody, when we return */
3321 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3322 PICO9918_COPY_WAIT(PICO9918_COPY);
3323
3324 return tempStatus;
3325}
3326
3327/** \brief return a register value - see the header for the locked-device aliasing */
3330{
3331 return TMS_REGISTER(tms9918, reg & tms9918->lockedMask); // was 0x07
3332}
3333
3334/** \brief return a status register value without the side effects of reading it */
3337{
3338 return TMS_STATUS(tms9918, reg & PICO9918_R15_STATUS_NUM);
3339}
3340
3342void __time_critical_func(pico9918_write_reg_value_impl)(PICO9918_INST_ARG uint8_t reg, uint8_t value)
3343{
3344 if (PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK) && PICO9918_UNLOCK_REG(reg))
3345 {
3346 /* Recomputed on every write, so a redundant unlock is a no-op and anything else locks */
3347 const bool unlockValue = PICO9918_UNLOCK_VALUE(value);
3348 const bool unlocked = unlockValue && tms9918->unlockCount;
3349
3350 TMS_REGISTER(tms9918, PICO9918_REG_UNLOCK) = value; // through even when locked
3351 tms9918->unlockCount = unlockValue;
3352
3353 if (unlocked != tms9918->isUnlocked)
3354 {
3355 tms9918->isUnlocked = unlocked;
3356 tms9918->lockedMask = unlocked ? 0x3f : 0x07;
3357 tms9918->palDirty = 1;
3358 if (unlocked) TMS_REGISTER(tms9918, PICO9918_REG_MAX_SCAN_SPRITES) = MAX_SPRITES - 1;
3359
3360 /* the scanline-interrupt term of the /INT predicate is gated on being unlocked */
3362 }
3363 }
3364 else
3365 {
3366 /* A write the personality never sees does not disturb the unlock counter either */
3367 if (((reg & ~tms9918->lockedMask) != 0x80) && PICO9918_M4(tms9918)) return;
3368
3369 tms9918->unlockCount = 0;
3370
3371 const int regIndex = reg & tms9918->lockedMask; // was 0x07
3372
3373 TMS_REGISTER(tms9918, regIndex) = value;
3374 if (regIndex < PICO9918_REG_STATUS_SELECT)
3375 {
3376 /* LOAD-BEARING: R0 and R1 hold the only register bits pico9918_interrupt_status_impl
3377 * reads, and regIndex is post-mask - a locked write to R25 lands on R1 and must
3378 * reconcile, so testing the byte the host sent would miss it. */
3379 if (regIndex <= TMS_REG_1) pico9918_write_reconcile_int_impl(PICO9918_INST_ONLY);
3380 return;
3381 }
3382
3383 if ((regIndex == PICO9918_REG_GPU_PC_LSB) ||
3384 ((regIndex == PICO9918_REG_GPU_CONTROL) && ((value & PICO9918_R56_GPU_RUN) == 0)))
3385 {
3386 tms9918->gpuAddress = ((TMS_REGISTER(tms9918, PICO9918_REG_GPU_PC_MSB) << 8) |
3387 TMS_REGISTER(tms9918, PICO9918_REG_GPU_PC_LSB)) &
3388 0xFFFE;
3389 if (regIndex == PICO9918_REG_GPU_PC_LSB)
3390 {
3391 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
3392 tms9918->gpuStatus = 0; /* a new program, not a resumed one */
3393 tms9918->restart = 1;
3394 pico9918_gpu_service(PICO9918_INST_ONLY);
3395 }
3396 }
3397 else if ((regIndex == PICO9918_REG_GPU_CONTROL) && (value & PICO9918_R56_GPU_RUN))
3398 {
3399 tms9918->restart = 1;
3400 pico9918_gpu_service(PICO9918_INST_ONLY);
3401 }
3402 else if (regIndex == PICO9918_REG_FLASH_CONTROL &&
3403 PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG)) // firmware update
3404 {
3405 // b7 : 0 = idle: 1 = execute
3406 // b6 : 0 = verify: 1 = write
3407 // b5 - b0 : address to read firmware data (256 byte boundaries)
3408 // reads one UF2 frame (512 bytes)
3409 if (TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) == 0)
3410 {
3411 TMS_STATUS(tms9918, PICO9918_SR_GPU) = 0x80; // set gpu processing flag
3412 tms9918->flash = 1;
3413 }
3414 else
3415 {
3416 TMS_STATUS(tms9918, PICO9918_SR_GPU) = 0x14; // error - busy
3417 }
3418 }
3419 else if (regIndex == PICO9918_REG_MAX_SCAN_SPRITES && value == 0)
3420 {
3421 TMS_REGISTER(tms9918, PICO9918_REG_MAX_SCAN_SPRITES) = MAX_SPRITES - 1;
3422 }
3423 else if ((regIndex == PICO9918_REG_ENHANCED2) && (value & PICO9918_R50_RESET))
3424 { // reset all registers?
3425 vdpRegisterReset(tms9918);
3426
3427 // reset palette, etc as well?
3428 if (value & 0x40)
3429 {
3430 tms9918->configDirty = true;
3431 tms9918->configVdpDirty = true;
3432 }
3433 }
3434 else if (regIndex == PICO9918_REG_STATUS_SELECT)
3435 {
3436 uint8_t statReg = (value & 0x0f);
3437 TMS_STATUS(tms9918, 0x0F) = statReg; // is this right? or should this be the read-ahead value?
3438 if (value & 0x40) tms9918->startTime = PICO9918_HOST_TIME_US(); // reset
3439 if (value & 0x20)
3440 tms9918->currentTime = PICO9918_HOST_TIME_US(); // snap
3441 else if (value & 0x10)
3442 tms9918->startTime += (tms9918->stopTime - tms9918->startTime);
3443 else
3444 tms9918->currentTime = tms9918->stopTime = PICO9918_HOST_TIME_US();
3445
3446 if (statReg > 3 && statReg < 12)
3447 {
3448 uint32_t elapsed = tms9918->currentTime - tms9918->startTime;
3449 uint32_t microQ, microR;
3450 PICO9918_DIVMOD_U32(elapsed, 1000, microQ, microR);
3451 uint32_t milliQ, milliR;
3452 PICO9918_DIVMOD_U32(microQ, 1000, milliQ, milliR);
3453
3454 TMS_STATUS(tms9918, PICO9918_SR_MICROS_LSB) = microR & 0x0ff;
3455 TMS_STATUS(tms9918, PICO9918_SR_MICROS_MSB) = microR >> 8;
3456 TMS_STATUS(tms9918, PICO9918_SR_MILLIS_LSB) = milliR & 0x0ff;
3457 TMS_STATUS(tms9918, PICO9918_SR_MILLIS_MSB) = milliR >> 8;
3458 TMS_STATUS(tms9918, PICO9918_SR_SECONDS_LSB) = milliQ & 0x00ff;
3459 TMS_STATUS(tms9918, PICO9918_SR_SECONDS_MSB) = milliQ >> 8;
3460 }
3461 }
3462 // SR12 holds the value of the option in VR58 (options)
3463 else if (regIndex == PICO9918_REG_CONFIG_INDEX && PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG))
3464 {
3465 const uint8_t option = TMS_REGISTER(tms9918, PICO9918_REG_CONFIG_INDEX);
3466
3467 TMS_STATUS(tms9918, PICO9918_SR_CONFIG_VALUE) = tms9918->config[option];
3468 }
3469 // option number in reg 58, value in 59 (options)
3470 else if (regIndex == PICO9918_REG_CONFIG_VALUE && PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG) &&
3471 TMS_REGISTER(tms9918, PICO9918_REG_CONFIG_INDEX) >= 8)
3472 {
3473 const uint8_t option = TMS_REGISTER(tms9918, PICO9918_REG_CONFIG_INDEX);
3474
3475 tms9918->config[option] = value;
3476 TMS_STATUS(tms9918, PICO9918_SR_CONFIG_VALUE) = value;
3477 tms9918->configDirty = true;
3478 }
3479 }
3480}
3481
3482
3483/** \brief return a value from vram */
3485uint8_t __time_critical_func(pico9918_vram_value)(PICO9918_INST_ARG uint16_t addr)
3486{
3487 return tms9918->vram.bytes[addr & VRAM_MASK];
3488}
3489
3490/** \brief check BLANK flag */
3493{
3494 return (TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_DISP_ACTIVE);
3495}
3496
3497/** \brief current display mode */
3500{
3501 return pico9918_cached_mode;
3502}
3503
3504#if PICO9918_BUILD_DEBUG_API
3505/** \brief see impl/pico9918_priv.h. What the scanline entry does, for a caller between two. */
3507{
3508 pico9918_cached_mode = tmsMode(tms9918);
3509}
3510#endif
3511
3512/**
3513 * \brief how many bytes of pixels[] this mode fills. Every mode is 256 but unlocked 80-column text on a
3514 * board with the 8bpp tier, which is 512 - so the palette expansion, the backdrop fill and anything
3515 * reading the line ask here rather than each deciding it again.
3516 */
3518uint32_t __time_critical_func(pico9918_line_bytes)(PICO9918_INST_ONLY_ARG)
3519{
3520 return TEXT80_WIDE_ROW ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X;
3521}
3522
3523/**
3524 * \brief where the scanline just generated actually is. Usually the buffer that was passed in, but on a
3525 * line with nothing to arbitrate it is a tile layer's own buffer and the passed one holds only the
3526 * backdrop fill - so read the line from here rather than from what was handed over.
3527 */
3529const uint8_t* __time_critical_func(pico9918_line_source)(PICO9918_INST_ONLY_ARG)
3530{
3531 return pico9918_cached_line_source;
3532}
3533
3534/** \brief a default palette entry, 0xargb */
3536uint16_t pico9918_default_palette(int index)
3537{
3538 return defaultPalette[index & 0x3f];
3539}
uint8_t pico9918_peek_status(pico9918_t *tms9918)
read from the status register without resetting it
Definition pico9918.c:385
static bool tmsSpriteMag(pico9918_t *tms9918)
sprite size (0 = 1x, 1 = 2x)
Definition pico9918.c:140
uint8_t pico9918_read_data(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
Definition pico9918.c:402
static uint16_t tmsSpritePatternTableAddr(pico9918_t *tms9918)
sprite pattern table base address
Definition pico9918.c:188
static uint16_t tmsPatternTableAddr(pico9918_t *tms9918)
pattern table base address
Definition pico9918.c:174
static PICO9918_NOINLINE void compositeTile2OnlyBuffer(pico9918_t *tms9918, uint8_t pixels[TMS9918_PIXELS_X], const int t2Scroll, const bool wide)
Composite with tile layer 1 disabled (reg 0x32 bit 4).
Definition pico9918.c:3069
static uint16_t tmsNameTable2Addr(pico9918_t *tms9918)
name table base address
Definition pico9918.c:152
PICO9918_INLINE_HOT void renderText80Row(pico9918_t *tms9918, const uint8_t *__restrict rowNames, const uint8_t *__restrict patternTable, const uint8_t *__restrict rowColors, const bool opaq, uint8_t *__restrict pixels, const uint32_t startCol, const uint32_t numCells, const bool scrolled)
one 80-column text row, six pixels a cell at half a byte each.
Definition pico9918.c:1870
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
Definition pico9918.c:3499
bool pico9918_display_enabled(pico9918_t *tms9918)
check BLANK flag
Definition pico9918.c:3492
static void tileRowAddr(pico9918_t *tms9918, const uint16_t y, const uint16_t rawY, const uint8_t colorReg, const bool gm2, const bool mcm, TileRowAddr *addr, uint16_t *colorTableAddr)
the per-mode half of a tile row's addressing, once per layer per scanline.
Definition pico9918.c:1306
void pico9918_destroy(pico9918_t *tms9918)
destroy a TMS9918
Definition pico9918.c:360
static bool bitmap_layer_scan_line(pico9918_t *tms9918, uint16_t y, uint8_t pixels[TMS9918_PIXELS_X], const bool intoTile1)
generate an F18A bitmap layer scanline
Definition pico9918.c:2767
void pico9918_write_addr(pico9918_t *tms9918, uint8_t data)
write an address (mode = 1) to the tms9918
Definition pico9918.c:373
PICO9918_INLINE_HOT void renderTileRowLocked(pico9918_t *tms9918, uint16_t rowNamesAddr, uint16_t colorTableAddr, uint8_t tileIndex, uint8_t pal, uint8_t pixels[TMS9918_PIXELS_X], const TileRowAddr *addr, const bool gm2, const bool mcm)
generate a locked (plain TMS9918) tile row, straight into the scanline
Definition pico9918.c:2328
static void f18a_tile1_scan_line(pico9918_t *tms9918, uint16_t y)
generate a Graphics I mode scanline for the T1 layer
Definition pico9918.c:2573
static void tmsClearRowBitsMask(const uint32_t xPos, const uint32_t tilePixels, const uint32_t tileWidth, BitMask rowBitsMask)
Clear the row pixels bit mask.
Definition pico9918.c:496
PICO9918_INLINE_HOT void f18a_tile_layer_scan_line(pico9918_t *tms9918, uint16_t y, const TileLayerConfig *config, const bool blend)
generate a tile mode scanline for either T1 or T2 layer
Definition pico9918.c:2499
void pico9918_set_chip(pico9918_t *tms9918, pico9918_chip_t chip)
select which chip this instance answers as
Definition pico9918.c:276
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
Definition pico9918.c:3529
static uint8_t renderSprites(pico9918_t *tms9918, const uint32_t spriteCount, const bool spriteMag, const bool wide, const bool ecm0, uint8_t pixels[TMS9918_PIXELS_X])
Output Sprites to a scanline.
Definition pico9918.c:928
static uint16_t tmsColorTable2Addr(pico9918_t *tms9918)
color table base address
Definition pico9918.c:166
uint8_t pico9918_read_data_no_inc(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
Definition pico9918.c:408
static uint16_t tmsColorTableAddr(pico9918_t *tms9918)
color table base address
Definition pico9918.c:158
static pico9918_color_t tmsMainFgColor(pico9918_t *tms9918)
foreground color
Definition pico9918.c:200
uint8_t pico9918_status_value(pico9918_t *tms9918, pico9918_status_register_t reg)
return a status register value without the side effects of reading it
Definition pico9918.c:3336
PICO9918_INLINE_HOT bool renderBitmapLayerBody(pico9918_t *tms9918, uint16_t y, bool opaque, const uint8_t width, const uint16_t addr, const uint8_t bmlCtl, uint8_t pixels[TMS9918_PIXELS_X], const bool wide, const bool intoTile1)
generate an F18A bitmap layer scanline
Definition pico9918.c:2596
static uint8_t graphics_i_scan_line(pico9918_t *tms9918, uint16_t y, uint8_t pixels[TMS9918_PIXELS_X])
generate a Graphics I or Graphics II mode scanline
Definition pico9918.c:3127
static uint32_t tmsTestRowBitsMaskAligned(const uint32_t xPos, const uint32_t tilePixels, const BitMask rowBitsMask)
Test against the row pixels bit mask (aligned - no word boundary crossing).
Definition pico9918.c:522
static void tmsSetTransparentSpriteMask(const uint32_t xPos, const uint32_t spritePixels, const uint32_t spriteWidth)
set the transparent sprite mask.
Definition pico9918.c:479
PICO9918_INLINE_HOT void renderTextRow(pico9918_t *tms9918, const uint8_t *__restrict rowNames, const TileRowAddr *__restrict addr, const uint8_t *__restrict rowColors, const uint32_t colorStride, uint32_t pal, uint8_t *__restrict dest, const uint32_t scroll, const bool alwaysOnTop, const uint32_t numCols, const bool isTile2, const uint32_t ecm, const bool blend)
one 40- or 80-column text row, six pixels a cell at one byte each
Definition pico9918.c:1539
static void textRowBorder(pico9918_t *tms9918, const int t1Scroll, const uint32_t padding, const uint32_t width)
A text row draws 240 of the 256 pixels and the composite copies all of them, so layer 1's buffer has ...
Definition pico9918.c:3113
void pico9918_debug_sync_mode_impl(pico9918_t *tms9918)
see impl/pico9918_priv.h.
Definition pico9918.c:3506
static void tileLayerAddr(pico9918_t *tms9918, const uint16_t rawY, const TileLayerConfig *config, const uint8_t numCols, const uint8_t nameAddrMask, const bool textMode, const bool gm2, const bool mcm, const bool attrPerPos, TileRowAddr *addr, uint16_t *namesAddr, uint16_t *colorAddr)
where one layer reads this scanline from: the vertical scroll and its page swap, the name and colour ...
Definition pico9918.c:1455
pico9918_chip_t pico9918_chip(pico9918_t *tms9918)
which chip this instance answers as
Definition pico9918.c:310
static void renderEcm0Tile(pico9918_t *tms9918, uint8_t *buffer, const uint32_t xPos, const uint8_t pattIdx, const uint8_t patternTable[], const uint32_t colorTableAddr, const uint32_t pal, const bool isTile2, const bool gm2Color, const bool mcm)
render an ECM0 (enhanced color mode) graphics I tile.
Definition pico9918.c:2170
void pico9918_write_reg_value_impl(pico9918_t *tms9918, uint8_t reg, uint8_t value)
set a register from the second byte of a host register write
Definition pico9918.c:3342
static uint32_t tmsTestCollisionMask(const uint32_t xPos, const uint32_t spritePixels, const uint32_t spriteWidth)
Test and update the sprite collision mask.
Definition pico9918.c:455
uint8_t pico9918_vram_value(pico9918_t *tms9918, uint16_t addr)
return a value from vram
Definition pico9918.c:3485
static void text_scan_line(pico9918_t *tms9918, uint16_t y, uint8_t pixels[TMS9918_PIXELS_X])
generate a 40- or 80-column text mode scanline.
Definition pico9918.c:2064
static uint16_t tmsSpriteAttrTableAddr(pico9918_t *tms9918)
sprite attribute table base address
Definition pico9918.c:182
static void tmsUpdateRowBitsMaskAligned(const uint32_t xPos, const uint32_t tilePixels, BitMask rowBitsMask)
Update the row pixels bit mask (aligned - no word boundary crossing).
Definition pico9918.c:515
void pico9918_interrupt_set(pico9918_t *tms9918)
raise the INT status flag
Definition pico9918.c:420
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
Definition pico9918.c:3518
void pico9918_set_status(pico9918_t *tms9918, uint8_t status)
set status flag
Definition pico9918.c:427
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
Definition pico9918.c:3256
static pico9918_color_t tmsFgColor(pico9918_t *tms9918, uint8_t colorByte)
foreground color
Definition pico9918.c:207
static void writeToAlignedBuffer(uint8_t *buffer, uint32_t xPos, const uint32_t left, const uint32_t right)
Write full tile to aligned buffer - 8 pixels at once.
Definition pico9918.c:2157
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
Definition pico9918.c:318
static uint8_t chipFeatures(pico9918_chip_t chip)
the feature bits a personality answers to - the ladder, in one place
Definition pico9918.c:259
static uint32_t spriteEcm(pico9918_t *tms9918)
the sprite ECM level, zero on a locked device - the one term a clone can pin
Definition pico9918.c:919
uint8_t pico9918_read_status(pico9918_t *tms9918)
read from the status register
Definition pico9918.c:379
static pico9918_color_t tmsBgColor(pico9918_t *tms9918, uint8_t colorByte)
background color
Definition pico9918.c:214
static uint16_t tmsNameTableAddr(pico9918_t *tms9918)
name table base address
Definition pico9918.c:146
static void f18a_tile2_scan_line(pico9918_t *tms9918, uint16_t y, const bool blend)
generate a Graphics I mode scanline for the T2 layer
Definition pico9918.c:2579
uint16_t pico9918_default_palette(int index)
a default palette entry, 0xargb
Definition pico9918.c:3536
static uint32_t tmsTestRowBitsMask(const uint32_t xPos, const uint32_t tilePixels, const uint32_t tileWidth, const bool update, const bool test, const bool testColl)
Test and update the row pixels bit mask.
Definition pico9918.c:597
bool pico9918_interrupt_status(pico9918_t *tms9918)
return true if both INT status and INT control set
Definition pico9918.c:414
static void renderEcmTileToAlignedBuffer(pico9918_t *tms9918, uint8_t *buffer, const uint32_t xPos, const uint32_t pixelOffset, const uint8_t pattIdx, const uint8_t patternTable[], const uint32_t colorTableAddr, const uint32_t ecm, const uint32_t ecmOffset, const uint32_t ecmColorMask, const uint32_t ecmColorOffset, const uint32_t pal, const bool attrPerPos, const int32_t flipY, const uint32_t tileIndex, uint32_t *lastEmpty, const bool isTile2, const bool alwaysOnTop)
render one ECM tile into the layer buffer
Definition pico9918.c:2220
uint8_t pico9918_reg_value(pico9918_t *tms9918, pico9918_register_t reg)
return a register value - see the header for the locked-device aliasing
Definition pico9918.c:3329
static pico9918_color_t tmsMainBgColor(pico9918_t *tms9918)
background color
Definition pico9918.c:194
static uint8_t tmsSpriteSize(pico9918_t *tms9918)
sprite size (8 or 16)
Definition pico9918.c:134
void pico9918_write_data(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
Definition pico9918.c:395
#define TMS_R1_SPRITE_MAG2
sprites drawn at twice their pattern size
Definition pico9918.h:310
pico9918_color_t
the sixteen TMS9918 colours, in palette-index order
Definition pico9918.h:177
#define PICO9918_R29_SPRITE_STRIDE
register 29 fields: scroll page sizes, and the stride between ECM pattern planes
Definition pico9918.h:322
pico9918_t * pico9918_new(void)
create a new TMS9918
#define PICO9918_R49_TILE2_ENABLE
register 49 bits: tile layer 2, row count, and the enhanced colour modes
Definition pico9918.h:342
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
Definition pico9918.h:276
#define PICO9918_R29_TILE_STRIDE
tile pattern plane stride, 0x800 >> n
Definition pico9918.h:325
#define PICO9918_R24_SPRITE_PS
register 24 bits: the sub-palette each layer takes
Definition pico9918.h:316
#define TMS_R1_MODE_TEXT
40-column text
Definition pico9918.h:306
#define PICO9918_R56_GPU_RUN
register 56 bit: the GPU trigger
Definition pico9918.h:365
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
#define TMS_R1_MODE_MULTICOLOR
Multicolor.
Definition pico9918.h:305
#define PICO9918_R50_TILE1_OFF
stop drawing tile layer 1
Definition pico9918.h:358
#define PICO9918_R50_RESET
register 50 bits: GPU triggers and the remaining layer controls
Definition pico9918.h:355
#define PICO9918_R24_TILE1_PS
tile layer 1 palette select
Definition pico9918.h:319
#define PICO9918_R49_ECM_TILE
tile ECM level field
Definition pico9918.h:344
#define PICO9918_R49_Y_REAL
sprite Y is the real row, not row minus one
Definition pico9918.h:348
#define TMS_R1_DISP_ACTIVE
render the active display
Definition pico9918.h:300
#define TMS_R0_MODE_GRAPHICS_II
Graphics II - the only mode R0 selects.
Definition pico9918.h:287
#define TMS_R1_SPRITE_16
16x16 sprite patterns
Definition pico9918.h:308
#define PICO9918_R15_STATUS_NUM
which status register S1 reads back
Definition pico9918.h:374
#define PICO9918_R50_REPORT_MAX
S0's sprite number reports the highest seen.
Definition pico9918.h:359
pico9918_register_t
the eight TMS9918 registers, by number and by what each one holds
Definition pico9918.h:198
@ PICO9918_REG_COLOR_TABLE2
tile layer 2 colour table base
Definition pico9918.h:218
@ PICO9918_REG_CONFIG_INDEX
PICO9918 only: which configuration byte R59 addresses.
Definition pico9918.h:243
@ PICO9918_REG_BML_X
bitmap layer left edge
Definition pico9918.h:230
@ PICO9918_REG_BML_BASE
bitmap layer base address, in 64-byte units
Definition pico9918.h:229
@ PICO9918_REG_VRAM_INC
signed VRAM address increment per access
Definition pico9918.h:235
@ PICO9918_REG_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
Definition pico9918.h:244
@ PICO9918_REG_NAME_TABLE2
tile layer 2 name table base
Definition pico9918.h:217
@ PICO9918_REG_ENHANCED1
tile layer 2, 30-row mode, ECM levels, real Y
Definition pico9918.h:236
@ PICO9918_REG_GPU_PC_MSB
GPU program counter, high byte.
Definition pico9918.h:239
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
Definition pico9918.h:219
@ PICO9918_REG_ENHANCED2
GPU triggers, per-position attributes, layer priority.
Definition pico9918.h:237
@ PICO9918_REG_T2_HSCROLL
tile layer 2 horizontal scroll
Definition pico9918.h:222
@ PICO9918_REG_PAGE_SIZE
scroll page sizes, and the ECM pattern plane stride
Definition pico9918.h:226
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
Definition pico9918.h:242
@ PICO9918_REG_BML_WIDTH
bitmap layer width in pixels
Definition pico9918.h:232
@ PICO9918_REG_MAX_SCAN_SPRITES
sprites drawn per scanline before the limit bites
Definition pico9918.h:227
@ PICO9918_REG_GPU_PC_LSB
GPU program counter, low byte - writing it also starts the GPU.
Definition pico9918.h:240
@ PICO9918_REG_BML_TOP_ROW
bitmap layer top row
Definition pico9918.h:231
@ PICO9918_REG_MAX_SPRITES
sprites processed per frame before the scan stops
Definition pico9918.h:238
@ PICO9918_REG_PALETTE_SELECT
sub-palette for sprites and each tile layer
Definition pico9918.h:221
@ PICO9918_REG_BML_CONTROL
bitmap layer enable, priority, transparency, fat pixels
Definition pico9918.h:228
@ PICO9918_REG_T1_HSCROLL
tile layer 1 horizontal scroll
Definition pico9918.h:224
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
Definition pico9918.h:241
@ PICO9918_REG_FLASH_CONTROL
PICO9918 only: flash operation control.
Definition pico9918.h:245
@ PICO9918_REG_BML_HEIGHT
bitmap layer height in rows
Definition pico9918.h:233
pico9918_status_register_t
the status registers, by number and by what each one reports
Definition pico9918.h:255
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
Definition pico9918.h:256
@ PICO9918_SR_MICROS_LSB
microsecond counter, low byte
Definition pico9918.h:262
@ PICO9918_SR_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
Definition pico9918.h:268
@ PICO9918_SR_MILLIS_LSB
millisecond counter, low byte
Definition pico9918.h:264
@ PICO9918_SR_SECONDS_LSB
second counter, low byte
Definition pico9918.h:266
@ PICO9918_SR_MICROS_MSB
microsecond counter, high bits
Definition pico9918.h:263
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
Definition pico9918.h:257
@ PICO9918_SR_MILLIS_MSB
millisecond counter, high bits
Definition pico9918.h:265
@ PICO9918_SR_VERSION
the F18A feature level, as major and minor nibbles
Definition pico9918.h:270
@ PICO9918_SR_SECONDS_MSB
second counter, high byte
Definition pico9918.h:267
@ PICO9918_SR_GPU
GPU running and its status byte.
Definition pico9918.h:258
#define PICO9918_R49_ECM_SPRITE
sprite ECM level field
Definition pico9918.h:349
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define PICO9918_CHIP_MAX
the highest personality this build can be, and what a new instance is
Definition pico9918.h:168
#define TMS9918_PIXELS_X
active display width, every mode
Definition pico9918.h:376
#define TMS_R0_DOUBLE_ROWS
PICO9918 only: twice the rows, drawn interlaced.
Definition pico9918.h:293
#define PICO9918_R31_BML_ENABLE
register 31 bits: the bitmap layer
Definition pico9918.h:330
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
Definition pico9918.h:277
#define PICO9918_R50_POS_ATTR
tile attributes come per position, not per tile
Definition pico9918.h:361
#define PICO9918_R49_ROW30
30 rows of tiles rather than 24
Definition pico9918.h:343
pico9918_chip_t
which chip an instance answers as
Definition pico9918.h:150
@ PICO9918_CHIP_PICO9918
an F18A plus the PICO9918's own extensions
Definition pico9918.h:154
@ PICO9918_CHIP_PICO9918_PRO
a PICO9918 PRO: 8bpp 80-column text, its own splash
Definition pico9918.h:155
@ PICO9918_CHIP_TMS9918A
a TMS9918A: locked, no GPU, no extensions
Definition pico9918.h:152
@ PICO9918_CHIP_F18A
an F18A: unlock, enhanced renderer, GPU
Definition pico9918.h:153
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:41
pico9918_mode_t
the display modes the VDP can be in, TMS9918A modes and F18A alike
Definition pico9918.h:104
#define PICO9918_BASE_TMS9918
vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE
pico9918-core - GPU privileged surface
pico9918-core - the private instance layout
PICO9918_INLINE_HOT void pico9918_write_addr_impl(pico9918_t *tms9918, uint8_t data)
write an address (mode = 1) to the tms9918
PICO9918_INLINE uint8_t pico9918_read_status_impl(pico9918_t *tms9918)
read from the status register
PICO9918_INLINE void pico9918_write_reconcile_int_impl(pico9918_t *tms9918)
bring /INT into agreement after a write that can change the predicate.
PICO9918_INLINE_HOT void pico9918_set_status_impl(pico9918_t *tms9918, uint8_t status)
set status flag
PICO9918_INLINE void pico9918_frame_reset_int_impl(pico9918_t *tms9918)
console-reset entry for the interrupt/status state.
PICO9918_INLINE_HOT void pico9918_interrupt_set_impl(pico9918_t *tms9918)
raise the interrupt flag in SR0 and the frame shadow
PICO9918_INLINE_HOT uint8_t pico9918_peek_status_impl(pico9918_t *tms9918)
read from the status register without resetting it
PICO9918_INLINE_HOT uint8_t pico9918_read_data_no_inc_impl(pico9918_t *tms9918)
return the buffered value without reading VRAM or advancing the address
PICO9918_INLINE_HOT bool pico9918_interrupt_status_impl(pico9918_t *tms9918)
THE single implementation of "a status register was just read" - shared by the public read (pico9918_...
PICO9918_INLINE_HOT uint8_t pico9918_read_data_impl(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
PICO9918_INLINE_HOT void pico9918_write_data_impl(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
void pico9918_splash_select_pro(bool pro)
render the F18A's power-on badge into the scanline buffer
Definition splash.c:36
void pico9918_splash_reset(void)
restart the splash animation (after... reset)
Definition splash.c:57
pico9918-core - Splash overlay