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
95 pico9918_t* tms9918 = (pico9918_t*)calloc(1,
sizeof(pico9918_t));
99#if PICO9918_BUILD_RUNTIME_CHIP
112static const pico9918_mode_t r1Modes[] = {TMS_MODE_GRAPHICS_I, TMS_MODE_MULTICOLOR, TMS_MODE_TEXT,
113 TMS_MODE_GRAPHICS_I};
120 return TMS_MODE_GRAPHICS_II;
123 else if (PICO9918_M4(tms9918))
125 return TMS_MODE_TEXT80;
148 return (TMS_REGISTER(tms9918, TMS_REG_NAME_TABLE) & 0x0f) << 10;
160 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
162 return (TMS_REGISTER(tms9918, TMS_REG_COLOR_TABLE) & mask) << 6;
168 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x80 : 0xff;
176 const uint8_t mask = (pico9918_cached_mode == TMS_MODE_GRAPHICS_II) ? 0x04 : 0x07;
178 return (TMS_REGISTER(tms9918, TMS_REG_PATTERN_TABLE) & mask) << 11;
184 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_ATTR_TABLE) & 0x7f) << 7;
190 return (TMS_REGISTER(tms9918, TMS_REG_SPRITE_PATT_TABLE) & 0x07) << 11;
196 return TMS_REGISTER(tms9918, TMS_REG_FG_BG_COLOR) & 0x0f;
222static const uint16_t defaultPalette[] = {
224 0x0000, 0xF000, 0xF2C3, 0xF5D6, 0xF54F, 0xF76F, 0xFD54, 0xF4EF, 0xFF54, 0xFF76, 0xFDC3, 0xFED6, 0xF2B2,
225 0xFC5C, 0xFCCC, 0xFFFF,
227 0x0000, 0xF2C3, 0xF000, 0xF54F, 0xF000, 0xFD54, 0xF000, 0xF4EF, 0xF000, 0xFCCC, 0xF000, 0xFDC3, 0xF000,
228 0xFC5C, 0xF000, 0xFFFF,
230 0x0000, 0xF00A, 0xF0A0, 0xF0AA, 0xFA00, 0xFA0A, 0xFA50, 0xFAAA, 0xF555, 0xF55F, 0xF5F5, 0xF5FF, 0xFF55,
231 0xFF5F, 0xFFF5, 0xFFFF,
233 0x0000, 0xF555, 0xF000, 0xF00A, 0xF000, 0xF0A0, 0xF000, 0xF0AA, 0xF000, 0xFA00, 0xF000, 0xFA0A, 0xF000,
234 0xFA50, 0xF000, 0xFFFF};
236static PICO9918_NOINLINE
void vdpRegisterReset(pico9918_t* tms9918)
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;
256#if PICO9918_BUILD_RUNTIME_CHIP
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;
271 default:
return PICO9918_FEAT_VRAM_4K;
284 tms9918->chip = (uint8_t)chip;
287#if !PICO9918_NO_SPLASH
292 tms9918->palDirty = 1;
295 tms9918->configDirty =
true;
296 tms9918->configVdpDirty =
true;
298 if (!PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK))
300 tms9918->isUnlocked =
false;
301 tms9918->unlockCount = 0;
302 tms9918->lockedMask = 0x07;
320 tms9918->regWriteStage0Value = 0;
321 tms9918->currentAddress = 0;
322 tms9918->gpuAddress = 0xFFFF;
323 tms9918->regWriteStage = 0;
325 tms9918->palWriteStage = 0;
326 tms9918->palWriteStage0Value = 0;
332 tms9918->readAheadBuffer = 0;
334 vdpRegisterReset(tms9918);
335 TMS_REGISTER(tms9918, TMS_REG_1) = 0x00;
336 TMS_REGISTER(tms9918, TMS_REG_7) = 0x00;
337 pico9918_cached_mode = TMS_MODE_GRAPHICS_I;
340 for (
int i = 0; i <
sizeof(defaultPalette) /
sizeof(uint16_t); ++i)
342 tms9918->vram.map.pram[i] = __builtin_bswap16(defaultPalette[i]);
346 tms9918->palDirty = 1;
362#if !PICO9918_SINGLE_INSTANCE
432static const uint32_t zeroWord = 0;
438typedef uint32_t BitMask[9];
439typedef uint32_t TileMask[SCANLINE_MASK_WORDS];
443#define MASK_NEXT_PIXEL 0x80000000u
447static PICO9918_SECTION_SCRATCH_X(lookup)
struct
450 BitMask rowTransparentSpriteBits;
451 BitMask rowSpriteBits;
452} __aligned(4) rowMasks;
456 const uint32_t spriteWidth)
458 uint32_t rowSpriteBitsWord = xPos >> 5;
459 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
461 uint32_t validPixels =
462 (~rowMasks.rowSpriteBits[rowSpriteBitsWord]) & (spritePixels >> rowSpriteBitsWordBit);
463 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= validPixels;
464 validPixels <<= rowSpriteBitsWordBit;
466 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
467 if (rowSpriteBitsWordBit < spriteWidth)
469 uint32_t right = (~rowMasks.rowSpriteBits[++rowSpriteBitsWord]) & (spritePixels << rowSpriteBitsWordBit);
470 rowMasks.rowSpriteBits[rowSpriteBitsWord] |= right;
471 validPixels |= (right >> rowSpriteBitsWordBit);
480 const uint32_t spriteWidth)
482 uint32_t rowSpriteBitsWord = xPos >> 5;
483 uint32_t rowSpriteBitsWordBit = xPos & 0x1f;
485 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord] |= spritePixels >> rowSpriteBitsWordBit;
487 rowSpriteBitsWordBit = 32 - rowSpriteBitsWordBit;
488 if (rowSpriteBitsWordBit < spriteWidth)
490 rowMasks.rowTransparentSpriteBits[rowSpriteBitsWord + 1] |= spritePixels << rowSpriteBitsWordBit;
497 const uint32_t tileWidth, BitMask rowBitsMask)
499 uint32_t rowBitsWord = xPos >> 5;
500 uint32_t rowBitsWordBit = xPos & 0x1f;
502 uint32_t validPixels = tilePixels >> rowBitsWordBit;
503 rowBitsMask[rowBitsWord] &= ~validPixels;
505 rowBitsWordBit = 32 - rowBitsWordBit;
506 if (rowBitsWordBit < tileWidth)
509 uint32_t right = (tilePixels << rowBitsWordBit);
510 rowBitsMask[rowBitsWord] &= ~right;
518 rowBitsMask[xPos >> 5] |= tilePixels >> (xPos & 0x1f);
523 const BitMask rowBitsMask)
525 return tilePixels & ~(rowBitsMask[xPos >> 5] << (xPos & 0x1f));
530static PICO9918_NOINLINE
void tmsCopyAlignMask(TileMask dstMask,
const TileMask srcMask,
int pixelShift)
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];
561 for (
int i = 0; i < SCANLINE_MASK_WORDS; i++)
563 uint32_t word = srcMask[i];
564 dstMask[i] = (word >> pixelShift) | carry;
565 carry = word << (32 - pixelShift);
571 pixelShift = -pixelShift;
573 for (
int i = SCANLINE_MASK_WORDS - 1; i >= 0; i--)
575 uint32_t word = srcMask[i];
576 dstMask[i] = (word << pixelShift) | carry;
577 carry = word >> (32 - pixelShift);
589static PICO9918_NOINLINE
void tmsRestoreAlignMask(TileMask dstMask,
const TileMask srcMask,
590 int pixelShift,
int otherShift)
592 if (pixelShift | otherShift) tmsCopyAlignMask(dstMask, srcMask, pixelShift);
598 const uint32_t tileWidth,
const bool update,
const bool test,
601 uint32_t rowBitsWord = xPos >> 5;
602 uint32_t rowBitsWordBit = xPos & 0x1f;
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;
610 rowBitsWordBit = 32 - rowBitsWordBit;
611 if (rowBitsWordBit < tileWidth)
614 uint32_t right = (tilePixels << rowBitsWordBit);
616 if (testColl) right &= ~rowMasks.rowSpriteBits[rowBitsWord];
617 if (test) right &= ~rowMasks.rowBits[rowBitsWord];
619 if (update) rowMasks.rowBits[rowBitsWord] |= right;
620 if (test || testColl) validPixels |= (right >> rowBitsWordBit);
623 return (test || testColl) ? validPixels : tilePixels;
638static uint32_t __aligned(8) PICO9918_UNINITIALIZED(ecmLookup)[16 * 16 * 16];
640static uint8_t PICO9918_IN_FLASH_FUNC(ecmByte)(
bool h,
bool m,
bool l)
642 return (h << 2) | (m << 1) | l;
649static void PICO9918_IN_FLASH_FUNC(ecmLookupInit)(void)
651 for (uint16_t i = 0; i < 16 * 16 * 16; ++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);
674static uint8_t __aligned(4) doubledBitsNibble[16] = {0x00, 0x03, 0x0c, 0x0f, 0x30, 0x33, 0x3c, 0x3f,
675 0xc0, 0xc3, 0xcc, 0xcf, 0xf0, 0xf3, 0xfc, 0xff};
678static PICO9918_SECTION_SCRATCH_X(lookup) uint16_t __aligned(4) doubledBits[256];
679static
void PICO9918_IN_FLASH_FUNC(doubledBitsInit)(
void)
681 for (
int i = 0; i < 256; ++i)
683 doubledBits[i] = (doubledBitsNibble[(i & 0xf0) >> 4] << 8) | doubledBitsNibble[i & 0x0f];
688static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits[256];
690static uint8_t PICO9918_IN_FLASH_FUNC(reverseBits)(uint8_t
byte)
692 byte = (
byte & 0xf0) >> 4 | (
byte & 0x0f) << 4;
693 byte = (
byte & 0xcc) >> 2 | (
byte & 0x33) << 2;
694 return (
byte & 0xaa) >> 1 | (
byte & 0x55) << 1;
701static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t __aligned(4) reversedBits6[256];
703static
void PICO9918_IN_FLASH_FUNC(reversedBitsInit)(
void)
705 for (
int i = 0; i < 256; ++i)
707 reversedBits[i] = reverseBits(i);
708 reversedBits6[i] = reverseBits(i) << 2;
721static inline uint32_t repeatedPalette(
const uint32_t index)
724 uint32_t repeated = index;
725 __asm__(
"mul %0, %1" :
"+l"(repeated) :
"l"(0x01010101u));
728 return index * 0x01010101u;
735static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t __aligned(4) ecm0Palette[64];
737static
void PICO9918_IN_FLASH_FUNC(ecm0PaletteInit)(
void)
739 for (
int i = 0; i < 64; ++i)
741 ecm0Palette[i] = repeatedPalette(i);
749static uint32_t transparentPixels[2];
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};
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};
766bool lookupsReady =
false;
767void PICO9918_IN_FLASH_FUNC(initLookups)(void)
769 if (lookupsReady)
return;
771 PICO9918_DMA_CLAIM();
779 PICO9918_FILL32_INIT(PICO9918_FILL_LINE, &bg);
782 PICO9918_FILL32_INIT(PICO9918_FILL_MASKS, &zeroWord);
783 PICO9918_FILL32_SET_COUNT(PICO9918_FILL_MASKS,
sizeof(rowMasks) /
sizeof(uint32_t));
785 PICO9918_FILL32_INIT(PICO9918_FILL_BORDER, &pico9918_border_bg);
787 PICO9918_COPY_INIT(PICO9918_COPY);
796static inline uint32_t ecmSplitQuads(
const uint32_t patt)
798 return (patt | (patt << 12)) & 0x000f000fu;
809static inline uint32_t calculateEcmIndex(
const uint32_t ecm,
const uint32_t sb0,
const uint32_t sb1,
812 uint32_t ecmIndex = 0;
816 ecmIndex = sb2 >> 28;
819 ecmIndex = (ecmIndex << 4) | (sb1 >> 28);
821 default: ecmIndex = (ecmIndex << 4) | (sb0 >> 28);
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)
833 uint32_t patt = vram[pattOffset];
834 if (flipX) patt = reversedBits[patt];
835 uint32_t bits = patt << ((flipX && sprite16) ? 16 : 24);
839 patt = vram[pattOffset + PATTERN_BYTES * 2];
840 if (flipX) patt = reversedBits[patt];
841 bits |= patt << (flipX ? 24 : 16);
843 spriteBits[i] = bits;
845 pattOffset += ecmOffset;
855static PICO9918_SECTION_SCRATCH_X(lookup) uint32_t spriteAttrRows[MAX_SPRITES];
856static PICO9918_SECTION_SCRATCH_X(lookup) uint8_t spriteIndices[MAX_SPRITES];
866static uint32_t __time_critical_func(collectSpriteRows)(
PICO9918_INST_ARG uint16_t y)
868 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
869 const uint32_t row30Mode =
874 const int32_t maxY = (row30Mode ? 0xf0 : 0xe0) - realY;
875 const int32_t yAdj = (int32_t)y - realY;
878 if (maxSprites > MAX_SPRITES) maxSprites = MAX_SPRITES;
883 for (uint32_t spriteIdx = 0; spriteIdx < maxSprites; ++spriteIdx, spriteAttr += SPRITE_ATTR_BYTES)
885 int32_t yPos = spriteAttr[SPRITE_ATTR_Y];
888 if (yPos == LAST_SPRITE_YPOS && !row30Mode)
894 if (yPos > maxY) yPos -= 256;
896 const int32_t pattRow = yAdj - yPos;
897 if ((uint32_t)pattRow > 31)
902 const uint32_t attr = *(
const uint32_t*)spriteAttr;
904 if (attr == 0 && unlockedMask)
909 spriteAttrRows[count] = attr;
910 ((uint8_t*)&spriteAttrRows[count])[SPRITE_ATTR_Y] = (uint8_t)pattRow;
911 spriteIndices[count] = (uint8_t)spriteIdx;
922 -(uint32_t)PICO9918_UNLOCKED(tms9918);
929 const bool spriteMag,
const bool wide,
933 const uint32_t unlockedMask = -(uint32_t)PICO9918_UNLOCKED(tms9918);
934 const uint8_t*
const vram = tms9918->vram.bytes;
935 bool hasSprites =
false;
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;
942 uint32_t spritesShown = 0;
945 uint8_t tempStatus = 0;
946 uint32_t transparentCount = 0;
950 const uint32_t ecmColorOffset = (ecm == 3) ? 2 : ecm;
951 const uint32_t ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
952 const uint32_t ecmOffset =
966 const uint32_t unlimited =
969 for (uint32_t n = 0; n < spriteCount; ++n)
971 const uint8_t* spriteAttr = (
const uint8_t*)&spriteAttrRows[n];
973 int32_t pattRow = spriteAttr[SPRITE_ATTR_Y] >> spriteMag;
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;
982 if (spriteAttrColor & 0x10)
993 thisSpriteIdxMask = 0xfc;
994 thisSpriteSizePx = thisSpriteSize << spriteMag;
999 if (pattRow >= thisSpriteSize)
1005 if (++spritesShown > MAX_SCANLINE_SPRITES)
1007 if (((tempStatus &
PICO9918_SR0_5S) == 0) && (!unlimited || spritesShown > scanlineSprites))
1012 if (spritesShown > scanlineSprites)
break;
1015 const int32_t earlyClockOffset = (spriteAttrColor & 0x80) ? -32 : 0;
1016 int32_t xPos = (int32_t)(spriteAttr[SPRITE_ATTR_X]) + earlyClockOffset;
1022 if (spriteAttrColor & 0x20) pattRow = thisSpriteSize - pattRow - 1;
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;
1030 uint32_t pattMask = 0;
1031 uint32_t spriteBits[3] = {0};
1032 const bool flipX = spriteAttrColor & 0x40;
1034 loadSpriteData(vram, spriteBits, pattOffset, &pattMask, ecm, ecmOffset, flipX, thisSprite16);
1036 if (opaq) pattMask = 0xffff0000;
1046 pattMask = ((uint32_t)doubledBits[pattMask >> 24] << 16) | doubledBits[(pattMask >> 16) & 0xff];
1052 int32_t absX = -xPos;
1053 uint32_t offset = absX >> spriteMag;
1054 spriteBits[2] <<= offset;
1055 spriteBits[1] <<= offset;
1056 spriteBits[0] <<= offset;
1065 thisSpriteSizePx += xPos;
1070 if (pixelsLeft < thisSpriteSizePx)
1072 thisSpriteSizePx = pixelsLeft;
1073 pattMask &= ~((1u << (32 - pixelsLeft)) - 1);
1080 if (validPixels != pattMask)
1086 if (ecm || (spriteColor != TMS_TRANSPARENT))
1093 uint32_t quadPal = repeatedPalette(spriteColor);
1097 uint8_t* p = pixels + (wide ? xPos * 2 : xPos);
1098 const uint32_t step = wide ? 2 : 1;
1099 uint32_t bits = validPixels;
1105 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1106 uint32_t quad = ecmLookup[ecmIndex] | quadPal;
1108 for (
int n = 0; n < 4; ++n)
1110 const uint8_t v = (uint8_t)quad;
1111 if (bits & MASK_NEXT_PIXEL)
1114 *(uint16_t*)p = v | (v << 8);
1119 if (bits & MASK_NEXT_PIXEL)
1122 *(uint16_t*)(p + 2) = v | (v << 8);
1136 spriteBits[2] <<= 4;
1137 spriteBits[1] <<= 4;
1138 spriteBits[0] <<= 4;
1147 uint32_t chunkMask = validPixels >> 28;
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;
1154 for (
int n = 0; n < 4; ++n)
1156 if (chunkMask & 0x8)
1158 const uint8_t v = (uint8_t)color;
1159 *(uint16_t*)q = v | (v << 8);
1166 spriteBits[2] <<= 4;
1167 spriteBits[1] <<= 4;
1168 spriteBits[0] <<= 4;
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;
1184 uint32_t* quadPixels = (uint32_t*)pixels;
1188 uint32_t chunkMask = validPixels >> 28;
1189 if (chunkMask == 0x0f)
1191 const uint32_t ecmIndex = calculateEcmIndex(ecm, spriteBits[0], spriteBits[1], spriteBits[2]);
1192 quadPixels[quadOffset] = ecmLookup[ecmIndex] | quadPal;
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);
1201 spriteBits[2] <<= 4;
1202 spriteBits[1] <<= 4;
1203 spriteBits[0] <<= 4;
1211 if (!wide && pico9918_cached_mode == TMS_MODE_TEXT80) spriteColor |= spriteColor << 4;
1215 if ((int32_t)validPixels < 0)
1218 *(uint16_t*)(pixels + xPos * 2) = spriteColor | (spriteColor << 8);
1220 pixels[xPos] = spriteColor;
1235 tms9918->scanlineHasSprites = hasSprites;
1238 if (transparentCount)
1240 for (
int i = 0; i < 9; ++i)
1242 rowMasks.rowSpriteBits[i] ^= rowMasks.rowTransparentSpriteBits[i];
1250static EMITTER_NOINLINE uint8_t
1255 if (TMS_REGISTER(tms9918, TMS_REG_0) &
1259 const uint32_t spriteCount = collectSpriteRows(
PICO9918_INST y);
1263 if (spriteCount == 0)
return 0;
1265#if PICO9918_TEXT80_8BPP
1267 if (TEXT80_WIDE_ROW)
1293 const uint8_t* pattern;
1307 const bool gm2,
const bool mcm,
TileRowAddr* addr, uint16_t* colorTableAddr)
1309 uint16_t pageOffset = 0;
1310 const uint8_t pattRow = mcm ? (((rawY >> 2) & 0x01) + ((rawY >> 3) & 0x03) * 2) : (y & 0x07);
1312 addr->nameMask = 0xff;
1315 addr->flipY = mcm ? 0 : (7 - 2 * pattRow);
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;
1324 addr->pattern = tms9918->vram.bytes +
tmsPatternTableAddr(tms9918) + pageOffset + pattRow;
1332static inline uint32_t textScrollCell(
const uint32_t hscrollPixels)
1334 return (hscrollPixels * 342) >> 11;
1343#define TEXT_SCROLL_CELL(s) ((s) & 0xffffu)
1344#define TEXT_SCROLL_OFFSET(s) ((s) >> 16)
1346static inline uint32_t textScrollSplit(
const uint32_t hscroll,
const bool wide)
1349 const uint32_t h = hscroll << wide;
1350 const uint32_t cell = textScrollCell(h);
1351 const uint32_t offset = h - cell * 6;
1356 if (wide && offset == 2)
return (cell ? cell - 1 : TEXT80_NUM_COLS - 1) | (8u << 16);
1357 return cell | (offset << 16);
1360static inline uint32_t textPixelOffset(
const uint32_t hscroll,
const bool wide)
1362 return TEXT_SCROLL_OFFSET(textScrollSplit(hscroll, wide));
1365static inline int scrollOffset(
const uint32_t hscroll,
const bool text,
const bool wide)
1367 return text ? (int)textPixelOffset(hscroll, wide) : (int)(hscroll & 0x07);
1372 uint8_t vertScrollReg;
1373 uint8_t yPageSwapMask;
1374 uint8_t paletteShift;
1375 uint8_t paletteMask;
1376 uint8_t startPattReg;
1378 uint8_t priorityReg;
1379 uint8_t priorityMask;
1380 uint8_t colorTableReg;
1382 uint16_t (*nameTableAddrFunc)(pico9918_t*);
1383 uint16_t (*colorTableAddrFunc)(pico9918_t*);
1387 .yPageSwapMask = 0x01,
1389 .paletteMask = 0x03,
1390 .startPattReg = 0x1b,
1394 .colorTableReg = TMS_REG_COLOR_TABLE,
1400 .yPageSwapMask = 0x10,
1402 .paletteMask = 0x0c,
1403 .startPattReg = 0x19,
1405 .priorityReg = 0x32,
1406 .priorityMask = 0x01,
1407 .colorTableReg = 11,
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;
1439 s.startCol = (cell >= bytes) ? (cell - bytes) : (cell + TEXT80_NUM_COLS - bytes);
1440 s.cells = bytes ? TEXT80_NUM_COLS + 4 : TEXT80_NUM_COLS;
1442 s.scrolled = hscroll != 0;
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)
1461 bool swapYPage =
false;
1463 if (TMS_REGISTER(tms9918, config->vertScrollReg))
1465 int virtY = y + TMS_REGISTER(tms9918, config->vertScrollReg);
1481 const uint16_t rowOffset = (y >> 3) * numCols;
1483 *namesAddr = (config->nameTableAddrFunc(tms9918) & (nameAddrMask << 10)) + rowOffset;
1484 if (swapYPage) *namesAddr ^= 0x800;
1486 *colorAddr = config->colorTableAddrFunc(tms9918);
1489 if (!textMode) *colorAddr += *namesAddr & 0xc00;
1490 *colorAddr = (*colorAddr + rowOffset) & VRAM_MASK;
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)))
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)};
1510#define TEXT80_MASK_WORD4(bits) \
1511 TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits), TEXT80_MASK_WORD(bits)
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)};
1538PICO9918_INLINE_HOT
void
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)
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);
1551 uint32_t* pix32 = (uint32_t*)PICO9918_ASSUME_ALIGNED(dest, 4);
1552 uint32_t xPos = ecm ? (padding - pixelOffset) : padding;
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;
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;
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;
1570 ecmColorOffset = (ecm == 3) ? 2 : ecm;
1571 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
1573 pal = (ecm == 1) ? (pal & 0x20) : 0;
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;
1581#define TEXT40_NEXT_CELL() \
1582 const uint32_t name = *names++; \
1583 const uint8_t color = colors[name & nameAttrMask]; \
1584 colors += colorStride;
1586#define TEXT40_CELL(wrapping) \
1588 TEXT40_NEXT_CELL() \
1592 if (++col == numCols) \
1596 colors -= colorWrap; \
1599 const uint32_t patt = patternTable[name * PATTERN_BYTES]; \
1600 if (color != lastColor) \
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; \
1610 coverBg = (uint32_t)(-(int32_t)bgColor >> 31); \
1611 coverDiff = coverBg ^ (uint32_t)(-(int32_t)fgColor >> 31); \
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); \
1624 m0 = coverBg ^ (coverDiff & maskLo); \
1625 m1 = coverBg ^ (coverDiff & maskHi); \
1630 const uint32_t cover = ((coverBg ^ (coverDiff & (patt >> 2))) & 0x3f) << 26; \
1632 coverAcc |= cover >> coverBit; \
1634 if (coverBit >= 32) \
1636 *coverWord++ |= coverAcc; \
1638 coverAcc = cover << (6 - coverBit); \
1644#define TEXT40_ECM_CELL() \
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}; \
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]; \
1661 patt[0] = reversedBits6[patt[0]]; \
1662 patt[1] = reversedBits6[patt[1]]; \
1663 patt[2] = reversedBits6[patt[2]]; \
1664 pattMask = reversedBits6[pattMask]; \
1666 cover = (pattMask & 0xfc) << 24; \
1667 if ((color | spritePriForced) & spritePriMask) \
1668 tmsClearRowBitsMask(xPos, cover, 6, rowMasks.rowSpriteBits); \
1669 uint32_t index = 0; \
1672 case 3: index = ecmSplitQuads(patt[0]) << 8; \
1673 case 2: index |= ecmSplitQuads(patt[1]) << 4; \
1674 default: index |= ecmSplitQuads(patt[2]); \
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)) \
1681 lo = clear ^ ((lo ^ clear) & maskExpandNibbleToWordRev[pattMask >> 4]); \
1682 hi = clear ^ ((hi ^ clear) & maskExpandNibbleToWordRev[pattMask & 0x0f]); \
1692 coverAcc |= cover >> coverBit; \
1694 if (coverBit >= 32) \
1696 *coverWord++ |= coverAcc; \
1698 coverAcc = cover << (6 - coverBit); \
1707 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1708 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1711 uint32_t remaining = numCells;
1712 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1716 if (run > remaining) run = remaining;
1722 *(uint16_t*)(p) = lo;
1723 *(uint16_t*)(p + 2) = lo >> 16;
1724 *(uint16_t*)(p + 4) = hi;
1733 if (isTile2) *coverWord |= coverAcc;
1738 uint32_t* coverWord = tms9918->layerSelectionMask;
1739 uint32_t coverAcc = 0, coverBit = 0;
1741 uint16_t* p = (uint16_t*)dest;
1742 uint32_t remaining = numCells;
1743 uint32_t run = (startCell < numCols) ? (numCols - startCell) : numCells;
1747 if (run > remaining) run = remaining;
1759 p[0] = d ^ ((d ^ lo) & m0);
1761 p[1] = d ^ ((d ^ (lo >> 16)) & (m0 >> 16));
1763 p[2] = d ^ ((d ^ hi) & m1);
1776 uint32_t* coverWord = tms9918->layerSelectionMask + (padding >> 5);
1777 uint32_t coverAcc = 0, coverBit = padding & 0x1f;
1779 for (uint32_t tileX = 0; tileX < numCells; tileX += 2)
1781 uint16_t* pix16 = (uint16_t*)pix32;
1788 pix32[2] = (lo >> 16) | (hi << 16);
1792 if (isTile2) *coverWord |= coverAcc;
1795#undef TEXT40_ECM_CELL
1797#undef TEXT40_NEXT_CELL
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) \
1810 renderTextRow(PICO9918_INST rowNames, addr, rowColors, colorStride, pal, dest, scroll, alwaysOnTop, \
1811 cols, t2, e, false); \
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)
1823static
void (*const textRowClones[2][4])(TEXT_ROW_PARAMS) = {
1824 {text40RowT1, text40RowT1Ecm1, text40RowT1Ecm2, text40RowT1Ecm3},
1825 {text40RowT2, text40RowT2Ecm1, text40RowT2Ecm2, text40RowT2Ecm3}};
1827#if PICO9918_TEXT80_8BPP
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)
1843static EMITTER_NOINLINE
void __time_critical_func(text80RowT2Blend)(TEXT_ROW_PARAMS)
1846 TEXT80_NUM_COLS,
true, 0,
true);
1849static void (*
const text80RowClones[2][4])(TEXT_ROW_PARAMS) = {
1850 {text80RowT1, text80RowT1Ecm1, text80RowT1Ecm2, text80RowT1Ecm3},
1851 {text80RowT2, text80RowT2Ecm1, text80RowT2Ecm2, text80RowT2Ecm3}};
1869PICO9918_INLINE_HOT
void
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)
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;
1883#define TEXT80_NEXT_CELL() \
1886 if (scrolled && ++col == TEXT80_NUM_COLS) \
1889 rowNamesTable = rowNames; \
1890 colors = rowColors; \
1892 else if (scrolled) \
1898 uint8_t lastColor = 0;
1899 uint32_t bgColorMask = text80ColorWord[bgc];
1900 uint32_t diffColorMask = 0;
1902 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
1904 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
1907#define TEXT80_OPAQUE_CELL() \
1914 color = (uint8_t)colorWord; \
1917 if (color != lastColor) \
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; \
1925 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
1926 TEXT80_NEXT_CELL(); \
1927 word = bgColorMask ^ (diffColorMask & mask); \
1930 TEXT80_OPAQUE_CELL();
1932 TEXT80_OPAQUE_CELL();
1933 *pix32++ = acc | (word << 24);
1935 TEXT80_OPAQUE_CELL();
1936 *pix32++ = acc | (word << 16);
1938 TEXT80_OPAQUE_CELL();
1939 *pix32++ = acc | (word << 8);
1941#undef TEXT80_OPAQUE_CELL
1946 for (uint8_t tileX = 0; tileX < numCells; tileX += 4)
1948 uint32_t colorWord = scrolled ? 0 : *colorTable32++;
1949 uint32_t val, sel, accVal, accSel;
1951 if (!scrolled && !colorWord)
1958#define TEXT80_OVERLAY_CELL() \
1960 const uint32_t mask = text80MaskWord[patternTable[*rowNamesTable * PATTERN_BYTES]]; \
1961 uint8_t colorByte; \
1963 colorByte = *colors; \
1966 colorByte = (uint8_t)colorWord; \
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); \
1978#define TEXT80_OVERLAY_STORE(shift) \
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); \
1986 TEXT80_OVERLAY_CELL();
1989 TEXT80_OVERLAY_CELL();
1990 TEXT80_OVERLAY_STORE(24);
1993 TEXT80_OVERLAY_CELL();
1994 TEXT80_OVERLAY_STORE(16);
1997 TEXT80_OVERLAY_CELL();
1998 TEXT80_OVERLAY_STORE(8);
2000#undef TEXT80_OVERLAY_CELL
2001#undef TEXT80_OVERLAY_STORE
2004#undef TEXT80_NEXT_CELL
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) \
2013 renderText80Row(PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, cells, \
2017TEXT80_ROW_CLONE(text80Row, TEXT80_NUM_COLS,
false)
2018TEXT80_ROW_CLONE(text80RowScrolled, numCells, true)
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)
2026 text80RowScrolled(
PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, startCol, numCells);
2028 text80Row(
PICO9918_INST rowNames, patternTable, rowColors, opaq, pixels, 0, TEXT80_NUM_COLS);
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)
2043 const uint32_t pixelWord = bgWord ^ (diffWord & text80MaskWord[patternTable[*names++ * PATTERN_BYTES]]);
2045 *pixels++ = pixelWord;
2046 *pixels++ = pixelWord >> 8;
2047 *pixels++ = pixelWord >> 16;
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;
2071 const bool attrPerPos =
2075 uint16_t rowNamesAddr, colorTableAddr;
2077 &rowNamesAddr, &colorTableAddr);
2079 const uint8_t* patternTable = addr.pattern;
2081 uint32_t* border = (uint32_t*)pixels;
2083 pixels += TEXT_PADDING_PX;
2087 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
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);
2101 &rowNamesAddr, &colorTableAddr);
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);
2111 const uint8_t* rowNamesTable = tms9918->vram.bytes + rowNamesAddr;
2115 const uint32_t bgWord = text80ColorWord[bgColor];
2116 const uint32_t diffWord = bgWord ^ text80ColorWord[fgColor];
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;
2124 text80TwoTone(rowNamesTable + t1.startCell, patternTable, bgWord, diffWord, pixels - t1.bytes, run);
2125 if (run < cells) text80TwoTone(rowNamesTable, patternTable, bgWord, diffWord, pixels, cells - run);
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);
2133 for (uint8_t tileX = 0; tileX < TEXT_NUM_COLS; tileX += 2)
2135 uint16_t* pix16 = (uint16_t*)pix32;
2136 uint32_t patt = patternTable[*rowNamesTable++ * PATTERN_BYTES];
2138 pix32[0] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt >> 4]);
2139 pix16[2] = bgWord ^ (diffWord & maskExpandNibbleToWordRev[patt & 0x0f]);
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]);
2146 pix32[2] = (lo >> 16) | (hi << 16);
2152 border[0] = border[1] = bg;
2153 border[62] = border[63] = bg;
2158 const uint32_t right)
2160 uint32_t* buffer_words = (uint32_t*)(buffer + xPos);
2161 buffer_words[0] = left;
2162 buffer_words[1] = right;
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)
2182 const uint32_t pattByte = patternTable[pattIdx * PATTERN_BYTES];
2183 const uint32_t colorByte =
2185 : tms9918->vram.bytes[colorTableAddr + (gm2Color ? pattIdx * PATTERN_BYTES : (pattIdx >> 3))];
2186 const uint32_t patt = mcm ? 0xf0 : pattByte;
2188 const uint32_t bgColor = colorByte & 0x0f;
2189 const uint32_t fgColor = colorByte >> 4;
2191 const uint32_t bgPalette = ecm0Palette[pal | bgColor];
2192 const uint32_t fgPalette = ecm0Palette[pal | fgColor];
2194 uint32_t pattMask = 0xff;
2195 if (!bgColor) pattMask &= patt;
2196 if (!fgColor) pattMask ^= patt;
2210 const uint32_t rightMask = maskExpandNibbleToWordRev[patt & 0xf];
2211 const uint32_t leftMask = maskExpandNibbleToWordRev[patt >> 4];
2214 (fgPalette & rightMask) | (bgPalette & ~rightMask));
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)
2227 if ((*lastEmpty == pattIdx) ||
2239 uint32_t colorTableOffset = attrPerPos ? tileIndex : pattIdx;
2240 uint32_t pattOffset = pattIdx * PATTERN_BYTES;
2242 const uint32_t colorByte = tms9918->vram.bytes[colorTableAddr + colorTableOffset];
2244 const uint8_t* pattData = patternTable + pattOffset;
2247 if (colorByte & 0x20) pattData += flipY;
2249 uint32_t pattMask = (colorByte & 0x10) ? 0 : 0xff;
2252 uint8_t patt[3] = {0};
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];
2264 if (colorByte & 0x40)
2266 patt[0] = reversedBits[patt[0]];
2267 patt[1] = reversedBits[patt[1]];
2268 patt[2] = reversedBits[patt[2]];
2269 pattMask = reversedBits[pattMask];
2272 const uint32_t priority = alwaysOnTop || (colorByte & 0x80);
2276 if (priority && tms9918->scanlineHasSprites)
2278 const uint32_t offScreen = xPos ? 0 : pixelOffset;
2279 tmsClearRowBitsMask(xPos - pixelOffset + offScreen, pattMask << offScreen, 8, rowMasks.rowSpriteBits);
2284 case 3: index = ecmSplitQuads(patt[0]) << 8;
2285 case 2: index |= ecmSplitQuads(patt[1]) << 4;
2286 default: index |= ecmSplitQuads(patt[2]);
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;
2293 if (!isTile2 && (colorByte & 0x10))
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]));
2312 *lastEmpty = pattIdx;
2320static inline void lockedFgBg(
PICO9918_INST_ARG uint32_t fgbg[2],
const uint8_t pal,
const uint32_t colorByte)
2322 fgbg[0] = repeatedPalette(pal |
tmsBgColor(tms9918, colorByte));
2323 fgbg[1] = fgbg[0] ^ repeatedPalette(pal |
tmsFgColor(tms9918, colorByte));
2327PICO9918_INLINE_HOT
void
2332 const uint8_t* pattTableRow = addr->pattern;
2333 const uint8_t nameMask = addr->nameMask;
2336 uint32_t numTiles = GRAPHICS_NUM_COLS;
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;
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];
2350 uint8_t pattIdx = *pattPtr++;
2351 if (gm2) pattIdx &= nameMask;
2353 if (lastPattIdx != pattIdx)
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)
2363 lastColorByte = colorByte;
2368 pix32[0] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte >> 4]);
2369 pix32[1] = fgbg[0] ^ (fgbg[1] & maskExpandNibbleToWordRev[pattByte & 0x0f]);
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) \
2379 renderTileRowLocked(PICO9918_INST rowNamesAddr, colorTableAddr, tileIndex, pal, pixels, addr, g, m); \
2382LOCKED_ROW_CLONE(rowLockedGm1,
false,
false)
2383LOCKED_ROW_CLONE(rowLockedGm2, true, false)
2384LOCKED_ROW_CLONE(rowLockedMcm, false, true)
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, \
2406PICO9918_INLINE_HOT
void renderTileRow(TILE_ROW_PARAMS,
const bool isTile2,
2407 const uint32_t ecm,
const bool gm2,
const bool mcm)
2410 uint32_t lastEmpty = -1;
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;
2417 uint32_t numTiles = GRAPHICS_NUM_COLS + (startPattBit != 0);
2419 uint32_t ecmColorOffset = 0, ecmColorMask = 0, ecmOffset = 0;
2422 ecmColorOffset = (ecm == 3) ? 2 : ecm;
2423 ecmColorMask = (ecm == 3) ? 0x0e : 0x0f;
2425 pal = (ecm == 1) ? (pal & 0x20) : 0;
2430 uint32_t run = GRAPHICS_NUM_COLS - tileIndex;
2431 if (run > numTiles) run = numTiles;
2434 const uint8_t* names = tms9918->vram.bytes + rowNamesAddr + tileIndex;
2438 uint8_t pattIdx = *names++;
2439 if (gm2 || ecm) pattIdx &= nameMask;
2443 colorTableAddr, ecm, ecmOffset, ecmColorMask, ecmColorOffset, pal,
2444 attrPerPos, flipY, tileIndex, &lastEmpty, isTile2, alwaysOnTop);
2458 rowNamesAddr ^= 0x400;
2459 if (attrPerPos) colorTableAddr = (colorTableAddr + ((rowNamesAddr & 0x400) << 1) - 0x400) & VRAM_MASK;
2465#define TILE_ROW_CLONE(name, t2, e, g, m) \
2466 static void __time_critical_func(name)(TILE_ROW_PARAMS) \
2468 renderTileRow(TILE_ROW_ARGS, t2, e, g, m); \
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)
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}};
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;
2510 const bool attrPerPos =
2514 uint16_t rowNamesAddr, colorTableAddr;
2516 &addr, &rowNamesAddr, &colorTableAddr);
2519 << config->paletteShift;
2521 const bool alwaysOnTop =
2522 config->priorityReg ? !(TMS_REGISTER(tms9918, config->priorityReg) & config->priorityMask) :
false;
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);
2534#if PICO9918_TEXT80_8BPP
2538 dest = tms9918->tileLayer1Buffer + TEXT80_PADDING_PX +
2540 TEXT_SCROLL_OFFSET(scroll);
2541 text80RowT2Blend(
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors, attrPerPos, pal,
2542 dest, scroll, alwaysOnTop);
2547#if PICO9918_TEXT80_8BPP
2550 text80RowClones[config->isTile2][ecm](
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr,
2551 colors, attrPerPos, pal, dest, scroll, alwaysOnTop);
2555 textRowClones[config->isTile2][ecm](
PICO9918_INST tms9918->vram.bytes + rowNamesAddr, &addr, colors,
2556 attrPerPos, pal, dest, scroll, alwaysOnTop);
2560 const uint8_t startPattBit = TMS_REGISTER(tms9918, config->startPattReg) & 0x07;
2561 const uint8_t tileIndex = (TMS_REGISTER(tms9918, config->startPattReg) >> 3);
2564 uint32_t slot = ecm;
2565 if (!ecm) slot = gm2 ? TILE_ROW_GM2 : (mcm ? TILE_ROW_MCM : 0);
2568 tileRowClones[config->isTile2][slot](
PICO9918_INST hpSize, rowNamesAddr, colorTableAddr, tileIndex,
2569 startPattBit, attrPerPos, pal, alwaysOnTop, &addr);
2584static bool underLayer =
false;
2590const uint8_t* pico9918_cached_line_source = 0;
2597 const uint16_t addr,
const uint8_t bmlCtl,
2599 const bool intoTile1)
2602 bool writeMask = (bmlCtl & 0x40) && !intoTile1;
2603 underLayer = !(bmlCtl & 0x40);
2605 bool returnVal =
true;
2607 if (writeMask && opaque && (width == 64))
2614 uint32_t currentMask = 0;
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;
2626 uint8_t pal = (bmlCtl & 0xc) << 2;
2628 for (
int xOff = 0; xOff < width; ++xOff)
2630 uint8_t data = tms9918->vram.bytes[addr + xOff];
2631 for (
int sp = 0; sp < colorCount; ++sp)
2633 uint8_t color = (data & colorMask);
2634 if (opaque || color)
2636 uint8_t finalColour = pal | (color >> colorOffset);
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;
2645 pixels[xPos] = finalColour;
2646 pixels[xPos + 1] = finalColour;
2648 currentMask |= maskPixelMask;
2652 maskPixelMask >>= 2;
2654 if (writeMask && !maskPixelMask && currentMask)
2658 maskPixelMask = 0x3u << 30;
2662 if (writeMask && currentMask)
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;
2676 uint8_t pal = (bmlCtl & 0xf) << 2;
2678 if (opaque && !wide && ((xPos & 3) == 0))
2683 const uint32_t palQuad = repeatedPalette(pal);
2684 uint32_t*
const quadPixels = (uint32_t*)pixels;
2685 uint32_t quadOffset = xPos >> 2;
2687 for (
int xOff = 0; xOff < width; ++xOff)
2689 const uint8_t data = tms9918->vram.bytes[addr + xOff];
2691 (bmlExpand2bpp[data >> 4] | ((uint32_t)bmlExpand2bpp[data & 0x0f] << 16)) | palQuad;
2698 uint32_t left = (uint32_t)width * colorCount;
2702 maskX = (maskX + 32) & 0xff;
2710 for (
int xOff = 0; xOff < width; ++xOff)
2712 uint8_t data = tms9918->vram.bytes[addr + xOff];
2713 for (
int sp = 0; sp < colorCount; ++sp)
2715 uint8_t color = (data & colorMask);
2716 if (opaque || color)
2718 const uint8_t v = pal | (color >> colorOffset);
2720 *(uint16_t*)(pixels + xPos * 2) = (uint16_t)(v | (v << 8));
2723 currentMask |= maskPixelMask;
2727 maskPixelMask >>= 1;
2730 if (writeMask && !maskPixelMask && currentMask)
2734 maskPixelMask = 0x1u << 31;
2738 if (writeMask && currentMask)
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,
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,
2769 const bool intoTile1)
2773 if (!(bmlCtl & 0x80))
return true;
2777 if (top > y)
return true;
2784 const uint8_t width = bmlWidth ? ((bmlWidth + 3) >> 2) : 64;
2787 const bool opaque = !(bmlCtl & 0x20);
2789#if PICO9918_TEXT80_8BPP
2790 if (TEXT80_WIDE_ROW)
2791 return renderBitmapLayer80(
PICO9918_INST y, opaque, width, addr, bmlCtl, pixels, intoTile1);
2793 return renderBitmapLayer40(
PICO9918_INST y, opaque, width, addr, bmlCtl, pixels);
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)
2808 for (
int i = 0; i < 8; ++i)
2810 const uint32_t layers = maskExpandNibbleToWordRev[sel >> 28];
2811 uint32_t merged = l1[i] ^ ((l1[i] ^ l2[i]) & layers);
2815 const uint32_t old = pix32[i];
2816 merged = old ^ ((old ^ merged) & maskExpandNibbleToWordRev[open >> 28]);
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)
2834 for (
int i = 0; i < 8; ++i)
2836#define UNDER_PIXEL(n) \
2837 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2839 const uint8_t pixel = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2840 if (pixel) pixels[n] = pixel; \
2843 if (hasSprites) spriteMask <<= 1;
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)
2862 for (
int i = 0; i < 8; ++i)
2864#define MIXED_PIXEL(n) \
2865 if (!hasSprites || !(spriteMask & MASK_NEXT_PIXEL)) \
2867 pixels[n] = (mask & MASK_NEXT_PIXEL) ? layer2[n] : layer1[n]; \
2870 if (hasSprites) spriteMask <<= 1;
2888static inline uint32_t spriteGridWord(
const uint32_t maskWord,
const BitMask mask,
const bool wide)
2890 if (!wide)
return mask[maskWord];
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];
2902static inline void overlaySpritesOnTile1(uint32_t* __restrict dst,
const uint32_t* __restrict src,
2905 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX :
TMS9918_PIXELS_X) / 32;
2907 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord, dst += 8, src += 8)
2909 uint32_t open = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
2911 for (
int i = 0; open; ++i, open <<= 4)
2913 const uint32_t nibble = open >> 28;
2916 const uint32_t take = maskExpandNibbleToWordRev[nibble];
2917 dst[i] = dst[i] ^ ((dst[i] ^ src[i]) & take);
2923PICO9918_INLINE_HOT
void
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;
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);
2938 for (uint32_t maskWord = 0; maskWord < maskWords; maskWord++)
2940 uint32_t mask = selectionMask[maskWord];
2943 uint32_t spriteMask = spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide) |
2944 (spriteGridWord(maskWord, rowMasks.rowBits, wide) & ~mask);
2946 if (spriteMask == 0xffffffffu)
2954 if (!underLayer && !spriteMask)
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);
2968 else if (mask == 0xffffffffu)
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);
2987 compositeChunkUnder(pixels, layer1, layer2, mask, spriteMask,
true);
2989 compositeChunkUnder(pixels, layer1, layer2, mask, 0,
false);
2995 else if (wordAligned)
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);
3002 compositeChunkWide(pix32, l1, l2, mask, ~(uint32_t)spriteMask,
true);
3004 compositeChunkWide(pix32, l1, l2, mask, 0,
false);
3013 compositeChunkBytes(pixels, layer1, layer2, mask, spriteMask,
true);
3015 compositeChunkBytes(pixels, layer1, layer2, mask, 0,
false);
3028#if PICO9918_TEXT80_8BPP
3029static EMITTER_NOINLINE
3035 const int t1Scroll,
const int t2Scroll)
3037 compositeAlignedBody(
PICO9918_INST pixels, t1Scroll, t2Scroll,
false);
3040#if PICO9918_TEXT80_8BPP
3041static EMITTER_NOINLINE
void
3045 compositeAlignedBody(
PICO9918_INST pixels, t1Scroll, t2Scroll,
true);
3050 const int t1Scroll,
const int t2Scroll,
const bool wide)
3052#if PICO9918_TEXT80_8BPP
3055 compositeAligned80(
PICO9918_INST pixels, t1Scroll, t2Scroll);
3059 compositeAligned40(
PICO9918_INST pixels, t1Scroll, t2Scroll);
3068static PICO9918_NOINLINE
void
3070 const int t2Scroll,
const bool wide)
3072 const uint8_t* layer2 = tms9918->tileLayer2Buffer + t2Scroll;
3073 const uint32_t maskWords = (wide ? SCANLINE_BYTES_MAX :
TMS9918_PIXELS_X) / 32;
3075 for (uint32_t maskWord = 0; maskWord < maskWords; ++maskWord)
3078 tms9918->layerSelectionMask[maskWord] & ~spriteGridWord(maskWord, rowMasks.rowSpriteBits, wide);
3087 for (
int i = 0; i < 8; ++i)
3089 if (mask & MASK_NEXT_PIXEL) pixels[0] = layer2[0];
3092 if (mask & MASK_NEXT_PIXEL) pixels[1] = layer2[1];
3095 if (mask & MASK_NEXT_PIXEL) pixels[2] = layer2[2];
3098 if (mask & MASK_NEXT_PIXEL) pixels[3] = layer2[3];
3114 const uint32_t width)
3116 uint8_t* left = tms9918->tileLayer1Buffer + t1Scroll;
3117 uint8_t* right = left + width - padding;
3119 for (uint32_t i = 0; i < padding; ++i)
3130 uint8_t tempStatus = 0;
3133 if (PICO9918_UNLOCKED(tms9918))
3136 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3145 (bmlCtlReg & 0x40) &&
3148 bool writeMask =
true;
3154 const uint32_t transparent = underLayer ? 0 : bg;
3155 transparentPixels[0] = transparentPixels[1] = transparent;
3156 ecm0Palette[0x00] = ecm0Palette[0x10] = ecm0Palette[0x20] = ecm0Palette[0x30] = transparent;
3158 tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3162 const bool wide = TEXT80_WIDE_ROW;
3163 const bool textRow = wide || pico9918_cached_mode == TMS_MODE_TEXT;
3169 const bool blend = wide && tile1Enabled && tile2Enabled &&
3173 if (tile2Enabled && !blend)
3176 tmsCopyAlignMask(tms9918->finalMask, tms9918->layerSelectionMask, t1Scroll - t2Scroll);
3190 if (tile2Enabled && !blend)
3191 tmsRestoreAlignMask(tms9918->layerSelectionMask, tms9918->finalMask, -t1Scroll, t2Scroll);
3193 if (textRow && !blend)
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));
3203 if (tile1Enabled && (!tile2Enabled || blend) && !underLayer && !(t1Scroll & 3) &&
3206 uint8_t* line = tms9918->tileLayer1Buffer + t1Scroll;
3208 if (tms9918->scanlineHasSprites)
3209 overlaySpritesOnTile1((uint32_t*)PICO9918_ASSUME_ALIGNED(line, 4),
3210 (
const uint32_t*)PICO9918_ASSUME_ALIGNED(pixels, 4), wide);
3212 pico9918_cached_line_source = line;
3214 else if (tile1Enabled)
3216 compositeAlignedTileBuffers(
PICO9918_INST pixels, t1Scroll, t2Scroll, wide);
3218 else if (tile2Enabled)
3227 const uint8_t tileY = y >> 3;
3230 const uint16_t rowOffset = tileY * GRAPHICS_NUM_COLS;
3234 const bool gm2 = pico9918_cached_mode == TMS_MODE_GRAPHICS_II;
3235 const bool mcm = pico9918_cached_mode == TMS_MODE_MULTICOLOR;
3240 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3243 rowLockedGm2(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3245 rowLockedMcm(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3247 rowLockedGm1(
PICO9918_INST rowNamesAddr, colorTableAddr, 0, 0, pixels, &addr);
3249 tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3258 uint8_t*
const pixels = scanlineBuffer;
3259 uint8_t tempStatus = 0;
3261 if (!lookupsReady) initLookups();
3264 if (currentCachedMode != pico9918_cached_mode)
3266 pico9918_cached_mode = currentCachedMode;
3267 tms9918->palDirty = 1;
3271 const bool packedNibbles = pico9918_cached_mode == TMS_MODE_TEXT80 && !TEXT80_WIDE_ROW;
3272 bg = repeatedPalette(
3274 (packedNibbles ? bgc << 4
3276#if PICO9918_TEXT80_8BPP
3280 PICO9918_FILL32_TRIGGER(PICO9918_FILL_LINE, pixels);
3281 pico9918_cached_line_source = pixels;
3289 PICO9918_FILL32_TRIGGER(PICO9918_FILL_MASKS, &rowMasks);
3291 for (
int i = 0; i < SCANLINE_MASK_WORDS; ++i)
3293 tms9918->layerSelectionMask[i] = 0;
3294 tms9918->finalMask[i] = 0;
3296 tms9918->scanlineHasSprites =
false;
3298 PICO9918_FILL32_WAIT(PICO9918_FILL_MASKS);
3300 switch (pico9918_cached_mode)
3302 case TMS_MODE_GRAPHICS_I:
3303 case TMS_MODE_GRAPHICS_II:
3307 case TMS_MODE_TEXT80:
3308 if (PICO9918_UNLOCKED(tms9918) && (pico9918_cached_mode == TMS_MODE_TEXT || TEXT80_WIDE_ROW))
3315 if (PICO9918_UNLOCKED(tms9918)) tempStatus = pico9918_output_sprites(
PICO9918_INST y, pixels);
3321 PICO9918_FILL32_WAIT(PICO9918_FILL_LINE);
3322 PICO9918_COPY_WAIT(PICO9918_COPY);
3331 return TMS_REGISTER(tms9918, reg & tms9918->lockedMask);
3344 if (PICO9918_HAS(tms9918, PICO9918_FEAT_UNLOCK) && PICO9918_UNLOCK_REG(reg))
3347 const bool unlockValue = PICO9918_UNLOCK_VALUE(value);
3348 const bool unlocked = unlockValue && tms9918->unlockCount;
3351 tms9918->unlockCount = unlockValue;
3353 if (unlocked != tms9918->isUnlocked)
3355 tms9918->isUnlocked = unlocked;
3356 tms9918->lockedMask = unlocked ? 0x3f : 0x07;
3357 tms9918->palDirty = 1;
3367 if (((reg & ~tms9918->lockedMask) != 0x80) && PICO9918_M4(tms9918))
return;
3369 tms9918->unlockCount = 0;
3371 const int regIndex = reg & tms9918->lockedMask;
3373 TMS_REGISTER(tms9918, regIndex) = value;
3392 tms9918->gpuStatus = 0;
3393 tms9918->restart = 1;
3399 tms9918->restart = 1;
3403 PICO9918_HAS(tms9918, PICO9918_FEAT_CONFIG))
3425 vdpRegisterReset(tms9918);
3430 tms9918->configDirty =
true;
3431 tms9918->configVdpDirty =
true;
3436 uint8_t statReg = (value & 0x0f);
3437 TMS_STATUS(tms9918, 0x0F) = statReg;
3438 if (value & 0x40) tms9918->startTime = PICO9918_HOST_TIME_US();
3440 tms9918->currentTime = PICO9918_HOST_TIME_US();
3441 else if (value & 0x10)
3442 tms9918->startTime += (tms9918->stopTime - tms9918->startTime);
3444 tms9918->currentTime = tms9918->stopTime = PICO9918_HOST_TIME_US();
3446 if (statReg > 3 && statReg < 12)
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);
3475 tms9918->config[option] = value;
3477 tms9918->configDirty =
true;
3487 return tms9918->vram.bytes[addr & VRAM_MASK];
3501 return pico9918_cached_mode;
3504#if PICO9918_BUILD_DEBUG_API
3508 pico9918_cached_mode = tmsMode(tms9918);
3531 return pico9918_cached_line_source;
3538 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)
uint8_t pico9918_read_data(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
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_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
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.
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 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.
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
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
#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
#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_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_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
void pico9918_splash_reset(void)
restart the splash animation (after... reset)
pico9918-core - Splash overlay