/* * Copyright (C) 2017 FIX94 * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ /* * URDE modifications to generate RGBA8 framebuffer rather than BGR565 */ #include #include #include #include #include "fixNES/mapper.h" #include "fixNES/ppu.h" //certain optimizations were taken from nestopias ppu code, //thanks to the people from there for all that //sprite byte 2 #define PPU_SPRITE_PRIO (1<<5) #define PPU_SPRITE_FLIP_X (1<<6) #define PPU_SPRITE_FLIP_Y (1<<7) //2000 #define PPU_INC_AMOUNT (1<<2) #define PPU_SPRITE_ADDR (1<<3) #define PPU_BACKGROUND_ADDR (1<<4) #define PPU_SPRITE_8_16 (1<<5) #define PPU_FLAG_NMI (1<<7) //2001 #define PPU_GRAY (1<<0) #define PPU_BG_8PX (1<<1) #define PPU_SPRITE_8PX (1<<2) #define PPU_BG_ENABLE (1<<3) #define PPU_SPRITE_ENABLE (1<<4) //2002 #define PPU_FLAG_OVERFLOW (1<<5) #define PPU_FLAG_SPRITEZERO (1<<6) #define PPU_FLAG_VBLANK (1<<7) #define DOTS 341 #define VISIBLE_DOTS 256 #define VISIBLE_LINES 240 #define PPU_VRAM_HORIZONTAL_MASK 0x41F #define PPU_VRAM_VERTICAL_MASK (~PPU_VRAM_HORIZONTAL_MASK) #define PPU_DEBUG_ULTRA 0 #define PPU_DEBUG_VSYNC 0 //set or used externally bool ppu4Screen = false; bool ppu816Sprite = false; bool ppuInFrame = false; bool ppuScanlineDone = false; uint8_t ppuDrawnXTile = 0; //from main.c extern uint32_t textureImage[0xF000]; extern bool nesPause; extern bool ppuDebugPauseFrame; extern bool doOverscan; static uint8_t ppuDoSprites(uint8_t color, uint16_t dot); // BMF Final 2 static const uint8_t PPU_Pal[192] = { 0x52, 0x52, 0x52, 0x00, 0x00, 0x80, 0x08, 0x00, 0x8A, 0x2C, 0x00, 0x7E, 0x4A, 0x00, 0x4E, 0x50, 0x00, 0x06, 0x44, 0x00, 0x00, 0x26, 0x08, 0x00, 0x0A, 0x20, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x32, 0x00, 0x00, 0x26, 0x0A, 0x00, 0x1C, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA4, 0xA4, 0x00, 0x38, 0xCE, 0x34, 0x16, 0xEC, 0x5E, 0x04, 0xDC, 0x8C, 0x00, 0xB0, 0x9A, 0x00, 0x4C, 0x90, 0x18, 0x00, 0x70, 0x36, 0x00, 0x4C, 0x54, 0x00, 0x0E, 0x6C, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6C, 0x2C, 0x00, 0x5E, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x4C, 0x9C, 0xFF, 0x7C, 0x78, 0xFF, 0xA6, 0x64, 0xFF, 0xDA, 0x5A, 0xFF, 0xF0, 0x54, 0xC0, 0xF0, 0x6A, 0x56, 0xD6, 0x86, 0x10, 0xBA, 0xA4, 0x00, 0x76, 0xC0, 0x00, 0x46, 0xCC, 0x1A, 0x2E, 0xC8, 0x66, 0x34, 0xC2, 0xBE, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xB6, 0xDA, 0xFF, 0xC8, 0xCA, 0xFF, 0xDA, 0xC2, 0xFF, 0xF0, 0xBE, 0xFF, 0xFC, 0xBC, 0xEE, 0xFA, 0xC2, 0xC0, 0xF2, 0xCC, 0xA2, 0xE6, 0xDA, 0x92, 0xCC, 0xE6, 0x8E, 0xB8, 0xEE, 0xA2, 0xAE, 0xEA, 0xBE, 0xAE, 0xE8, 0xE2, 0xB0, 0xB0, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } ; static const uint8_t ppuRunCyclesNTSC[5] = { 3, 3, 3, 3, 3 }; static const uint8_t ppuRunCyclesPAL[5] = { 3, 3, 3, 3, 4 }; static const uint8_t ppuOddArrNTSC[2] = { 0, 1 }; static const uint8_t ppuOddArrPAL[2] = { 0, 0 }; static struct { const uint8_t *Pal; const uint8_t *RunCycles; const uint8_t *OddArr; uint32_t BGRLUT[0x200]; uint8_t TILELUT[0x400][4]; uint16_t PALRAM2[0x20]; uint16_t NameTbl[4]; uint16_t NameTblBak[4]; uint8_t VRAM[0x1000]; uint8_t OAM[0x100]; uint8_t OAM2[0x20]; uint8_t PALRAM[0x20]; uint8_t Sprites[8][12]; uint8_t BGTiles[16]; uint8_t Reg[8]; uint8_t Count; uint8_t OddNum; uint16_t curLine; uint16_t LinesTotal; uint16_t PreRenderLine; uint16_t curDot; uint16_t VramAddr; uint16_t TmpVramAddr; uint8_t ToDraw; uint8_t VramReadBuf; uint8_t OAMpos; uint8_t OAM2pos; uint8_t OAMcpPos; uint8_t OAMzSpritePos; uint8_t FineXScroll; uint8_t SpriteOAM2Pos; uint8_t SpriteTilePos; uint8_t BGRegA; uint8_t BGRegB; uint8_t BGAttribReg; uint8_t BGIndex; uint8_t BytesCopied; uint8_t lastVal; uint8_t CurSpriteLn; uint8_t CurSpriteIndex; uint8_t CurSpriteByte2; uint8_t CurSpriteByte3; uint8_t TmpOAMVal; bool NextHasZSprite; bool FrameDone; bool TmpWrite; bool NMIallowed; bool NMITriggered; bool VBlankFlagCleared; bool VBlankClearCycle; bool CurNMIStat; bool OddFrame; bool ReadReg2; bool OAMoverflow; bool SearchY; bool LineOverflow; bool DoOverscan; bool BGEnable; bool SprEnable; } ppu; extern bool nesPAL; void ppuInit() { memset(ppu.PALRAM2,0,0x40); memset(ppu.NameTbl,0,8); memset(ppu.NameTblBak,0,8); memset(ppu.VRAM,0,0x1000); memset(ppu.OAM,0,0x100); memset(ppu.OAM2,0xFF,0x20); memset(ppu.PALRAM,0,0x20); int32_t i; for(i = 0; i < 8; i++) memset(ppu.Sprites[i],0,12); memset(ppu.BGTiles,0,0x10); memset(ppu.Reg,0,8); ppu.Pal = PPU_Pal; //ppuCycles = 0; //start out being in vblank ppu.Reg[2] |= PPU_FLAG_VBLANK; ppu.LinesTotal = nesPAL ? 312 : 262; ppu.PreRenderLine = ppu.LinesTotal - 1; ppu.curLine = ppu.LinesTotal - 11; ppu.curDot = 0; ppu.VramAddr = 0; ppu.TmpVramAddr = 0; ppu.ToDraw = 0; ppu.VramReadBuf = 0; ppu.OAMpos = 0; ppu.OAM2pos = 0; ppu.OAMcpPos = 0; ppu.OAMzSpritePos = 0; ppu.FineXScroll = 0; ppu.SpriteOAM2Pos = 0; ppu.SpriteTilePos = 0; ppu.BGRegA = 0; ppu.BGRegB = 0; ppu.BGAttribReg = 0; ppu.BGIndex = 0; ppu.BytesCopied = 0; ppu.lastVal = 0; ppu.CurSpriteLn = 0; ppu.CurSpriteIndex = 0; ppu.CurSpriteByte2 = 0; ppu.CurSpriteByte3 = 0; ppu.TmpOAMVal = 0xFF; ppu.NextHasZSprite = false; ppu.FrameDone = false; ppu.TmpWrite = false; ppu.NMIallowed = false; ppu.NMITriggered = false; ppu.VBlankFlagCleared = false; ppu.VBlankClearCycle = false; ppu.CurNMIStat = false; ppu.OddFrame = false; ppu.ReadReg2 = false; ppu.OAMoverflow = false; ppu.SearchY = false; ppu.LineOverflow = false; ppu.DoOverscan = false; ppu.BGEnable = false; ppu.SprEnable = false; //generate full BGR LUT uint8_t rtint = nesPAL ? (1<<6) : (1<<5); uint8_t gtint = nesPAL ? (1<<5) : (1<<6); uint8_t btint = (1<<7); for(i = 0; i < 0x200; i++) { uint8_t palpos = (i&0x3F)*3; uint8_t r = ppu.Pal[palpos]; uint8_t g = ppu.Pal[palpos+1]; uint8_t b = ppu.Pal[palpos+2]; if((i & 0xF) <= 0xD) { //reduce red if((i>>1) & btint) r = (uint8_t)(((float)r)*0.75f); if((i>>1) & gtint) r = (uint8_t)(((float)r)*0.75f); //reduce green if((i>>1) & rtint) g = (uint8_t)(((float)g)*0.75f); if((i>>1) & btint) g = (uint8_t)(((float)g)*0.75f); //reduce blue if((i>>1) & rtint) b = (uint8_t)(((float)b)*0.75f); if((i>>1) & gtint) b = (uint8_t)(((float)b)*0.75f); } //save new color into LUT ppu.BGRLUT[i] = (0xFFu<<24) //Alpha | (b<<16) //Blue | (g<<8) //Green | r; //Red } //tile LUT from nestopia for (i = 0; i < 0x400; ++i) { ppu.TILELUT[i][0] = (i & 0xC0) ? (i >> 6 & 0xC) | (i >> 6 & 0x3) : 0; ppu.TILELUT[i][1] = (i & 0x30) ? (i >> 6 & 0xC) | (i >> 4 & 0x3) : 0; ppu.TILELUT[i][2] = (i & 0x0C) ? (i >> 6 & 0xC) | (i >> 2 & 0x3) : 0; ppu.TILELUT[i][3] = (i & 0x03) ? (i >> 6 & 0xC) | (i >> 0 & 0x3) : 0; } ppu.Count = 0; ppu.RunCycles = nesPAL ? ppuRunCyclesPAL : ppuRunCyclesNTSC; ppu.OddNum = 0; ppu.OddArr = nesPAL ? ppuOddArrPAL : ppuOddArrNTSC; } extern uint8_t m5_exMode; extern uint8_t m5exGetAttrib(uint16_t addr); static inline uint16_t ppuGetVramTbl(uint16_t tblStart) { return ppu.NameTbl[(tblStart>>10)&3]; } extern bool nesEmuNSFPlayback; //tile load from nestopia static void loadTiles() { uint8_t* src[] = { ppu.TILELUT[ppu.BGRegA | (ppu.BGAttribReg & 3) << 8], ppu.TILELUT[ppu.BGRegB | (ppu.BGAttribReg & 3) << 8] }; uint8_t* dst = ppu.BGTiles+ppu.BGIndex; ppu.BGIndex ^= 8; dst[0] = src[0][0]; dst[1] = src[1][0]; dst[2] = src[0][1]; dst[3] = src[1][1]; dst[4] = src[0][2]; dst[5] = src[1][2]; dst[6] = src[0][3]; dst[7] = src[1][3]; } static void grabNextSpriteTilesP1() { ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++]; ppu.CurSpriteLn = ppu.TmpOAMVal; } static void grabNextSpriteTilesP2() { ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++]; ppu.CurSpriteIndex = ppu.TmpOAMVal; } static void grabNextSpriteTilesP3() { ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++]; ppu.CurSpriteByte2 = ppu.TmpOAMVal; } static void grabNextSpriteTilesP4() { ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++]; ppu.CurSpriteByte3 = ppu.TmpOAMVal; } static void grabNextSpriteTilesP5(uint16_t line) { uint8_t cSprLn = ppu.CurSpriteLn, cSprInd = ppu.CurSpriteIndex, cSprB2 = ppu.CurSpriteByte2; uint8_t cSpriteAdd = 0; //used to select which 8 by 16 tile bool spr816 = ppu.Reg[0] & PPU_SPRITE_8_16; uint8_t cSpriteY = (line - cSprLn)&(spr816 ? 15 : 7); if(cSpriteY > 7) //8 by 16 select { cSpriteAdd = 16; cSpriteY &= 7; } uint16_t chrROMSpriteAdd = 0; if(spr816) { chrROMSpriteAdd = ((cSprInd & 1) << 12); cSprInd &= ~1; } else if(ppu.Reg[0] & PPU_SPRITE_ADDR) chrROMSpriteAdd = 0x1000; if(cSprB2 & PPU_SPRITE_FLIP_Y) { cSpriteY ^= 7; if(spr816) cSpriteAdd ^= 16; //8 by 16 select } /* write processed values into internal draw buffer */ mapperChrMode = 1; uint8_t p0 = mapperChrGet8(((chrROMSpriteAdd+(cSprInd<<4)+cSpriteY+cSpriteAdd)&0xFFF) | chrROMSpriteAdd); uint8_t p1 = mapperChrGet8(((chrROMSpriteAdd+(cSprInd<<4)+cSpriteY+8+cSpriteAdd)&0xFFF) | chrROMSpriteAdd); if ((p0 | p1) && (cSprLn < VISIBLE_LINES)) //sprite contains data and is on a valid line, so process { //pixels uint8_t sprTilePos = ppu.SpriteTilePos; uint8_t a = (cSprB2 & PPU_SPRITE_FLIP_X) ? 7 : 0; uint16_t p = (p0 >> 1 & 0x0055) | (p1 << 0 & 0x00AA) | (p0 << 8 & 0x5500) | (p1 << 9 & 0xAA00); ppu.Sprites[sprTilePos][( a^=6 )] = ( p ) & 0x3; ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=6 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=7 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=6 )] = ( p >>= 2 ) & 0x3; ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ); //x position ppu.Sprites[sprTilePos][8] = ppu.CurSpriteByte3; //is zero sprite (compare to 4 because OAM2 was just read, 4 is next position) ppu.Sprites[sprTilePos][9] = (ppu.SpriteOAM2Pos == 4 && ppu.NextHasZSprite) ? 0x3 : 0x0; //priority ppu.Sprites[sprTilePos][10] = (cSprB2 & PPU_SPRITE_PRIO) ? 0x3 : 0x0; //palette ppu.Sprites[sprTilePos][11] = (cSprB2&3)<<2 | 0x10; //increase pos ppu.SpriteTilePos = sprTilePos+1; } } static void updateBGTileAddress() { if((ppu.VramAddr & 0x1F) == 0x1F) ppu.VramAddr ^= 0x41F; else ppu.VramAddr++; } static void updateBGHoriAddress() { ppu.VramAddr = (ppu.VramAddr & (~PPU_VRAM_HORIZONTAL_MASK)) | (ppu.TmpVramAddr & PPU_VRAM_HORIZONTAL_MASK); } static void updateBGVertAddress() { ppu.VramAddr = (ppu.VramAddr & (~PPU_VRAM_VERTICAL_MASK)) | (ppu.TmpVramAddr & PPU_VRAM_VERTICAL_MASK); } static void updateBGYAddress() { /* update Y position for writes */ if((ppu.VramAddr & 0x7000) != (7<<12)) ppu.VramAddr += (1<<12); else switch(ppu.VramAddr & 0x3E0) { default: ppu.VramAddr = (ppu.VramAddr & 0xFFF) + (1 << 5); break; case (29<<5): ppu.VramAddr ^= 0x800; case (31<<5): ppu.VramAddr &= 0xC1F; break; } } static void updateBGRegsA(uint16_t dot) { /* MMC5 Scroll Related */ if(dot == 320) ppuDrawnXTile = 0; else ppuDrawnXTile++; if(m5_exMode == 1) { /* MMC5 Ex Mode 1 has different Attribute for every Tile */ ppu.BGAttribReg = m5exGetAttrib(ppu.VramAddr); } else { uint16_t cPpuTbl = ppuGetVramTbl(ppu.VramAddr); /* Select new BG Background Attribute */ uint8_t cAttrib = ((ppu.VramAddr>>4)&0x38) | ((ppu.VramAddr>>2)&7); uint16_t attributeAddr = cPpuTbl | (0x3C0 | cAttrib); ppu.BGAttribReg = mapperVramGet8(attributeAddr) >> ((ppu.VramAddr & 0x2) | (ppu.VramAddr >> 4 & 0x4)); } } static void updateBGRegsB() { uint16_t cPpuTbl = ppuGetVramTbl(ppu.VramAddr); /* Select new BG Tiles */ uint16_t chrROMBG = (ppu.Reg[0] & PPU_BACKGROUND_ADDR) ? 0x1000 : 0; uint16_t workAddr = cPpuTbl | (ppu.VramAddr & 0x3FF); uint8_t curBGtileReg = mapperVramGet8(workAddr); uint8_t curTileY = (ppu.VramAddr>>12)&7; uint16_t curBGTile = chrROMBG+(curBGtileReg<<4)+curTileY; mapperChrMode = 0; uint8_t tmp = mapperChrGet8(curBGTile); ppu.BGRegB = tmp >> 0 & 0x55; ppu.BGRegA = tmp >> 1 & 0x55; tmp = mapperChrGet8(curBGTile+8); ppu.BGRegA |= tmp << 0 & 0xAA; ppu.BGRegB |= tmp << 1 & 0xAA; } static void spriteEvalInit() { ppu.OAMzSpritePos = ppu.OAMpos; //OAM Bug in 2C02 if(ppu.OAMpos & 0xF8) memcpy(ppu.OAM,ppu.OAM + (ppu.OAMpos & 0xF8), 8); } static void spriteEvalA() { ppu.TmpOAMVal = ppu.OAM[(ppu.OAMpos+ppu.OAMcpPos)&0xFF]; //printf("%i %i %i %02x\n", ppu.OAMpos, ppu.OAMcpPos, (ppu.OAMpos+ppu.OAMcpPos)&0xFF, ppu.TmpOAMVal); } static void spriteEvalB(uint16_t line) { if(!ppu.OAMoverflow) { if(ppu.SearchY) { uint8_t cSpriteLn = ppu.TmpOAMVal; uint8_t cSpriteAdd = (ppu.Reg[0] & PPU_SPRITE_8_16) ? 16 : 8; if(cSpriteLn <= line && (cSpriteLn+cSpriteAdd) > line) { ppu.SearchY = false; if(ppu.OAM2pos == 0 && ppu.OAMpos == ppu.OAMzSpritePos) ppu.NextHasZSprite = true; if(ppu.OAM2pos != 0x20) { ppu.OAM2[ppu.OAM2pos+ppu.OAMcpPos] = ppu.TmpOAMVal; //printf("Copying sprite with line %i at line %i pos oam %i oam2 %i\n", cSpriteLn, ppu.curLine, ppu.OAMpos, ppu.OAM2pos); } else { //if(!(PPU_Reg[2] & PPU_FLAG_OVERFLOW) && !ppu.SpriteOverflow) // printf("Overflow with line %i at line %i pos oam %i oam2 %i\n", cSpriteLn, ppu.curLine, ppu.OAMpos, ppu.OAM2pos); ppu.Reg[2] |= PPU_FLAG_OVERFLOW; ppu.LineOverflow = true; } ppu.BytesCopied++; if(ppu.OAMcpPos < 3) ppu.OAMcpPos++; else { ppu.OAMcpPos = 0; ppu.OAMpos += 4; if(ppu.OAMpos == 0) ppu.OAMoverflow = true; } } else //no matching sprite, increase N { ppu.OAMpos += 4; if(ppu.OAM2pos == 0x20 && !ppu.LineOverflow) /* nes sprite overflow bug */ { if(ppu.OAMcpPos < 3) ppu.OAMcpPos++; else ppu.OAMcpPos = 0; } if(ppu.OAMpos == 0) ppu.OAMoverflow = true; } } else { if(ppu.OAM2pos != 0x20) ppu.OAM2[ppu.OAM2pos+ppu.OAMcpPos] = ppu.TmpOAMVal; if(ppu.OAMcpPos < 3) ppu.OAMcpPos++; else { ppu.OAMcpPos = 0; ppu.OAMpos += 4; if(ppu.OAMpos == 0) ppu.OAMoverflow = true; } if(ppu.BytesCopied < 3) //we still have to read from this ppu.BytesCopied++; else //Back to next sprite { if(ppu.OAM2pos != 0x20) ppu.OAM2pos += 4; ppu.BytesCopied = 0; ppu.OAMcpPos = 0; ppu.SearchY = true; if(ppu.LineOverflow) //overflow sprite copied, end ppu.OAMoverflow = true; } } } else ppu.OAMpos += 4; if(ppu.OAM2pos == 0x20) ppu.TmpOAMVal = ppu.OAM2[0]; } static void clearSpriteWorkRegs() { ppu.OAMcpPos = 0; ppu.BytesCopied = 0; ppu.OAMoverflow = false; ppu.SearchY = true; ppu.LineOverflow = false; } static void resetOAMPos() { ppu.OAMpos = 0; } static void ppuCheckLine241(uint16_t dot) { /* VBlank start at first dot after post-render line */ /* Though results are better when starting it a bit later */ if(dot == 2) { ppu.NMITriggered = false; if(!ppu.ReadReg2) ppu.Reg[2] |= PPU_FLAG_VBLANK; #if PPU_DEBUG_VSYNC printf("PPU Start VBlank\n"); #endif } else if(dot == 4 && ppu.Reg[2] & PPU_FLAG_VBLANK) ppu.NMIallowed = true; ppu.ReadReg2 = false; } static void ppuLastDot(uint16_t *dot, uint16_t *line, bool curPicOutStat) { *dot = 0; (*line)++; if(*line == 241) /* done drawing this frame */ ppu.FrameDone = true; else if(*line == ppu.LinesTotal) /* Wrap back down after pre-render line */ *line = 0; ppu.curLine = *line; /* For MMC5 Scanline Detect */ if((*line <= VISIBLE_LINES) && curPicOutStat) { ppuInFrame = true; ppuScanlineDone = true; } else ppuInFrame = false; ppu.DoOverscan = (doOverscan && (*line < 8 || *line >= 232)); ppu.NextHasZSprite = false; //reset ppu.OAMzSpritePos = 0; // reset ppu.SpriteOAM2Pos = 0; //reset ppu.ToDraw = ppu.SpriteTilePos; ppu.SpriteTilePos = 0; //reset ppu.OAM2pos = 0; //reset ppu.TmpOAMVal = 0xFF; memset(ppu.OAM2, ppu.TmpOAMVal, 0x20); //printf("Line done\n"); } void ppuCycle() { //try and make the pic out stat, cur dot and cur line local variables to reduce the time it gets pulled from bss! size_t drawPos; uint16_t dot = ppu.curDot, line = ppu.curLine, cPalIdx; uint8_t curCol; //these 2 stats are set by cpu, no need to have them in loop ppu.CurNMIStat = !!(ppu.Reg[0] & PPU_FLAG_NMI); bool picOutStat = (ppu.Reg[1] & (PPU_BG_ENABLE | PPU_SPRITE_ENABLE)) != 0; ppu.VBlankClearCycle = false; //get value of how often ppu has to run uint8_t ppuLoop = ppu.RunCycles[ppu.Count]; ppu.Count = (ppu.Count+1)%5; if(nesEmuNSFPlayback) { while(ppuLoop--) { if(line == ppu.PreRenderLine) { /* Mini-switch for basic vblank handle */ switch(dot) { case 0: /* VBlank ends at first dot of the pre-render line */ ppu.Reg[2] &= ~(PPU_FLAG_SPRITEZERO | PPU_FLAG_OVERFLOW); goto add_dot_nsf; case 2: /* Though results are better when clearing it a bit later */ #if PPU_DEBUG_VSYNC printf("PPU End VBlank\n"); #endif ppu.Reg[2] &= ~(PPU_FLAG_VBLANK); goto add_dot_nsf; case 7: ppu.NMIallowed = false; goto add_dot_nsf; case 339: ppu.OddFrame = ppu.OddArr[ppu.OddNum^=1]; if(ppu.OddFrame && (ppu.Reg[1] & PPU_BG_ENABLE)) { ppuLastDot(&dot,&line,picOutStat); break; } goto add_dot_nsf; case 340: ppuLastDot(&dot,&line,picOutStat); break; default: add_dot_nsf: dot++; break; } } else { if(line == 241) ppuCheckLine241(dot); if(dot == 340) ppuLastDot(&dot,&line,picOutStat); else dot++; } } } else { while(ppuLoop--) { if(line == ppu.PreRenderLine || line < VISIBLE_LINES) { switch(dot) { case 0: /* VBlank ends at first dot of the pre-render line */ if(line == ppu.PreRenderLine) { ppu.Reg[2] &= ~(PPU_FLAG_SPRITEZERO | PPU_FLAG_OVERFLOW); goto add_dot; } //else if line < VISIBLE_LINES ppu.BGEnable = (ppu.Reg[1] & PPU_BG_8PX) && (ppu.Reg[1] & PPU_BG_ENABLE); ppu.SprEnable = (ppu.Reg[1] & PPU_SPRITE_8PX) && (ppu.Reg[1] & PPU_SPRITE_ENABLE); goto do_render_pixel; case 2: /* Though results are better when clearing it a bit later */ if(line == ppu.PreRenderLine) { #if PPU_DEBUG_VSYNC printf("PPU End VBlank\n"); #endif ppu.Reg[2] &= ~(PPU_FLAG_VBLANK); goto add_dot; } //else if line < VISIBLE_LINES goto do_render_pixel; case 7: if(line == ppu.PreRenderLine) { ppu.NMIallowed = false; goto add_dot; } //else if line < VISIBLE_LINES goto do_render_pixel; case 1: case 3: case 5: case 6: case 9: case 10: case 11: case 13: case 14: case 15: case 17: case 18: case 19: case 21: case 22: case 23: case 25: case 26: case 27: case 29: case 30: case 31: case 33: case 34: case 35: case 37: case 38: case 39: case 41: case 42: case 43: case 45: case 46: case 47: case 49: case 50: case 51: case 53: case 54: case 55: case 57: case 58: case 59: case 61: case 62: case 63: if(line < VISIBLE_LINES) //needs a lot of optimization, takes a lot of cpu atm { do_render_pixel: /* Grab color to render from BG and Sprites */ curCol = ppu.BGEnable ? ppu.BGTiles[(dot + ppu.FineXScroll) & 15] : 0; if(ppu.SprEnable) curCol = ppuDoSprites(curCol, dot); /* Draw current dot on screen */ drawPos = (dot)+(line<<8); if(ppu.DoOverscan) /* Draw clipped area as black */ textureImage[drawPos] = 0xFF000000; else { if(picOutStat) //use color from bg or sprite input cPalIdx = ppu.PALRAM2[curCol&0x1F]; else if((ppu.VramAddr & 0x3F00) == 0x3F00) //bg and sprite disabled but address within PALRAM cPalIdx = ppu.PALRAM2[ppu.VramAddr&0x1F]; else //bg and sprite disabled and address not within PALRAM cPalIdx = ppu.PALRAM[0]; textureImage[drawPos] = ppu.BGRLUT[cPalIdx]; } } goto add_dot; case 4: case 12: case 20: case 28: case 36: case 44: case 52: case 60: updateBGRegsB(); if(line < VISIBLE_LINES) goto do_render_pixel; goto add_dot; case 8: if(picOutStat) updateBGTileAddress(); loadTiles(); updateBGRegsA(dot); if(line < VISIBLE_LINES) { ppu.BGEnable = (ppu.Reg[1] & PPU_BG_ENABLE); ppu.SprEnable = (ppu.Reg[1] & PPU_SPRITE_ENABLE); goto do_render_pixel; } goto add_dot; case 16: case 24: case 32: case 40: case 48: case 56: if(picOutStat) updateBGTileAddress(); loadTiles(); updateBGRegsA(dot); if(line < VISIBLE_LINES) goto do_render_pixel; goto add_dot; case 64: if(picOutStat) updateBGTileAddress(); loadTiles(); updateBGRegsA(dot); if(line < VISIBLE_LINES) { if(picOutStat) { spriteEvalInit(); spriteEvalA(); } goto do_render_pixel; } goto add_dot; case 65: case 67: case 69: case 71: case 73: case 75: case 77: case 79: case 81: case 83: case 85: case 87: case 89: case 91: case 93: case 95: case 97: case 99: case 101: case 103: case 105: case 107: case 109: case 111: case 113: case 115: case 117: case 119: case 121: case 123: case 125: case 127: case 129: case 131: case 133: case 135: case 137: case 139: case 141: case 143: case 145: case 147: case 149: case 151: case 153: case 155: case 157: case 159: case 161: case 163: case 165: case 167: case 169: case 171: case 173: case 175: case 177: case 179: case 181: case 183: case 185: case 187: case 189: case 191: case 193: case 195: case 197: case 199: case 201: case 203: case 205: case 207: case 209: case 211: case 213: case 215: case 217: case 219: case 221: case 223: case 225: case 227: case 229: case 231: case 233: case 235: case 237: case 239: case 241: case 243: case 245: case 247: case 249: case 253: case 255: do_sprite_eval_b: if(line < VISIBLE_LINES) { if(picOutStat) spriteEvalB(line); goto do_render_pixel; } goto add_dot; case 66: case 70: case 74: case 78: case 82: case 86: case 90: case 94: case 98: case 102: case 106: case 110: case 114: case 118: case 122: case 126: case 130: case 134: case 138: case 142: case 146: case 150: case 154: case 158: case 162: case 166: case 170: case 174: case 178: case 182: case 186: case 190: case 194: case 198: case 202: case 206: case 210: case 214: case 218: case 222: case 226: case 230: case 234: case 238: case 242: case 246: case 250: case 254: do_sprite_eval_a: if(line < VISIBLE_LINES) { if(picOutStat) spriteEvalA(); goto do_render_pixel; } goto add_dot; case 68: case 76: case 84: case 92: case 100: case 108: case 116: case 124: case 132: case 140: case 148: case 156: case 164: case 172: case 180: case 188: case 196: case 204: case 212: case 220: case 228: case 236: case 244: case 252: updateBGRegsB(); goto do_sprite_eval_a; case 72: case 80: case 88: case 96: case 104: case 112: case 120: case 128: case 136: case 144: case 152: case 160: case 168: case 176: case 184: case 192: case 200: case 208: case 216: case 224: case 232: case 240: case 248: if(picOutStat) updateBGTileAddress(); loadTiles(); updateBGRegsA(dot); goto do_sprite_eval_a; case 251: if(picOutStat) updateBGYAddress(); goto do_sprite_eval_b; case 256: if(line < VISIBLE_LINES) clearSpriteWorkRegs(); loadTiles(); updateBGRegsA(dot); if(picOutStat) { updateBGTileAddress(); goto do_spritetiles_P1; } goto add_dot; case 257: if(picOutStat) { updateBGHoriAddress(); goto do_spritetiles_P2; } goto add_dot; case 258: case 266: case 274: case 306: case 314: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); do_spritetiles_P3: grabNextSpriteTilesP3(); } goto add_dot; case 259: case 267: case 275: case 307: case 315: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); do_spritetiles_P4: grabNextSpriteTilesP4(); } goto add_dot; case 260: case 268: case 276: case 308: case 316: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); do_spritetiles_P5: grabNextSpriteTilesP5(line); } goto add_dot; case 261: case 262: case 263: case 269: case 270: case 271: case 277: case 278: case 279: case 309: case 310: case 311: case 317: case 318: case 321: case 322: case 323: case 325: case 326: case 327: case 329: case 330: case 331: case 333: case 334: case 335: case 337: case 338: if(picOutStat && line < VISIBLE_LINES) resetOAMPos(); goto add_dot; case 264: case 272: case 312: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); do_spritetiles_P1: grabNextSpriteTilesP1(); } goto add_dot; case 265: case 273: case 305: case 313: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); do_spritetiles_P2: grabNextSpriteTilesP2(); } goto add_dot; case 280: case 288: case 296: case 304: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); grabNextSpriteTilesP1(); } goto add_dot; case 281: case 289: case 297: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); grabNextSpriteTilesP2(); } goto add_dot; case 282: case 290: case 298: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); goto do_spritetiles_P3; } goto add_dot; case 283: case 291: case 299: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); goto do_spritetiles_P4; } goto add_dot; case 284: case 292: case 300: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); goto do_spritetiles_P5; } goto add_dot; case 285: case 286: case 287: case 293: case 294: case 295: case 301: case 302: case 303: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); else if(line == ppu.PreRenderLine) updateBGVertAddress(); } goto add_dot; case 319: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); ppu.TmpOAMVal = ppu.OAM2[0]; } goto add_dot; case 320: if(picOutStat && line < VISIBLE_LINES) resetOAMPos(); updateBGRegsA(dot); goto add_dot; case 324: case 332: if(picOutStat && line < VISIBLE_LINES) resetOAMPos(); updateBGRegsB(); goto add_dot; case 328: ppu.BGIndex = 0; case 336: if(picOutStat) { if(line < VISIBLE_LINES) resetOAMPos(); updateBGTileAddress(); } loadTiles(); updateBGRegsA(dot); goto add_dot; case 339: if(line == ppu.PreRenderLine) { ppu.OddFrame = ppu.OddArr[ppu.OddNum^=1]; if(ppu.OddFrame && (ppu.Reg[1] & PPU_BG_ENABLE)) { ppuLastDot(&dot,&line,picOutStat); break; } } else if(picOutStat && line < VISIBLE_LINES) resetOAMPos(); goto add_dot; case 340: if(picOutStat && line < VISIBLE_LINES) resetOAMPos(); updateBGRegsB(); ppuLastDot(&dot,&line,picOutStat); break; default: add_dot: dot++; break; } } else { if(line == 241) ppuCheckLine241(dot); if(dot == 340) ppuLastDot(&dot,&line,picOutStat); else dot++; } } } ppu.curDot = dot; } void ppuPrintcurLineDot() { printf("%i %i %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos); } static uint8_t ppuDoSprites(uint8_t color, uint16_t dot) { uint8_t i; for(i = 0; i < ppu.ToDraw; i++) { uint16_t x = dot - ppu.Sprites[i][8]; if (x > 7) continue; //required pixel x = ppu.Sprites[i][x]; if (x) { if ((color & ppu.Sprites[i][9]) && dot < 255) { ppu.Reg[2] |= PPU_FLAG_SPRITEZERO; #if PPU_DEBUG_ULTRA printf("Zero sprite hit at x %i y %i cSpriteDot %i " "table %04x color %02x sprCol %02x\n", dot, ppu.curLine, cSpriteDot, ppuGetVramTbl((ppu.Reg[0]&3)<<10), color, sprCol); #endif //if(ppu.curLine < 224) // ppuDebugPauseFrame = true; } if (!(color & ppu.Sprites[i][10])) color = ppu.Sprites[i][11] | x; break; } } return color; } bool ppuDrawDone() { if(ppu.FrameDone) { //printf("%i\n",ppuCycles); //ppuCycles = 0; ppu.FrameDone = false; return true; } return false; } void ppuSet8(uint8_t reg, uint8_t val) { ppu.lastVal = val; if(reg == 0) { ppu.Reg[0] = val; ppu.TmpVramAddr &= ~0xC00; ppu.TmpVramAddr |= ((val&3)<<10); ppu816Sprite = (val & PPU_SPRITE_8_16) != 0; //printf("%d %d %d\n", (PPU_Reg[0] & PPU_BACKGROUND_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_8_16) != 0); } else if(reg == 1) { if((ppu.Reg[1]^val)&(PPU_GRAY|0xE0)) { uint8_t i; for(i = 0; i < 0x20; i++) ppu.PALRAM2[i] = (ppu.PALRAM[i]&((val&PPU_GRAY)?0x30:0x3F))|((val&0xE0)<<1); } ppu.BGEnable = (ppu.curDot >= 8 || (val & PPU_BG_8PX)) && (val & PPU_BG_ENABLE); ppu.SprEnable = (ppu.curDot >= 8 || (val & PPU_SPRITE_8PX)) && (val & PPU_SPRITE_ENABLE); ppu.Reg[1] = val; } else if(reg == 3) { #if PPU_DEBUG_ULTRA printf("ppu.OAMpos at line %i dot %i was %02x set to %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos, val); #endif ppu.OAMpos = val; } else if(reg == 4) { #if PPU_DEBUG_ULTRA printf("Setting OAM at line %i dot %i addr %02x to %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos, val); #endif ppu.OAM[ppu.OAMpos++] = val; } else if(reg == 5) { #if PPU_DEBUG_ULTRA printf("ppuScrollWrite (%d) %02x pc %04x\n", ppu.TmpWrite, val, cpuGetPc()); #endif if(!ppu.TmpWrite) { ppu.TmpWrite = true; ppu.FineXScroll = val&7; ppu.TmpVramAddr &= ~0x1F; ppu.TmpVramAddr |= ((val>>3)&0x1F); } else { ppu.TmpWrite = false; ppu.TmpVramAddr &= ~0x73E0; ppu.TmpVramAddr |= ((val&7)<<12) | ((val>>3)<<5); } } else if(reg == 6) { #if PPU_DEBUG_ULTRA printf("ppu.VramAddrWrite (%d) %02x pc %04x\n", ppu.TmpWrite, val, cpuGetPc()); #endif if(!ppu.TmpWrite) { ppu.TmpWrite = true; ppu.TmpVramAddr &= 0xFF; ppu.TmpVramAddr |= ((val&0x3F)<<8); } else { ppu.TmpWrite = false; ppu.TmpVramAddr &= ~0xFF; ppu.TmpVramAddr |= val; ppu.VramAddr = ppu.TmpVramAddr; //For MMC3 IRQ (Shadow Read) if((ppu.VramAddr&0x3FFF) < 0x2000) mapperChrGet8(ppu.VramAddr&0x3FFF); } } else if(reg == 7) { uint16_t writeAddr = (ppu.VramAddr & 0x3FFF); if(writeAddr < 0x2000) { mapperChrSet8(writeAddr, val); } else if(writeAddr < 0x3F00) { uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); //printf("ppuVRAMwrite %04x %02x\n", workAddr, val); mapperVramSet8(workAddr, val); } else { uint8_t palRamAddr = (writeAddr&0x1F); if((palRamAddr&3) == 0) { ppu.PALRAM[palRamAddr^0x10] = val; ppu.PALRAM2[palRamAddr^0x10] = (val&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F))|((ppu.Reg[1]&0xE0)<<1); } ppu.PALRAM[palRamAddr] = val; ppu.PALRAM2[palRamAddr] = (val&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F))|((ppu.Reg[1]&0xE0)<<1); } ppu.VramAddr += (ppu.Reg[0] & PPU_INC_AMOUNT) ? 32 : 1; //For MMC3 IRQ (Shadow Read) if((ppu.VramAddr&0x3FFF) < 0x2000) mapperChrGet8(ppu.VramAddr&0x3FFF); } else if(reg != 2) { ppu.Reg[reg] = val; //printf("ppuSet8 odd %d ppu.curDot %i ppu.curLine %i %04x %02x\n", ppu.OddFrame, ppu.curDot, ppu.curLine, reg, val); } } uint8_t ppuGet8(uint8_t reg) { uint8_t ret = ppu.lastVal; if(reg == 2) { ret = ppu.Reg[reg]; ppu.Reg[reg] &= ~PPU_FLAG_VBLANK; if(ret & PPU_FLAG_VBLANK) { ppu.VBlankFlagCleared = true; ppu.VBlankClearCycle = true; } ppu.TmpWrite = false; ppu.ReadReg2 = true; } else if(reg == 4) { if(ppu.Reg[2] & PPU_FLAG_VBLANK || (ppu.Reg[1] & (PPU_BG_ENABLE | PPU_SPRITE_ENABLE)) == 0) ret = ppu.OAM[ppu.OAMpos]; else ret = ppu.TmpOAMVal; //printf("Cycle %i line %i val %02x\n", ppu.curDot, ppu.curLine, ret); } else if(reg == 7) { uint16_t writeAddr = (ppu.VramAddr & 0x3FFF); if(writeAddr < 0x2000) { ret = ppu.VramReadBuf; mapperChrMode = 2; ppu.VramReadBuf = mapperChrGet8(writeAddr); } else if(writeAddr < 0x3F00) { ret = ppu.VramReadBuf; uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); ppu.VramReadBuf = mapperVramGet8(workAddr); //printf("ppuVRAMread pc %04x addr %04x ret %02x\n", cpuGetPc(), workAddr, ret); } else { uint8_t palRamAddr = (writeAddr&0x1F); if((palRamAddr&3) == 0) palRamAddr &= ~0x10; ret = ppu.PALRAM[palRamAddr]&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F); //shadow read uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF); ppu.VramReadBuf = mapperVramGet8(workAddr); } ppu.VramAddr += (ppu.Reg[0] & PPU_INC_AMOUNT) ? 32 : 1; //For MMC3 IRQ (Shadow Read) if((ppu.VramAddr&0x3FFF) < 0x2000) mapperChrGet8(ppu.VramAddr&0x3FFF); } //if(ret & PPU_FLAG_VBLANK) //printf("ppuGet8 %04x:%02x\n",reg,ret); ppu.lastVal = ret; return ret; } bool ppuNMI() { if(ppu.VBlankFlagCleared && !ppu.VBlankClearCycle) { ppu.VBlankFlagCleared = false; ppu.NMIallowed = false; } if(ppu.CurNMIStat && ppu.NMIallowed) { if(ppu.NMITriggered == false) { ppu.NMITriggered = true; return true; } else return false; } ppu.NMITriggered = false; return false; } void ppuDumpMem() { FILE *f = fopen("PPU_VRAM.bin","wb"); if(f) { fwrite(ppu.VRAM,1,0x1000,f); fclose(f); } f = fopen("PPU_OAM.bin","wb"); if(f) { fwrite(ppu.OAM,1,0x100,f); fclose(f); } f = fopen("PPU_Sprites.bin","wb"); if(f) { fwrite(ppu.Sprites,1,0x20,f); fclose(f); } } uint16_t ppuGetCurVramAddr() { return ppu.VramAddr; } void ppuSetNameTblSingleLower() { ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0; ppu.NameTbl[2] = 0; ppu.NameTbl[3] = 0; } void ppuSetNameTblSingleUpper() { ppu.NameTbl[0] = 0x400; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0x400; ppu.NameTbl[3] = 0x400; } void ppuSetNameTblVertical() { ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0; ppu.NameTbl[3] = 0x400; } void ppuSetNameTblHorizontal() { ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0; ppu.NameTbl[2] = 0x400; ppu.NameTbl[3] = 0x400; } void ppuSetNameTbl4Screen() { ppu4Screen = true; ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0x800; ppu.NameTbl[3] = 0xC00; } void ppuSetNameTblCustom(uint16_t addrA, uint16_t addrB, uint16_t addrC, uint16_t addrD) { //printf("%04x %04x %04x %04x\n", addrA, addrB, addrC, addrD); //ppuPrintppu.curLineDot(); ppu.NameTbl[0] = addrA; ppu.NameTbl[1] = addrB; ppu.NameTbl[2] = addrC; ppu.NameTbl[3] = addrD; } void ppuBackUpTbl() { ppu.NameTblBak[0] = ppu.NameTbl[0]; ppu.NameTblBak[1] = ppu.NameTbl[1]; ppu.NameTblBak[2] = ppu.NameTbl[2]; ppu.NameTblBak[3] = ppu.NameTbl[3]; } void ppuRestoreTbl() { ppu.NameTbl[0] = ppu.NameTblBak[0]; ppu.NameTbl[1] = ppu.NameTblBak[1]; ppu.NameTbl[2] = ppu.NameTblBak[2]; ppu.NameTbl[3] = ppu.NameTblBak[3]; } //64x12 1BPP "Track" static const uint8_t ppuNSFTextTrack[96] = { 0x0C, 0x1C, 0x03, 0xD8, 0x7C, 0x71, 0xC0, 0x00, 0x0C, 0x1C, 0x07, 0xF8, 0xFE, 0x73, 0x80, 0x00, 0x0C, 0x1C, 0x06, 0x39, 0xE2, 0x77, 0x00, 0x00, 0x0C, 0x1C, 0x07, 0x39, 0xC0, 0x7E, 0x00, 0x00, 0x0C, 0x1C, 0x07, 0xF9, 0xC0, 0x7C, 0x00, 0x00, 0x0C, 0x1C, 0x01, 0xF9, 0xC0, 0x7C, 0x00, 0x00, 0x0C, 0x1E, 0x60, 0x38, 0xE2, 0x7E, 0x00, 0x00, 0x0C, 0x1F, 0xE3, 0xF8, 0xFE, 0x77, 0x00, 0x00, 0x0C, 0x1D, 0xC3, 0xF0, 0x3C, 0x73, 0xC0, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, }; //128x12 1BPP "0123456789/" static const uint8_t ppuNsfTextRest[192] = { 0x0E, 0x1F, 0xF7, 0xF8, 0xF8, 0x03, 0x9F, 0x01, 0xF0, 0x60, 0x1F, 0x0F, 0x83, 0x00, 0x00, 0x00, 0x3F, 0x9F, 0xF7, 0xF9, 0xFC, 0x03, 0x9F, 0xC3, 0xF8, 0x70, 0x3F, 0x8F, 0xC3, 0x00, 0x00, 0x00, 0x3B, 0x83, 0x83, 0x80, 0x0E, 0x7F, 0xC1, 0xE7, 0x1C, 0x70, 0x71, 0xC0, 0xE1, 0x80, 0x00, 0x00, 0x71, 0xC3, 0x81, 0xC0, 0x0E, 0x7F, 0xC0, 0xE7, 0x1C, 0x30, 0x71, 0xC0, 0x71, 0x80, 0x00, 0x00, 0x79, 0xC3, 0x80, 0xE0, 0x0E, 0x63, 0x80, 0xE7, 0x1C, 0x38, 0x71, 0xC7, 0x70, 0xC0, 0x00, 0x00, 0x7D, 0xC3, 0x80, 0x70, 0x7E, 0x33, 0x81, 0xE7, 0x1C, 0x18, 0x3F, 0x8F, 0xF0, 0xC0, 0x00, 0x00, 0x77, 0xC3, 0x80, 0x70, 0x7C, 0x13, 0x9F, 0xC7, 0xF8, 0x1C, 0x1F, 0x1C, 0x70, 0x60, 0x00, 0x00, 0x73, 0xC3, 0x80, 0x38, 0x0E, 0x1B, 0x9F, 0x87, 0x70, 0x1C, 0x31, 0x9C, 0x70, 0x60, 0x00, 0x00, 0x71, 0xC3, 0x80, 0x38, 0x0E, 0x0B, 0x9C, 0x07, 0x00, 0x0C, 0x71, 0xDC, 0x70, 0x30, 0x00, 0x00, 0x3B, 0x9F, 0x80, 0x38, 0x0E, 0x0F, 0x9C, 0x03, 0x80, 0x0E, 0x71, 0xDC, 0x70, 0x30, 0x00, 0x00, 0x3F, 0x8F, 0x83, 0xF1, 0xFC, 0x07, 0x9F, 0xC1, 0xF9, 0xFE, 0x3F, 0x8F, 0xE0, 0x18, 0x00, 0x00, 0x0E, 0x03, 0x81, 0xE0, 0xF8, 0x03, 0x9F, 0xC0, 0xF9, 0xFE, 0x1F, 0x07, 0xC0, 0x18, 0x00, 0x00, }; static void ppuDrawRest(uint8_t curX, uint8_t sym) { uint8_t i, j; for(i = 0; i < 12; i++) { for(j = 0; j < 10; j++) { size_t drawPos = (j+curX)+((i+9)*256); uint8_t xSel = (j+(sym*10)); if(ppuNsfTextRest[((11-i)<<4)+(xSel>>3)]&(0x80>>(xSel&7))) textureImage[drawPos] = 0xFFFFFFFF; //White else textureImage[drawPos] = 0xFF000000; //Black } } } void ppuDrawNSFTrackNum(uint8_t cTrack, uint8_t trackTotal) { memset(textureImage,0,0xB400); uint8_t curX = 4; //draw "Track" uint8_t i, j; for(i = 0; i < 12; i++) { for(j = 0; j < 50; j++) { size_t drawPos = (j+curX)+((i+9)*256); if(ppuNSFTextTrack[((11-i)<<3)+(j>>3)]&(0x80>>(j&7))) textureImage[drawPos] = 0xFFFFFFFF; //White else textureImage[drawPos] = 0xFF000000; //Black } } //"Track" len+space curX+=60; //draw current num if(cTrack > 99) { ppuDrawRest(curX, (cTrack/100)%10); curX+=10; } if(cTrack > 9) { ppuDrawRest(curX, (cTrack/10)%10); curX+=10; } ppuDrawRest(curX, cTrack%10); curX+=10; //draw the "/" ppuDrawRest(curX, 10); curX+=10; //draw total num if(trackTotal > 99) { ppuDrawRest(curX, (trackTotal/100)%10); curX+=10; } if(trackTotal > 9) { ppuDrawRest(curX, (trackTotal/10)%10); curX+=10; } ppuDrawRest(curX, trackTotal%10); curX+=10; } uint8_t ppuVRAMGet8(uint16_t addr) { return ppu.VRAM[addr&0xFFF]; } void ppuVRAMSet8(uint16_t addr, uint8_t val) { ppu.VRAM[addr&0xFFF] = val; } uint8_t* ppuGetVRAM() { return ppu.VRAM; }