diff --git a/Core/GbEventManager.cpp b/Core/GbEventManager.cpp index bfabc88..b701e8e 100644 --- a/Core/GbEventManager.cpp +++ b/Core/GbEventManager.cpp @@ -234,14 +234,10 @@ void GbEventManager::GetDisplayBuffer(uint32_t* buffer, uint32_t bufferSize, Eve } } - constexpr uint32_t vblankScanlineColor = 0xFF55FFFF; constexpr uint32_t currentScanlineColor = 0xFFFFFF55; - int vblankScanline = GbEventManager::VBlankScanline * 2 * GbEventManager::ScanlineWidth; uint32_t scanlineOffset = _snapshotScanline * 2 * GbEventManager::ScanlineWidth; - for(int i = 0; i < GbEventManager::ScanlineWidth; i++) { - buffer[vblankScanline + i] = vblankScanlineColor; - buffer[vblankScanline + GbEventManager::ScanlineWidth + i] = vblankScanlineColor; - if(_snapshotScanline != 0) { + if(_snapshotScanline != 0) { + for(int i = 0; i < GbEventManager::ScanlineWidth; i++) { buffer[scanlineOffset + i] = currentScanlineColor; buffer[scanlineOffset + GbEventManager::ScanlineWidth + i] = currentScanlineColor; } diff --git a/Core/GbPpu.cpp b/Core/GbPpu.cpp index 205784a..94e6612 100644 --- a/Core/GbPpu.cpp +++ b/Core/GbPpu.cpp @@ -12,6 +12,9 @@ #include "../Utilities/HexUtilities.h" #include "../Utilities/Serializer.h" +constexpr uint16_t bwRgbPalette[4] = { 0x7FFF, 0x6318, 0x318C, 0x0000 }; +constexpr uint16_t evtColors[6] = { 0x18C6, 0x294A, 0x108C, 0x4210, 0x3084, 0x1184 }; + void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryManager, uint8_t* vram, uint8_t* oam) { _console = console; @@ -81,10 +84,8 @@ void GbPpu::Exec() return; } - ExecCycle(); - ExecCycle(); - if(!_memoryManager->IsHighSpeed()) { - ExecCycle(); + uint8_t cyclesToRun = _memoryManager->IsHighSpeed() ? 2 : 4; + for(int i = 0; i < cyclesToRun; i++) { ExecCycle(); } } @@ -93,109 +94,157 @@ void GbPpu::ExecCycle() { _state.Cycle++; - PpuMode mode = _state.Mode; - bool lyCoincidenceFlag = _state.LyCoincidenceFlag; - _state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline); + PpuMode oldMode = _state.Mode; + bool oldCoincidenceFlag = _state.LyCoincidenceFlag; - if(_state.Cycle == 456) { - _state.Cycle = 0; + switch(_state.Cycle) { + case 4: { + if(_state.Scanline < 144) { + _spriteCount = 0; + _prevSprite = 0; + ChangeMode(PpuMode::OamEvaluation); + } else if(_state.Scanline == 144) { + ChangeMode(PpuMode::VBlank); + _memoryManager->RequestIrq(GbIrqSource::VerticalBlank); + SendFrame(); + } + break; + } - _state.Scanline++; - _spriteCount = 0; + case 84: { + if(_state.Scanline < 144) { + std::sort(_spriteIndexes, _spriteIndexes + _spriteCount, [=](uint8_t a, uint8_t b) { + if(_oam[a + 1] == _oam[b + 1]) { + return a < b; + } else { + return _oam[a + 1] < _oam[b + 1]; + } + }); + std::sort(_spriteX, _spriteX + _spriteCount); + ChangeMode(PpuMode::Drawing); + ResetRenderer(); + } + break; + } - if(_state.Scanline < 144) { - ChangeMode(PpuMode::OamEvaluation); - } else if(_state.Scanline == 144) { - ChangeMode(PpuMode::VBlank); - _memoryManager->RequestIrq(GbIrqSource::VerticalBlank); + case 456: { + _state.Cycle = 0; + _state.Scanline++; - SendFrame(); - } else if(_state.Scanline == 154) { - _state.Scanline = 0; - _console->ProcessEvent(EventType::StartFrame); - if(_console->IsDebugging()) { - _currentEventViewerBuffer = _currentEventViewerBuffer == _eventViewerBuffers[0] ? _eventViewerBuffers[1] : _eventViewerBuffers[0]; - for(int i = 0; i < 456 * 154; i++) { - _currentEventViewerBuffer[i] = 0x18C6; + if(_state.Scanline < 144) { + ChangeMode(PpuMode::HBlank); + } else if(_state.Scanline == 154) { + _state.Scanline = 0; + ChangeMode(PpuMode::HBlank); + _console->ProcessEvent(EventType::StartFrame); + if(_console->IsDebugging()) { + _currentEventViewerBuffer = _currentEventViewerBuffer == _eventViewerBuffers[0] ? _eventViewerBuffers[1] : _eventViewerBuffers[0]; } } + break; } + } + + if(_state.Mode == PpuMode::Drawing) { + if(_drawnPixels < 160) { + RunDrawCycle(); + } else { + ChangeMode(PpuMode::HBlank); + } + } else if(_state.Mode == PpuMode::OamEvaluation) { + RunSpriteEvaluation(); } - if(mode != _state.Mode || _state.LyCoincidenceFlag != lyCoincidenceFlag) { + UpdateLyCoincidenceFlag(); + if(_state.Mode != oldMode || _state.LyCoincidenceFlag != oldCoincidenceFlag) { UpdateStatIrq(); } - _console->ProcessPpuCycle(_state.Scanline, _state.Cycle); + ProcessPpuCycle(); +} - //TODO: Dot-based renderer, currently draws at the end of the scanline - if(_state.Scanline < 144) { - if(_state.Mode == PpuMode::Drawing) { - bool fetchWindow = _state.WindowEnabled && _shiftedPixels >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY; - if(_fetchWindow != fetchWindow) { - //Switched between window & background, reset fetcher & pixel FIFO - _fetchWindow = fetchWindow; - _fetchColumn = 0; - _fetcherStep = 0; - _fifoPosition = 0; - _fifoSize = 0; +void GbPpu::ProcessPpuCycle() +{ + if(_console->IsDebugging()) { + _console->ProcessPpuCycle(_state.Scanline, _state.Cycle); + if(_state.Mode <= PpuMode::OamEvaluation) { + _currentEventViewerBuffer[456 * _state.Scanline + _state.Cycle] = evtColors[(int)_state.Mode]; + } else if(_prevDrawnPixels != _drawnPixels && _drawnPixels > 0) { + uint16_t color = _currentBuffer[_state.Scanline * 256 + (_drawnPixels - 1)]; + _currentEventViewerBuffer[456 * _state.Scanline + _state.Cycle] = color; + } else { + _currentEventViewerBuffer[456 * _state.Scanline + _state.Cycle] = evtColors[(int)_evtColor]; + } + _prevDrawnPixels = _drawnPixels; + } +} + +void GbPpu::RunDrawCycle() +{ + if(_state.Cycle < 89) { + //Idle cycles + _evtColor = EvtColor::RenderingIdle; + return; + } + + bool fetchWindow = _state.WindowEnabled && _drawnPixels >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY; + if(_fetchWindow != fetchWindow) { + //Switched between window & background, reset fetcher & pixel FIFO + _fetchWindow = fetchWindow; + _fetchColumn = 0; + + _bgFetcher.Step = 0; + _bgFifo.Reset(); + + //Idle cycle when switching to window + _evtColor = EvtColor::RenderingIdle; + return; + } + + if(_fetchSprite == -1 && _bgFifo.Size > 0) { + if(_drawnPixels >= 0) { + uint16_t outOffset = _state.Scanline * 256 + _drawnPixels; + + bool isSprite = false; + GbFifoEntry entry = _bgFifo.Content[_bgFifo.Position]; + if(_oamFifo.Size > 0 && _oamFifo.Content[_oamFifo.Position].Color != 0 && (entry.Color == 0 || !(_oamFifo.Content[_oamFifo.Position].Attributes & 0x80))) { + entry = _oamFifo.Content[_oamFifo.Position]; + isSprite = true; } - - ClockTileFetcher(); - if(_fetchSprite == -1 && _fifoSize > 8) { - if(!fetchWindow && _shiftedPixels < (_state.ScrollX & 0x07)) { - //Throw away pixels that are outside the screen due to the ScrollX value - _fifoPosition = (_fifoPosition + 1) & 0x0F; + uint16_t rgbColor; + if(_gameboy->IsCgb()) { + if(isSprite) { + rgbColor = _state.CgbObjPalettes[entry.Color | ((entry.Attributes & 0x07) << 2)]; } else { - uint16_t outOffset = _state.Scanline * 256 + _drawnPixels; - - FifoEntry& entry = _fifoContent[_fifoPosition]; - - uint16_t rgbColor; - if(_gameboy->IsCgb()) { - if(entry.Attributes & 0x40) { - rgbColor = _state.CgbObjPalettes[entry.Color | ((entry.Attributes & 0x07) << 2)]; - } else { - rgbColor = _state.CgbBgPalettes[entry.Color | ((entry.Attributes & 0x07) << 2)]; - } - } else { - uint16_t palette[4]; - if(entry.Attributes & 0x40) { - GetPalette(palette, (entry.Attributes & 0x10) ? _state.ObjPalette1 : _state.ObjPalette0); - } else { - GetPalette(palette, _state.BgPalette); - } - rgbColor = palette[entry.Color]; - } - - _currentBuffer[outOffset] = rgbColor; - _fifoPosition = (_fifoPosition + 1) & 0x0F; - - if(_console->IsDebugging()) { - _currentEventViewerBuffer[456 * _state.Scanline + _state.Cycle] = rgbColor; - } - - _drawnPixels++; + rgbColor = _state.CgbBgPalettes[entry.Color | ((entry.Attributes & 0x07) << 2)]; + } + } else { + if(isSprite) { + rgbColor = bwRgbPalette[(((entry.Attributes & 0x10) ? _state.ObjPalette1 : _state.ObjPalette0) >> (entry.Color * 2)) & 0x03]; + } else { + rgbColor = bwRgbPalette[(_state.BgPalette >> (entry.Color * 2)) & 0x03]; } - _fifoSize--; - _shiftedPixels++; } + _currentBuffer[outOffset] = rgbColor; + } - if(_drawnPixels >= 160) { - ChangeMode(PpuMode::HBlank); - } - } else if(_state.Cycle < 80) { - RunSpriteEvaluation(); + _bgFifo.Pop(); + _drawnPixels++; + + if(_oamFifo.Size > 0) { + _oamFifo.Pop(); } } + + ClockTileFetcher(); } void GbPpu::RunSpriteEvaluation() { if(_state.Cycle & 0x01) { if(_spriteCount < 10) { - uint8_t spriteIndex = (_state.Cycle >> 1) * 4; + uint8_t spriteIndex = ((_state.Cycle - 4) >> 1) * 4; int16_t sprY = (int16_t)_oam[spriteIndex] - 16; if(_state.Scanline >= sprY && _state.Scanline < sprY + (_state.LargeSprites ? 16 : 8)) { _spriteX[_spriteCount] = _oam[spriteIndex + 1]; @@ -203,159 +252,183 @@ void GbPpu::RunSpriteEvaluation() _spriteCount++; } } - - if(_state.Cycle == 79) { - ChangeMode(PpuMode::Drawing); - ResetRenderer(); - } } else { - //Hardware probably reads sprite Y and loads the X counter with the value on the next cycle + //TODO check proper timing for even&odd cycles } } void GbPpu::ResetRenderer() { //Reset fetcher & pixel FIFO - _fetcherStep = 0; - _fifoPosition = 0; - _fifoSize = 0; - _shiftedPixels = 0; - _drawnPixels = 0; + _oamFifo.Reset(); + _oamFetcher.Step = 0; + + _bgFifo.Reset(); + _bgFifo.Size = 8; + _bgFetcher.Step = 0; + + _drawnPixels = -8 - (_state.ScrollX & 0x07); _fetchSprite = -1; _fetchWindow = false; _fetchColumn = _state.ScrollX / 8; } -void GbPpu::ClockTileFetcher() +void GbPpu::ClockSpriteFetcher() { - if(_fetchSprite < 0 && _fifoSize >= 8) { - for(int i = 0; i < _spriteCount; i++) { - if((int)_spriteX[i] - 8 <= _drawnPixels) { + switch(_oamFetcher.Step++) { + case 1: { + //Fetch tile index + int16_t sprY = (int16_t)_oam[_fetchSprite] - 16; + uint8_t sprTile = _oam[_fetchSprite + 2]; + uint8_t sprAttr = _oam[_fetchSprite + 3]; + bool vMirror = (sprAttr & 0x40) != 0; + uint16_t tileBank = _gameboy->IsCgb() ? ((sprAttr & 0x08) ? 0x2000 : 0x0000) : 0; + + uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY); + if(_state.LargeSprites) { + sprTile &= 0xFE; + } + + uint16_t sprTileAddr = (sprTile * 16 + sprOffsetY * 2) | tileBank; + _oamFetcher.Addr = sprTileAddr; + _oamFetcher.Attributes = sprAttr; + break; + } + + case 3: _oamFetcher.LowByte = _vram[_oamFetcher.Addr]; break; + + case 5: { + //Fetch sprite data (high byte) + _oamFetcher.HighByte = _vram[_oamFetcher.Addr + 1]; + PushSpriteToPixelFifo(); + break; + } + } +} + +void GbPpu::FindNextSprite() +{ + if(_prevSprite < _spriteCount && _fetchSprite < 0 && (_state.SpritesEnabled || _gameboy->IsCgb())) { + for(int i = _prevSprite; i < _spriteCount; i++) { + if((int)_spriteX[i] - 8 == _drawnPixels) { _fetchSprite = _spriteIndexes[i]; - _fetchSpriteOffset = _spriteX[i] < 8 ? (8 - _spriteX[i]) : 0; - _spriteX[i] = 0xFF; //prevent processing this sprite again - _fetcherStep = 0; + _prevSprite++; + _oamFetcher.Step = 0; break; } } } +} - switch(_fetcherStep++) { - case 0: { - //Fetch tile index - if(_fetchSprite >= 0) { - int16_t sprY = (int16_t)_oam[_fetchSprite] - 16; - uint8_t sprTile = _oam[_fetchSprite + 2]; - uint8_t sprAttr = _oam[_fetchSprite + 3]; - bool vMirror = (sprAttr & 0x40) != 0; - uint16_t tileBank = _gameboy->IsCgb() ? ((sprAttr & 0x08) ? 0x2000 : 0x0000) : 0; - - uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY); - if(_state.LargeSprites) { - sprTile &= 0xFE; - } - - uint16_t sprTileAddr = (sprTile * 16 + sprOffsetY * 2) | tileBank; - _fetcherTileAddr = sprTileAddr; - _fetcherAttributes = (sprAttr & 0xBF) | 0x40; //Use 0x40 as a marker to designate this pixel as a sprite pixel - } else { - uint16_t tilemapAddr; - uint8_t yOffset; - if(_fetchWindow) { - tilemapAddr = _state.WindowTilemapSelect ? 0x1C00 : 0x1800; - yOffset = _state.Scanline - _state.WindowY; - } else { - tilemapAddr = _state.BgTilemapSelect ? 0x1C00 : 0x1800; - yOffset = _state.ScrollY + _state.Scanline; - } - - uint8_t row = yOffset >> 3; - uint16_t tileAddr = tilemapAddr + _fetchColumn + row * 32; - uint8_t tileIndex = _vram[tileAddr]; - - uint8_t attributes = _gameboy->IsCgb() ? _vram[tileAddr | 0x2000] : 0; - bool vMirror = (attributes & 0x40) != 0; - uint16_t tileBank = (attributes & 0x08) ? 0x2000 : 0x0000; - - uint16_t baseTile = _state.BgTileSelect ? 0 : 0x1000; - uint8_t tileY = vMirror ? (7 - (yOffset & 0x07)) : (yOffset & 0x07); - uint16_t tileRowAddr = baseTile + (baseTile ? (int8_t)tileIndex * 16 : tileIndex * 16) + tileY * 2; - tileRowAddr |= tileBank; - _fetcherTileAddr = tileRowAddr; - _fetcherAttributes = (attributes & 0xBF); - } - break; - } - - case 2: { - //Fetch tile data (low byte) - _fetcherTileLowByte = _vram[_fetcherTileAddr]; - break; - } - - case 4: { - //Fetch tile data (high byte) - _fetcherTileHighByte = _vram[_fetcherTileAddr + 1]; - break; - } +void GbPpu::ClockTileFetcher() +{ + FindNextSprite(); + if(_fetchSprite >= 0 && _bgFetcher.Step >= 5 && _bgFifo.Size > 0) { + _evtColor = EvtColor::RenderingOamLoad; + ClockSpriteFetcher(); + FindNextSprite(); + return; } - if(_fetcherStep > 4) { - if(_fetchSprite >= 0) { - PushSpriteToPixelFifo(); - } else if(_fifoSize <= 8) { - PushTileToPixelFifo(); + _evtColor = EvtColor::RenderingBgLoad; + + switch(_bgFetcher.Step++) { + case 1: { + //Fetch tile index + uint16_t tilemapAddr; + uint8_t yOffset; + if(_fetchWindow) { + tilemapAddr = _state.WindowTilemapSelect ? 0x1C00 : 0x1800; + yOffset = _state.Scanline - _state.WindowY; + } else { + tilemapAddr = _state.BgTilemapSelect ? 0x1C00 : 0x1800; + yOffset = _state.ScrollY + _state.Scanline; + } + + uint8_t row = yOffset >> 3; + uint16_t tileAddr = tilemapAddr + _fetchColumn + row * 32; + uint8_t tileIndex = _vram[tileAddr]; + + uint8_t attributes = _gameboy->IsCgb() ? _vram[tileAddr | 0x2000] : 0; + bool vMirror = (attributes & 0x40) != 0; + uint16_t tileBank = (attributes & 0x08) ? 0x2000 : 0x0000; + + uint16_t baseTile = _state.BgTileSelect ? 0 : 0x1000; + uint8_t tileY = vMirror ? (7 - (yOffset & 0x07)) : (yOffset & 0x07); + uint16_t tileRowAddr = baseTile + (baseTile ? (int8_t)tileIndex * 16 : tileIndex * 16) + tileY * 2; + tileRowAddr |= tileBank; + _bgFetcher.Addr = tileRowAddr; + _bgFetcher.Attributes = (attributes & 0xBF); + break; } + + case 3: { + //Fetch tile data (low byte) + _bgFetcher.LowByte = _vram[_bgFetcher.Addr]; + break; + } + + case 5: { + //Fetch tile data (high byte) + _bgFetcher.HighByte = _vram[_bgFetcher.Addr + 1]; + + //Fallthrough + } + + case 6: + case 7: + if(_bgFifo.Size == 0) { + PushTileToPixelFifo(); + } else if(_bgFetcher.Step == 8) { + //Wait until fifo is empty to push pixels + _bgFetcher.Step = 7; + } + break; } } void GbPpu::PushSpriteToPixelFifo() { _fetchSprite = -1; - _fetcherStep = 0; + _oamFetcher.Step = 0; if(!_state.SpritesEnabled) { return; } - uint8_t pos = _fifoPosition; + uint8_t pos = _oamFifo.Position; //Overlap sprite - for(int i = _fetchSpriteOffset; i < 8; i++) { - uint8_t shift = (_fetcherAttributes & 0x20) ? i : (7 - i); - uint8_t bits = ((_fetcherTileLowByte >> shift) & 0x01); - bits |= ((_fetcherTileHighByte >> shift) & 0x01) << 1; + for(int i = 0; i < 8; i++) { + uint8_t shift = (_oamFetcher.Attributes & 0x20) ? i : (7 - i); + uint8_t bits = ((_oamFetcher.LowByte >> shift) & 0x01); + bits |= ((_oamFetcher.HighByte >> shift) & 0x01) << 1; - if(bits > 0) { - if(!(_fifoContent[pos].Attributes & 0x40) && !(_fifoContent[pos].Attributes & 0x80) && (_fifoContent[pos].Color == 0 || !(_fetcherAttributes & 0x80))) { - //Draw pixel if the current pixel: - // -Is a BG pixel, and - // -Does not have the BG priority flag turned on (CGB only) - // -Is color 0, or the sprite is NOT background priority - _fifoContent[pos].Color = bits; - _fifoContent[pos].Attributes = _fetcherAttributes; - } + if(bits > 0 && _oamFifo.Content[pos].Color == 0) { + _oamFifo.Content[pos].Color = bits; + _oamFifo.Content[pos].Attributes = _oamFetcher.Attributes; } - pos = (pos + 1) & 0x0F; + pos = (pos + 1) & 0x07; } + _oamFifo.Size = 8; } void GbPpu::PushTileToPixelFifo() { //Add new tile to fifo for(int i = 0; i < 8; i++) { - uint8_t shift = (_fetcherAttributes & 0x20) ? i : (7 - i); - uint8_t bits = ((_fetcherTileLowByte >> shift) & 0x01); - bits |= ((_fetcherTileHighByte >> shift) & 0x01) << 1; + uint8_t shift = (_bgFetcher.Attributes & 0x20) ? i : (7 - i); + uint8_t bits = ((_bgFetcher.LowByte >> shift) & 0x01); + bits |= ((_bgFetcher.HighByte >> shift) & 0x01) << 1; - uint8_t pos = (_fifoPosition + _fifoSize + i) & 0x0F; - _fifoContent[pos].Color = _state.BgEnabled ? bits : 0; - _fifoContent[pos].Attributes = _fetcherAttributes; + _bgFifo.Content[i].Color = _state.BgEnabled ? bits : 0; + _bgFifo.Content[i].Attributes = _bgFetcher.Attributes; } _fetchColumn = (_fetchColumn + 1) & 0x1F; - _fifoSize += 8; - _fetcherStep = 0; + _bgFifo.Position = 0; + _bgFifo.Size = 8; + _bgFetcher.Step = 0; } void GbPpu::ChangeMode(PpuMode mode) @@ -363,11 +436,24 @@ void GbPpu::ChangeMode(PpuMode mode) _state.Mode = mode; } +void GbPpu::UpdateLyCoincidenceFlag() +{ + if(_state.Scanline < 153) { + _state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline) && (_state.Cycle >= 4 || _state.Scanline == 0); + } else { + if(_state.LyCompare == 153) { + _state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline) && _state.Cycle >= 4 && _state.Cycle < 8; + } else { + _state.LyCoincidenceFlag = (_state.LyCompare == 0) && _state.Cycle >= 12; + } + } +} + void GbPpu::UpdateStatIrq() { bool irqFlag = ( _state.LcdEnabled && - ((_state.Scanline == _state.LyCompare && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) || + ((_state.LyCoincidenceFlag && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) || (_state.Mode == PpuMode::HBlank && (_state.Status & GbPpuStatusFlags::HBlankIrq)) || (_state.Mode == PpuMode::OamEvaluation && (_state.Status & GbPpuStatusFlags::OamIrq)) || (_state.Mode == PpuMode::VBlank && ((_state.Status & GbPpuStatusFlags::VBlankIrq) || (_state.Status & GbPpuStatusFlags::OamIrq)))) @@ -381,11 +467,10 @@ void GbPpu::UpdateStatIrq() void GbPpu::GetPalette(uint16_t out[4], uint8_t palCfg) { - constexpr uint16_t rgbPalette[4] = { 0x7FFF, 0x6318, 0x318C, 0x0000 }; - out[0] = rgbPalette[palCfg & 0x03]; - out[1] = rgbPalette[(palCfg >> 2) & 0x03]; - out[2] = rgbPalette[(palCfg >> 4) & 0x03]; - out[3] = rgbPalette[(palCfg >> 6) & 0x03]; + out[0] = bwRgbPalette[palCfg & 0x03]; + out[1] = bwRgbPalette[(palCfg >> 2) & 0x03]; + out[2] = bwRgbPalette[(palCfg >> 4) & 0x03]; + out[3] = bwRgbPalette[(palCfg >> 6) & 0x03]; } void GbPpu::SendFrame() @@ -463,9 +548,20 @@ void GbPpu::Write(uint16_t addr, uint8_t value) std::fill(_outputBuffers[1], _outputBuffers[1] + 256 * 239, 0x7FFF); SendFrame(); } else { - _state.Cycle = 6; + _state.Cycle = 4; _state.Scanline = 0; - ChangeMode(PpuMode::Drawing); + ResetRenderer(); + ChangeMode(PpuMode::HBlank); + UpdateLyCoincidenceFlag(); + UpdateStatIrq(); + + _console->ProcessEvent(EventType::StartFrame); + if(_console->IsDebugging()) { + _currentEventViewerBuffer = _currentEventViewerBuffer == _eventViewerBuffers[0] ? _eventViewerBuffers[1] : _eventViewerBuffers[0]; + for(int i = 0; i < 456 * 154; i++) { + _currentEventViewerBuffer[i] = 0x18C6; + } + } } } _state.WindowTilemapSelect = (value & 0x40) != 0; @@ -477,7 +573,11 @@ void GbPpu::Write(uint16_t addr, uint8_t value) _state.BgEnabled = (value & 0x01) != 0; break; - case 0xFF41: _state.Status = value & 0xF8; break; + case 0xFF41: + _state.Status = value & 0xF8; + UpdateStatIrq(); + break; + case 0xFF42: _state.ScrollY = value; break; case 0xFF43: _state.ScrollX = value; break; case 0xFF45: _state.LyCompare = value; break; @@ -524,8 +624,12 @@ uint8_t GbPpu::ReadOam(uint8_t addr) void GbPpu::WriteOam(uint8_t addr, uint8_t value, bool forDma) { //During DMA or rendering/oam evaluation, ignore writes to OAM - if(addr < 0xA0 && (forDma || ((int)_state.Mode <= (int)PpuMode::VBlank && !_memoryManager->IsOamDmaRunning()))) { - _oam[addr] = value; + //The DMA controller is always allowed to write to OAM (presumably the PPU can't read OAM during that time? TODO implement) + //On the DMG, there is apparently a ~4 clock gap (80 to 84) between OAM evaluation & rendering where writing is allowed? + if(addr < 0xA0) { + if(forDma || ((int)_state.Mode <= (int)PpuMode::VBlank && !_memoryManager->IsOamDmaRunning()) || (_state.Cycle >= 80 && _state.Cycle < 84)) { + _oam[addr] = value; + } } } @@ -606,16 +710,17 @@ void GbPpu::Serialize(Serializer& s) s.StreamArray(_state.CgbObjPalettes, 4 * 8); s.Stream( - _fifoPosition, _fifoSize, _shiftedPixels, _drawnPixels, - _fetcherAttributes, _fetcherStep, _fetchColumn, _fetcherTileAddr, - _fetcherTileLowByte, _fetcherTileHighByte, _fetchWindow, _fetchSprite, - _spriteCount, _fetchSpriteOffset + _bgFetcher.Attributes, _bgFetcher.Step, _bgFetcher.Addr, _bgFetcher.LowByte, _bgFetcher.HighByte, + _oamFetcher.Attributes, _oamFetcher.Step, _oamFetcher.Addr, _oamFetcher.LowByte, _oamFetcher.HighByte, + _drawnPixels, _fetchColumn, _fetchWindow, _fetchSprite, _spriteCount, _prevSprite, + _bgFifo.Position, _bgFifo.Size, _oamFifo.Position, _oamFifo.Size ); + for(int i = 0; i < 8; i++) { + s.Stream(_bgFifo.Content[i].Color, _bgFifo.Content[i].Attributes); + s.Stream(_oamFifo.Content[i].Color, _oamFifo.Content[i].Attributes); + } + s.StreamArray(_spriteX, 10); s.StreamArray(_spriteIndexes, 10); - - for(int i = 0; i < 16; i++) { - s.Stream(_fifoContent[i].Color, _fifoContent[i].Attributes); - } } diff --git a/Core/GbPpu.h b/Core/GbPpu.h index 67adef8..1520557 100644 --- a/Core/GbPpu.h +++ b/Core/GbPpu.h @@ -7,12 +7,6 @@ class Console; class Gameboy; class GbMemoryManager; -struct FifoEntry -{ - uint8_t Color; - uint8_t Attributes; -}; - class GbPpu : public ISerializable { private: @@ -25,41 +19,45 @@ private: uint16_t* _eventViewerBuffers[2] = {}; uint16_t* _currentEventViewerBuffer = nullptr; + EvtColor _evtColor = EvtColor::HBlank; + int16_t _prevDrawnPixels = 0; uint8_t* _vram = nullptr; uint8_t* _oam = nullptr; uint64_t _lastFrameTime = 0; - uint8_t _fifoPosition = 0; - uint8_t _fifoSize = 0; - FifoEntry _fifoContent[16]; - uint8_t _shiftedPixels = 0; - uint8_t _drawnPixels = 0; + GbPpuFifo _bgFifo; + GbPpuFetcher _bgFetcher; - uint8_t _fetcherAttributes = 0; - uint8_t _fetcherStep = 0; + GbPpuFifo _oamFifo; + GbPpuFetcher _oamFetcher; + + int16_t _drawnPixels = 0; uint8_t _fetchColumn = 0; - uint16_t _fetcherTileAddr = 0; - uint8_t _fetcherTileLowByte = 0; - uint8_t _fetcherTileHighByte = 0; bool _fetchWindow = false; int16_t _fetchSprite = -1; - int16_t _fetchSpriteOffset = -1; + uint8_t _prevSprite = 0; uint8_t _spriteCount = 0; uint8_t _spriteX[10] = {}; uint8_t _spriteIndexes[10] = {}; - void ExecCycle(); - void RunSpriteEvaluation(); - void ResetRenderer(); - void ClockTileFetcher(); - void PushSpriteToPixelFifo(); - void PushTileToPixelFifo(); + __forceinline void ProcessPpuCycle(); - void ChangeMode(PpuMode mode); - void UpdateStatIrq(); + __forceinline void ExecCycle(); + __forceinline void RunDrawCycle(); + __forceinline void RunSpriteEvaluation(); + void ResetRenderer(); + void ClockSpriteFetcher(); + void FindNextSprite(); + __forceinline void ClockTileFetcher(); + __forceinline void PushSpriteToPixelFifo(); + __forceinline void PushTileToPixelFifo(); + + __forceinline void ChangeMode(PpuMode mode); + __forceinline void UpdateLyCoincidenceFlag(); + __forceinline void UpdateStatIrq(); void WriteCgbPalette(uint8_t& pos, uint16_t* pal, bool autoInc, uint8_t value); diff --git a/Core/GbTypes.h b/Core/GbTypes.h index 8b18fce..5a5a231 100644 --- a/Core/GbTypes.h +++ b/Core/GbTypes.h @@ -102,6 +102,53 @@ namespace GbPpuStatusFlags }; } +enum class EvtColor +{ + HBlank = 0, + VBlank = 1, + OamEvaluation = 2, + RenderingIdle = 3, + RenderingBgLoad = 4, + RenderingOamLoad = 5, + LcdColor = 6, +}; + +struct GbFifoEntry +{ + uint8_t Color; + uint8_t Attributes; +}; + +struct GbPpuFifo +{ + uint8_t Position = 0; + uint8_t Size = 0; + GbFifoEntry Content[8] = {}; + + void Reset() + { + Size = 0; + Position = 0; + memset(Content, 0, sizeof(Content)); + } + + void Pop() + { + Content[Position].Color = 0; + Position = (Position + 1) & 0x07; + Size--; + } +}; + +struct GbPpuFetcher +{ + uint16_t Addr = 0; + uint8_t Attributes = 0; + uint8_t Step = 0; + uint8_t LowByte = 0; + uint8_t HighByte = 0; +}; + struct GbPpuState { uint8_t Scanline;