753 lines
24 KiB
C++
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_);
|
|
}
|
|
|
|
}
|