BizHawk/libgambatte/src/video.cpp

753 lines
24 KiB
C++

/***************************************************************************
* 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 <cstring>
#include <algorithm>
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<ONESHOT_LCDSTATIRQ>(state.ppu.pendingLcdstatIrq
? ppu.now() + 1 : static_cast<unsigned long>(DISABLED_TIME));
eventTimes_.setm<ONESHOT_UPDATEWY2>(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A]
? ppu.now() + 1 : static_cast<unsigned long>(DISABLED_TIME));
eventTimes_.set<LY_COUNT>(ppu.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu.lyCounter(), ppu.now()));
eventTimes_.setm<LYC_IRQ>(lycIrq.time());
eventTimes_.setm<MODE1_IRQ>(ppu.lyCounter().nextFrameCycle(144 * 456, ppu.now()));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg, ppu.lyCounter(), ppu.now()));
eventTimes_.setm<MODE0_IRQ>((statReg & 0x08) ? ppu.now() + state.ppu.nextM0Irq : static_cast<unsigned long>(DISABLED_TIME));
eventTimes_.setm<HDMA_REQ>(state.mem.hdmaTransfer
? nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu.now(), isDoubleSpeed())
: static_cast<unsigned long>(DISABLED_TIME));
} else for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(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<typename T>
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<MemEvent>(i)) != DISABLED_TIME)
eventTimes_.set(static_cast<MemEvent>(i), eventTimes_(static_cast<MemEvent>(i)) - dec);
}
eventTimes_.set<LY_COUNT>(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<LY_COUNT>(ppu.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter));
eventTimes_.setm<LYC_IRQ>(lycIrq.time());
eventTimes_.setm<MODE1_IRQ>(ppu.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg, ppu.lyCounter(), cycleCounter));
if (eventTimes_(MODE0_IRQ) != DISABLED_TIME && eventTimes_(MODE0_IRQ) - cycleCounter > 1)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1) {
eventTimes_.setm<HDMA_REQ>(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<HDMA_REQ>(nextHdmaTime(ppu.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
void LCD::disableHdma(const unsigned long cycleCounter) {
if (cycleCounter >= eventTimes_.nextEventTime())
update(cycleCounter);
eventTimes_.setm<HDMA_REQ>(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<MODE0_IRQ>(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<HDMA_REQ>(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<ONESHOT_UPDATEWY2>(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<SPRITE_MAP>(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<SPRITE_MAP>(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<LY_COUNT>(ppu.lyCounter().time());
eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu.lyCounter(), cycleCounter));
eventTimes_.setm<LYC_IRQ>(lycIrq.time());
eventTimes_.setm<MODE1_IRQ>(ppu.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg, ppu.lyCounter(), cycleCounter));
if (statReg & 0x08)
eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed()));
if (hdmaIsEnabled()) {
eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu.lastM0Time(),
nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
}
} else for (int i = 0; i < NUM_MEM_EVENTS; ++i)
eventTimes_.set(static_cast<MemEvent>(i), DISABLED_TIME);
} else if (data & 0x80) {
if (ppu.cgb()) {
ppu.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cycleCounter);
if ((oldLcdc ^ data) & 0x04)
eventTimes_.setm<SPRITE_MAP>(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<SPRITE_MAP>(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<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed()));
}
eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(data, ppu.lyCounter(), cycleCounter));
eventTimes_.setm<LYC_IRQ>(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<LYC_IRQ>(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<ONESHOT_LCDSTATIRQ>(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<MODE2_IRQ>(nextTime);
} else
eventTimes_.setm<MODE2_IRQ>(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<MODE1_IRQ>(eventTimes_(MODE1_IRQ) + (70224 << isDoubleSpeed()));
break;
case LYC_IRQ: {
unsigned char ifreg = 0;
lycIrq.doEvent(&ifreg, ppu.lyCounter());
eventTimes_.flagIrq(ifreg);
eventTimes_.setm<LYC_IRQ>(lycIrq.time());
break;
}
case SPRITE_MAP:
eventTimes_.setm<SPRITE_MAP>(ppu.doSpriteMapEvent(eventTimes_(SPRITE_MAP)));
mode3CyclesChange();
break;
case HDMA_REQ:
eventTimes_.flagHdmaReq();
nextM0Time_.predictNextM0Time(ppu);
eventTimes_.setm<HDMA_REQ>(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<MODE0_IRQ>((statReg & 0x08)
? m0IrqTimeFromXpos166Time(ppu.predictedNextXposTime(166), ppu.cgb(), isDoubleSpeed())
: static_cast<unsigned long>(DISABLED_TIME));
break;
case ONESHOT_LCDSTATIRQ:
eventTimes_.flagIrq(2);
eventTimes_.setm<ONESHOT_LCDSTATIRQ>(DISABLED_TIME);
break;
case ONESHOT_UPDATEWY2:
ppu.updateWy2();
mode3CyclesChange();
eventTimes_.setm<ONESHOT_UPDATEWY2>(DISABLED_TIME);
break;
}
break;
case LY_COUNT:
ppu.doLyCountEvent();
eventTimes_.set<LY_COUNT>(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_);
}
}