/*************************************************************************** * Copyright (C) 2007 by Sindre AamÄs * * aamas@stud.ntnu.no * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License version 2 for more details. * * * * You should have received a copy of the GNU General Public License * * version 2 along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "video.h" #include "savestate.h" #include #include namespace gambatte { void LCD::setDmgPalette(unsigned long *const palette, const unsigned long *const dmgColors, const unsigned data) { palette[0] = dmgColors[data & 3]; palette[1] = dmgColors[data >> 2 & 3]; palette[2] = dmgColors[data >> 4 & 3]; palette[3] = dmgColors[data >> 6 & 3]; } void LCD::setCgbPalette(unsigned *lut) { for (int i = 0; i < 32768; i++) cgbColorsRgb32[i] = lut[i]; refreshPalettes(); } unsigned long LCD::gbcToRgb32(const unsigned bgr15) { return cgbColorsRgb32[bgr15 & 0x7FFF]; } LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram, const VideoInterruptRequester memEventRequester) : ppu(nextM0Time_, oamram, vram), eventTimes_(memEventRequester), statReg(0), m2IrqStatReg_(0), m1IrqStatReg_(0), scanlinecallback(0), scanlinecallbacksl(0) { std::memset( bgpData, 0, sizeof bgpData); std::memset(objpData, 0, sizeof objpData); for (std::size_t i = 0; i < sizeof(dmgColorsRgb32) / sizeof(dmgColorsRgb32[0]); ++i) setDmgPaletteColor(i, (3 - (i & 3)) * 85 * 0x010101); reset(oamram, vram, false); setVideoBuffer(0, 160); } void LCD::reset(const unsigned char *const oamram, const unsigned char *vram, const bool cgb) { ppu.reset(oamram, vram, cgb); lycIrq.setCgb(cgb); refreshPalettes(); } static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter) { if (!(statReg & 0x20)) return DISABLED_TIME; unsigned next = lyCounter.time() - cycleCounter; if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4) || (statReg & 0x08)) { next += (153u - lyCounter.ly()) * lyCounter.lineTime(); } else { if (next <= 4) next += lyCounter.lineTime(); next -= 4; } return cycleCounter + next; } static inline unsigned long m0IrqTimeFromXpos166Time(const unsigned long xpos166Time, const bool cgb, const bool ds) { return xpos166Time + cgb - ds; } static inline unsigned long hdmaTimeFromM0Time(const unsigned long m0Time, const bool ds) { return m0Time + 1 - ds; } static unsigned long nextHdmaTime(const unsigned long lastM0Time, const unsigned long nextM0Time, const unsigned long cycleCounter, const bool ds) { return cycleCounter < hdmaTimeFromM0Time(lastM0Time, ds) ? hdmaTimeFromM0Time(lastM0Time, ds) : hdmaTimeFromM0Time(nextM0Time, ds); } void LCD::setStatePtrs(SaveState &state) { state.ppu.bgpData.set( bgpData, sizeof bgpData); state.ppu.objpData.set(objpData, sizeof objpData); ppu.setStatePtrs(state); } void LCD::loadState(const SaveState &state, const unsigned char *const oamram) { statReg = state.mem.ioamhram.get()[0x141]; m2IrqStatReg_ = statReg; m1IrqStatReg_ = statReg; ppu.loadState(state, oamram); lycIrq.loadState(state); m0Irq_.loadState(state); if (ppu.lcdc() & 0x80) { nextM0Time_.predictNextM0Time(ppu); lycIrq.reschedule(ppu.lyCounter(), ppu.now()); eventTimes_.setm(state.ppu.pendingLcdstatIrq ? ppu.now() + 1 : static_cast(DISABLED_TIME)); eventTimes_.setm(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A] ? ppu.now() + 1 : static_cast(DISABLED_TIME)); eventTimes_.set(ppu.lyCounter().time()); eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), ppu.now())); eventTimes_.setm(lycIrq.time()); eventTimes_.setm(ppu.lyCounter().nextFrameCycle(144 * 456, ppu.now())); eventTimes_.setm(mode2IrqSchedule(statReg, ppu.lyCounter(), ppu.now())); eventTimes_.setm((statReg & 0x08) ? ppu.now() + state.ppu.nextM0Irq : static_cast(DISABLED_TIME)); eventTimes_.setm(state.mem.hdmaTransfer ? nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu.now(), isDoubleSpeed()) : static_cast(DISABLED_TIME)); } else for (int i = 0; i < NUM_MEM_EVENTS; ++i) eventTimes_.set(static_cast(i), DISABLED_TIME); refreshPalettes(); } void LCD::refreshPalettes() { if (ppu.cgb()) { for (unsigned i = 0; i < 8 * 8; i += 2) { ppu.bgPalette()[i >> 1] = gbcToRgb32( bgpData[i] | bgpData[i + 1] << 8); ppu.spPalette()[i >> 1] = gbcToRgb32(objpData[i] | objpData[i + 1] << 8); } } else { setDmgPalette(ppu.bgPalette() , dmgColorsRgb32 , bgpData[0]); setDmgPalette(ppu.spPalette() , dmgColorsRgb32 + 4, objpData[0]); setDmgPalette(ppu.spPalette() + 4, dmgColorsRgb32 + 8, objpData[1]); } } namespace { template static void clear(T *buf, const unsigned long color, const int dpitch) { unsigned lines = 144; while (lines--) { std::fill_n(buf, 160, color); buf += dpitch; } } } void LCD::updateScreen(const bool blanklcd, const unsigned long cycleCounter) { update(cycleCounter); if (blanklcd && ppu.frameBuf().fb()) { const unsigned long color = ppu.cgb() ? gbcToRgb32(0xFFFF) : dmgColorsRgb32[0]; clear(ppu.frameBuf().fb(), color, ppu.frameBuf().pitch()); } } void LCD::resetCc(const unsigned long oldCc, const unsigned long newCc) { update(oldCc); ppu.resetCc(oldCc, newCc); if (ppu.lcdc() & 0x80) { const unsigned long dec = oldCc - newCc; nextM0Time_.invalidatePredictedNextM0Time(); lycIrq.reschedule(ppu.lyCounter(), newCc); for (int i = 0; i < NUM_MEM_EVENTS; ++i) { if (eventTimes_(static_cast(i)) != DISABLED_TIME) eventTimes_.set(static_cast(i), eventTimes_(static_cast(i)) - dec); } eventTimes_.set(ppu.lyCounter().time()); } } void LCD::speedChange(const unsigned long cycleCounter) { update(cycleCounter); ppu.speedChange(cycleCounter); if (ppu.lcdc() & 0x80) { nextM0Time_.predictNextM0Time(ppu); lycIrq.reschedule(ppu.lyCounter(), cycleCounter); eventTimes_.set(ppu.lyCounter().time()); eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); eventTimes_.setm(lycIrq.time()); eventTimes_.setm(ppu.lyCounter().nextFrameCycle(144 * 456, cycleCounter)); eventTimes_.setm(mode2IrqSchedule(statReg, ppu.lyCounter(), cycleCounter)); if (eventTimes_(MODE0_IRQ) != DISABLED_TIME && eventTimes_(MODE0_IRQ) - cycleCounter > 1) eventTimes_.setm(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed())); if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1) { eventTimes_.setm(nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed())); } } } static inline unsigned long m0TimeOfCurrentLine(const unsigned long nextLyTime, const unsigned long lastM0Time, const unsigned long nextM0Time) { return nextM0Time < nextLyTime ? nextM0Time : lastM0Time; } unsigned long LCD::m0TimeOfCurrentLine(const unsigned long cc) { if (cc >= nextM0Time_.predictedNextM0Time()) { update(cc); nextM0Time_.predictNextM0Time(ppu); } return gambatte::m0TimeOfCurrentLine(ppu.lyCounter().time(), ppu.lastM0Time(), nextM0Time_.predictedNextM0Time()); } static bool isHdmaPeriod(const LyCounter &lyCounter, const unsigned long m0TimeOfCurrentLy, const unsigned long cycleCounter) { const unsigned timeToNextLy = lyCounter.time() - cycleCounter; return /*(ppu.lcdc & 0x80) && */lyCounter.ly() < 144 && timeToNextLy > 4 && cycleCounter >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed()); } void LCD::enableHdma(const unsigned long cycleCounter) { if (cycleCounter >= nextM0Time_.predictedNextM0Time()) { update(cycleCounter); nextM0Time_.predictNextM0Time(ppu); } else if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); if (isHdmaPeriod(ppu.lyCounter(), gambatte::m0TimeOfCurrentLine(ppu.lyCounter().time(), ppu.lastM0Time(), nextM0Time_.predictedNextM0Time()), cycleCounter)) { eventTimes_.flagHdmaReq(); } eventTimes_.setm(nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed())); } void LCD::disableHdma(const unsigned long cycleCounter) { if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); eventTimes_.setm(DISABLED_TIME); } bool LCD::vramAccessible(const unsigned long cycleCounter) { if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); return !(ppu.lcdc() & 0x80) || ppu.lyCounter().ly() >= 144 || ppu.lyCounter().lineCycles(cycleCounter) < 80U || cycleCounter + isDoubleSpeed() - ppu.cgb() + 2 >= m0TimeOfCurrentLine(cycleCounter); } bool LCD::cgbpAccessible(const unsigned long cycleCounter) { if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); return !(ppu.lcdc() & 0x80) || ppu.lyCounter().ly() >= 144 || ppu.lyCounter().lineCycles(cycleCounter) < 80U + isDoubleSpeed() || cycleCounter >= m0TimeOfCurrentLine(cycleCounter) + 3 - isDoubleSpeed(); } void LCD::doCgbColorChange(unsigned char *const pdata, unsigned long *const palette, unsigned index, const unsigned data) { pdata[index] = data; index >>= 1; palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8); } void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { if (cgbpAccessible(cycleCounter)) { update(cycleCounter); doCgbColorChange(bgpData, ppu.bgPalette(), index, data); } } void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { if (cgbpAccessible(cycleCounter)) { update(cycleCounter); doCgbColorChange(objpData, ppu.spPalette(), index, data); } } bool LCD::oamReadable(const unsigned long cycleCounter) { if (!(ppu.lcdc() & 0x80) || ppu.inactivePeriodAfterDisplayEnable(cycleCounter)) return true; if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); if (ppu.lyCounter().lineCycles(cycleCounter) + 4 - ppu.lyCounter().isDoubleSpeed() * 3u >= 456) return ppu.lyCounter().ly() >= 144-1 && ppu.lyCounter().ly() != 153; return ppu.lyCounter().ly() >= 144 || cycleCounter + isDoubleSpeed() - ppu.cgb() + 2 >= m0TimeOfCurrentLine(cycleCounter); } bool LCD::oamWritable(const unsigned long cycleCounter) { if (!(ppu.lcdc() & 0x80) || ppu.inactivePeriodAfterDisplayEnable(cycleCounter)) return true; if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); if (ppu.lyCounter().lineCycles(cycleCounter) + 3 + ppu.cgb() - ppu.lyCounter().isDoubleSpeed() * 2u >= 456) return ppu.lyCounter().ly() >= 144-1 && ppu.lyCounter().ly() != 153; return ppu.lyCounter().ly() >= 144 || cycleCounter + isDoubleSpeed() - ppu.cgb() + 2 >= m0TimeOfCurrentLine(cycleCounter); } void LCD::mode3CyclesChange() { nextM0Time_.invalidatePredictedNextM0Time(); if (eventTimes_(MODE0_IRQ) != DISABLED_TIME && eventTimes_(MODE0_IRQ) > m0IrqTimeFromXpos166Time(ppu.now(), ppu.cgb(), isDoubleSpeed())) { eventTimes_.setm(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed())); } if (eventTimes_(HDMA_REQ) != DISABLED_TIME && eventTimes_(HDMA_REQ) > hdmaTimeFromM0Time(ppu.lastM0Time(), isDoubleSpeed())) { nextM0Time_.predictNextM0Time(ppu); eventTimes_.setm(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed())); } } void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter) { update(cycleCounter + isDoubleSpeed() + 1); ppu.setWx(newValue); mode3CyclesChange(); } void LCD::wyChange(const unsigned newValue, const unsigned long cycleCounter) { update(cycleCounter + 1); ppu.setWy(newValue); // mode3CyclesChange(); // should be safe to wait until after wy2 delay, because no mode3 events are close to when wy1 is read. // wy2 is a delayed version of wy. really just slowness of ly == wy comparison. if (ppu.cgb() && (ppu.lcdc() & 0x80)) { eventTimes_.setm(cycleCounter + 5); } else { update(cycleCounter + 2); ppu.updateWy2(); mode3CyclesChange(); } } void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) { update(cycleCounter + ppu.cgb() + isDoubleSpeed()); ppu.setScx(newScx); mode3CyclesChange(); } void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) { update(cycleCounter + ppu.cgb() + isDoubleSpeed()); ppu.setScy(newValue); } void LCD::oamChange(const unsigned long cycleCounter) { if (ppu.lcdc() & 0x80) { update(cycleCounter); ppu.oamChange(cycleCounter); eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); } } void LCD::oamChange(const unsigned char *const oamram, const unsigned long cycleCounter) { update(cycleCounter); ppu.oamChange(oamram, cycleCounter); if (ppu.lcdc() & 0x80) eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); } void LCD::lcdcChange(const unsigned data, const unsigned long cycleCounter) { const unsigned oldLcdc = ppu.lcdc(); update(cycleCounter); if ((oldLcdc ^ data) & 0x80) { ppu.setLcdc(data, cycleCounter); if (data & 0x80) { lycIrq.lcdReset(); m0Irq_.lcdReset(statReg, lycIrq.lycReg()); if (lycIrq.lycReg() == 0 && (statReg & 0x40)) eventTimes_.flagIrq(2); nextM0Time_.predictNextM0Time(ppu); lycIrq.reschedule(ppu.lyCounter(), cycleCounter); eventTimes_.set(ppu.lyCounter().time()); eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); eventTimes_.setm(lycIrq.time()); eventTimes_.setm(ppu.lyCounter().nextFrameCycle(144 * 456, cycleCounter)); eventTimes_.setm(mode2IrqSchedule(statReg, ppu.lyCounter(), cycleCounter)); if (statReg & 0x08) eventTimes_.setm(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed())); if (hdmaIsEnabled()) { eventTimes_.setm(nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed())); } } else for (int i = 0; i < NUM_MEM_EVENTS; ++i) eventTimes_.set(static_cast(i), DISABLED_TIME); } else if (data & 0x80) { if (ppu.cgb()) { ppu.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cycleCounter); if ((oldLcdc ^ data) & 0x04) eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); update(cycleCounter + isDoubleSpeed() + 1); ppu.setLcdc(data, cycleCounter + isDoubleSpeed() + 1); if ((oldLcdc ^ data) & 0x20) mode3CyclesChange(); } else { ppu.setLcdc(data, cycleCounter); if ((oldLcdc ^ data) & 0x04) eventTimes_.setm(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter)); if ((oldLcdc ^ data) & 0x22) mode3CyclesChange(); } } else ppu.setLcdc(data, cycleCounter); } namespace { struct LyCnt { unsigned ly; int timeToNextLy; LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {} }; static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) { unsigned ly = lyCounter.ly(); int timeToNextLy = lyCounter.time() - cc; if (ly == 153) { if (timeToNextLy - (448 << lyCounter.isDoubleSpeed()) > 0) { timeToNextLy -= (448 << lyCounter.isDoubleSpeed()); } else { ly = 0; timeToNextLy += lyCounter.lineTime(); } } return LyCnt(ly, timeToNextLy); } } void LCD::lcdstatChange(unsigned const data, unsigned long const cycleCounter) { if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); unsigned const old = statReg; statReg = data; lycIrq.statRegChange(data, ppu.lyCounter(), cycleCounter); if (ppu.lcdc() & 0x80) { int const timeToNextLy = ppu.lyCounter().time() - cycleCounter; LyCnt const lycCmp = getLycCmpLy(ppu.lyCounter(), cycleCounter); if (!ppu.cgb()) { if (ppu.lyCounter().ly() < 144) { if (cycleCounter + 1 < m0TimeOfCurrentLine(cycleCounter)) { if (lycCmp.ly == lycIrq.lycReg() && !(old & 0x40)) eventTimes_.flagIrq(2); } else { if (!(old & 0x08) && !(lycCmp.ly == lycIrq.lycReg() && (old & 0x40))) eventTimes_.flagIrq(2); } } else { if (!(old & 0x10) && !(lycCmp.ly == lycIrq.lycReg() && (old & 0x40))) eventTimes_.flagIrq(2); } } else if (data & ~old & 0x78) { bool const lycperiod = lycCmp.ly == lycIrq.lycReg() && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4; if (!(lycperiod && (old & 0x40))) { if (ppu.lyCounter().ly() < 144) { if (cycleCounter + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cycleCounter) || timeToNextLy <= 4) { if (lycperiod && (data & 0x40)) eventTimes_.flagIrq(2); } else if (!(old & 0x08)) { if ((data & 0x08) || (lycperiod && (data & 0x40))) eventTimes_.flagIrq(2); } } else if (!(old & 0x10)) { if ((data & 0x10) && (ppu.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4)) { eventTimes_.flagIrq(2); } else if (lycperiod && (data & 0x40)) eventTimes_.flagIrq(2); } } if ((data & 0x28) == 0x20 && !(old & 0x20) && ((timeToNextLy <= 4 && ppu.lyCounter().ly() < 143) || (timeToNextLy == 456*2 && ppu.lyCounter().ly() < 144))) { eventTimes_.flagIrq(2); } } if ((data & 0x08) && eventTimes_(MODE0_IRQ) == DISABLED_TIME) { update(cycleCounter); eventTimes_.setm(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed())); } eventTimes_.setm(mode2IrqSchedule(data, ppu.lyCounter(), cycleCounter)); eventTimes_.setm(lycIrq.time()); } m2IrqStatReg_ = eventTimes_(MODE2_IRQ) - cycleCounter > (ppu.cgb() - isDoubleSpeed()) * 4U ? data : (m2IrqStatReg_ & 0x10) | (statReg & ~0x10); m1IrqStatReg_ = eventTimes_(MODE1_IRQ) - cycleCounter > (ppu.cgb() - isDoubleSpeed()) * 4U ? data : (m1IrqStatReg_ & 0x08) | (statReg & ~0x08); m0Irq_.statRegChange(data, eventTimes_(MODE0_IRQ), cycleCounter, ppu.cgb()); } void LCD::lycRegChange(unsigned const data, unsigned long const cycleCounter) { unsigned const old = lycIrq.lycReg(); if (data == old) return; if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); m0Irq_.lycRegChange(data, eventTimes_(MODE0_IRQ), cycleCounter, isDoubleSpeed(), ppu.cgb()); lycIrq.lycRegChange(data, ppu.lyCounter(), cycleCounter); if (!(ppu.lcdc() & 0x80)) return; eventTimes_.setm(lycIrq.time()); int const timeToNextLy = ppu.lyCounter().time() - cycleCounter; if ((statReg & 0x40) && data < 154 && (ppu.lyCounter().ly() < 144 ? !(statReg & 0x08) || cycleCounter < m0TimeOfCurrentLine(cycleCounter) || timeToNextLy <= 4 << ppu.cgb() : !(statReg & 0x10) || (ppu.lyCounter().ly() == 153 && timeToNextLy <= 4 && ppu.cgb() && !isDoubleSpeed()))) { LyCnt lycCmp = getLycCmpLy(ppu.lyCounter(), cycleCounter); if (lycCmp.timeToNextLy <= 4 << ppu.cgb()) { lycCmp.ly = old != lycCmp.ly || (lycCmp.timeToNextLy <= 4 && ppu.cgb() && !isDoubleSpeed()) ? (lycCmp.ly == 153 ? 0 : lycCmp.ly + 1) : 0xFF; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger. } if (data == lycCmp.ly) { if (ppu.cgb() && !isDoubleSpeed()) { eventTimes_.setm(cycleCounter + 5); } else eventTimes_.flagIrq(2); } } } unsigned LCD::getStat(unsigned const lycReg, unsigned long const cycleCounter) { unsigned stat = 0; if (ppu.lcdc() & 0x80) { if (cycleCounter >= eventTimes_.nextEventTime()) update(cycleCounter); int const timeToNextLy = ppu.lyCounter().time() - cycleCounter; if (ppu.lyCounter().ly() > 143) { if (ppu.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4) stat = 1; } else { unsigned const lineCycles = 456 - (timeToNextLy >> isDoubleSpeed()); if (lineCycles < 80) { if (!ppu.inactivePeriodAfterDisplayEnable(cycleCounter)) stat = 2; } else if (cycleCounter + isDoubleSpeed() - ppu.cgb() + 2 < m0TimeOfCurrentLine(cycleCounter)) stat = 3; } LyCnt const lycCmp = getLycCmpLy(ppu.lyCounter(), cycleCounter); if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4) stat |= 4; } return stat; } inline void LCD::doMode2IrqEvent() { const unsigned ly = eventTimes_(LY_COUNT) - eventTimes_(MODE2_IRQ) < 8 ? (ppu.lyCounter().ly() == 153 ? 0 : ppu.lyCounter().ly() + 1) : ppu.lyCounter().ly(); if ((ly != 0 || !(m2IrqStatReg_ & 0x10)) && (!(m2IrqStatReg_ & 0x40) || (lycIrq.lycReg() != 0 ? ly != (lycIrq.lycReg() + 1U) : ly > 1))) { eventTimes_.flagIrq(2); } m2IrqStatReg_ = statReg; if (!(statReg & 0x08)) { unsigned long nextTime = eventTimes_(MODE2_IRQ) + ppu.lyCounter().lineTime(); if (ly == 0) { nextTime -= 4; } else if (ly == 143) nextTime += ppu.lyCounter().lineTime() * 10 + 4; eventTimes_.setm(nextTime); } else eventTimes_.setm(eventTimes_(MODE2_IRQ) + (70224 << isDoubleSpeed())); } inline void LCD::event() { switch (eventTimes_.nextEvent()) { case MEM_EVENT: switch (eventTimes_.nextMemEvent()) { case MODE1_IRQ: eventTimes_.flagIrq((m1IrqStatReg_ & 0x18) == 0x10 ? 3 : 1); m1IrqStatReg_ = statReg; eventTimes_.setm(eventTimes_(MODE1_IRQ) + (70224 << isDoubleSpeed())); break; case LYC_IRQ: { unsigned char ifreg = 0; lycIrq.doEvent(&ifreg, ppu.lyCounter()); eventTimes_.flagIrq(ifreg); eventTimes_.setm(lycIrq.time()); break; } case SPRITE_MAP: eventTimes_.setm(ppu.doSpriteMapEvent(eventTimes_(SPRITE_MAP))); mode3CyclesChange(); break; case HDMA_REQ: eventTimes_.flagHdmaReq(); nextM0Time_.predictNextM0Time(ppu); eventTimes_.setm(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed())); break; case MODE2_IRQ: doMode2IrqEvent(); break; case MODE0_IRQ: { unsigned char ifreg = 0; m0Irq_.doEvent(&ifreg, ppu.lyCounter().ly(), statReg, lycIrq.lycReg()); eventTimes_.flagIrq(ifreg); } eventTimes_.setm((statReg & 0x08) ? m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed()) : static_cast(DISABLED_TIME)); break; case ONESHOT_LCDSTATIRQ: eventTimes_.flagIrq(2); eventTimes_.setm(DISABLED_TIME); break; case ONESHOT_UPDATEWY2: ppu.updateWy2(); mode3CyclesChange(); eventTimes_.setm(DISABLED_TIME); break; } break; case LY_COUNT: ppu.doLyCountEvent(); eventTimes_.set(ppu.lyCounter().time()); if (scanlinecallback && ppu.lyCounter().ly() == (unsigned)scanlinecallbacksl) scanlinecallback(); break; } } void LCD::update(const unsigned long cycleCounter) { if (!(ppu.lcdc() & 0x80)) return; while (cycleCounter >= eventTimes_.nextEventTime()) { ppu.update(eventTimes_.nextEventTime()); event(); } ppu.update(cycleCounter); } void LCD::setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { ppu.setFrameBuf(videoBuf, pitch); } void LCD::setDmgPaletteColor(const unsigned index, const unsigned long rgb32) { dmgColorsRgb32[index] = rgb32; } void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, const unsigned long rgb32) { if (palNum > 2 || colorNum > 3) return; setDmgPaletteColor(palNum * 4 | colorNum, rgb32); refreshPalettes(); } // don't need to save or load rgb32 color data SYNCFUNC(LCD) { SSS(ppu); NSS(bgpData); NSS(objpData); SSS(eventTimes_); SSS(m0Irq_); SSS(lycIrq); SSS(nextM0Time_); NSS(statReg); NSS(m2IrqStatReg_); NSS(m1IrqStatReg_); } }