26#define EMITTER_NOINLINE PICO9918_NOINLINE
42PICO9918_SECTION_SCRATCH_Y(buffer)
static uint32_t bg;
46static PICO9918_SECTION_SCRATCH_X(buffer) uint8_t __aligned(4) scanlineBuffer[SCANLINE_BUFFER_BYTES];
55void initLookups(
void);
57#if PICO9918_SINGLE_INSTANCE
69static pico9918_t __aligned(256) PICO9918_UNINITIALIZED(tms9918Inst);
74pico9918_t* const tms9918 = &tms9918Inst;
78void __time_critical_func(pico9918_init)(
void)
81#if PICO9918_BUILD_RUNTIME_CHIP
101 pico9918_t* tms9918 = (pico9918_t*)calloc(1,
sizeof(pico9918_t));
105#if PICO9918_BUILD_RUNTIME_CHIP
120 return sizeof(pico9918_t);
126 return PICO9918_UNLOCKED(tms9918);
130#if PICO9918_SINGLE_INSTANCE
136#define INTERRUPT_CB interruptCb
138#define INTERRUPT_CB tms9918->interrupt
144 INTERRUPT_CB.fn = cb;
145 INTERRUPT_CB.userdata = userdata;
151 if (INTERRUPT_CB.fn) INTERRUPT_CB.fn(tms9918, active, INTERRUPT_CB.userdata);
157 "PICO9918_MAP_PRAM does not match the memory map");
159 "PICO9918_MAP_REGISTERS does not match the memory map");
161 "PICO9918_MAP_SCANLINE does not match the memory map");
163 "PICO9918_MAP_STATUS does not match the memory map");
166static const pico9918_mode_t r1Modes[] = {TMS_MODE_GRAPHICS_I, TMS_MODE_MULTICOLOR, TMS_MODE_TEXT,
167 TMS_MODE_GRAPHICS_I};
174 return TMS_MODE_GRAPHICS_II;
177 else if (PICO9918_M4(tms9918))
179 return TMS_MODE_TEXT80;
202 return (TMS_REGISTER(tms9918, TMS_REG_NAME_TABLE) & 0x0f) << 10;
214 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
216 return (TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE) & mask) << 6;
222 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
230 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x04 : 0x07;
232 return (TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & mask) << 11;
238 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_ATTR_TABLE) & 0x7f) << 7;
244 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_PATT_TABLE) & 0x07) << 11;
250 return TMS_REGISTER(tms9918, TMS_REG_FG_BG_COLOR) & 0x0f;
276static const uint16_t defaultPalette[] = {
278 0x0000, 0xF000, 0xF2C3, 0xF5D6, 0xF54F, 0xF76F, 0xFD54, 0xF4EF, 0xFF54, 0xFF76, 0xFDC3, 0xFED6, 0xF2B2,
279 0xFC5C, 0xFCCC, 0xFFFF,
281 0x0000, 0xF2C3, 0xF000, 0xF54F, 0xF000, 0xFD54, 0xF000, 0xF4EF, 0xF000, 0xFCCC, 0xF000, 0xFDC3, 0xF000,
282 0xFC5C, 0xF000, 0xFFFF,
284 0x0000, 0xF00A, 0xF0A0, 0xF0AA, 0xFA00, 0xFA0A, 0xFA50, 0xFAAA, 0xF555, 0xF55F, 0xF5F5, 0xF5FF, 0xFF55,
285 0xFF5F, 0xFFF5, 0xFFFF,
287 0x0000, 0xF555, 0xF000, 0xF00A, 0xF000, 0xF0A0, 0xF000, 0xF0AA, 0xF000, 0xFA00, 0xF000, 0xFA0A, 0xF000,
288 0xFA50, 0xF000, 0xFFFF};
290static PICO9918_NOINLINE
void vdpRegisterReset(pico9918_t* tms9918)
292 tms9918->isUnlocked =
false;
293 tms9918->restart = 0;
294 tms9918->unlockCount = 0;
295 tms9918->lockedMask = 0x07;
296 memset(&TMS_REGISTER(tms9918, TMS_REG_0), 0, TMS_REGISTERS);
297 TMS_REGISTER(tms9918, TMS_REG_1) = 0x40;
298 TMS_REGISTER(tms9918, TMS_REG_3) = 0x10;
299 TMS_REGISTER(tms9918, TMS_REG_4) = 0x01;
300 TMS_REGISTER(tms9918, TMS_REG_5) = 0x0A;
301 TMS_REGISTER(tms9918, TMS_REG_6) = 0x02;
302 TMS_REGISTER(tms9918, TMS_REG_7) = 0xF2;
310#if PICO9918_BUILD_RUNTIME_CHIP
318 return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_CONFIG | PICO9918_FEAT_OVERLAY |
319 PICO9918_FEAT_BITMAP | PICO9918_FEAT_WIDE_T80 | PICO9918_FEAT_GPU_RAM;
321 return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_CONFIG | PICO9918_FEAT_OVERLAY |
322 PICO9918_FEAT_BITMAP | PICO9918_FEAT_GPU_RAM;
323 case PICO9918_CHIP_F18A:
return PICO9918_FEAT_UNLOCK | PICO9918_FEAT_BITMAP | PICO9918_FEAT_WIDE_T80;
325 default:
return PICO9918_FEAT_VRAM_4K;
338 tms9918->chip = (uint8_t)chip;
342 if (!PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG))
347#if !PICO9918_NO_SPLASH
352 tms9918->palDirty = 1;
355 tms9918->configDirty =
true;
356 tms9918->configVdpDirty =
true;
358 if (!PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK))
360 tms9918->isUnlocked =
false;
361 tms9918->unlockCount = 0;
362 tms9918->lockedMask = 0x07;
380 tms9918->regWriteStage0Value = 0;
381 tms9918->currentAddress = 0;
382 tms9918->gpuAddress = 0xFFFF;
383 tms9918->regWriteStage = 0;
385 tms9918->palWriteStage = 0;
386 tms9918->palWriteStage0Value = 0;
392 tms9918->readAheadBuffer = 0;
394 vdpRegisterReset(tms9918);
395 TMS_REGISTER(tms9918, TMS_REG_1) = 0x00;
396 TMS_REGISTER(tms9918, TMS_REG_7) = 0x00;
397 pico9918_cached_mode = TMS_MODE_GRAPHICS_I;
400 for (
int i = 0; i <
sizeof(defaultPalette) /
sizeof(uint16_t); ++i)
402 tms9918->vram.map.pram[i] = __builtin_bswap16(defaultPalette[i]);
406 tms9918->palDirty = 1;
422#if !PICO9918_SINGLE_INSTANCE
492static const uint32_t zeroWord = 0;
498typedef uint32_t BitMask[9];
499typedef uint32_t TileMask[SCANLINE_MASK_WORDS];
503#define MASK_NEXT_PIXEL 0x80000000u
507static PICO9918_SECTION_SCRATCH_X(lookup)
struct
510 BitMask rowTransparentSpriteBits;
511 BitMask rowSpriteBits;
512} __aligned(4) rowMasks;
516 const uint32_t spriteWidth)
518 uint32_t rowSpriteBitsWord = xPos >> 5;
519 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
521 uint32_t validPixels =
522 (~rowMasks.rowSpriteBits[rowSpriteBitsWord]) & (spritePixels >> rowSpriteBitsWordBit);
523 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= validPixels;
524 validPixels <<= rowSpriteBitsWordBit;
526 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
527 if (rowSpriteBitsWordBit < spriteWidth)
529 uint32_t right = (~rowMasks.rowSpriteBits[++rowSpriteBitsWord]) & (spritePixels << rowSpriteBitsWordBit);
530 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= right;
531 validPixels |= (right >> rowSpriteBitsWordBit);
540 const uint32_t spriteWidth)
542 uint32_t rowSpriteBitsWord = xPos >> 5;
543 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
545 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord] |= spritePixels >> rowSpriteBitsWordBit;
547 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
548 if (rowSpriteBitsWordBit < spriteWidth)
550 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord + 1] |= spritePixels << rowSpriteBitsWordBit;
557 const uint32_t tileWidth, BitMask rowBitsMask)
559 uint32_t rowBitsWord = xPos >> 5;
560 uint32_t rowBitsWordBit = xPos & 0x1f;
562 uint32_t validPixels = tilePixels >> rowBitsWordBit;
563 rowBitsMask[rowBitsWord] &= ~validPixels;
565 rowBitsWordBit = 32 - rowBitsWordBit;
566 if (rowBitsWordBit < tileWidth)
569 uint32_t right = (tilePixels << rowBitsWordBit);
570 rowBitsMask[rowBitsWord] &= ~right;
578 rowBitsMask[xPos >> 5] |= tilePixels >> (xPos & 0x1f);
583 const BitMask rowBitsMask)
585 return tilePixels & ~(rowBitsMask[xPos >> 5] << (xPos & 0x1f));
590static PICO9918_NOINLINE
void tmsCopyAlignMask(TileMask dstMask,
const TileMask srcMask,
int pixelShift)
595 dstMask[0] = srcMask[0];
596 dstMask[1] = srcMask[1];
597 dstMask[2] = srcMask[2];
598 dstMask[3] = srcMask[3];
599 dstMask[4] = srcMask[4];
600 dstMask[5] = srcMask[5];
601 dstMask[6] = srcMask[6];
602 dstMask[7] = srcMask[7];
603 dstMask[8] = srcMask[8];
604#if SCANLINE_MASK_WORDS > 9
605 dstMask[9] = srcMask[9];
606 dstMask[10] = srcMask[10];
607 dstMask[11] = srcMask[11];
608 dstMask[12] = srcMask[12];
609 dstMask[13] = srcMask[13];
610 dstMask[14] = srcMask[14];
611 dstMask[15] = srcMask[15];
612 dstMask[16] = srcMask[16];
621 for (
int i = 0; i < SCANLINE_MASK_WORDS; i++)
623 uint32_t word = srcMask[i];
624 dstMask[i] = (word >> pixelShift) | carry;
625 carry = word << (32 - pixelShift);
631 pixelShift = -pixelShift;
633 for (
int i = SCANLINE_MASK_WORDS - 1; i >= 0; i--)
635 uint32_t word = srcMask[i];
636 dstMask[i] = (word << pixelShift) | carry;
637 carry = word >> (32 - pixelShift);
649static PICO9918_NOINLINE
void tmsRestoreAlignMask(TileMask dstMask,
const TileMask srcMask,
650 int pixelShift,
int otherShift)
652 if (pixelShift | otherShift) tmsCopyAlignMask(dstMask, srcMask, pixelShift);
658 const uint32_t tileWidth,
const bool update,
const bool test,
661 uint32_t rowBitsWord = xPos >> 5;
662 uint32_t rowBitsWordBit = xPos & 0x1f;
664 uint32_t validPixels = tilePixels >> rowBitsWordBit;
665 if (testColl) validPixels &= ~rowMasks.rowSpriteBits[rowBitsWord];
666 if (test) validPixels &= ~rowMasks.rowBits[rowBitsWord];
667 if (update) rowMasks.rowBits[rowBitsWord] |= validPixels;
668 if (test || testColl) validPixels <<= rowBitsWordBit;
670 rowBitsWordBit = 32 - rowBitsWordBit;
671 if (rowBitsWordBit < tileWidth)
674 uint32_t right = (tilePixels << rowBitsWordBit);
676 if (testColl) right &= ~rowMasks.rowSpriteBits[rowBitsWord];
677 if (test) right &= ~rowMasks.rowBits[rowBitsWord];
679 if (update) rowMasks.rowBits[rowBitsWord] |= right;
680 if (test || testColl) validPixels |= (right >> rowBitsWordBit);
683 return (test || testColl) ? validPixels : tilePixels;
698static uint32_t __aligned(8) PICO9918_UNINITIALIZED(ecmLookup)[16 * 16 * 16];
700static uint8_t PICO9918_IN_FLASH_FUNC(ecmByte)(
bool h,
bool m,
bool l)
702 return (h << 2) | (m << 1) | l;
709static void PICO9918_IN_FLASH_FUNC(ecmLookupInit)(void)
711 for (uint16_t i = 0; i < 16 * 16 * 16; ++i)
714 (ecmByte(i & 0x800, i & 0x080, i & 0x008)) | (ecmByte(i & 0x400, i & 0x040, i & 0x004) << 8) |
715 (ecmByte(i & 0x200, i & 0x020, i & 0x002) << 16) | (ecmByte(i & 0x100, i & 0x010, i & 0x001) << 24);
734static uint8_t __aligned(4) doubledBitsNibble[16] = {0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f,
735 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff};
738static PICO9918_SECTION_SCRATCH_X(lookup) uint16_t __aligned(4) doubledBits[256];
739static
void PICO9918_IN_FLASH_FUNC(doubledBitsInit)(
void)
741 for (
int i = 0; i < 256; ++i)
743 doubledBits[i] = (doubledBitsNibble[(i & 0xf0) >> 4] << 8) | doubledBitsNibble[i & 0x0f];
748static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits[256];
750static uint8_t PICO9918_IN_FLASH_FUNC(reverseBits)(uint8_t
byte)
752 byte = (
byte & 0xf0) >> 4 | (
byte & 0x0f) << 4;
753 byte = (
byte & 0xcc) >> 2 | (
byte & 0x33) << 2;
754 return (
byte & 0xaa) >> 1 | (
byte & 0x55) << 1;
761static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits6[256];
763static
void PICO9918_IN_FLASH_FUNC(reversedBitsInit)(
void)
765 for (
int i = 0; i < 256; ++i)
767 reversedBits[i] = reverseBits(i);
768 reversedBits6[i] = reverseBits(i) << 2;
781static inline uint32_t repeatedPalette(
const uint32_t index)
784 uint32_t repeated = index;
785 __asm__(
"mul %0, %1" :
"+l"(repeated) :
"l"(0x01010101u));
788 return index * 0x01010101u;
795static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t __aligned(4) ecm0Palette[64];
797static
void PICO9918_IN_FLASH_FUNC(ecm0PaletteInit)(
void)
799 for (
int i = 0; i < 64; ++i)
801 ecm0Palette[i] = repeatedPalette(i);
809static uint32_t transparentPixels[2];
812static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t __aligned(4) maskExpandNibbleToWordRev[16] = {
813 0x00000000, 0xff000000, 0x00ff0000, 0xffff0000, 0x0000ff00, 0xff00ff00, 0x00ffff00, 0xffffff00,
814 0x000000ff, 0xff0000ff, 0x00ff00ff, 0xffff00ff, 0x0000ffff, 0xff00ffff, 0x00ffffff, 0xffffffff};
822static PICO9918_SECTION_SCRATCH_X(lookup) uint16_t __aligned(4) bmlExpand2bpp[16] = {
823 0x0000, 0x0100, 0x0200, 0x0300, 0x0001, 0x0101, 0x0201, 0x0301,
824 0x0002, 0x0102, 0x0202, 0x0302, 0x0003, 0x0103, 0x0203, 0x0303};
826bool lookupsReady =
false;
827void PICO9918_IN_FLASH_FUNC(initLookups)(void)
829 if (lookupsReady)
return;
831 PICO9918_DMA_CLAIM();
839 PICO9918_FILL32_INIT(PICO9918_FILL_LINE, &bg);
842 PICO9918_FILL32_INIT(PICO9918_FILL_MASKS, &zeroWord);
843 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_MASKS,
sizeof(rowMasks) /
sizeof(uint32_t));
845 PICO9918_FILL32_INIT(PICO9918_FILL_BORDER, &pico9918_border_bg);
847 PICO9918_COPY_INIT(PICO9918_COPY);
856static inline uint32_t ecmSplitQuads(
const uint32_t patt)
858 return (patt | (patt << 12)) & 0x000f000fu;
869static inline uint32_t calculateEcmIndex(
const uint32_t ecm,
const uint32_t sb0,
const uint32_t sb1,
872 uint32_t ecmIndex = 0;
876 ecmIndex = sb2 >> 28;
879 ecmIndex = (ecmIndex << 4) | (sb1 >> 28);
881 default: ecmIndex = (ecmIndex << 4) | (sb0 >> 28);
886static inline void loadSpriteData(
const uint8_t* vram, uint32_t* spriteBits, uint32_t pattOffset,
887 uint32_t* pattMask,
const uint32_t ecm,
const uint32_t ecmOffset,
888 const bool flipX,
const bool sprite16)
893 uint32_t patt = vram[pattOffset];
894 if (flipX) patt = reversedBits[patt];
895 uint32_t bits = patt << ((flipX && sprite16) ? 16 : 24);
899 patt = vram[pattOffset + PATTERN_BYTES * 2];
900 if (flipX) patt = reversedBits[patt];
901 bits |= patt << (flipX ? 24 : 16);
903 spriteBits[i] = bits;
905 pattOffset += ecmOffset;
915static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t spriteAttrRows[MAX_SPRITES];
916static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t spriteIndices[MAX_SPRITES];
926static uint32_t __time_critical_func(collectSpriteRows)(
PICO9918_INST_ARG uint16_t y)
928 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
929 const uint32_t row30Mode =
934 const int32_t maxY = (row30Mode ? 0xf0 : 0xe0) - realY;
935 const int32_t yAdj = (int32_t)y - realY;
938 if (maxSprites > MAX_SPRITES) maxSprites = MAX_SPRITES;
943 for (uint32_t spriteIdx = 0; spriteIdx < maxSprites; ++spriteIdx, spriteAttr += SPRITE_ATTR_BYTES)
945 int32_t yPos = spriteAttr[SPRITE_ATTR_Y];
948 if (yPos == LAST_SPRITE_YPOS && !row30Mode)
954 if (yPos > maxY) yPos -= 256;
956 const int32_t pattRow = yAdj - yPos;
957 if ((uint32_t)pattRow > 31)
962 const uint32_t attr = *(
const uint32_t*)spriteAttr;
964 if (attr == 0 && unlockedMask)
969 spriteAttrRows[count] = attr;
970 ((uint8_t*)&spriteAttrRows[count])[SPRITE_ATTR_Y] = (uint8_t)pattRow;
971 spriteIndices[count] = (uint8_t)spriteIdx;
982 -(uint32_t)PICO9918_UNLOCKED(tms9918);
989 const bool spriteMag,
const bool wide,
993 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
994 const uint8_t*
const vram = tms9918->vram.bytes;
995 bool hasSprites =
false;
997 const bool sprite16 = spriteSize == 16;
998 const uint8_t spriteIdxMask = sprite16 ? 0xfc : 0xff;
999 const uint8_t spriteColorMask = 0x8f | unlockedMask;
1000 const uint8_t spriteSizePx = spriteSize << spriteMag;
1002 uint32_t spritesShown = 0;
1005 uint8_t tempStatus = 0;
1006 uint32_t transparentCount = 0;
1010 const uint32_t ecmColorOffset = (ecm == 3) ? 2 : ecm;
1011 const uint32_t ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
1012 const uint32_t ecmOffset =
1026 const uint32_t unlimited =
1029 for (uint32_t n = 0; n < spriteCount; ++n)
1031 const uint8_t* spriteAttr = (
const uint8_t*)&spriteAttrRows[n];
1033 int32_t pattRow = spriteAttr[SPRITE_ATTR_Y] >> spriteMag;
1035 uint8_t thisSpriteSize = spriteSize;
1036 bool thisSprite16 = sprite16;
1037 uint8_t thisSpriteIdxMask = spriteIdxMask;
1038 uint8_t thisSpriteSizePx = spriteSizePx;
1039 uint8_t spriteAttrColor = spriteAttr[SPRITE_ATTR_COLOR] & spriteColorMask;
1042 if (spriteAttrColor & 0x10)
1051 thisSpriteSize = 16;
1052 thisSprite16 =
true;
1053 thisSpriteIdxMask = 0xfc;
1054 thisSpriteSizePx = thisSpriteSize << spriteMag;
1059 if (pattRow >= thisSpriteSize)
1065 if (++spritesShown > MAX_SCANLINE_SPRITES)
1067 if (((tempStatus &
PICO9918_SR0_5S) == 0) && (!unlimited || spritesShown > scanlineSprites))
1072 if (spritesShown > scanlineSprites)
break;
1075 const int32_t earlyClockOffset = (spriteAttrColor & 0x80) ? -32 : 0;
1076 int32_t xPos = (int32_t)(spriteAttr[SPRITE_ATTR_X]) + earlyClockOffset;
1082 if (spriteAttrColor & 0x20) pattRow = thisSpriteSize - pattRow - 1;
1085 uint8_t spriteColor = (spriteAttrColor & ecmColorMask) << ecmColorOffset;
1086 const uint8_t pattIdx = spriteAttr[SPRITE_ATTR_NAME] & thisSpriteIdxMask;
1087 uint16_t pattOffset = spritePatternAddr + pattIdx * PATTERN_BYTES + (uint16_t)pattRow;
1090 uint32_t pattMask = 0;
1091 uint32_t spriteBits[3] = {0};
1092 const bool flipX = spriteAttrColor & 0x40;
1094 loadSpriteData(vram, spriteBits, pattOffset, &pattMask, ecm, ecmOffset, flipX, thisSprite16);
1096 if (opaq) pattMask = 0xffff0000;
1106 pattMask = ((uint32_t)doubledBits[pattMask >> 24] << 16) | doubledBits[(pattMask >> 16) & 0xff];
1112 int32_t absX = -xPos;
1113 uint32_t offset = absX >> spriteMag;
1114 spriteBits[2] <<= offset;
1115 spriteBits[1] <<= offset;
1116 spriteBits[0] <<= offset;
1125 thisSpriteSizePx += xPos;
1130 if (pixelsLeft < thisSpriteSizePx)
1132 thisSpriteSizePx = pixelsLeft;
1133 pattMask &= ~((1u << (32 - pixelsLeft)) - 1);
1140 if (validPixels != pattMask)
1147 if (PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_SPRITES) &&
1148 (ecm || (spriteColor != TMS_TRANSPARENT)))
1155 uint32_t quadPal = repeatedPalette(spriteColor);
1159 uint8_t* p = pixels + (wide ? xPos * 2 : xPos);
1160 const uint32_t step = wide ? 2 : 1;
1161 uint32_t bits = validPixels;
1167 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1168 uint32_t quad = ecmLookup[ecmIndex] | quadPal;
1170 for (
int n = 0; n < 4; ++n)
1172 const uint8_t v = (uint8_t)quad;
1173 if (bits & MASK_NEXT_PIXEL)
1176 *(uint16_t*)p = v | (v << 8);
1181 if (bits & MASK_NEXT_PIXEL)
1184 *(uint16_t*)(p + 2) = v | (v << 8);
1198 spriteBits[2] <<= 4;
1199 spriteBits[1] <<= 4;
1200 spriteBits[0] <<= 4;
1209 uint32_t chunkMask = validPixels >> 28;
1212 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1213 uint32_t color = ecmLookup[ecmIndex] | quadPal;
1214 uint8_t* q = pixels + x * 2;
1216 for (
int n = 0; n < 4; ++n)
1218 if (chunkMask & 0x8)
1220 const uint8_t v = (uint8_t)color;
1221 *(uint16_t*)q = v | (v << 8);
1228 spriteBits[2] <<= 4;
1229 spriteBits[1] <<= 4;
1230 spriteBits[0] <<= 4;
1239 uint32_t quadOffset = xPos >> 2;
1240 const uint32_t pixOffset = xPos & 0x3;
1241 validPixels >>= pixOffset;
1242 spriteBits[2] >>= pixOffset;
1243 spriteBits[1] >>= pixOffset;
1244 spriteBits[0] >>= pixOffset;
1246 uint32_t* quadPixels = (uint32_t*)pixels;
1250 uint32_t chunkMask = validPixels >> 28;
1251 if (chunkMask == 0x0f)
1253 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1254 quadPixels[quadOffset] = ecmLookup[ecmIndex] | quadPal;
1258 const uint32_t maskQuad = maskExpandNibbleToWordRev[chunkMask];
1259 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1260 const uint32_t color = ecmLookup[ecmIndex] | quadPal;
1261 quadPixels[quadOffset] = (quadPixels[quadOffset] & ~maskQuad) | (color & maskQuad);
1263 spriteBits[2] <<= 4;
1264 spriteBits[1] <<= 4;
1265 spriteBits[0] <<= 4;
1273 if (!wide && pico9918_cached_mode == TMS_MODE_TEXT80) spriteColor |= spriteColor << 4;
1277 if ((int32_t)validPixels < 0)
1280 *(uint16_t*)(pixels + xPos * 2) = spriteColor | (spriteColor << 8);
1282 pixels[xPos] = spriteColor;
1297 tms9918->scanlineHasSprites = hasSprites;
1300 if (transparentCount)
1302 for (
int i = 0; i < 9; ++i)
1304 rowMasks.rowSpriteBits[i] ^= rowMasks.rowTransparentSpriteBits[i];
1312static EMITTER_NOINLINE uint8_t
1317 if (TMS_REGISTER(tms9918, TMS_REG_0) &
1321 const uint32_t spriteCount = collectSpriteRows(
PICO9918_INST y);
1325 if (spriteCount == 0)
return 0;
1327#if PICO9918_TEXT80_8BPP
1329 if (TEXT80_WIDE_ROW)
1355 const uint8_t* pattern;
1369 const bool gm2,
const bool mcm,
TileRowAddr* addr, uint16_t* colorTableAddr)
1371 uint16_t pageOffset = 0;
1372 const uint8_t pattRow = mcm ? (((rawY >> 2) & 0x01) + ((rawY >> 3) & 0x03) * 2) : (y & 0x07);
1374 addr->nameMask = 0xff;
1377 addr->flipY = mcm ? 0 : (7 - 2 * pattRow);
1381 pageOffset = (((y >> 6) & 0x03) & (TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & 0x03)) << 11;
1382 addr->nameMask = ((colorReg & 0x7f) << 3) | 0x07;
1383 *colorTableAddr += (pageOffset & ((colorReg & 0x60) << 6)) + pattRow;
1386 addr->pattern = tms9918->vram.bytes +
tmsPatternTableAddr(tms9918) + pageOffset + pattRow;
1394static inline uint32_t textScrollCell(
const uint32_t hscrollPixels)
1396 return (hscrollPixels * 342) >> 11;
1405#define TEXT_SCROLL_CELL(s) ((s) & 0xffffu)
1406#define TEXT_SCROLL_OFFSET(s) ((s) >> 16)
1408static inline uint32_t textScrollSplit(
const uint32_t hscroll,
const bool wide)
1411 const uint32_t h = hscroll << wide;
1412 const uint32_t cell = textScrollCell(h);
1413 const uint32_t offset = h - cell * 6;
1418 if (wide && offset == 2)
return (cell ? cell - 1 : TEXT80_NUM_COLS - 1) | (8u << 16);
1419 return cell | (offset << 16);
1422static inline uint32_t textPixelOffset(
const uint32_t hscroll,
const bool wide)
1424 return TEXT_SCROLL_OFFSET(textScrollSplit(hscroll, wide));
1427static inline int scrollOffset(
const uint32_t hscroll,
const bool text,
const bool wide)
1429 return text ? (int)textPixelOffset(hscroll, wide) : (int)(hscroll & 0x07);
1434 uint8_t vertScrollReg;
1435 uint8_t yPageSwapMask;
1436 uint8_t paletteShift;
1437 uint8_t paletteMask;
1438 uint8_t startPattReg;
1440 uint8_t priorityReg;
1441 uint8_t priorityMask;
1442 uint8_t colorTableReg;
1444 uint16_t (*nameTableAddrFunc)(pico9918_t*);
1445 uint16_t (*colorTableAddrFunc)(pico9918_t*);
1449 .yPageSwapMask = 0x01,
1451 .paletteMask = 0x03,
1452 .startPattReg = 0x1b,
1456 .colorTableReg = TMS_REG_COLOR_TABLE,
1462 .yPageSwapMask = 0x10,
1464 .paletteMask = 0x0c,
1465 .startPattReg = 0x19,
1467 .priorityReg = 0x32,
1468 .priorityMask = 0x01,
1469 .colorTableReg = 11,
1494 const uint32_t hscroll =
1495 PICO9918_UNLOCKED(tms9918) ? TMS_REGISTER(tms9918, config->startPattReg) * 2 : 0;
1496 const uint32_t cell = textScrollCell(hscroll);
1497 const uint32_t bytes = (hscroll - cell * 6) >> 1;
1501 s.startCol = (cell >= bytes) ? (cell - bytes) : (cell + TEXT80_NUM_COLS - bytes);
1502 s.cells = bytes ? TEXT80_NUM_COLS + 4 : TEXT80_NUM_COLS;
1504 s.scrolled = hscroll != 0;
1518 const uint8_t numCols,
const uint8_t nameAddrMask,
const bool textMode,
1519 const bool gm2,
const bool mcm,
const bool attrPerPos,
TileRowAddr* addr,
1520 uint16_t* namesAddr, uint16_t* colorAddr)
1523 bool swapYPage =
false;
1525 if (TMS_REGISTER(tms9918, config->vertScrollReg))
1527 int virtY = y + TMS_REGISTER(tms9918, config->vertScrollReg);
1543 const uint16_t rowOffset = (y >> 3) * numCols;
1545 *namesAddr = (config->nameTableAddrFunc(tms9918) & (nameAddrMask << 10)) + rowOffset;
1546 if (swapYPage) *namesAddr ^= 0x800;
1548 *colorAddr = config->colorTableAddrFunc(tms9918);
1551 if (!textMode) *colorAddr += *namesAddr & 0xc00;
1552 *colorAddr = (*colorAddr + rowOffset) & VRAM_MASK;
1558#define TEXT80_COLOR_WORD(n) ((uint32_t)((n) * 0x111111u))
1559#define TEXT80_MASK_WORD(bits) \
1560 (((uint32_t)((bits) & 0x20 ? 0x0000F0u : 0x0)) | ((uint32_t)((bits) & 0x10 ? 0x00000Fu : 0x0)) | \
1561 ((uint32_t)((bits) & 0x08 ? 0x00F000u : 0x0)) | ((uint32_t)((bits) & 0x04 ? 0x000F00u : 0x0)) | \
1562 ((uint32_t)((bits) & 0x02 ? 0xF00000u : 0x0)) | ((uint32_t)((bits) & 0x01 ? 0x0F0000u : 0x0)))
1564static const uint32_t text80ColorWord[16] = {
1565 TEXT80_COLOR_WORD(0x0), TEXT80_COLOR_WORD(0x1), TEXT80_COLOR_WORD(0x2), TEXT80_COLOR_WORD(0x3),
1566 TEXT80_COLOR_WORD(0x4), TEXT80_COLOR_WORD(0x5), TEXT80_COLOR_WORD(0x6), TEXT80_COLOR_WORD(0x7),
1567 TEXT80_COLOR_WORD(0x8), TEXT80_COLOR_WORD(0x9), TEXT80_COLOR_WORD(0xa), TEXT80_COLOR_WORD(0xb),
1568 TEXT80_COLOR_WORD(0xc), TEXT80_COLOR_WORD(0xd), TEXT80_COLOR_WORD(0xe), TEXT80_COLOR_WORD(0xf)};
1572#define TEXT80_MASK_WORD4(bits) \
1573 TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits)
1575static const uint32_t text80MaskWord[256] = {
1576 TEXT80_MASK_WORD4(0x00), TEXT80_MASK_WORD4(0x01), TEXT80_MASK_WORD4(0x02), TEXT80_MASK_WORD4(0x03),
1577 TEXT80_MASK_WORD4(0x04), TEXT80_MASK_WORD4(0x05), TEXT80_MASK_WORD4(0x06), TEXT80_MASK_WORD4(0x07),
1578 TEXT80_MASK_WORD4(0x08), TEXT80_MASK_WORD4(0x09), TEXT80_MASK_WORD4(0x0a), TEXT80_MASK_WORD4(0x0b),
1579 TEXT80_MASK_WORD4(0x0c), TEXT80_MASK_WORD4(0x0d), TEXT80_MASK_WORD4(0x0e), TEXT80_MASK_WORD4(0x0f),
1580 TEXT80_MASK_WORD4(0x10), TEXT80_MASK_WORD4(0x11), TEXT80_MASK_WORD4(0x12), TEXT80_MASK_WORD4(0x13),
1581 TEXT80_MASK_WORD4(0x14), TEXT80_MASK_WORD4(0x15), TEXT80_MASK_WORD4(0x16), TEXT80_MASK_WORD4(0x17),
1582 TEXT80_MASK_WORD4(0x18), TEXT80_MASK_WORD4(0x19), TEXT80_MASK_WORD4(0x1a), TEXT80_MASK_WORD4(0x1b),
1583 TEXT80_MASK_WORD4(0x1c), TEXT80_MASK_WORD4(0x1d), TEXT80_MASK_WORD4(0x1e), TEXT80_MASK_WORD4(0x1f),
1584 TEXT80_MASK_WORD4(0x20), TEXT80_MASK_WORD4(0x21), TEXT80_MASK_WORD4(0x22), TEXT80_MASK_WORD4(0x23),
1585 TEXT80_MASK_WORD4(0x24), TEXT80_MASK_WORD4(0x25), TEXT80_MASK_WORD4(0x26), TEXT80_MASK_WORD4(0x27),
1586 TEXT80_MASK_WORD4(0x28), TEXT80_MASK_WORD4(0x29), TEXT80_MASK_WORD4(0x2a), TEXT80_MASK_WORD4(0x2b),
1587 TEXT80_MASK_WORD4(0x2c), TEXT80_MASK_WORD4(0x2d), TEXT80_MASK_WORD4(0x2e), TEXT80_MASK_WORD4(0x2f),
1588 TEXT80_MASK_WORD4(0x30), TEXT80_MASK_WORD4(0x31), TEXT80_MASK_WORD4(0x32), TEXT80_MASK_WORD4(0x33),
1589 TEXT80_MASK_WORD4(0x34), TEXT80_MASK_WORD4(0x35), TEXT80_MASK_WORD4(0x36), TEXT80_MASK_WORD4(0x37),
1590 TEXT80_MASK_WORD4(0x38), TEXT80_MASK_WORD4(0x39), TEXT80_MASK_WORD4(0x3a), TEXT80_MASK_WORD4(0x3b),
1591 TEXT80_MASK_WORD4(0x3c), TEXT80_MASK_WORD4(0x3d), TEXT80_MASK_WORD4(0x3e), TEXT80_MASK_WORD4(0x3f)};
1600PICO9918_INLINE_HOT
void
1602 const uint8_t* __restrict rowColors,
const uint32_t colorStride, uint32_t pal,
1603 uint8_t* __restrict dest,
const uint32_t scroll,
const bool alwaysOnTop,
1604 const uint32_t numCols,
const bool isTile2,
const uint32_t ecm,
const bool blend)
1606 const uint8_t* __restrict patternTable = addr->pattern;
1607 const bool wide = numCols == TEXT80_NUM_COLS;
1608 const uint32_t padding = wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX;
1609 const uint32_t startCell = TEXT_SCROLL_CELL(scroll);
1610 const uint32_t pixelOffset = TEXT_SCROLL_OFFSET(scroll);
1611 const uint32_t numCells = numCols + (pixelOffset ? 2 : 0);
1613 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(dest, 4);
1614 uint32_t xPos = ecm ? (padding - pixelOffset) : padding;
1616 const uint8_t* __restrict names = rowNames + startCell;
1617 const uint8_t* __restrict colors = rowColors + startCell * colorStride;
1618 const uint32_t colorWrap = numCols * colorStride;
1619 uint32_t col = startCell;
1621 const uint32_t nameAttrMask = (ecm && !colorStride) ? 0xff : 0;
1622 const uint32_t spritePriMask = tms9918->scanlineHasSprites ? 0x80 : 0;
1623 const uint32_t spritePriForced = (isTile2 && alwaysOnTop) ? spritePriMask : 0;
1626 const uint32_t clear = transparentPixels[0];
1627 const int32_t flipY = addr->flipY;
1628 uint32_t ecmOffset = 0, ecmColorMask = 0, ecmColorOffset = 0;
1629 const uint32_t* __restrict palette = ecm0Palette + pal;
1632 ecmColorOffset = (ecm == 3) ? 2 : ecm;
1633 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
1635 pal = (ecm == 1) ? (pal & 0x20) : 0;
1638 uint8_t lastColor = 0;
1639 uint32_t bgWord = palette[0], diffWord = 0;
1640 uint32_t coverBg = 0, coverDiff = 0;
1641 uint32_t lo = 0, hi = 0, m0 = 0, m1 = 0;
1643#define TEXT40_NEXT_CELL() \
1644 const uint32_t name = *names++; \
1645 const uint8_t color = colors[name & nameAttrMask]; \
1646 colors += colorStride;
1648#define TEXT40_CELL(wrapping) \
1650 TEXT40_NEXT_CELL() \
1654 if (++col == numCols) \
1658 colors -= colorWrap; \
1661 const uint32_t patt = patternTable[name * PATTERN_BYTES]; \
1662 if (color != lastColor) \
1664 const uint8_t bgColor = color & 0xf; \
1665 const uint8_t fgColor = color >> 4; \
1666 bgWord = palette[bgColor]; \
1667 diffWord = bgWord ^ palette[fgColor]; \
1668 lastColor = color; \
1672 coverBg = (uint32_t)(-(int32_t)bgColor >> 31); \
1673 coverDiff = coverBg ^ (uint32_t)(-(int32_t)fgColor >> 31); \
1677 const uint32_t maskLo = maskExpandNibbleToWordRev[patt >> 4]; \
1678 const uint32_t maskHi = maskExpandNibbleToWordRev[patt & 0x0c]; \
1679 lo = bgWord ^ (diffWord & maskLo); \
1680 hi = bgWord ^ (diffWord & maskHi); \
1686 m0 = coverBg ^ (coverDiff & maskLo); \
1687 m1 = coverBg ^ (coverDiff & maskHi); \
1692 const uint32_t cover = ((coverBg ^ (coverDiff & (patt >> 2))) & 0x3f) << 26; \
1694 coverAcc |= cover >> coverBit; \
1696 if (coverBit >= 32) \
1698 *coverWord++ |= coverAcc; \
1700 coverAcc = cover << (6 - coverBit); \
1706#define TEXT40_ECM_CELL() \
1708 TEXT40_NEXT_CELL() \
1709 const uint8_t* pattData = patternTable + name * PATTERN_BYTES + ((color & 0x20) ? flipY : 0); \
1710 uint32_t pattMask = (color & 0x10) ? 0 : 0xff; \
1711 uint32_t cover = 0; \
1712 uint8_t patt[3] = {0}; \
1715 case 3: patt[0] = pattData[ecmOffset * 2]; pattMask |= patt[0]; \
1716 case 2: patt[1] = pattData[ecmOffset]; pattMask |= patt[1]; \
1717 default: patt[2] = *pattData; pattMask |= patt[2]; \
1723 patt[0] = reversedBits6[patt[0]]; \
1724 patt[1] = reversedBits6[patt[1]]; \
1725 patt[2] = reversedBits6[patt[2]]; \
1726 pattMask = reversedBits6[pattMask]; \
1728 cover = (pattMask & 0xfc) << 24; \
1729 if ((color | spritePriForced) & spritePriMask) \
1730 tmsClearRowBitsMask(xPos, cover, 6, rowMasks.rowSpriteBits); \
1731 uint32_t index = 0; \
1734 case 3: index = ecmSplitQuads(patt[0]) << 8; \
1735 case 2: index |= ecmSplitQuads(patt[1]) << 4; \
1736 default: index |= ecmSplitQuads(patt[2]); \
1738 const uint32_t cellPal = repeatedPalette(pal | ((color & ecmColorMask) << ecmColorOffset)); \
1739 lo = ecmLookup[index >> 16] | cellPal; \
1740 hi = ecmLookup[(uint16_t)index] | cellPal; \
1741 if (!isTile2 && (color & 0x10)) \
1743 lo = clear ^ ((lo ^ clear) & maskExpandNibbleToWordRev[pattMask >> 4]); \
1744 hi = clear ^ ((hi ^ clear) & maskExpandNibbleToWordRev[pattMask & 0x0f]); \
1754 coverAcc |= cover >> coverBit; \
1756 if (coverBit >= 32) \
1758 *coverWord++ |= coverAcc; \
1760 coverAcc = cover << (6 - coverBit); \
1769 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1770 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1773 uint32_t remaining = numCells;
1774 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1778 if (run > remaining) run = remaining;
1784 *(uint16_t*)(p) = lo;
1785 *(uint16_t*)(p + 2) = lo >> 16;
1786 *(uint16_t*)(p + 4) = hi;
1795 if (isTile2) *coverWord |= coverAcc;
1800 uint32_t* coverWord = tms9918->layerSelectionMask;
1801 uint32_t coverAcc = 0, coverBit = 0;
1803 uint16_t* p = (uint16_t*)dest;
1804 uint32_t remaining = numCells;
1805 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1809 if (run > remaining) run = remaining;
1821 p[0] = d ^ ((d ^ lo) & m0);
1823 p[1] = d ^ ((d ^ (lo >> 16)) & (m0 >> 16));
1825 p[2] = d ^ ((d ^ hi) & m1);
1838 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1839 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1841 for (uint32_t tileX = 0; tileX < numCells; tileX += 2)
1843 uint16_t* pix16 = (uint16_t*)pix32;
1850 pix32[2] = (lo >> 16) | (hi << 16);
1854 if (isTile2) *coverWord |= coverAcc;
1857#undef TEXT40_ECM_CELL
1859#undef TEXT40_NEXT_CELL
1865#define TEXT_ROW_PARAMS \
1866 PICO9918_INST_ARG const uint8_t *rowNames, const TileRowAddr *addr, const uint8_t *rowColors, \
1867 const uint32_t colorStride, const uint32_t pal, uint8_t *dest, const uint32_t scroll, \
1868 const bool alwaysOnTop
1869#define TEXT_ROW_CLONE(name, cols, t2, e) \
1870 static EMITTER_NOINLINE void __time_critical_func(name)(TEXT_ROW_PARAMS) \
1872 renderTextRow(PICO9918_INST rowNames, addr, rowColors, colorStride, pal, dest, scroll, alwaysOnTop, \
1873 cols, t2, e, false); \
1876TEXT_ROW_CLONE(text40RowT1, TEXT_NUM_COLS,
false, 0)
1877TEXT_ROW_CLONE(text40RowT1Ecm1, TEXT_NUM_COLS, false, 1)
1878TEXT_ROW_CLONE(text40RowT1Ecm2, TEXT_NUM_COLS, false, 2)
1879TEXT_ROW_CLONE(text40RowT1Ecm3, TEXT_NUM_COLS, false, 3)
1880TEXT_ROW_CLONE(text40RowT2, TEXT_NUM_COLS, true, 0)
1881TEXT_ROW_CLONE(text40RowT2Ecm1, TEXT_NUM_COLS, true, 1)
1882TEXT_ROW_CLONE(text40RowT2Ecm2, TEXT_NUM_COLS, true, 2)
1883TEXT_ROW_CLONE(text40RowT2Ecm3, TEXT_NUM_COLS, true, 3)
1885static
void (*const textRowClones[2][4])(TEXT_ROW_PARAMS) = {
1886 {text40RowT1, text40RowT1Ecm1, text40RowT1Ecm2, text40RowT1Ecm3},
1887 {text40RowT2, text40RowT2Ecm1, text40RowT2Ecm2, text40RowT2Ecm3}};
1889#if PICO9918_TEXT80_8BPP
1893TEXT_ROW_CLONE(text80RowT1, TEXT80_NUM_COLS,
false, 0)
1894TEXT_ROW_CLONE(text80RowT1Ecm1, TEXT80_NUM_COLS, false, 1)
1895TEXT_ROW_CLONE(text80RowT1Ecm2, TEXT80_NUM_COLS, false, 2)
1896TEXT_ROW_CLONE(text80RowT1Ecm3, TEXT80_NUM_COLS, false, 3)
1897TEXT_ROW_CLONE(text80RowT2, TEXT80_NUM_COLS, true, 0)
1898TEXT_ROW_CLONE(text80RowT2Ecm1, TEXT80_NUM_COLS, true, 1)
1899TEXT_ROW_CLONE(text80RowT2Ecm2, TEXT80_NUM_COLS, true, 2)
1900TEXT_ROW_CLONE(text80RowT2Ecm3, TEXT80_NUM_COLS, true, 3)
1905static EMITTER_NOINLINE
void __time_critical_func(text80RowT2Blend)(TEXT_ROW_PARAMS)
1908 TEXT80_NUM_COLS,
true, 0,
true);
1911static void (*
const text80RowClones[2][4])(TEXT_ROW_PARAMS) = {
1912 {text80RowT1, text80RowT1Ecm1, text80RowT1Ecm2, text80RowT1Ecm3},
1913 {text80RowT2, text80RowT2Ecm1, text80RowT2Ecm2, text80RowT2Ecm3}};
1931PICO9918_INLINE_HOT
void
1933 const uint8_t* __restrict patternTable,
const uint8_t* __restrict rowColors,
1934 const bool opaq, uint8_t* __restrict pixels,
const uint32_t startCol,
1935 const uint32_t numCells,
const bool scrolled)
1938 const uint8_t* rowNamesTable = rowNames + startCol;
1939 const uint8_t* colors = rowColors + startCol;
1940 const uint32_t* colorTable32 = (
const uint32_t*)PICO9918_ASSUME_ALIGNED(rowColors, 4);
1941 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
1942 uint32_t col = startCol;
1945#define TEXT80_NEXT_CELL() \
1948 if (scrolled && ++col == TEXT80_NUM_COLS) \
1951 rowNamesTable = rowNames; \
1952 colors = rowColors; \
1954 else if (scrolled) \
1960 uint8_t lastColor = 0;
1961 uint32_t bgColorMask = text80ColorWord[bgc];
1962 uint32_t diffColorMask = 0;
1964 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
1966 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
1969#define TEXT80_OPAQUE_CELL() \
1976 color = (uint8_t)colorWord; \
1979 if (color != lastColor) \
1981 const uint8_t bgColor = color & 0xf; \
1982 const uint8_t fgColor = color >> 4; \
1983 bgColorMask = text80ColorWord[bgColor ? bgColor : bgc]; \
1984 diffColorMask = bgColorMask ^ text80ColorWord[fgColor ? fgColor : bgc]; \
1985 lastColor = color; \
1987 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
1988 TEXT80_NEXT_CELL(); \
1989 word = bgColorMask ^ (diffColorMask & mask); \
1992 TEXT80_OPAQUE_CELL();
1994 TEXT80_OPAQUE_CELL();
1995 *pix32++ = acc | (word << 24);
1997 TEXT80_OPAQUE_CELL();
1998 *pix32++ = acc | (word << 16);
2000 TEXT80_OPAQUE_CELL();
2001 *pix32++ = acc | (word << 8);
2003#undef TEXT80_OPAQUE_CELL
2008 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
2010 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
2011 uint32_t val, sel, accVal, accSel;
2013 if (!scrolled && !colorWord)
2020#define TEXT80_OVERLAY_CELL() \
2022 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
2023 uint8_t colorByte; \
2025 colorByte = *colors; \
2028 colorByte = (uint8_t)colorWord; \
2031 TEXT80_NEXT_CELL(); \
2032 const uint32_t fgWord = text80ColorWord[colorByte >> 4]; \
2033 const uint32_t bgWord = text80ColorWord[colorByte & 0xf]; \
2034 const uint32_t fgSel = fgWord ? mask : 0; \
2035 const uint32_t bgSel = bgWord ? (~mask & 0xffffffu) : 0; \
2036 sel = fgSel | bgSel; \
2037 val = (fgWord & fgSel) | (bgWord & bgSel); \
2040#define TEXT80_OVERLAY_STORE(shift) \
2042 const uint32_t m = accSel | (sel << (shift)); \
2043 const uint32_t v = accVal | (val << (shift)); \
2044 const uint32_t old = *pix32; \
2045 *pix32++ = old ^ ((old ^ v) & m); \
2048 TEXT80_OVERLAY_CELL();
2051 TEXT80_OVERLAY_CELL();
2052 TEXT80_OVERLAY_STORE(24);
2055 TEXT80_OVERLAY_CELL();
2056 TEXT80_OVERLAY_STORE(16);
2059 TEXT80_OVERLAY_CELL();
2060 TEXT80_OVERLAY_STORE(8);
2062#undef TEXT80_OVERLAY_CELL
2063#undef TEXT80_OVERLAY_STORE
2066#undef TEXT80_NEXT_CELL
2070#define TEXT80_ROW_CLONE(name, cells, scroll) \
2071 static EMITTER_NOINLINE void __time_critical_func(name)( \
2072 PICO9918_INST_ARG const uint8_t* rowNames, const uint8_t* patternTable, const uint8_t* rowColors, \
2073 const bool opaq, uint8_t* pixels, const uint32_t startCol, const uint32_t numCells) \
2075 renderText80Row(PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, cells, \
2079TEXT80_ROW_CLONE(text80Row, TEXT80_NUM_COLS,
false)
2080TEXT80_ROW_CLONE(text80RowScrolled, numCells, true)
2083 const uint8_t* patternTable, const uint8_t* rowColors,
2084 const
bool opaq, uint8_t* pixels, const uint32_t startCol,
2085 const uint32_t numCells, const
bool scrolled)
2088 text80RowScrolled(
PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, numCells);
2090 text80Row(
PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, 0, TEXT80_NUM_COLS);
2099static inline uint8_t* text80TwoTone(
const uint8_t* __restrict names,
const uint8_t* __restrict patternTable,
2100 const uint32_t bgWord,
const uint32_t diffWord,
2101 uint8_t* __restrict pixels, uint32_t cells)
2105 const uint32_t pixelWord = bgWord ^ (diffWord & text80MaskWord[patternTable[*names++ * PATTERN_BYTES]]);
2107 *pixels++ = pixelWord;
2108 *pixels++ = pixelWord >> 8;
2109 *pixels++ = pixelWord >> 16;
2129 const bool wide = pico9918_cached_mode == TMS_MODE_TEXT80;
2130 const uint8_t numCols = wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS;
2131 const uint8_t nameTableMask = (wide && !PICO9918_UNLOCKED(tms9918)) ? 0x0c : 0x0f;
2133 const bool attrPerPos =
2137 uint16_t rowNamesAddr, colorTableAddr;
2139 &rowNamesAddr, &colorTableAddr);
2141 const uint8_t* patternTable = addr.pattern;
2143 uint32_t* border = (uint32_t*)pixels;
2145 pixels += TEXT_PADDING_PX;
2149 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
2155 renderText80Layer(
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, patternTable,
2156 tms9918->vram.bytes + colorTableAddr,
true, pixels - 4 * t1.bytes, t1.startCol,
2157 t1.cells, t1.scrolled);
2163 &rowNamesAddr, &colorTableAddr);
2165 renderText80Layer(
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, addr.pattern,
2166 tms9918->vram.bytes + colorTableAddr,
false, pixels - 4 * t2.bytes, t2.startCol,
2167 t2.cells, t2.scrolled);
2173 const uint8_t* rowNamesTable = tms9918->vram.bytes + rowNamesAddr;
2177 const uint32_t bgWord = text80ColorWord[bgColor];
2178 const uint32_t diffWord = bgWord ^ text80ColorWord[fgColor];
2180 const uint32_t cells = t1.bytes ? TEXT80_NUM_COLS + 1 : TEXT80_NUM_COLS;
2181 uint32_t run = cells;
2182 if (t1.startCell < TEXT80_NUM_COLS && t1.startCell + run > TEXT80_NUM_COLS)
2183 run = TEXT80_NUM_COLS - t1.startCell;
2186 text80TwoTone(rowNamesTable + t1.startCell, patternTable, bgWord, diffWord, pixels - t1.bytes, run);
2187 if (run < cells) text80TwoTone(rowNamesTable, patternTable, bgWord, diffWord, pixels, cells - run);
2191 const uint32_t bgWord = repeatedPalette(bgColor);
2192 const uint32_t diffWord = bgWord ^ repeatedPalette(fgColor);
2193 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
2195 for (uint8_t tileX = 0; tileX < TEXT_NUM_COLS; tileX += 2)
2197 uint16_t* pix16 = (uint16_t*)pix32;
2198 uint32_t patt = patternTable[*rowNamesTable++ * PATTERN_BYTES];
2200 pix32[0] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt >> 4]);
2201 pix16[2] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt & 0x0f]);
2203 patt = patternTable[*rowNamesTable++ * PATTERN_BYTES];
2204 const uint32_t lo = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt >> 4]);
2205 const uint32_t hi = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt & 0x0f]);
2208 pix32[2] = (lo >> 16) | (hi << 16);
2214 border[0] = border[1] = bg;
2215 border[62] = border[63] = bg;
2220 const uint32_t right)
2222 uint32_t* buffer_words = (uint32_t*)(buffer + xPos);
2223 buffer_words[0] = left;
2224 buffer_words[1] = right;
2233 const uint8_t pattIdx,
const uint8_t patternTable[],
2234 const uint32_t colorTableAddr,
const uint32_t pal,
const bool isTile2,
2235 const bool gm2Color,
const bool mcm)
2244 const uint32_t pattByte = patternTable[pattIdx * PATTERN_BYTES];
2245 const uint32_t colorByte =
2247 : tms9918->vram.bytes[colorTableAddr + (gm2Color ? pattIdx * PATTERN_BYTES : (pattIdx >> 3))];
2248 const uint32_t patt = mcm ? 0xf0 : pattByte;
2250 const uint32_t bgColor = colorByte & 0x0f;
2251 const uint32_t fgColor = colorByte >> 4;
2253 const uint32_t bgPalette = ecm0Palette[pal | bgColor];
2254 const uint32_t fgPalette = ecm0Palette[pal | fgColor];
2256 uint32_t pattMask = 0xff;
2257 if (!bgColor) pattMask &= patt;
2258 if (!fgColor) pattMask ^= patt;
2272 const uint32_t rightMask = maskExpandNibbleToWordRev[patt & 0xf];
2273 const uint32_t leftMask = maskExpandNibbleToWordRev[patt >> 4];
2276 (fgPalette & rightMask) | (bgPalette & ~rightMask));
2283 const uint8_t pattIdx,
const uint8_t patternTable[],
2284 const uint32_t colorTableAddr,
const uint32_t ecm,
const uint32_t ecmOffset,
2285 const uint32_t ecmColorMask,
const uint32_t ecmColorOffset,
const uint32_t pal,
2286 const bool attrPerPos,
const int32_t flipY,
const uint32_t tileIndex,
2287 uint32_t* lastEmpty,
const bool isTile2,
const bool alwaysOnTop)
2289 if ((*lastEmpty == pattIdx) ||
2301 uint32_t colorTableOffset = attrPerPos ? tileIndex : pattIdx;
2302 uint32_t pattOffset = pattIdx * PATTERN_BYTES;
2304 const uint32_t colorByte = tms9918->vram.bytes[colorTableAddr + colorTableOffset];
2306 const uint8_t* pattData = patternTable + pattOffset;
2309 if (colorByte & 0x20) pattData += flipY;
2311 uint32_t pattMask = (colorByte & 0x10) ? 0 : 0xff;
2314 uint8_t patt[3] = {0};
2318 case 3: patt[0] = pattData[ecmOffset * 2]; pattMask |= patt[0];
2319 case 2: patt[1] = pattData[ecmOffset]; pattMask |= patt[1];
2320 default: patt[2] = *pattData; pattMask |= patt[2];
2326 if (colorByte & 0x40)
2328 patt[0] = reversedBits[patt[0]];
2329 patt[1] = reversedBits[patt[1]];
2330 patt[2] = reversedBits[patt[2]];
2331 pattMask = reversedBits[pattMask];
2334 const uint32_t priority = alwaysOnTop || (colorByte & 0x80);
2338 if (priority && tms9918->scanlineHasSprites)
2340 const uint32_t offScreen = xPos ? 0 : pixelOffset;
2341 tmsClearRowBitsMask(xPos - pixelOffset + offScreen, pattMask << offScreen, 8, rowMasks.rowSpriteBits);
2346 case 3: index = ecmSplitQuads(patt[0]) << 8;
2347 case 2: index |= ecmSplitQuads(patt[1]) << 4;
2348 default: index |= ecmSplitQuads(patt[2]);
2351 const uint32_t palette = repeatedPalette(pal | ((colorByte & ecmColorMask) << ecmColorOffset));
2352 const uint32_t left = ecmLookup[index >> 16] | palette;
2353 const uint32_t right = ecmLookup[(uint16_t)index] | palette;
2355 if (!isTile2 && (colorByte & 0x10))
2357 const uint32_t clear = transparentPixels[0];
2358 writeToAlignedBuffer(buffer, xPos, clear ^ ((left ^ clear) & maskExpandNibbleToWordRev[pattMask >> 28]),
2359 clear ^ ((right ^ clear) & maskExpandNibbleToWordRev[(pattMask >> 24) & 0x0f]));
2374 *lastEmpty = pattIdx;
2382static inline void lockedFgBg(
PICO9918_INST_ARG uint32_t fgbg[2],
const uint8_t pal,
const uint32_t colorByte)
2384 fgbg[0] = repeatedPalette(pal |
tmsBgColor(tms9918, colorByte));
2385 fgbg[1] = fgbg[0] ^ repeatedPalette(pal |
tmsFgColor(tms9918, colorByte));
2389PICO9918_INLINE_HOT
void
2394 const uint8_t* pattTableRow = addr->pattern;
2395 const uint8_t nameMask = addr->nameMask;
2398 uint32_t numTiles = GRAPHICS_NUM_COLS;
2400 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
2401 uint8_t* pattPtr = tms9918->vram.bytes + rowNamesAddr + tileIndex;
2402 uint32_t pattOffset = 0;
2404 uint8_t lastPattIdx = 0;
2405 uint32_t pattByte = mcm ? 0xf0 : (uint8_t)pattTableRow[0];
2406 uint32_t lastColorByte = mcm ? (uint8_t)pattTableRow[0] : tms9918->vram.bytes[colorTableAddr];
2409 lastColorByte = PICO9918_LAYER_SUB(tms9918, PICO9918_SUPPRESS_GM2_COLOUR, 0xf1, lastColorByte);
2410 pattByte = PICO9918_LAYER_SUB(tms9918, PICO9918_SUPPRESS_GM2_PATTERN, 0xff, pattByte);
2417 uint8_t pattIdx = *pattPtr++;
2418 if (gm2) pattIdx &= nameMask;
2420 if (lastPattIdx != pattIdx)
2422 lastPattIdx = pattIdx;
2423 pattOffset = lastPattIdx * PATTERN_BYTES;
2424 uint32_t colorByte =
2425 mcm ? pattTableRow[pattOffset]
2426 : tms9918->vram.bytes[colorTableAddr + (gm2 ? pattOffset : (pattIdx >> 3))];
2427 if (!mcm) pattByte = (uint8_t)pattTableRow[pattOffset];
2430 colorByte = PICO9918_LAYER_SUB(tms9918, PICO9918_SUPPRESS_GM2_COLOUR, 0xf1, colorByte);
2431 pattByte = PICO9918_LAYER_SUB(tms9918, PICO9918_SUPPRESS_GM2_PATTERN, 0xff, pattByte);
2433 if (lastColorByte != colorByte)
2435 lastColorByte = colorByte;
2440 pix32[0] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte >> 4]);
2441 pix32[1] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte & 0x0f]);
2446#define LOCKED_ROW_CLONE(name, g, m) \
2447 static void __time_critical_func(name)(PICO9918_INST_ARG uint16_t rowNamesAddr, uint16_t colorTableAddr, \
2448 uint8_t tileIndex, uint8_t pal, uint8_t pixels[TMS9918_PIXELS_X], \
2449 const TileRowAddr* addr) \
2451 renderTileRowLocked(PICO9918_INST rowNamesAddr, colorTableAddr, tileIndex, pal, pixels, addr, g, m); \
2454LOCKED_ROW_CLONE(rowLockedGm1,
false,
false)
2455LOCKED_ROW_CLONE(rowLockedGm2, true, false)
2456LOCKED_ROW_CLONE(rowLockedMcm, false, true)
2470#define TILE_ROW_PARAMS \
2471 PICO9918_INST_ARG const bool hpSize, uint16_t rowNamesAddr, uint16_t colorTableAddr, uint8_t tileIndex, \
2472 uint8_t startPattBit, const bool attrPerPos, uint8_t pal, const bool alwaysOnTop, \
2473 const TileRowAddr *addr
2474#define TILE_ROW_ARGS \
2475 PICO9918_INST hpSize, rowNamesAddr, colorTableAddr, tileIndex, startPattBit, attrPerPos, pal, alwaysOnTop, \
2478PICO9918_INLINE_HOT
void renderTileRow(TILE_ROW_PARAMS,
const bool isTile2,
2479 const uint32_t ecm,
const bool gm2,
const bool mcm)
2482 uint32_t lastEmpty = -1;
2484 const int32_t flipY = addr->flipY;
2485 const uint8_t* patternTable = addr->pattern;
2486 const uint8_t nameMask = addr->nameMask;
2487 uint8_t* targetBuffer = isTile2 ? tms9918->tileLayer2Buffer : tms9918->tileLayer1Buffer;
2489 uint32_t numTiles = GRAPHICS_NUM_COLS + (startPattBit != 0);
2491 uint32_t ecmColorOffset = 0, ecmColorMask = 0, ecmOffset = 0;
2494 ecmColorOffset = (ecm == 3) ? 2 : ecm;
2495 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
2497 pal = (ecm == 1) ? (pal & 0x20) : 0;
2502 uint32_t run = GRAPHICS_NUM_COLS - tileIndex;
2503 if (run > numTiles) run = numTiles;
2506 const uint8_t* names = tms9918->vram.bytes + rowNamesAddr + tileIndex;
2510 uint8_t pattIdx = *names++;
2511 if (gm2 || ecm) pattIdx &= nameMask;
2515 colorTableAddr, ecm, ecmOffset, ecmColorMask, ecmColorOffset, pal,
2516 attrPerPos, flipY, tileIndex, &lastEmpty, isTile2, alwaysOnTop);
2530 rowNamesAddr ^= 0x400;
2531 if (attrPerPos) colorTableAddr = (colorTableAddr + ((rowNamesAddr & 0x400) << 1) - 0x400) & VRAM_MASK;
2537#define TILE_ROW_CLONE(name, t2, e, g, m) \
2538 static void __time_critical_func(name)(TILE_ROW_PARAMS) \
2540 renderTileRow(TILE_ROW_ARGS, t2, e, g, m); \
2543TILE_ROW_CLONE(rowT1Ecm0,
false, 0,
false,
false)
2544TILE_ROW_CLONE(rowT1Ecm1, false, 1, false, false)
2545TILE_ROW_CLONE(rowT1Ecm2, false, 2, false, false)
2546TILE_ROW_CLONE(rowT1Ecm3, false, 3, false, false)
2547TILE_ROW_CLONE(rowT1Gm2, false, 0, true, false)
2548TILE_ROW_CLONE(rowT1Mcm, false, 0, false, true)
2549TILE_ROW_CLONE(rowT2Ecm0, true, 0, false, false)
2550TILE_ROW_CLONE(rowT2Ecm1, true, 1, false, false)
2551TILE_ROW_CLONE(rowT2Ecm2, true, 2, false, false)
2552TILE_ROW_CLONE(rowT2Ecm3, true, 3, false, false)
2553TILE_ROW_CLONE(rowT2Gm2, true, 0, true, false)
2554TILE_ROW_CLONE(rowT2Mcm, true, 0, false, true)
2558#define TILE_ROW_GM2 4
2559#define TILE_ROW_MCM 5
2560static void (*
const tileRowClones[2][6])(TILE_ROW_PARAMS) = {
2561 {rowT1Ecm0, rowT1Ecm1, rowT1Ecm2, rowT1Ecm3, rowT1Gm2, rowT1Mcm},
2562 {rowT2Ecm0, rowT2Ecm1, rowT2Ecm2, rowT2Ecm3, rowT2Gm2, rowT2Mcm}};
2575 const bool gm2 = pico9918_cached_mode == TMS_MODE_GRAPHICS_II;
2576 const bool mcm = pico9918_cached_mode == TMS_MODE_MULTICOLOR;
2577 const bool wide = TEXT80_WIDE_ROW;
2578 const bool text = wide || pico9918_cached_mode == TMS_MODE_TEXT;
2579 const uint8_t textCols = wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS;
2582 const bool attrPerPos =
2586 uint16_t rowNamesAddr, colorTableAddr;
2588 &addr, &rowNamesAddr, &colorTableAddr);
2591 << config->paletteShift;
2593 const bool alwaysOnTop =
2594 config->priorityReg ? !(TMS_REGISTER(tms9918, config->priorityReg) & config->priorityMask) :
false;
2601 const uint8_t* colors = (attrPerPos || ecm) ? tms9918->vram.bytes + colorTableAddr : &fixed;
2602 uint8_t* dest = (config->isTile2 ? tms9918->tileLayer2Buffer : tms9918->tileLayer1Buffer) +
2603 (wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX);
2604 const uint32_t scroll = textScrollSplit(TMS_REGISTER(tms9918, config->startPattReg), wide);
2606#if PICO9918_TEXT80_8BPP
2610 dest = tms9918->tileLayer1Buffer + TEXT80_PADDING_PX +
2612 TEXT_SCROLL_OFFSET(scroll);
2613 text80RowT2Blend(
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors, attrPerPos, pal,
2614 dest, scroll, alwaysOnTop);
2619#if PICO9918_TEXT80_8BPP
2622 text80RowClones[config->isTile2][ecm](
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr,
2623 colors, attrPerPos, pal, dest, scroll, alwaysOnTop);
2627 textRowClones[config->isTile2][ecm](
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors,
2628 attrPerPos, pal, dest, scroll, alwaysOnTop);
2632 const uint8_t startPattBit = TMS_REGISTER(tms9918, config->startPattReg) & 0x07;
2633 const uint8_t tileIndex = (TMS_REGISTER(tms9918, config->startPattReg) >> 3);
2636 uint32_t slot = ecm;
2637 if (!ecm) slot = gm2 ? TILE_ROW_GM2 : (mcm ? TILE_ROW_MCM : 0);
2640 tileRowClones[config->isTile2][slot](
PICO9918_INST hpSize, rowNamesAddr, colorTableAddr, tileIndex,
2641 startPattBit, attrPerPos, pal, alwaysOnTop, &addr);
2656static bool underLayer =
false;
2662const uint8_t* pico9918_cached_line_source = 0;
2669 const uint16_t addr,
const uint8_t bmlCtl,
2671 const bool intoTile1)
2674 bool writeMask = (bmlCtl & 0x40) && !intoTile1;
2675 underLayer = !(bmlCtl & 0x40);
2677 bool returnVal =
true;
2679 if (writeMask && opaque && (width == 64))
2686 uint32_t currentMask = 0;
2691 const uint8_t colorMask = 0xf0;
2692 const uint8_t colorOffset = 4;
2693 const uint8_t colorCount = 2;
2694 const uint8_t colorSize = 4;
2695 uint32_t maskPixelMask = 0x3u << 30;
2696 uint32_t maskX = xPos;
2698 uint8_t pal = (bmlCtl & 0xc) << 2;
2700 for (
int xOff = 0; xOff < width; ++xOff)
2702 uint8_t data = tms9918->vram.bytes[addr + xOff];
2703 for (
int sp = 0; sp < colorCount; ++sp)
2705 uint8_t color = (data & colorMask);
2706 if (opaque || color)
2708 uint8_t finalColour = pal | (color >> colorOffset);
2711 const uint16_t pair = (uint16_t)(finalColour | (finalColour << 8));
2712 *(uint16_t*)(pixels + xPos * 2) = pair;
2713 *(uint16_t*)(pixels + xPos * 2 + 2) = pair;
2717 pixels[xPos] = finalColour;
2718 pixels[xPos + 1] = finalColour;
2720 currentMask |= maskPixelMask;
2724 maskPixelMask >>= 2;
2726 if (writeMask && !maskPixelMask && currentMask)
2730 maskPixelMask = 0x3u << 30;
2734 if (writeMask && currentMask)
2741 const uint8_t colorMask = 0xc0;
2742 const uint8_t colorOffset = 6;
2743 const uint8_t colorCount = 4;
2744 const uint8_t colorSize = 2;
2745 uint32_t maskPixelMask = 0x1u << 31;
2746 uint32_t maskX = xPos;
2748 uint8_t pal = (bmlCtl & 0xf) << 2;
2750 if (opaque && !wide && ((xPos & 3) == 0))
2755 const uint32_t palQuad = repeatedPalette(pal);
2756 uint32_t*
const quadPixels = (uint32_t*)pixels;
2757 uint32_t quadOffset = xPos >> 2;
2759 for (
int xOff = 0; xOff < width; ++xOff)
2761 const uint8_t data = tms9918->vram.bytes[addr + xOff];
2763 (bmlExpand2bpp[data >> 4] | ((uint32_t)bmlExpand2bpp[data & 0x0f] << 16)) | palQuad;
2770 uint32_t left = (uint32_t)width * colorCount;
2774 maskX = (maskX + 32) & 0xff;
2782 for (
int xOff = 0; xOff < width; ++xOff)
2784 uint8_t data = tms9918->vram.bytes[addr + xOff];
2785 for (
int sp = 0; sp < colorCount; ++sp)
2787 uint8_t color = (data & colorMask);
2788 if (opaque || color)
2790 const uint8_t v = pal | (color >> colorOffset);
2792 *(uint16_t*)(pixels + xPos * 2) = (uint16_t)(v | (v << 8));
2795 currentMask |= maskPixelMask;
2799 maskPixelMask >>= 1;
2802 if (writeMask && !maskPixelMask && currentMask)
2806 maskPixelMask = 0x1u << 31;
2810 if (writeMask && currentMask)
2821#if PICO9918_TEXT80_8BPP
2822static EMITTER_NOINLINE
bool __time_critical_func(renderBitmapLayer80)(
2823 PICO9918_INST_ARG uint16_t y,
bool opaque,
const uint8_t width,
const uint16_t addr,
const uint8_t bmlCtl,
2830static inline bool __time_critical_func(renderBitmapLayer40)(
PICO9918_INST_ARG uint16_t y,
bool opaque,
2831 const uint8_t width,
const uint16_t addr,
2832 const uint8_t bmlCtl,
2841 const bool intoTile1)
2845 if (!(bmlCtl & 0x80) || !PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_BITMAP))
return true;
2849 if (top > y)
return true;
2856 const uint8_t width = bmlWidth ? ((bmlWidth + 3) >> 2) : 64;
2859 const bool opaque = !(bmlCtl & 0x20);
2861#if PICO9918_TEXT80_8BPP
2862 if (TEXT80_WIDE_ROW)
2863 return renderBitmapLayer80(
PICO9918_INST y, opaque, width, addr, bmlCtl, pixels, intoTile1);
2865 return renderBitmapLayer40(
PICO9918_INST y, opaque, width, addr, bmlCtl, pixels);
2876static inline void compositeChunkWide(uint32_t* __restrict pix32,
const uint32_t* __restrict l1,
2877 const uint32_t* __restrict l2, uint32_t sel, uint32_t open,
2878 const bool hasSprites)
2880 for (
int i = 0; i < 8; ++i)
2882 const uint32_t layers = maskExpandNibbleToWordRev[sel >> 28];
2883 uint32_t merged = l1[i] ^ ((l1[i] ^ l2[i]) & layers);
2887 const uint32_t old = pix32[i];
2888 merged = old ^ ((old ^ merged) & maskExpandNibbleToWordRev[open >> 28]);
2902static inline void compositeChunkUnder(uint8_t* __restrict pixels,
const uint8_t* __restrict layer1,
2903 const uint8_t* __restrict layer2, uint32_t mask, uint32_t spriteMask,
2904 const bool hasSprites)
2906 for (
int i = 0; i < 8; ++i)
2908#define UNDER_PIXEL(n) \
2909 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2911 const uint8_t pixel = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2912 if (pixel) pixels[n] = pixel; \
2915 if (hasSprites) spriteMask <<= 1;
2930static inline void compositeChunkBytes(uint8_t* __restrict pixels,
const uint8_t* __restrict layer1,
2931 const uint8_t* __restrict layer2, uint32_t mask, uint32_t spriteMask,
2932 const bool hasSprites)
2934 for (
int i = 0; i < 8; ++i)
2936#define MIXED_PIXEL(n) \
2937 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2939 pixels[n] = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2942 if (hasSprites) spriteMask <<= 1;
2960static inline uint32_t spriteGridWord(
const uint32_t maskWord,
const BitMask mask,
const bool wide)
2962 if (!wide)
return mask[maskWord];
2964 const uint32_t half = (maskWord & 1) ? (mask[maskWord >> 1] & 0xffff) : (mask[maskWord >> 1] >> 16);
2965 return ((uint32_t)doubledBits[half >> 8] << 16) | doubledBits[half & 0xff];
2974static inline void overlaySpritesOnTile1(uint32_t* __restrict dst,
const uint32_t* __restrict src,
2977 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX :
TMS9918_PIXELS_X) / 32;
2979 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord, dst += 8, src += 8)
2981 uint32_t open = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
2983 for (
int i = 0; open; ++i, open <<= 4)
2985 const uint32_t nibble = open >> 28;
2988 const uint32_t take = maskExpandNibbleToWordRev[nibble];
2989 dst[i] = dst[i] ^ ((dst[i] ^ src[i]) & take);
2995PICO9918_INLINE_HOT
void
2999 uint8_t* layer1 = tms9918->tileLayer1Buffer + t1Scroll;
3000 uint8_t* layer2 = tms9918->tileLayer2Buffer + t2Scroll;
3001 uint32_t* selectionMask = tms9918->layerSelectionMask;
3002 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX :
TMS9918_PIXELS_X) / 32;
3005 const bool wordAligned = ((t1Scroll | t2Scroll) & 3) == 0;
3006 const uint32_t chunkCount = wordAligned ? 32 /
sizeof(uint32_t) : 32;
3007 PICO9918_COPY_SET_WIDTH(PICO9918_COPY, wordAligned);
3010 for (uint32_t maskWord = 0; maskWord < maskWords; maskWord++)
3012 uint32_t mask = selectionMask[maskWord];
3015 uint32_t spriteMask = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide) |
3016 (spriteGridWord(maskWord, rowMasks.rowBits, wide) & ~mask);
3018 if (spriteMask == 0xffffffffu)
3026 if (!underLayer && !spriteMask)
3031 PICO9918_COPY_WAIT(PICO9918_COPY);
3032 PICO9918_COPY_SET_SRC(PICO9918_COPY, layer1);
3033 PICO9918_COPY_SET_DST(PICO9918_COPY, pixels);
3034 PICO9918_COPY_TRIGGER(PICO9918_COPY, chunkCount);
3040 else if (mask == 0xffffffffu)
3043 PICO9918_COPY_WAIT(PICO9918_COPY);
3044 PICO9918_COPY_SET_SRC(PICO9918_COPY, layer2);
3045 PICO9918_COPY_SET_DST(PICO9918_COPY, pixels);
3046 PICO9918_COPY_TRIGGER(PICO9918_COPY, chunkCount);
3059 compositeChunkUnder(pixels, layer1, layer2, mask, spriteMask,
true);
3061 compositeChunkUnder(pixels, layer1, layer2, mask, 0,
false);
3067 else if (wordAligned)
3069 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4);
3070 const uint32_t* l1 = (
const uint32_t*)PICO9918_ASSUME_ALIGNED(layer1, 4);
3071 const uint32_t* l2 = (
const uint32_t*)PICO9918_ASSUME_ALIGNED(layer2, 4);
3074 compositeChunkWide(pix32, l1, l2, mask, ~(uint32_t)spriteMask,
true);
3076 compositeChunkWide(pix32, l1, l2, mask, 0,
false);
3085 compositeChunkBytes(pixels, layer1, layer2, mask, spriteMask,
true);
3087 compositeChunkBytes(pixels, layer1, layer2, mask, 0,
false);
3100#if PICO9918_TEXT80_8BPP
3101static EMITTER_NOINLINE
3107 const int t1Scroll,
const int t2Scroll)
3109 compositeAlignedBody(
PICO9918_INST pixels, t1Scroll, t2Scroll,
false);
3112#if PICO9918_TEXT80_8BPP
3113static EMITTER_NOINLINE
void
3117 compositeAlignedBody(
PICO9918_INST pixels, t1Scroll, t2Scroll,
true);
3122 const int t1Scroll,
const int t2Scroll,
const bool wide)
3124#if PICO9918_TEXT80_8BPP
3127 compositeAligned80(
PICO9918_INST pixels, t1Scroll, t2Scroll);
3131 compositeAligned40(
PICO9918_INST pixels, t1Scroll, t2Scroll);
3140static PICO9918_NOINLINE
void
3142 const int t2Scroll,
const bool wide)
3144 const uint8_t* layer2 = tms9918->tileLayer2Buffer + t2Scroll;
3145 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX :
TMS9918_PIXELS_X) / 32;
3147 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord)
3150 tms9918->layerSelectionMask[maskWord] & ~spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
3159 for (
int i = 0; i < 8; ++i)
3161 if (mask & MASK_NEXT_PIXEL) pixels[0] = layer2[0];
3164 if (mask & MASK_NEXT_PIXEL) pixels[1] = layer2[1];
3167 if (mask & MASK_NEXT_PIXEL) pixels[2] = layer2[2];
3170 if (mask & MASK_NEXT_PIXEL) pixels[3] = layer2[3];
3186 const uint32_t width)
3188 uint8_t* left = tms9918->tileLayer1Buffer + t1Scroll;
3189 uint8_t* right = left + width - padding;
3191 for (uint32_t i = 0; i < padding; ++i)
3202 uint8_t tempStatus = 0;
3205 if (PICO9918_UNLOCKED(tms9918))
3208 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3217 (bmlCtlReg & 0x40) &&
3219 PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_TILE1) &&
3220 PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_BITMAP);
3222 bool writeMask =
true;
3228 const uint32_t transparent = underLayer ? 0 : bg;
3229 transparentPixels[0] = transparentPixels[1] = transparent;
3230 ecm0Palette[0x00] = ecm0Palette[0x10] = ecm0Palette[0x20] = ecm0Palette[0x30] = transparent;
3232 tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3236 const bool wide = TEXT80_WIDE_ROW;
3237 const bool textRow = wide || pico9918_cached_mode == TMS_MODE_TEXT;
3241 PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_TILE2);
3243 PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_TILE1);
3245 const bool blend = wide && tile1Enabled && tile2Enabled &&
3249 if (tile2Enabled && !blend)
3252 tmsCopyAlignMask(tms9918->finalMask, tms9918->layerSelectionMask, t1Scroll - t2Scroll);
3266 if (tile2Enabled && !blend)
3267 tmsRestoreAlignMask(tms9918->layerSelectionMask, tms9918->finalMask, -t1Scroll, t2Scroll);
3269 if (textRow && !blend)
3271 const uint32_t pad = wide ? TEXT80_PADDING_PX : TEXT_PADDING_PX;
3272 const uint32_t end = pad + (wide ? TEXT80_NUM_COLS : TEXT_NUM_COLS) * TEXT_CHAR_WIDTH;
3273 tms9918->layerSelectionMask[0] &= 0xffffffffu >> pad;
3274 tms9918->layerSelectionMask[(end - 1) >> 5] &= ~(0xffffffffu >> (end & 0x1f));
3279 if (tile1Enabled && (!tile2Enabled || blend) && !underLayer && !(t1Scroll & 3) &&
3282 uint8_t* line = tms9918->tileLayer1Buffer + t1Scroll;
3284 if (tms9918->scanlineHasSprites)
3285 overlaySpritesOnTile1((uint32_t*)PICO9918_ASSUME_ALIGNED(line, 4),
3286 (
const uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4), wide);
3288 pico9918_cached_line_source = line;
3290 else if (tile1Enabled)
3292 compositeAlignedTileBuffers(
PICO9918_INST pixels, t1Scroll, t2Scroll, wide);
3294 else if (tile2Enabled)
3303 const uint8_t tileY = y >> 3;
3306 const uint16_t rowOffset = tileY * GRAPHICS_NUM_COLS;
3310 const bool gm2 = pico9918_cached_mode == TMS_MODE_GRAPHICS_II;
3311 const bool mcm = pico9918_cached_mode == TMS_MODE_MULTICOLOR;
3316 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3318 if (PICO9918_DRAWS(tms9918, PICO9918_SUPPRESS_TILE1))
3321 rowLockedGm2(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3323 rowLockedMcm(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3325 rowLockedGm1(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3328 tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3337 uint8_t*
const pixels = scanlineBuffer;
3338 uint8_t tempStatus = 0;
3340 if (!lookupsReady) initLookups();
3343 if (currentCachedMode != pico9918_cached_mode)
3345 pico9918_cached_mode = currentCachedMode;
3346 tms9918->palDirty = 1;
3350 const bool packedNibbles = pico9918_cached_mode == TMS_MODE_TEXT80 && !TEXT80_WIDE_ROW;
3351 bg = repeatedPalette(
3353 (packedNibbles ? bgc << 4
3355#if PICO9918_TEXT80_8BPP
3359 PICO9918_FILL32_TRIGGER(PICO9918_FILL_LINE, pixels);
3360 pico9918_cached_line_source = pixels;
3364 PICO9918_SUPPRESSED(tms9918, PICO9918_SUPPRESS_BLANKING);
3369 PICO9918_FILL32_TRIGGER(PICO9918_FILL_MASKS, &rowMasks);
3371 for (
int i = 0; i < SCANLINE_MASK_WORDS; ++i)
3373 tms9918->layerSelectionMask[i] = 0;
3374 tms9918->finalMask[i] = 0;
3376 tms9918->scanlineHasSprites =
false;
3378 PICO9918_FILL32_WAIT(PICO9918_FILL_MASKS);
3384#if defined(__GNUC__) || defined(__clang__)
3385#pragma GCC diagnostic push
3386#pragma GCC diagnostic ignored "-Wswitch"
3388 switch (pico9918_cached_mode)
3390 case TMS_MODE_GRAPHICS_I:
3391 case TMS_MODE_GRAPHICS_II:
3395 case TMS_MODE_TEXT80:
3396 if (PICO9918_UNLOCKED(tms9918) && (pico9918_cached_mode == TMS_MODE_TEXT || TEXT80_WIDE_ROW))
3403 if (PICO9918_UNLOCKED(tms9918)) tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3406#if defined(__GNUC__) || defined(__clang__)
3407#pragma GCC diagnostic pop
3412 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3413 PICO9918_COPY_WAIT(PICO9918_COPY);
3422 return TMS_REGISTER(tms9918, reg & tms9918->lockedMask);
3435 if (PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK) && PICO9918_UNLOCK_REG(reg))
3438 const bool unlockValue = PICO9918_UNLOCK_VALUE(value);
3439 const bool unlocked = unlockValue && tms9918->unlockCount;
3442 tms9918->unlockCount = unlockValue;
3444 if (unlocked != tms9918->isUnlocked)
3446 tms9918->isUnlocked = unlocked;
3447 tms9918->lockedMask = unlocked ? 0x3f : 0x07;
3448 tms9918->palDirty = 1;
3455 if (((reg & ~tms9918->lockedMask) != 0x80) && PICO9918_M4(tms9918))
return;
3457 tms9918->unlockCount = 0;
3459 const int regIndex = reg & tms9918->lockedMask;
3461 TMS_REGISTER(tms9918, regIndex) = value;
3480 tms9918->gpuStatus = 0;
3481 tms9918->restart = 1;
3487 tms9918->restart = 1;
3491 PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG))
3513 vdpRegisterReset(tms9918);
3518 tms9918->configDirty =
true;
3519 tms9918->configVdpDirty =
true;
3524 uint8_t statReg = (value & 0x0f);
3525 TMS_STATUS(tms9918, 0x0F) = statReg;
3526 if (value & 0x40) tms9918->startTime = PICO9918_HOST_TIME_US();
3528 tms9918->currentTime = PICO9918_HOST_TIME_US();
3529 else if (value & 0x10)
3530 tms9918->startTime += (tms9918->stopTime - tms9918->startTime);
3532 tms9918->currentTime = tms9918->stopTime = PICO9918_HOST_TIME_US();
3534 if (statReg > 3 && statReg < 12)
3536 uint32_t elapsed = tms9918->currentTime - tms9918->startTime;
3537 uint32_t microQ, microR;
3538 PICO9918_DIVMOD_U32(elapsed, 1000, microQ, microR);
3539 uint32_t milliQ, milliR;
3540 PICO9918_DIVMOD_U32(microQ, 1000, milliQ, milliR);
3563 tms9918->config[option] = value;
3565 tms9918->configDirty =
true;
3575 return tms9918->vram.bytes[addr & VRAM_MASK];
3589 return pico9918_cached_mode;
3592#if PICO9918_BUILD_DEBUG_API
3596 pico9918_cached_mode = tmsMode(tms9918);
3619 return pico9918_cached_line_source;
3626 return defaultPalette[index & 0x3f];
uint8_t pico9918_peek_status(pico9918_t *tms9918)
read from the status register without resetting it
static bool tmsSpriteMag(pico9918_t *tms9918)
sprite size (0 = 1x, 1 = 2x)
bool pico9918_unlocked(pico9918_t *tms9918)
see the header.
uint8_t pico9918_read_data(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
PICO9918_INTERNAL 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
static uint16_t tmsSpritePatternTableAddr(pico9918_t *tms9918)
sprite pattern table base address
static uint16_t tmsPatternTableAddr(pico9918_t *tms9918)
pattern table base address
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).
static uint16_t tmsNameTable2Addr(pico9918_t *tms9918)
name table base address
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.
pico9918_mode_t pico9918_display_mode(pico9918_t *tms9918)
current display mode
bool pico9918_display_enabled(pico9918_t *tms9918)
check BLANK flag
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.
void pico9918_destroy(pico9918_t *tms9918)
destroy a TMS9918
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
void pico9918_set_interrupt_callback(pico9918_t *tms9918, pico9918_interrupt_fn cb, void *userdata)
register the host's /INT hook, so a host need not poll
void pico9918_write_addr(pico9918_t *tms9918, uint8_t data)
write an address (mode = 1) to the tms9918
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
static void f18a_tile1_scan_line(pico9918_t *tms9918, uint16_t y)
generate a Graphics I mode scanline for the T1 layer
static void tmsClearRowBitsMask(const uint32_t xPos, const uint32_t tilePixels, const uint32_t tileWidth, BitMask rowBitsMask)
Clear the row pixels bit mask.
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
void pico9918_set_chip(pico9918_t *tms9918, pico9918_chip_t chip)
select which chip this instance answers as
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
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.
static uint16_t tmsColorTable2Addr(pico9918_t *tms9918)
color table base address
uint8_t pico9918_read_data_no_inc(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
static uint16_t tmsColorTableAddr(pico9918_t *tms9918)
color table base address
static pico9918_color_t tmsMainFgColor(pico9918_t *tms9918)
foreground color
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
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
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
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).
static void tmsSetTransparentSpriteMask(const uint32_t xPos, const uint32_t spritePixels, const uint32_t spriteWidth)
set the transparent sprite mask.
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
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 ...
void pico9918_debug_sync_mode_impl(pico9918_t *tms9918)
see impl/pico9918_priv.h.
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 ...
pico9918_chip_t pico9918_chip(pico9918_t *tms9918)
which chip this instance answers as
void pico9918_interrupt_dispatch(pico9918_t *tms9918, bool active)
see impl.
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.
static uint32_t tmsTestCollisionMask(const uint32_t xPos, const uint32_t spritePixels, const uint32_t spriteWidth)
Test and update the sprite collision mask.
uint8_t pico9918_vram_value(pico9918_t *tms9918, uint16_t addr)
return a value from vram
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.
size_t pico9918_instance_size(void)
see the header.
static uint16_t tmsSpriteAttrTableAddr(pico9918_t *tms9918)
sprite attribute table base address
static void tmsUpdateRowBitsMaskAligned(const uint32_t xPos, const uint32_t tilePixels, BitMask rowBitsMask)
Update the row pixels bit mask (aligned - no word boundary crossing).
void pico9918_interrupt_set(pico9918_t *tms9918)
raise the INT status flag
uint32_t pico9918_line_bytes(pico9918_t *tms9918)
how many bytes of pixels[] this mode fills.
void pico9918_set_status(pico9918_t *tms9918, uint8_t status)
set status flag
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
static pico9918_color_t tmsFgColor(pico9918_t *tms9918, uint8_t colorByte)
foreground color
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.
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
static uint8_t chipFeatures(pico9918_chip_t chip)
the feature bits a personality answers to - the ladder, in one place
static uint32_t spriteEcm(pico9918_t *tms9918)
the sprite ECM level, zero on a locked device - the one term a clone can pin
uint8_t pico9918_read_status(pico9918_t *tms9918)
read from the status register
static pico9918_color_t tmsBgColor(pico9918_t *tms9918, uint8_t colorByte)
background color
static uint16_t tmsNameTableAddr(pico9918_t *tms9918)
name table base address
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
uint16_t pico9918_default_palette(int index)
a default palette entry, 0xargb
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.
bool pico9918_interrupt_status(pico9918_t *tms9918)
return true if both INT status and INT control set
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
uint8_t pico9918_reg_value(pico9918_t *tms9918, pico9918_register_t reg)
return a register value - see the header for the locked-device aliasing
static pico9918_color_t tmsMainBgColor(pico9918_t *tms9918)
background color
static uint8_t tmsSpriteSize(pico9918_t *tms9918)
sprite size (8 or 16)
void pico9918_write_data(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918
#define TMS_R1_SPRITE_MAG2
sprites drawn at twice their pattern size
pico9918_color_t
the sixteen TMS9918 colours, in palette-index order
#define PICO9918_R29_SPRITE_STRIDE
register 29 fields: scroll page sizes, and the stride between ECM pattern planes
#define PICO9918_MAP_REGISTERS
the register file, VR0-VR63
pico9918_t * pico9918_new(void)
create a new TMS9918
#define PICO9918_MAP_PRAM
palette RAM, 64 entries of RGB444
#define PICO9918_R49_TILE2_ENABLE
register 49 bits: tile layer 2, row count, and the enhanced colour modes
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
#define PICO9918_R29_TILE_STRIDE
tile pattern plane stride, 0x800 >> n
#define PICO9918_R24_SPRITE_PS
register 24 bits: the sub-palette each layer takes
#define TMS_R1_MODE_TEXT
40-column text
#define PICO9918_R56_GPU_RUN
register 56 bit: the GPU trigger
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
#define PICO9918_INST_ONLY
pass the instance as the only argument
void(* pico9918_interrupt_fn)(pico9918_t *instance, bool active, void *userdata)
the host's /INT hook: called whenever the library drives the line
#define TMS_R1_MODE_MULTICOLOR
Multicolor.
#define PICO9918_R50_TILE1_OFF
stop drawing tile layer 1
#define PICO9918_R50_RESET
register 50 bits: GPU triggers and the remaining layer controls
#define PICO9918_R24_TILE1_PS
tile layer 1 palette select
#define PICO9918_R49_ECM_TILE
tile ECM level field
#define PICO9918_R49_Y_REAL
sprite Y is the real row, not row minus one
#define TMS_R1_DISP_ACTIVE
render the active display
#define TMS_R0_MODE_GRAPHICS_II
Graphics II - the only mode R0 selects.
#define TMS_R1_SPRITE_16
16x16 sprite patterns
#define PICO9918_MAP_STATUS
the status registers, SR0-SR15
#define PICO9918_MAP_SCANLINE
the current scanline, then the blanking flag
#define PICO9918_R15_STATUS_NUM
which status register S1 reads back
#define PICO9918_R50_REPORT_MAX
S0's sprite number reports the highest seen.
pico9918_register_t
the eight TMS9918 registers, by number and by what each one holds
@ PICO9918_REG_COLOR_TABLE2
tile layer 2 colour table base
@ PICO9918_REG_CONFIG_INDEX
PICO9918 only: which configuration byte R59 addresses.
@ PICO9918_REG_BML_X
bitmap layer left edge
@ PICO9918_REG_BML_BASE
bitmap layer base address, in 64-byte units
@ PICO9918_REG_VRAM_INC
signed VRAM address increment per access
@ PICO9918_REG_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
@ PICO9918_REG_NAME_TABLE2
tile layer 2 name table base
@ PICO9918_REG_ENHANCED1
tile layer 2, 30-row mode, ECM levels, real Y
@ PICO9918_REG_GPU_PC_MSB
GPU program counter, high byte.
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
@ PICO9918_REG_ENHANCED2
GPU triggers, per-position attributes, layer priority.
@ PICO9918_REG_T2_HSCROLL
tile layer 2 horizontal scroll
@ PICO9918_REG_PAGE_SIZE
scroll page sizes, and the ECM pattern plane stride
@ PICO9918_REG_UNLOCK
0x1c twice unlocks the F18A personality; any other value locks
@ PICO9918_REG_BML_WIDTH
bitmap layer width in pixels
@ PICO9918_REG_MAX_SCAN_SPRITES
sprites drawn per scanline before the limit bites
@ PICO9918_REG_GPU_PC_LSB
GPU program counter, low byte - writing it also starts the GPU.
@ PICO9918_REG_BML_TOP_ROW
bitmap layer top row
@ PICO9918_REG_MAX_SPRITES
sprites processed per frame before the scan stops
@ PICO9918_REG_PALETTE_SELECT
sub-palette for sprites and each tile layer
@ PICO9918_REG_BML_CONTROL
bitmap layer enable, priority, transparency, fat pixels
@ PICO9918_REG_T1_HSCROLL
tile layer 1 horizontal scroll
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
@ PICO9918_REG_FLASH_CONTROL
PICO9918 only: flash operation control.
@ PICO9918_REG_BML_HEIGHT
bitmap layer height in rows
pico9918_status_register_t
the status registers, by number and by what each one reports
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
@ PICO9918_SR_MICROS_LSB
microsecond counter, low byte
@ PICO9918_SR_CONFIG_VALUE
PICO9918 only: the configuration byte R58 selected.
@ PICO9918_SR_MILLIS_LSB
millisecond counter, low byte
@ PICO9918_SR_SECONDS_LSB
second counter, low byte
@ PICO9918_SR_MICROS_MSB
microsecond counter, high bits
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
@ PICO9918_SR_MILLIS_MSB
millisecond counter, high bits
@ PICO9918_SR_VERSION
the F18A feature level, as major and minor nibbles
@ PICO9918_SR_SECONDS_MSB
second counter, high byte
@ PICO9918_SR_GPU
GPU running and its status byte.
#define PICO9918_R49_ECM_SPRITE
sprite ECM level field
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
#define PICO9918_CHIP_MAX
the highest personality this build can be, and what a new instance is
#define TMS9918_PIXELS_X
active display width, every mode
#define TMS_R0_DOUBLE_ROWS
PICO9918 only: twice the rows, drawn interlaced.
#define PICO9918_R31_BML_ENABLE
register 31 bits: the bitmap layer
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
#define PICO9918_R50_POS_ATTR
tile attributes come per position, not per tile
#define PICO9918_R49_ROW30
30 rows of tiles rather than 24
pico9918_chip_t
which chip an instance answers as
@ PICO9918_CHIP_PICO9918
an F18A plus the PICO9918's own extensions
@ PICO9918_CHIP_PICO9918_PRO
a PICO9918 PRO: 8bpp 80-column text, its own splash
@ PICO9918_CHIP_TMS9918A
a TMS9918A: locked, no GPU, no extensions
@ PICO9918_CHIP_F18A
an F18A: unlock, enhanced renderer, GPU
#define PICO9918_INST
pass the instance ahead of other arguments
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
pico9918_mode_t
the display modes the VDP can be in, TMS9918A modes and F18A alike
#define PICO9918_INTERNAL
cross-TU linkage for what is not public API, so a DLL or wasm build exports none of it
#define PICO9918_BASE_TMS9918
vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE
#define PICO9918_CONFIG_FIRST_SETTABLE
the first config byte a guest may write through VR58/59
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)
whether /INT should be asserted
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
void pico9918_splash_reset(void)
restart the splash animation (after... reset)
pico9918-core - Splash overlay