Files
DbNesEmulator/nescorelib/emu/ppu.cpp
2018-12-16 22:19:06 +01:00

984 lines
30 KiB
C++

#include "ppu.h"
// Qt includes
#include <QDataStream>
// dbcorelib includes
#include "utils/datastreamutils.h"
// local includes
#include "nesemulator.h"
#include "emusettings.h"
Ppu::Ppu(NesEmulator &emu) :
QObject(&emu),
m_emu(emu)
{
}
void Ppu::hardReset()
{
m_ppuReg2001Grayscale = 0xF3;
// oam
m_ppuOamBank.fill(0);
m_ppuOamBankSecondary.fill(0);
oamReset();
// pallettes
m_ppuPaletteBank = {
0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, // Bkg palette
0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 0x08, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08 // Spr palette
};
//ppu_palette = PaletteFileWrapper.LoadFile("");
m_ppuColorAnd = m_ppuReg2001Grayscale | m_ppuReg2001Emphasis;
m_ppuRegIoDb = 0;
m_ppuRegIoAddr = 0;
m_ppuRegAccessHappened = false;
m_ppuRegAccessW = false;
m_ppuReg2000VramAddressIncreament = 1;
m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0;
m_ppuReg2000BackgroundPatternTableAddress = 0;
m_ppuReg2000SpriteSize = 0;
m_ppuReg2000Vbi = false;
m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen = false;
m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen = false;
m_ppuReg2001ShowBackground = false;
m_ppuReg2001ShowSprites = false;
m_ppuReg2001Grayscale = 0;
m_ppuReg2001Emphasis = 0;
m_ppuReg2002SpriteOverflow = false;
m_ppuReg2002Sprite0Hit = false;
m_ppuReg2002VblankStartedFlag = false;
m_ppuReg2003OamAddr = 0;
m_ppuIsSprfetch = false;
}
void Ppu::clock()
{
static constexpr std::array<void (Ppu::*)(), 320> ppuVClocks = []() {
std::array<void (Ppu::*)(), 320> ppuVClocks {};
for(std::size_t i = 0; i < SCREEN_HEIGHT; i++)
ppuVClocks[i] = &Ppu::scanlineRender;
ppuVClocks[SCREEN_HEIGHT] = &Ppu::scanlineVBlank;
if(EmuSettings::region == EmuRegion::DENDY)
for(std::size_t i = 241; i <= 290; i++)
ppuVClocks[i] = &Ppu::scanlineVBlank;
ppuVClocks[EmuSettings::ppuClockVBlankStart] = &Ppu::scanlineVBlankStart;
for(std::size_t i = EmuSettings::ppuClockVBlankStart + 1; i <= EmuSettings::ppuClockVBlankEnd - 1; i++)
ppuVClocks[i] = &Ppu::scanlineVBlank;
ppuVClocks[EmuSettings::ppuClockVBlankEnd] = &Ppu::scanlineVBlankEnd;
return ppuVClocks;
}();
static constexpr std::array<void (Ppu::*)(), 8> ppuRegUpdateFuncs {
&Ppu::onRegister2000, &Ppu::onRegister2001, &Ppu::onRegister2002, &Ppu::onRegister2003,
&Ppu::onRegister2004, &Ppu::onRegister2005, &Ppu::onRegister2006, &Ppu::onRegister2007
};
m_emu.memory().board()->onPpuClock();
// Clock a scanline
const auto callback = ppuVClocks[m_ppuClockV];
(this->*callback)();
// Advance
if(m_ppuClockH == 340)
{
m_emu.memory().board()->onPpuScanlineTick();
// Advance scanline ...
if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd)
{
m_ppuClockV = 0;
Q_EMIT frameFinished(m_ppuScreenPixels);
}
else
m_ppuClockV++;
m_ppuClockH = -1;
}
m_ppuClockH++;
/* THEORY:
* After read/write (io access at 0x200x), the registers take effect at the end of the ppu cycle.
* That's why we have the vbl and nmi effects on writing at 0x2000 and reading from 0x2002.
* Same forother registers. First, when cpu access the IO at 0x200x, a data register sets from cpu written value.
* At the end of the next ppu clock, ppu check out what happened in IO, then updates flags and other stuff.
* forwrites, first cpu sets the data bus, then AT THE END OF PPU CYCLE, update flags and data from the data bus to use at the next cycle.
* forreads, first update the data bus with flags and data, then return the value to cpu.
* After that, AT THE END OF PPU CYCLE, ppu acknowledge the read: flags get reset, data updated ...etc
*/
if(m_ppuRegAccessHappened)
{
m_ppuRegAccessHappened = false;
(this->*ppuRegUpdateFuncs[m_ppuRegIoAddr])();
}
}
void Ppu::scanlineRender()
{
static constexpr std::array<void (Ppu::*)(), 8> ppuBkgFetches {
&Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3,
&Ppu::bkgFetch4, &Ppu::bkgFetch5, &Ppu::bkgFetch6, &Ppu::bkgFetch7
};
static constexpr std::array<void (Ppu::*)(), 8> ppuSprFetches {
&Ppu::bkgFetch0, &Ppu::bkgFetch1, &Ppu::bkgFetch2, &Ppu::bkgFetch3,
&Ppu::sprFetch0, &Ppu::sprFetch1, &Ppu::sprFetch2, &Ppu::sprFetch3
};
static constexpr std::array<void (Ppu::*)(), 9> ppuOamPhases {
&Ppu::oamPhase0, &Ppu::oamPhase1, &Ppu::oamPhase2, &Ppu::oamPhase3, &Ppu::oamPhase4,
&Ppu::oamPhase5, &Ppu::oamPhase6, &Ppu::oamPhase7, &Ppu::oamPhase8
};
// 0 - 239 scanlines and pre-render scanline 261
if(m_ppuClockH > 0)
{
if(m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites)
{
if(m_ppuClockH < SCREEN_WIDTH + 1)
{
// H clocks 1 - 256
// OAM evaluation doesn't occur on pre-render scanline.
if(m_ppuClockV != EmuSettings::ppuClockVBlankEnd)
{
// Sprite evaluation
if(m_ppuClockH < 65)
{
m_ppuOamBankSecondary[(m_ppuClockH - 1) & 0x1F] = 0xFF;
}
else
{
if(m_ppuClockH == 65)
oamReset();
if((m_ppuClockH & 1) == 1)
oamEvFetch();
else
(this->*ppuOamPhases[m_ppuPhaseIndex])();
if(m_ppuClockH == SCREEN_WIDTH)
oamClear();
}
}
// BKG fetches
(this->*ppuBkgFetches[(m_ppuClockH - 1) & 7])();
if(m_ppuClockH < SCREEN_WIDTH + 1)
renderPixel();
}
else if(m_ppuClockH < 321)
{
// H clocks 256 - 320
// BKG garbage fetches and sprite fetches
(this->*ppuSprFetches[(m_ppuClockH - 1) & 7])();
if(m_ppuClockH == SCREEN_WIDTH + 1)
m_ppuVramAddr = (m_ppuVramAddr & 0x7BE0) | (m_ppuVramAddrTemp & 0x041F);
if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd && m_ppuClockH >= 280 && m_ppuClockH <= 304)
m_ppuVramAddr = (m_ppuVramAddr & 0x041F) | (m_ppuVramAddrTemp & 0x7BE0);
}
else
{
// 321 - 340
// BKG dummy fetch
(this->*ppuBkgFetches[(m_ppuClockH - 1) & 7])();
}
}
else if(m_ppuClockV < SCREEN_HEIGHT && m_ppuClockH < SCREEN_WIDTH + 1)
{
// Rendering is off, draw color at vram address ifit in range 0x3F00 - 0x3FFF
if((m_ppuVramAddr & 0x3F00) == 0x3F00)
{
if((m_ppuVramAddr & 0x03) == 0)
{
const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH);
const auto index2 = m_ppuPaletteBank[m_ppuVramAddr & 0x0C] & m_ppuColorAnd;
const auto value = EmuSettings::Video::palette[index2];
m_ppuScreenPixels[index1] = value;
}
else
{
const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH);
const auto index2 = m_ppuPaletteBank[m_ppuVramAddr & 0x1F] & m_ppuColorAnd;
const auto value = EmuSettings::Video::palette[index2];
m_ppuScreenPixels[index1] = value;
}
}
else
{
const auto index1 = m_ppuClockH - 1 + (m_ppuClockV * SCREEN_WIDTH);
const auto index2 = m_ppuPaletteBank[0] & m_ppuColorAnd;
const auto value = EmuSettings::Video::palette[index2];
m_ppuScreenPixels[index1] = value;
}
}
}// else is the idle clock
}
void Ppu::scanlineVBlankStart()
{
// This is scanline 241
m_ppuIsNmiTime = m_ppuClockH >= 1 && m_ppuClockH <= 4;
if(m_ppuIsNmiTime)// 0 - 3
{
if(m_ppuClockH == 1)
m_ppuReg2002VblankStartedFlag = true;
m_emu.interrupts().setNmiCurrent(m_ppuReg2002VblankStartedFlag & m_ppuReg2000Vbi);
}
}
void Ppu::scanlineVBlankEnd()
{
// This is scanline 261, also called pre-render line
m_ppuIsNmiTime = m_ppuClockH >= 1 && m_ppuClockH <= 4;
// Pre-render line is here !
if(m_ppuClockH > 0)
{
// Do a pre-render
scanlineRender();
if(m_ppuClockH == 1)
{
// Clear vbl flag
m_ppuReg2002Sprite0Hit = false;
m_ppuReg2002VblankStartedFlag = false;
m_ppuReg2002SpriteOverflow = false;
}
if(EmuSettings::ppuUseOddCycle)
{
if(m_ppuClockH == 339)
{
// ODD Cycle
m_ppuUseOddSwap = !m_ppuUseOddSwap;
if(!m_ppuUseOddSwap & (m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites))
m_ppuClockH++;
}
}
}
}
void Ppu::scanlineVBlank()
{
}
void Ppu::bkgFetch0()
{
// Calculate NT address
m_ppuBkgfetchNtAddr = 0x2000 | (m_ppuVramAddr & 0x0FFF);
m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchNtAddr);
}
void Ppu::bkgFetch1()
{
// Fetch NT data
m_ppuBkgfetchNtData = m_emu.memory().board()->readNmt(m_ppuBkgfetchNtAddr);
}
void Ppu::bkgFetch2()
{
// Calculate AT address
m_ppuBkgfetchAtAddr = 0x23C0 | (m_ppuVramAddr & 0xC00) | ((m_ppuVramAddr >> 4) & 0x38) | ((m_ppuVramAddr >> 2) & 0x7);
m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchAtAddr);
}
void Ppu::bkgFetch3()
{
// Fetch AT data
m_ppuBkgfetchAtData = m_emu.memory().board()->readNmt(m_ppuBkgfetchAtAddr);
m_ppuBkgfetchAtData = m_ppuBkgfetchAtData >> ((m_ppuVramAddr >> 4 & 0x04) | (m_ppuVramAddr & 0x02));
}
void Ppu::bkgFetch4()
{
// Calculate tile low-bit address
m_ppuBkgfetchLbAddr = m_ppuReg2000BackgroundPatternTableAddress | (m_ppuBkgfetchNtData << 4) | (m_ppuVramAddr >> 12 & 7);
m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchLbAddr);
}
void Ppu::bkgFetch5()
{
// Fetch tile low-bit data
m_ppuBkgfetchLbData = m_emu.memory().board()->readChr(m_ppuBkgfetchLbAddr);
}
void Ppu::bkgFetch6()
{
// Calculate tile high-bit address
m_ppuBkgfetchHbAddr = m_ppuReg2000BackgroundPatternTableAddress | (m_ppuBkgfetchNtData << 4) | 8 | (m_ppuVramAddr >> 12 & 7);
m_emu.memory().board()->onPpuAddressUpdate(m_ppuBkgfetchHbAddr);
}
void Ppu::bkgFetch7()
{
// Fetch tile high-bit data
m_ppuBkgfetchHbData = m_emu.memory().board()->readChr(m_ppuBkgfetchHbAddr);
const auto ppuBkgRenderPos = m_ppuClockH > 320 ? m_ppuClockH - 327 : m_ppuClockH + 9;
// Rendering background pixel
for(auto i = 0; i < 8; i++)
{
const auto temp = ((m_ppuBkgfetchAtData << 2) & 0xC) | (m_ppuBkgfetchLbData >> 7 & 1) | (m_ppuBkgfetchHbData >> 6 & 2);
if((temp & 3) != 0)
m_ppuBkgPixels[i + ppuBkgRenderPos] = temp;
else
m_ppuBkgPixels[i + ppuBkgRenderPos] = 0;
m_ppuBkgfetchLbData <<= 1;
m_ppuBkgfetchHbData <<= 1;
}
// Increments
if(m_ppuClockH == SCREEN_WIDTH)
{
// Increment Y
if((m_ppuVramAddr & 0x7000) != 0x7000)
m_ppuVramAddr += 0x1000;
else
{
m_ppuVramAddr ^= 0x7000;
switch (m_ppuVramAddr & 0x3E0)
{
case 0x3A0: m_ppuVramAddr ^= 0xBA0; break;
case 0x3E0: m_ppuVramAddr ^= 0x3E0; break;
default: m_ppuVramAddr += 0x20; break;
}
}
}
else
{
// Increment X
if((m_ppuVramAddr & 0x001F) == 0x001F)
m_ppuVramAddr ^= 0x041F;
else
m_ppuVramAddr++;
}
}
void Ppu::sprFetch0()
{
m_ppuSprfetchSlot = (((m_ppuClockH - 1) >> 3) & 7);
m_ppuSprfetchYData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4)];
m_ppuSprfetchTData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 1];
m_ppuSprfetchAtData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 2];
m_ppuSprfetchXData = m_ppuOamBankSecondary[(m_ppuSprfetchSlot * 4) + 3];
const auto pputempcomparator = (m_ppuClockV - m_ppuSprfetchYData) ^ ((m_ppuSprfetchAtData & 0x80) != 0 ? 0x0F : 0x00);
if(m_ppuReg2000SpriteSize == 0x10)
m_ppuSprfetchLbAddr = (m_ppuSprfetchTData << 0x0C & 0x1000) | (m_ppuSprfetchTData << 0x04 & 0x0FE0) |
(pputempcomparator << 0x01 & 0x0010) | (pputempcomparator & 0x0007);
else
m_ppuSprfetchLbAddr = m_ppuReg2000SpritePatternTableAddressFor8x8Sprites | (m_ppuSprfetchTData << 0x04) | (pputempcomparator & 0x0007);
m_emu.memory().board()->onPpuAddressUpdate(m_ppuSprfetchLbAddr);
}
void Ppu::sprFetch1()
{
// Fetch tile low-bit data
m_ppuIsSprfetch = true;
m_ppuSprfetchLbData = m_emu.memory().board()->readChr(m_ppuSprfetchLbAddr);
m_ppuIsSprfetch = false;
}
void Ppu::sprFetch2()
{
m_ppuSprfetchHbAddr = m_ppuSprfetchLbAddr | 0x08;
m_emu.memory().board()->onPpuAddressUpdate(m_ppuSprfetchHbAddr);
}
void Ppu::sprFetch3()
{
if(m_ppuSprfetchXData == 255)
return;
// Fetch tile high-bit data
m_ppuIsSprfetch = true;
m_ppuSprfetchHbData = m_emu.memory().board()->readChr(m_ppuSprfetchHbAddr);
m_ppuIsSprfetch = false;
// Render the sprite
int ppuBkgRenderPos = m_ppuSprfetchXData;
if((m_ppuSprfetchAtData & 0x40) == 0)
{
// Rendering sprite pixel
for(auto i = 0; i < 8; i++)
{
if(ppuBkgRenderPos < 255)
{
const auto temp = ((m_ppuSprfetchAtData << 2) & 0xC) | (m_ppuSprfetchLbData >> 7 & 1) | (m_ppuSprfetchHbData >> 6 & 2);
if((temp & 3) != 0 && (m_ppuSprPixels[ppuBkgRenderPos] & 3) == 0)
m_ppuSprPixels[ppuBkgRenderPos] = temp;
if(m_ppuSprfetchSlot == 0 && m_ppuSprite0ShouldHit)
{
//m_ppuSprite0ShouldHit = false;
m_ppuSprPixels[ppuBkgRenderPos] |= 0x4000;// Sprite 0
}
if((m_ppuSprfetchAtData & 0x20) == 0)
m_ppuSprPixels[ppuBkgRenderPos] |= 0x8000;
m_ppuSprfetchLbData <<= 1;
m_ppuSprfetchHbData <<= 1;
ppuBkgRenderPos++;
}
}
}
else
{
// H flip
for(auto i = 0; i < 8; i++)
{
if(ppuBkgRenderPos < 255)
{
const auto temp = ((m_ppuSprfetchAtData << 2) & 0xC) | (m_ppuSprfetchLbData & 1) | (m_ppuSprfetchHbData << 1 & 2);
if((temp & 3) && (m_ppuSprPixels[ppuBkgRenderPos] & 3) == 0)
m_ppuSprPixels[ppuBkgRenderPos] = temp;
if(m_ppuSprfetchSlot == 0 && m_ppuSprite0ShouldHit)
{
//m_ppuSprite0ShouldHit = false;
m_ppuSprPixels[ppuBkgRenderPos] |= 0x4000;// Sprite 0
}
if((m_ppuSprfetchAtData & 0x20) == 0)
m_ppuSprPixels[ppuBkgRenderPos] |= 0x8000;
m_ppuSprfetchLbData >>= 1;
m_ppuSprfetchHbData >>= 1;
ppuBkgRenderPos++;
}
}
}
}
void Ppu::oamReset()
{
m_ppuOamEvN = 0;
m_ppuOamEvM = 0;
m_ppuOamevSlot = 0;
m_ppuPhaseIndex = 0;
m_ppuSprite0ShouldHit = false;
}
void Ppu::oamClear()
{
m_ppuSprPixels.fill(0);
}
void Ppu::oamEvFetch()
{
m_ppuFetchData = m_ppuOamBank[(m_ppuOamEvN * 4) + m_ppuOamEvM];
}
void Ppu::oamPhase0()
{
m_ppuOamevCompare = m_ppuClockV >= m_ppuFetchData && m_ppuClockV < m_ppuFetchData + m_ppuReg2000SpriteSize;
// Check ifread data in range
if(m_ppuOamevCompare)
{
m_ppuOamBankSecondary[(m_ppuOamevSlot * 4)] = m_ppuFetchData;
m_ppuOamEvM = 1;
m_ppuPhaseIndex++;
if(m_ppuOamEvN == 0)
m_ppuSprite0ShouldHit = true;
}
else
{
m_ppuOamEvM = 0;
m_ppuOamEvN++;
if(m_ppuOamEvN == 64)
{
m_ppuOamEvN = 0;
m_ppuPhaseIndex = 8;
}
}
}
void Ppu::oamPhase1()
{
m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData;
m_ppuOamEvM = 2;
m_ppuPhaseIndex++;
}
void Ppu::oamPhase2()
{
m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData;
m_ppuOamEvM = 3;
m_ppuPhaseIndex++;
}
void Ppu::oamPhase3()
{
m_ppuOamBankSecondary[(m_ppuOamevSlot * 4) + m_ppuOamEvM] = m_ppuFetchData;
m_ppuOamEvM = 0;
m_ppuOamEvN++;
m_ppuOamevSlot++;
if(m_ppuOamEvN == 64)
{
m_ppuOamEvN = 0;
m_ppuPhaseIndex = 8;
}
else if(m_ppuOamevSlot < 8)
m_ppuPhaseIndex = 0;
else if(m_ppuOamevSlot == 8)
m_ppuPhaseIndex = 4;
}
void Ppu::oamPhase4()
{
m_ppuOamevCompare = m_ppuClockV >= m_ppuFetchData &&
m_ppuClockV < m_ppuFetchData + m_ppuReg2000SpriteSize;
// Check ifread data in range
if(m_ppuOamevCompare)
{
m_ppuOamEvM = 1;
m_ppuPhaseIndex++;
m_ppuReg2002SpriteOverflow = true;
}
else
{
m_ppuOamEvM++;
if(m_ppuOamEvM == 4)
m_ppuOamEvM = 0;
m_ppuOamEvN++;
if(m_ppuOamEvN == 64)
{
m_ppuOamEvN = 0;
m_ppuPhaseIndex = 8;
}
else
m_ppuPhaseIndex = 4;
}
}
void Ppu::oamPhase5()
{
m_ppuOamEvM = 2;
m_ppuPhaseIndex++;
}
void Ppu::oamPhase6()
{
m_ppuOamEvM = 3;
m_ppuPhaseIndex++;
}
void Ppu::oamPhase7()
{
m_ppuOamEvM = 0;
m_ppuOamEvN++;
if(m_ppuOamEvN == 64)
{
m_ppuOamEvN = 0;
m_ppuPhaseIndex = 8;
}
}
void Ppu::oamPhase8()
{
m_ppuOamEvN++;
if(m_ppuOamEvN >= 64)
m_ppuOamEvN = 0;
}
void Ppu::renderPixel()
{
if(m_ppuClockV == EmuSettings::ppuClockVBlankEnd)
return;
const auto ppuRenderX = m_ppuClockH - 1;
int ppuBkgCurrentPixel;
int ppuSprCurrentPixel;
// Get the pixels.
if(ppuRenderX < 8)
{
// This area is not rendered
if(m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen)
ppuBkgCurrentPixel = 0x3F00 | m_ppuBkgPixels[ppuRenderX + m_ppuVramFinex + 1];
else
ppuBkgCurrentPixel = 0x3F00;
if(m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen && m_ppuClockV != 0)
ppuSprCurrentPixel = 0x3F10 | (m_ppuSprPixels[ppuRenderX] & 0xFF);
else
ppuSprCurrentPixel = 0x3F10;
}
else
{
if(!m_ppuReg2001ShowBackground)
ppuBkgCurrentPixel = 0x3F00;
else
ppuBkgCurrentPixel = 0x3F00 | m_ppuBkgPixels[ppuRenderX + m_ppuVramFinex + 1];
if(!m_ppuReg2001ShowSprites || m_ppuClockV == 0)
ppuSprCurrentPixel = 0x3F10;
else
ppuSprCurrentPixel = 0x3F10 | (m_ppuSprPixels[ppuRenderX] & 0xFF);
}
int ppuCurrentPixel;
if((ppuBkgCurrentPixel & 3) == 0)
{
ppuCurrentPixel = ppuSprCurrentPixel;
goto render;
}
if((ppuSprCurrentPixel & 3) == 0)
{
ppuCurrentPixel = ppuBkgCurrentPixel;
goto render;
}
// Use priority
if(m_ppuSprPixels[ppuRenderX] & 0x8000)
ppuCurrentPixel = ppuSprCurrentPixel;
else
ppuCurrentPixel = ppuBkgCurrentPixel;
// Sprite 0 Hit
if(m_ppuSprPixels[ppuRenderX] & 0x4000)
m_ppuReg2002Sprite0Hit = true;
render:
if((ppuCurrentPixel & 0x03) == 0)
{
const auto index1 = ppuRenderX + (m_ppuClockV * SCREEN_WIDTH);
const auto index2 = m_ppuPaletteBank[ppuCurrentPixel & 0x0C] & m_ppuColorAnd;
const auto value = EmuSettings::Video::palette[index2];
m_ppuScreenPixels[index1] = value;
}
else
{
const auto index1 = ppuRenderX + (m_ppuClockV * SCREEN_WIDTH);
const auto index2 = m_ppuPaletteBank[ppuCurrentPixel & 0x1F] & m_ppuColorAnd;
const auto value = EmuSettings::Video::palette[index2];
m_ppuScreenPixels[index1] = value;
}
}
quint8 Ppu::_ioRead(const quint16 address)
{
static constexpr std::array<void (Ppu::*)(), 8> ppuRegReadFuncs {
&Ppu::read2000, &Ppu::read2001, &Ppu::read2002, &Ppu::read2003,
&Ppu::read2004, &Ppu::read2005, &Ppu::read2006, &Ppu::read2007
};
// PPU IO Registers. Emulating bus here.
m_ppuRegIoAddr = address & 0x7;
m_ppuRegAccessHappened = true;
m_ppuRegAccessW = false;
(this->*ppuRegReadFuncs[m_ppuRegIoAddr])();
return m_ppuRegIoDb;
}
quint8 Ppu::ioRead(const quint16 address)
{
auto result = _ioRead(address);
return result;
}
void Ppu::ioWrite(const quint16 address, const quint8 value)
{
// PPU IO Registers. Emulating bus here.
m_ppuRegIoAddr = address & 0x7;
m_ppuRegIoDb = value;
m_ppuRegAccessW = true;
m_ppuRegAccessHappened = true;
}
void Ppu::onRegister2000()
{
// Only writes accepted
if(!m_ppuRegAccessW)
return;
// Update vram address
m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x73FF) | ((m_ppuRegIoDb & 0x3) << 10);
if((m_ppuRegIoDb & 4) != 0)
m_ppuReg2000VramAddressIncreament = 32;
else
m_ppuReg2000VramAddressIncreament = 1;
if((m_ppuRegIoDb & 0x8) != 0)
m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0x1000;
else
m_ppuReg2000SpritePatternTableAddressFor8x8Sprites = 0x0000;
if((m_ppuRegIoDb & 0x10) != 0)
m_ppuReg2000BackgroundPatternTableAddress = 0x1000;
else
m_ppuReg2000BackgroundPatternTableAddress = 0x0000;
if((m_ppuRegIoDb & 0x20) != 0)
m_ppuReg2000SpriteSize = 0x0010;
else
m_ppuReg2000SpriteSize = 0x0008;
if(!m_ppuReg2000Vbi && ((m_ppuRegIoDb & 0x80) != 0))
{
if(m_ppuReg2002VblankStartedFlag)// Special case ! NMI can be enabled anytime ifvbl already set
m_emu.interrupts().setNmiCurrent(true);
}
m_ppuReg2000Vbi = m_ppuRegIoDb & 0x80;
if(!m_ppuReg2000Vbi)// NMI disable effect only at vbl set period (HClock between 1 and 3)
if(m_ppuIsNmiTime)// 0 - 3
m_emu.interrupts().setNmiCurrent(false);
}
void Ppu::onRegister2001()
{
// Only writes accepted
if(!m_ppuRegAccessW)
return;
m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen = m_ppuRegIoDb & 0x2;
m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen = m_ppuRegIoDb & 0x4;
m_ppuReg2001ShowBackground = m_ppuRegIoDb & 0x8;
m_ppuReg2001ShowSprites = m_ppuRegIoDb & 0x10;
m_ppuReg2001Grayscale = m_ppuRegIoDb & 0x01 ? 0x30 : 0x3F;
m_ppuReg2001Emphasis = (m_ppuRegIoDb & 0xE0) << 1;
m_ppuColorAnd = m_ppuReg2001Grayscale | m_ppuReg2001Emphasis;
}
void Ppu::onRegister2002()
{
// Only reads accepted
if(m_ppuRegAccessW)
return;
m_ppuVramFlipFlop = false;
m_ppuReg2002VblankStartedFlag = false;
if(m_ppuClockV == EmuSettings::ppuClockVBlankStart)
m_emu.interrupts().setNmiCurrent(m_ppuReg2002VblankStartedFlag & m_ppuReg2000Vbi);
}
void Ppu::onRegister2003()
{
// Only writes accepted
if(!m_ppuRegAccessW)
return;
m_ppuReg2003OamAddr = m_ppuRegIoDb;
}
void Ppu::onRegister2004()
{
if(m_ppuRegAccessW)
{
// ON Writes
m_ppuOamBank[m_ppuReg2003OamAddr] = m_ppuRegIoDb;
m_ppuReg2003OamAddr = (m_ppuReg2003OamAddr + 1) & 0xFF;
}
// Nothing happens on reads
}
void Ppu::onRegister2005()
{
// Only writes accepted
if(!m_ppuRegAccessW)
return;
if(!m_ppuVramFlipFlop)
{
m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x7FE0) | ((m_ppuRegIoDb & 0xF8) >> 3);
m_ppuVramFinex = m_ppuRegIoDb & 0x07;
}
else
m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x0C1F) | ((m_ppuRegIoDb & 0x7) << 12) | ((m_ppuRegIoDb & 0xF8) << 2);
m_ppuVramFlipFlop = !m_ppuVramFlipFlop;
}
void Ppu::onRegister2006()
{
// Only writes accepted
if(!m_ppuRegAccessW)
return;
if(!m_ppuVramFlipFlop)
m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x00FF) | ((m_ppuRegIoDb & 0x3F) << 8);
else
{
m_ppuVramAddrTemp = (m_ppuVramAddrTemp & 0x7F00) | m_ppuRegIoDb;
m_ppuVramAddr = m_ppuVramAddrTemp;
m_emu.memory().board()->onPpuAddressUpdate(m_ppuVramAddr);
}
m_ppuVramFlipFlop = !m_ppuVramFlipFlop;
}
void Ppu::onRegister2007()
{
if(m_ppuRegAccessW)
{
m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF;
// ON Writes
if(m_ppuVramAddrAccessTemp < 0x2000)
m_emu.memory().board()->writeChr(m_ppuVramAddrAccessTemp, m_ppuRegIoDb);
else if(m_ppuVramAddrAccessTemp < 0x3F00)
m_emu.memory().board()->writeNmt(m_ppuVramAddrAccessTemp, m_ppuRegIoDb);
else
{
if(m_ppuVramAddrAccessTemp & 3)
m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x1F] = m_ppuRegIoDb;
else
m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x0C] = m_ppuRegIoDb;
}
}
else
{
// ON Reads
if((m_ppuVramAddr & 0x3F00) == 0x3F00)
m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x2FFF;
else
m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF;
// Update the vram data bus
if(m_ppuVramAddrAccessTemp < 0x2000)
m_ppuVramData = m_emu.memory().board()->readChr(m_ppuVramAddrAccessTemp);
else if(m_ppuVramAddrAccessTemp < 0x3F00)
m_ppuVramData = m_emu.memory().board()->readNmt(m_ppuVramAddrAccessTemp);
}
m_ppuVramAddr = (m_ppuVramAddr + m_ppuReg2000VramAddressIncreament) & 0x7FFF;
m_emu.memory().board()->onPpuAddressUpdate(m_ppuVramAddr);
}
void Ppu::read2000()
{
}
void Ppu::read2001()
{
}
void Ppu::read2002()
{
m_ppuRegIoDb = (m_ppuRegIoDb & 0xDF) | (m_ppuReg2002SpriteOverflow ? 0x20 : 0x0);
m_ppuRegIoDb = (m_ppuRegIoDb & 0xBF) | (m_ppuReg2002Sprite0Hit ? 0x40 : 0x0);
m_ppuRegIoDb = (m_ppuRegIoDb & 0x7F) | (m_ppuReg2002VblankStartedFlag ? 0x80 : 0x0);
}
void Ppu::read2003()
{
}
void Ppu::read2004()
{
m_ppuRegIoDb = m_ppuOamBank[m_ppuReg2003OamAddr];
}
void Ppu::read2005()
{
}
void Ppu::read2006()
{
}
void Ppu::read2007()
{
m_ppuVramAddrAccessTemp = m_ppuVramAddr & 0x3FFF;
if(m_ppuVramAddrAccessTemp < 0x3F00)
// Reading will put the vram data bus into the io bus,
// then later it will transfer the data from vram datas bus into io data bus.
// This causes the 0x2007 dummy reads effect.
m_ppuRegIoDb = m_ppuVramData;
else
{
// Reading from palettes puts the value in the io bus immediately
if(m_ppuVramAddrAccessTemp & 3)
m_ppuRegIoDb = m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x1F];
else
m_ppuRegIoDb = m_ppuPaletteBank[m_ppuVramAddrAccessTemp & 0x0C];
}
}
bool Ppu::isRenderingOn() const
{
return m_ppuReg2001ShowBackground || m_ppuReg2001ShowSprites;
}
bool Ppu::isInRender() const
{
return m_ppuClockV < SCREEN_HEIGHT || m_ppuClockV == EmuSettings::ppuClockVBlankEnd;
}
void Ppu::readState(QDataStream &dataStream)
{
dataStream >> m_ppuClockH >> m_ppuClockV >> m_ppuUseOddSwap >> m_ppuIsNmiTime >> m_ppuOamBank >> m_ppuOamBankSecondary >> m_ppuPaletteBank >> m_ppuRegIoDb
>> m_ppuRegIoAddr >> m_ppuRegAccessHappened >> m_ppuRegAccessW >> m_ppuReg2000VramAddressIncreament >> m_ppuReg2000SpritePatternTableAddressFor8x8Sprites
>> m_ppuReg2000BackgroundPatternTableAddress >> m_ppuReg2000SpriteSize >> m_ppuReg2000Vbi >> m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen
>> m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen >> m_ppuReg2001ShowBackground >> m_ppuReg2001ShowSprites >> m_ppuReg2001Grayscale >> m_ppuReg2001Emphasis
>> m_ppuReg2002SpriteOverflow >> m_ppuReg2002Sprite0Hit >> m_ppuReg2002VblankStartedFlag >> m_ppuReg2003OamAddr >> m_ppuVramAddr >> m_ppuVramData >> m_ppuVramAddrTemp
>> m_ppuVramAddrAccessTemp >> m_ppuVramFlipFlop >> m_ppuVramFinex >> m_ppuBkgfetchNtAddr >> m_ppuBkgfetchNtData >> m_ppuBkgfetchAtAddr >> m_ppuBkgfetchAtData
>> m_ppuBkgfetchLbAddr >> m_ppuBkgfetchLbData >> m_ppuBkgfetchHbAddr >> m_ppuBkgfetchHbData >> m_ppuSprfetchSlot >> m_ppuSprfetchYData >> m_ppuSprfetchTData
>> m_ppuSprfetchAtData >> m_ppuSprfetchXData >> m_ppuSprfetchLbAddr >> m_ppuSprfetchLbData >> m_ppuSprfetchHbAddr >> m_ppuSprfetchHbData >> m_ppuColorAnd
>> m_ppuOamEvN >> m_ppuOamEvM >> m_ppuOamevCompare >> m_ppuOamevSlot >> m_ppuFetchData >> m_ppuPhaseIndex >> m_ppuSprite0ShouldHit;
}
void Ppu::writeState(QDataStream &dataStream) const
{
dataStream << m_ppuClockH << m_ppuClockV << m_ppuUseOddSwap << m_ppuIsNmiTime << m_ppuOamBank << m_ppuOamBankSecondary << m_ppuPaletteBank << m_ppuRegIoDb
<< m_ppuRegIoAddr << m_ppuRegAccessHappened << m_ppuRegAccessW << m_ppuReg2000VramAddressIncreament << m_ppuReg2000SpritePatternTableAddressFor8x8Sprites
<< m_ppuReg2000BackgroundPatternTableAddress << m_ppuReg2000SpriteSize << m_ppuReg2000Vbi << m_ppuReg2001ShowBackgroundInLeftmost8PixelsOfScreen
<< m_ppuReg2001ShowSpritesInLeftmost8PixelsOfScreen << m_ppuReg2001ShowBackground << m_ppuReg2001ShowSprites << m_ppuReg2001Grayscale << m_ppuReg2001Emphasis
<< m_ppuReg2002SpriteOverflow << m_ppuReg2002Sprite0Hit << m_ppuReg2002VblankStartedFlag << m_ppuReg2003OamAddr << m_ppuVramAddr << m_ppuVramData << m_ppuVramAddrTemp
<< m_ppuVramAddrAccessTemp << m_ppuVramFlipFlop << m_ppuVramFinex << m_ppuBkgfetchNtAddr << m_ppuBkgfetchNtData << m_ppuBkgfetchAtAddr << m_ppuBkgfetchAtData
<< m_ppuBkgfetchLbAddr << m_ppuBkgfetchLbData << m_ppuBkgfetchHbAddr << m_ppuBkgfetchHbData << m_ppuSprfetchSlot << m_ppuSprfetchYData << m_ppuSprfetchTData
<< m_ppuSprfetchAtData << m_ppuSprfetchXData << m_ppuSprfetchLbAddr << m_ppuSprfetchLbData << m_ppuSprfetchHbAddr << m_ppuSprfetchHbData << m_ppuColorAnd
<< m_ppuOamEvN << m_ppuOamEvM << m_ppuOamevCompare << m_ppuOamevSlot << m_ppuFetchData << m_ppuPhaseIndex << m_ppuSprite0ShouldHit;
}
const std::array<qint32, Ppu::SCREEN_WIDTH*Ppu::SCREEN_HEIGHT> &Ppu::screenPixels() const
{
return m_ppuScreenPixels;
}