diff --git a/NESEmulator/CNESEmulator.cpp b/NESEmulator/CNESEmulator.cpp index dcf044df0..40e3d7954 100644 --- a/NESEmulator/CNESEmulator.cpp +++ b/NESEmulator/CNESEmulator.cpp @@ -133,6 +133,7 @@ bool emuSkipFrame = false; extern bool fdsSwitch; bool apuCycleURDE(); +uint32_t apuGetMaxBufSize(); uint8_t* ppuGetVRAM(); int audioUpdate() @@ -269,7 +270,7 @@ void CNESEmulator::InitializeEmulator() double useFreq = apuGetFrequency(); m_booVoice = CAudioSys::GetVoiceEngine()->allocateNewStereoVoice(useFreq, this); m_booVoice->start(); - uint32_t apuBufSz = apuGetBufSize(); + uint32_t apuBufSz = apuGetMaxBufSize(); m_audioBufBlock.reset(new u8[apuBufSz * NUM_AUDIO_BUFFERS]); for (int i=0 ; i= 14) { #if CATCHUP_SKIP diff --git a/NESEmulator/CNESEmulator.hpp b/NESEmulator/CNESEmulator.hpp index 9df186274..a2509f400 100644 --- a/NESEmulator/CNESEmulator.hpp +++ b/NESEmulator/CNESEmulator.hpp @@ -15,7 +15,7 @@ class IDvdRequest; namespace MP1 { -#define NUM_AUDIO_BUFFERS 10 +#define NUM_AUDIO_BUFFERS 3 class CNESEmulator final : public boo::IAudioVoiceCallback { diff --git a/NESEmulator/CNESShader.cpp b/NESEmulator/CNESShader.cpp index 06c3dc448..569cf85de 100644 --- a/NESEmulator/CNESShader.cpp +++ b/NESEmulator/CNESShader.cpp @@ -48,7 +48,7 @@ BOO_GLSL_BINDING_HEAD "TBINDING0 uniform sampler2D tex;\n" "void main()\n" "{\n" -" colorOut = vtf.color * vec4(texture(tex, vtf.uv).bgr, 1.0);\n" +" colorOut = vtf.color * texture(tex, vtf.uv);\n" "}\n"; #if _WIN32 @@ -94,7 +94,7 @@ static const char* FS_HLSL = "\n" "float4 main(in VertToFrag vtf) : SV_Target0\n" "{\n" -" return vtf.color * float4(tex.Sample(samp, vtf.uv).bgr, 1.0);\n" +" return vtf.color * tex.Sample(samp, vtf.uv);\n" "}\n"; #endif @@ -145,7 +145,7 @@ static const char* FS_METAL = " sampler clampSamp [[ sampler(4) ]],\n" " texture2d tex [[ texture(0) ]])\n" "{\n" -" return vtf.color * float4(tex.sample(clampSamp, vtf.uv).bgr, 1.0);\n" +" return vtf.color * tex.sample(clampSamp, vtf.uv);\n" "}\n"; #endif diff --git a/NESEmulator/apu.c b/NESEmulator/apu.c index c52a3f807..9d0c4e7ad 100644 --- a/NESEmulator/apu.c +++ b/NESEmulator/apu.c @@ -313,3 +313,8 @@ bool apuCycleURDE() return true; } + +uint32_t apuGetMaxBufSize() +{ + return apu.BufSizeBytes; +} diff --git a/NESEmulator/ppu.c b/NESEmulator/ppu.c index 8057b6b9a..408490484 100644 --- a/NESEmulator/ppu.c +++ b/NESEmulator/ppu.c @@ -1,6 +1,1428 @@ -#include "fixNES/ppu.c" +/* + * Copyright (C) 2017 FIX94 + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ -/* Non-invasive way to access VRAM buffer directly */ +/* + * URDE modification 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() {