private void ChangeMode(LcdMode newMode) { _currentMode = newMode; _counter = 0; if (newMode == LcdMode.OamSearch) { if (_stat.OamSearchInterruptEnabled.Value) { _lcdStatusInterrupt.Trigger(); } } else if (newMode == LcdMode.DataTransfer) { // Do nothing. } else if (newMode == LcdMode.HorizontalBlank) { RenderScanline(_ly.Value); if (_stat.HBlankInterruptEnabled.Value) { _lcdStatusInterrupt.Trigger(); } } else if (newMode == LcdMode.VerticalBlank) { //Console.WriteLine($"Vblank after {_globalCounter - _lastVblank} cycles."); _lastVblank = _globalCounter; OnCompletedFrame(); _vblankInterrupt.Trigger(); // TODO does vblank really have two interrupts? if (_stat.VBlankInterruptEnabled.Value) { _vblankInterrupt.Trigger(); } } }
void SetStatLcdMode(LcdMode mode) { stat = (byte)((stat & 0xFC) | (byte)mode); }
void SetLcdMode(LcdMode mode) { this.lcdMode = mode; SetStatLcdMode(mode); }
public override void Update(uint cycleCount) { if (_oamDma.Status != OamDmaStatus.Inactive) { UpdateOamDma(cycleCount); } // only run lcd controller update if display is enabled if (!_displayEnabled) { return; } _frameClock += cycleCount; // LCD runs for 70224 cpu cycles a frame, 456 cycles a scanline and 154 total scanlines // 10 of the scanlines are VBlank and the rest are the active portion of the cycle // mode 2, 3 and 0 occur in he first 65664 cycles of a frame (70224 - 4560) if (_frameClock < 65664) { // Mode 2 - OAM read, 77-83 cycles of frame if ((_frameClock % 456) < 80) { if (_lcdMode != LcdMode.OamRead) { // We've just entered OAM read so we're at the beginning of the line, // increment the current scanline unless we're coming from VBlank, then // don't inrecement because we've just set it to 0 if (_lcdMode != LcdMode.VBlank) { _currentScanline++; CompareScanlineValue(); } _lcdMode = LcdMode.OamRead; // raise OAM stat interrupt if requested if ((_lcdStatus & 0x20) == 0x20) { _interruptController.RequestInterrupt(Interrupt.LCDCStatus); } } } // Mode 3 - OAM and VRAM read, 162-175 cycles of frame else if ((_frameClock % 456) < 252) { if (_lcdMode != LcdMode.VramRead) { _lcdMode = LcdMode.VramRead; } } // Mode 0 - HBlank, 201-207 cycles of frame else { if (_lcdMode != LcdMode.HBlank) { _lcdMode = LcdMode.HBlank; // raise hblank stat interrupt if requested if ((_lcdStatus & 0x08) == 0x08) { _interruptController.RequestInterrupt(Interrupt.LCDCStatus); } RenderScanline(); } } } // Mode 1 - VBlank else { // are we entering vblank from another mode? if (_lcdMode != LcdMode.VBlank) { _lcdMode = LcdMode.VBlank; _currentScanline++; CompareScanlineValue(); // count 10 scanlines in vblank, include any already pushing into vblank timing _vblankClock = _frameClock - 65664; // raise vblank stat interrupt if requested if ((_lcdStatus & 0x10) == 0x10) { _interruptController.RequestInterrupt(Interrupt.LCDCStatus); } // raise vblank interrupt _interruptController.RequestInterrupt(Interrupt.VBlank); //DEBUG_DumpVramTiles(); // we've just entered vblank so the rendering for the frame is finished // present the screen data _frameSink?.AppendFrame(_screenData); // clear the _screenData for the next frame Array.Clear(_screenData, 0, _screenData.Length); } // processing vblank else { _vblankClock += cycleCount; if (_vblankClock >= 456) { _vblankClock -= 456; _currentScanline++; if (_currentScanline > 153) { _frameClock -= 70224; _currentScanline = 0; } CompareScanlineValue(); } } } }
public void WriteByte(ushort address, byte value) { SynchroniseWithSystemClock(); // 0x8000 - 0x9FFF - vram if (address >= 0x8000 && address <= 0x9fff) { // cannot access vram in mode 3 if (_lcdMode == LcdMode.VramRead) { return; } _vram[address & 0x1FFF] = value; // invalidate the tile cache for any tiles updated if (address < 0x9800) { _updateTileCache = true; _tileCacheInvalid[(address & 0x1FF0) >> 4] = true; } } // 0xFE00 - 0xFE9F - OAM memory else if (address >= 0xFE00 && address <= 0xFE9F) { // cannot access oam in mode 2 or 3 if (_lcdMode == LcdMode.OamRead || _lcdMode == LcdMode.VramRead || !_oamDma.OamAvailable) { return; } _oam[address & 0xFF] = value; } else { switch (address) { case Registers.LCDC: _lcdControl = value; bool enableDisplay = (_lcdControl & 0x80) == 0x80; // are we being asked to turn on the display and if so are we currently off? if (enableDisplay && !_displayEnabled) { // lcd starts in mode 2 when enabled _displayEnabled = true; _lcdMode = LcdMode.OamRead; CompareScanlineValue(); } // reset LY when display is disabled else if (!enableDisplay && _displayEnabled) { _displayEnabled = false; _currentScanline = 0; _frameClock = 0; _lcdMode = 0; } _windowTileBaseAddress = (ushort)((_lcdControl & 0x40) == 0x40 ? 0x9C00 : 0x9800); _windowEnabled = (_lcdControl & 0x20) == 0x20; _backgroundCharBaseAddress = (ushort)((_lcdControl & 0x10) == 0x10 ? 0x8000 : 0x8800); // if the background char data is in the range 0x8800 - 0x97FF then tile identifiers are signed _signedCharIdentifier = _backgroundCharBaseAddress == 0x8800; _backgroundTileBaseAddress = (ushort)((_lcdControl & 0x08) == 0x08 ? 0x9C00 : 0x9800); _spriteHeight = (_lcdControl & 0x04) == 0x04 ? 16 : 8; _spritesEnabled = (_lcdControl & 0x02) == 0x02; _backgroundEnabled = (_lcdControl & 0x01) == 0x01; break; case Registers.STAT: // capture lcd interrupt selection from value // interrupt selection stored in bits 6-3, preserve bits 0-2 // and mask bits 6-3 from value passed in _lcdStatus = (byte)((_lcdStatus & 0x07) | (value & 0x78)); // a write to the LY = LYC match flag (bit 2) clears the flag if ((value & 0x04) == 0x04) { unchecked { _lcdStatus &= (byte)~0x04; } } break; case Registers.SCY: _scrollY = value; break; case Registers.SCX: _scrollX = value; break; case Registers.LYC: _scanlineCompare = value; break; case Registers.DMA: _oamDma.Source = (ushort)(value << 8); _oamDma.Status = OamDmaStatus.Requested; break; case Registers.BGP: _bgp = value; _backgroundPalette = ExtractPaletteData(_bgp, 0); break; case Registers.OBP0: _obp0 = value; // extract sprite palette data, start from index 1, palette entry 00 is always 00 _spritePalette[0] = ExtractPaletteData(_obp0, 1); break; case Registers.OBP1: _obp1 = value; // extract sprite palette data, start from index 1, palette entry 00 is always 00 _spritePalette[1] = ExtractPaletteData(_obp1, 1); break; case Registers.WY: _windowY = value; break; case Registers.WX: _windowX = value; break; default: throw new ArgumentOutOfRangeException("address"); } } }