1 /***************************************************************************
2 * Copyright (C) 2007 by Sindre Aamås *
3 * aamas@stud.ntnu.no *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License version 2 as *
7 * published by the Free Software Foundation. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License version 2 for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * version 2 along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19 #include "video.h"
20 #include "savestate.h"
21 #include <cstring>
22 #include <algorithm>
23 #include <string>
24
25 namespace gambatte
26 {
27
reset(const unsigned char * const oamram,unsigned char const * vram,const bool cgb)28 void LCD::reset(const unsigned char *const oamram, unsigned char const *vram, const bool cgb)
29 {
30 ppu_.reset(oamram, vram, cgb);
31 lycIrq_.setCgb(cgb);
32 refreshPalettes();
33 }
34
mode2IrqSchedule(const unsigned statReg,const LyCounter & lyCounter,const unsigned long cycleCounter)35 static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter)
36 {
37 if (!(statReg & 0x20))
38 return disabled_time;
39
40 unsigned next = lyCounter.time() - cycleCounter;
41
42 if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4) || (statReg & 0x08))
43 next += (153u - lyCounter.ly()) * lyCounter.lineTime();
44 else
45 {
46 if (next <= 4)
47 next += lyCounter.lineTime();
48
49 next -= 4;
50 }
51
52 return cycleCounter + next;
53 }
54
m0IrqTimeFromXpos166Time(const unsigned long xpos166Time,const bool cgb,const bool ds)55 static inline unsigned long m0IrqTimeFromXpos166Time(
56 const unsigned long xpos166Time, const bool cgb, const bool ds)
57 {
58 return xpos166Time + cgb - ds;
59 }
60
hdmaTimeFromM0Time(const unsigned long m0Time,const bool ds)61 static inline unsigned long hdmaTimeFromM0Time(
62 const unsigned long m0Time, const bool ds)
63 {
64 return m0Time + 1 - ds;
65 }
66
nextHdmaTime(const unsigned long lastM0Time,const unsigned long nextM0Time,const unsigned long cycleCounter,const bool ds)67 static unsigned long nextHdmaTime(const unsigned long lastM0Time,
68 const unsigned long nextM0Time, const unsigned long cycleCounter, const bool ds)
69 {
70 return cycleCounter < hdmaTimeFromM0Time(lastM0Time, ds)
71 ? hdmaTimeFromM0Time(lastM0Time, ds)
72 : hdmaTimeFromM0Time(nextM0Time, ds);
73 }
74
setStatePtrs(SaveState & state)75 void LCD::setStatePtrs(SaveState &state)
76 {
77 state.ppu.bgpData.set( bgpData_, sizeof bgpData_);
78 state.ppu.objpData.set(objpData_, sizeof objpData_);
79 ppu_.setStatePtrs(state);
80 }
81
saveState(SaveState & state) const82 void LCD::saveState(SaveState &state) const
83 {
84 state.mem.hdmaTransfer = hdmaIsEnabled();
85 state.ppu.nextM0Irq = eventTimes_(MODE0_IRQ) - ppu_.now();
86 state.ppu.pendingLcdstatIrq = eventTimes_(ONESHOT_LCDSTATIRQ) != disabled_time;
87
88 if (isCgb())
89 std::memcpy(state.ppu.dmgPalette, dmgColorsGBC_, 8 * 3);
90
91
92 lycIrq_.saveState(state);
93 m0Irq_.saveState(state);
94 ppu_.saveState(state);
95 }
96
loadState(const SaveState & state,const unsigned char * const oamram)97 void LCD::loadState(const SaveState &state, const unsigned char *const oamram)
98 {
99 statReg_ = state.mem.ioamhram.get()[0x141];
100 m2IrqStatReg_ = statReg_;
101 m1IrqStatReg_ = statReg_;
102
103 ppu_.loadState(state, oamram);
104 lycIrq_.loadState(state);
105 m0Irq_.loadState(state);
106
107 if (ppu_.lcdc() & 0x80)
108 {
109 nextM0Time_.predictNextM0Time(ppu_);
110 lycIrq_.reschedule(ppu_.lyCounter(), ppu_.now());
111
112 eventTimes_.setm<ONESHOT_LCDSTATIRQ>(state.ppu.pendingLcdstatIrq
113 ? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
114 eventTimes_.setm<ONESHOT_UPDATEWY2>(state.ppu.oldWy != state.mem.ioamhram.get()[0x14A]
115 ? ppu_.now() + 1 : static_cast<unsigned long>(disabled_time));
116 eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
117 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), ppu_.now()));
118 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
119 eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, ppu_.now()));
120 eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), ppu_.now()));
121 eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08) ? ppu_.now() + state.ppu.nextM0Irq : static_cast<unsigned long>(disabled_time));
122 eventTimes_.setm<HDMA_REQ>(state.mem.hdmaTransfer
123 ? nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), ppu_.now(), isDoubleSpeed())
124 : static_cast<unsigned long>(disabled_time));
125 }
126 else
127 {
128 for (int i = 0; i < NUM_MEM_EVENTS; ++i)
129 eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
130 }
131
132 if (isCgb())
133 std::memcpy(dmgColorsGBC_, state.ppu.dmgPalette, 8 * 3);
134
135 refreshPalettes();
136 }
137
refreshPalettes()138 void LCD::refreshPalettes()
139 {
140 if (ppu_.cgb() && !ppu_.inDmgMode())
141 {
142 for (unsigned i = 0; i < 8 * 8; i += 2)
143 {
144 ppu_.bgPalette()[i >> 1] = gbcToRgb32( bgpData_[i] | bgpData_[i + 1] << 8);
145 ppu_.spPalette()[i >> 1] = gbcToRgb32(objpData_[i] | objpData_[i + 1] << 8);
146 }
147 }
148 else
149 {
150 if (ppu_.inDmgMode())
151 {
152 for (unsigned i = 0; i < 8 * 3; i += 2)
153 dmgColorsRgb32_[i >> 1] = gbcToRgb32( dmgColorsGBC_[i] | dmgColorsGBC_[i + 1] << 8);
154 }
155 setDmgPalette(ppu_.bgPalette() , dmgColorsRgb32_ , bgpData_[0]);
156 setDmgPalette(ppu_.spPalette() , dmgColorsRgb32_ + 4, objpData_[0]);
157 setDmgPalette(ppu_.spPalette() + 4, dmgColorsRgb32_ + 8, objpData_[1]);
158 }
159 }
160
resetCc(const unsigned long oldCc,const unsigned long newCc)161 void LCD::resetCc(const unsigned long oldCc, const unsigned long newCc)
162 {
163 update(oldCc);
164 ppu_.resetCc(oldCc, newCc);
165
166 if (ppu_.lcdc() & 0x80)
167 {
168 const unsigned long dec = oldCc - newCc;
169
170 nextM0Time_.invalidatePredictedNextM0Time();
171 lycIrq_.reschedule(ppu_.lyCounter(), newCc);
172
173 for (int i = 0; i < NUM_MEM_EVENTS; ++i)
174 {
175 if (eventTimes_(static_cast<MemEvent>(i)) != disabled_time)
176 eventTimes_.set(static_cast<MemEvent>(i), eventTimes_(static_cast<MemEvent>(i)) - dec);
177 }
178
179 eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
180 }
181 }
182
speedChange(const unsigned long cycleCounter)183 void LCD::speedChange(const unsigned long cycleCounter)
184 {
185 update(cycleCounter);
186 ppu_.speedChange(cycleCounter);
187
188 if (ppu_.lcdc() & 0x80)
189 {
190 nextM0Time_.predictNextM0Time(ppu_);
191 lycIrq_.reschedule(ppu_.lyCounter(), cycleCounter);
192
193 eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
194 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cycleCounter));
195 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
196 eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cycleCounter));
197 eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cycleCounter));
198
199 if (eventTimes_(MODE0_IRQ) != disabled_time && eventTimes_(MODE0_IRQ) - cycleCounter > 1)
200 eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
201
202 if (hdmaIsEnabled() && eventTimes_(HDMA_REQ) - cycleCounter > 1)
203 {
204 eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
205 nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
206 }
207 }
208 }
209
m0TimeOfCurrentLine(const unsigned long nextLyTime,const unsigned long lastM0Time,const unsigned long nextM0Time)210 static inline unsigned long m0TimeOfCurrentLine(const unsigned long nextLyTime,
211 const unsigned long lastM0Time, const unsigned long nextM0Time)
212 {
213 return nextM0Time < nextLyTime ? nextM0Time : lastM0Time;
214 }
215
m0TimeOfCurrentLine(const unsigned long cc)216 unsigned long LCD::m0TimeOfCurrentLine(const unsigned long cc)
217 {
218 if (cc >= nextM0Time_.predictedNextM0Time())
219 {
220 update(cc);
221 nextM0Time_.predictNextM0Time(ppu_);
222 }
223
224 return gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(), ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time());
225 }
226
isHdmaPeriod(const LyCounter & lyCounter,const unsigned long m0TimeOfCurrentLy,const unsigned long cycleCounter)227 static bool isHdmaPeriod(const LyCounter &lyCounter,
228 const unsigned long m0TimeOfCurrentLy, const unsigned long cycleCounter)
229 {
230 const unsigned timeToNextLy = lyCounter.time() - cycleCounter;
231
232 return /*(ppu_.lcdc & 0x80) && */lyCounter.ly() < 144 && timeToNextLy > 4
233 && cycleCounter >= hdmaTimeFromM0Time(m0TimeOfCurrentLy, lyCounter.isDoubleSpeed());
234 }
235
enableHdma(const unsigned long cycleCounter)236 void LCD::enableHdma(const unsigned long cycleCounter)
237 {
238 if (cycleCounter >= nextM0Time_.predictedNextM0Time())
239 {
240 update(cycleCounter);
241 nextM0Time_.predictNextM0Time(ppu_);
242 }
243 else if (cycleCounter >= eventTimes_.nextEventTime())
244 update(cycleCounter);
245
246 if (isHdmaPeriod(ppu_.lyCounter(),
247 gambatte::m0TimeOfCurrentLine(ppu_.lyCounter().time(),
248 ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time()), cycleCounter))
249 eventTimes_.flagHdmaReq();
250
251 eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(), nextM0Time_.predictedNextM0Time(), cycleCounter, isDoubleSpeed()));
252 }
253
disableHdma(const unsigned long cycleCounter)254 void LCD::disableHdma(const unsigned long cycleCounter)
255 {
256 if (cycleCounter >= eventTimes_.nextEventTime())
257 update(cycleCounter);
258
259 eventTimes_.setm<HDMA_REQ>(disabled_time);
260 }
261
vramAccessible(const unsigned long cc)262 bool LCD::vramAccessible(const unsigned long cc)
263 {
264 if (cc >= eventTimes_.nextEventTime())
265 update(cc);
266
267 return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
268 || ppu_.lyCounter().lineCycles(cc) < 80U
269 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
270 }
271
cgbpAccessible(const unsigned long cc)272 bool LCD::cgbpAccessible(const unsigned long cc)
273 {
274 if (cc >= eventTimes_.nextEventTime())
275 update(cc);
276
277 return !(ppu_.lcdc() & 0x80) || ppu_.lyCounter().ly() >= 144
278 || ppu_.lyCounter().lineCycles(cc) < 80U + isDoubleSpeed()
279 || cc >= m0TimeOfCurrentLine(cc) + 3 - isDoubleSpeed();
280 }
281
doCgbBgColorChange(unsigned index,const unsigned data,const unsigned long cc)282 void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cc)
283 {
284 if (cgbpAccessible(cc))
285 {
286 update(cc);
287 doCgbColorChange(bgpData_, ppu_.bgPalette(), index, data);
288 }
289 }
290
doCgbSpColorChange(unsigned index,const unsigned data,const unsigned long cc)291 void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cc)
292 {
293 if (cgbpAccessible(cc))
294 {
295 update(cc);
296 doCgbColorChange(objpData_, ppu_.spPalette(), index, data);
297 }
298 }
299
oamReadable(const unsigned long cc)300 bool LCD::oamReadable(const unsigned long cc)
301 {
302 if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
303 return true;
304
305 if (cc >= eventTimes_.nextEventTime())
306 update(cc);
307
308 if (ppu_.lyCounter().lineCycles(cc) + 4 - ppu_.lyCounter().isDoubleSpeed() * 3u >= 456)
309 return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
310
311 return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
312 }
313
oamWritable(const unsigned long cc)314 bool LCD::oamWritable(const unsigned long cc)
315 {
316 if (!(ppu_.lcdc() & 0x80) || ppu_.inactivePeriodAfterDisplayEnable(cc))
317 return true;
318
319 if (cc >= eventTimes_.nextEventTime())
320 update(cc);
321
322 if (ppu_.lyCounter().lineCycles(cc) + 3 + ppu_.cgb() - ppu_.lyCounter().isDoubleSpeed() * 2u >= 456)
323 return ppu_.lyCounter().ly() >= 144-1 && ppu_.lyCounter().ly() != 153;
324
325 return ppu_.lyCounter().ly() >= 144 || cc + isDoubleSpeed() - ppu_.cgb() + 2 >= m0TimeOfCurrentLine(cc);
326 }
327
mode3CyclesChange()328 void LCD::mode3CyclesChange()
329 {
330 nextM0Time_.invalidatePredictedNextM0Time();
331
332 if (eventTimes_(MODE0_IRQ) != disabled_time
333 && eventTimes_(MODE0_IRQ) > m0IrqTimeFromXpos166Time(ppu_.now(), ppu_.cgb(), isDoubleSpeed())) {
334 eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
335 }
336
337 if (eventTimes_(HDMA_REQ) != disabled_time
338 && eventTimes_(HDMA_REQ) > hdmaTimeFromM0Time(ppu_.lastM0Time(), isDoubleSpeed())) {
339 nextM0Time_.predictNextM0Time(ppu_);
340 eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
341 }
342 }
343
wxChange(const unsigned newValue,const unsigned long cycleCounter)344 void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter)
345 {
346 update(cycleCounter + isDoubleSpeed() + 1);
347 ppu_.setWx(newValue);
348 mode3CyclesChange();
349 }
350
wyChange(const unsigned newValue,const unsigned long cc)351 void LCD::wyChange(const unsigned newValue, const unsigned long cc)
352 {
353 update(cc + 1);
354 ppu_.setWy(newValue);
355 // mode3CyclesChange(); // should be safe to wait until after wy2 delay, because no mode3 events are close to when wy1 is read.
356
357 // wy2 is a delayed version of wy. really just slowness of ly == wy comparison.
358 if (ppu_.cgb() && (ppu_.lcdc() & 0x80)) {
359 eventTimes_.setm<ONESHOT_UPDATEWY2>(cc + 5);
360 } else {
361 update(cc + 2);
362 ppu_.updateWy2();
363 mode3CyclesChange();
364 }
365 }
366
scxChange(const unsigned newScx,const unsigned long cycleCounter)367 void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) {
368 update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
369 ppu_.setScx(newScx);
370 mode3CyclesChange();
371 }
372
scyChange(const unsigned newValue,const unsigned long cycleCounter)373 void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) {
374 update(cycleCounter + ppu_.cgb() + isDoubleSpeed());
375 ppu_.setScy(newValue);
376 }
377
oamChange(const unsigned long cc)378 void LCD::oamChange(const unsigned long cc) {
379 if (ppu_.lcdc() & 0x80) {
380 update(cc);
381 ppu_.oamChange(cc);
382 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
383 }
384 }
385
oamChange(const unsigned char * const oamram,const unsigned long cc)386 void LCD::oamChange(const unsigned char *const oamram, const unsigned long cc) {
387 update(cc);
388 ppu_.oamChange(oamram, cc);
389
390 if (ppu_.lcdc() & 0x80)
391 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
392 }
393
lcdcChange(const unsigned data,const unsigned long cc)394 void LCD::lcdcChange(const unsigned data, const unsigned long cc) {
395 const unsigned oldLcdc = ppu_.lcdc();
396 update(cc);
397
398 if ((oldLcdc ^ data) & 0x80)
399 {
400 ppu_.setLcdc(data, cc);
401
402 if (data & 0x80) {
403 lycIrq_.lcdReset();
404 m0Irq_.lcdReset(statReg_, lycIrq_.lycReg());
405
406 if (lycIrq_.lycReg() == 0 && (statReg_ & 0x40))
407 eventTimes_.flagIrq(2);
408
409 nextM0Time_.predictNextM0Time(ppu_);
410 lycIrq_.reschedule(ppu_.lyCounter(), cc);
411
412 eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
413 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
414 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
415 eventTimes_.setm<MODE1_IRQ>(ppu_.lyCounter().nextFrameCycle(144 * 456, cc));
416 eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(statReg_, ppu_.lyCounter(), cc));
417
418 if (statReg_ & 0x08)
419 eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
420
421 if (hdmaIsEnabled())
422 eventTimes_.setm<HDMA_REQ>(nextHdmaTime(ppu_.lastM0Time(),
423 nextM0Time_.predictedNextM0Time(), cc, isDoubleSpeed()));
424 }
425 else
426 {
427 for (int i = 0; i < NUM_MEM_EVENTS; ++i)
428 eventTimes_.set(static_cast<MemEvent>(i), disabled_time);
429 }
430 }
431 else if (data & 0x80)
432 {
433 if (ppu_.cgb())
434 {
435 ppu_.setLcdc((oldLcdc & ~0x14) | (data & 0x14), cc);
436
437 if ((oldLcdc ^ data) & 0x04)
438 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
439
440 update(cc + isDoubleSpeed() + 1);
441 ppu_.setLcdc(data, cc + isDoubleSpeed() + 1);
442
443 if ((oldLcdc ^ data) & 0x20)
444 mode3CyclesChange();
445 }
446 else
447 {
448 ppu_.setLcdc(data, cc);
449
450 if ((oldLcdc ^ data) & 0x04)
451 eventTimes_.setm<SPRITE_MAP>(SpriteMapper::schedule(ppu_.lyCounter(), cc));
452
453 if ((oldLcdc ^ data) & 0x22)
454 mode3CyclesChange();
455 }
456 }
457 else
458 ppu_.setLcdc(data, cc);
459 }
460
461 namespace {
462 struct LyCnt {
463 unsigned ly; int timeToNextLy;
LyCntgambatte::__anon67436c150111::LyCnt464 LyCnt(unsigned ly, int timeToNextLy) : ly(ly), timeToNextLy(timeToNextLy) {}
465 };
466
getLycCmpLy(LyCounter const & lyCounter,unsigned long cc)467 static LyCnt const getLycCmpLy(LyCounter const &lyCounter, unsigned long cc) {
468 unsigned ly = lyCounter.ly();
469 int timeToNextLy = lyCounter.time() - cc;
470
471 if (ly == 153)
472 {
473 unsigned is_doublespeed = (unsigned)lyCounter.isDoubleSpeed();
474 if (timeToNextLy - (448 << is_doublespeed) > 0)
475 timeToNextLy -= (448 << is_doublespeed);
476 else
477 {
478 ly = 0;
479 timeToNextLy += lyCounter.lineTime();
480 }
481 }
482 return LyCnt(ly, timeToNextLy);
483 }
484 }
485
lcdstatChange(const unsigned data,const unsigned long cc)486 void LCD::lcdstatChange(const unsigned data, const unsigned long cc)
487 {
488 if (cc >= eventTimes_.nextEventTime())
489 update(cc);
490
491 const unsigned old = statReg_;
492 statReg_ = data;
493 lycIrq_.statRegChange(data, ppu_.lyCounter(), cc);
494
495 if (ppu_.lcdc() & 0x80)
496 {
497 int const timeToNextLy = ppu_.lyCounter().time() - cc;
498 LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
499
500 if (!ppu_.cgb())
501 {
502 if (ppu_.lyCounter().ly() < 144)
503 {
504 if (cc + 1 < m0TimeOfCurrentLine(cc))
505 {
506 if (lycCmp.ly == lycIrq_.lycReg() && !(old & 0x40))
507 eventTimes_.flagIrq(2);
508 }
509 else
510 {
511 if (!(old & 0x08) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
512 eventTimes_.flagIrq(2);
513 }
514 }
515 else
516 {
517 if (!(old & 0x10) && !(lycCmp.ly == lycIrq_.lycReg() && (old & 0x40)))
518 eventTimes_.flagIrq(2);
519 }
520 }
521 else if (data & ~old & 0x78)
522 {
523 bool const lycperiod = lycCmp.ly == lycIrq_.lycReg() && lycCmp.timeToNextLy > 4 - isDoubleSpeed() * 4;
524
525 if (!(lycperiod && (old & 0x40)))
526 {
527 if (ppu_.lyCounter().ly() < 144)
528 {
529 if (cc + isDoubleSpeed() * 2 < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4)
530 {
531 if (lycperiod && (data & 0x40))
532 eventTimes_.flagIrq(2);
533 }
534 else if (!(old & 0x08))
535 {
536 if ((data & 0x08) || (lycperiod && (data & 0x40)))
537 eventTimes_.flagIrq(2);
538 }
539 }
540 else if (!(old & 0x10))
541 {
542 if ((data & 0x10) && (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - isDoubleSpeed() * 4))
543 {
544 eventTimes_.flagIrq(2);
545 }
546 else if (lycperiod && (data & 0x40))
547 eventTimes_.flagIrq(2);
548 }
549 }
550
551 if ((data & 0x28) == 0x20 && !(old & 0x20)
552 && ((timeToNextLy <= 4 && ppu_.lyCounter().ly() < 143)
553 || (timeToNextLy == 456*2 && ppu_.lyCounter().ly() < 144)))
554 {
555 eventTimes_.flagIrq(2);
556 }
557 }
558
559 if ((data & 0x08) && eventTimes_(MODE0_IRQ) == disabled_time)
560 {
561 update(cc);
562 eventTimes_.setm<MODE0_IRQ>(m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed()));
563 }
564
565 eventTimes_.setm<MODE2_IRQ>(mode2IrqSchedule(data, ppu_.lyCounter(), cc));
566 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
567 }
568
569 m2IrqStatReg_ = eventTimes_(MODE2_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
570 ? data : (m2IrqStatReg_ & 0x10) | (statReg_ & ~0x10);
571 m1IrqStatReg_ = eventTimes_(MODE1_IRQ) - cc > (ppu_.cgb() - isDoubleSpeed()) * 4U
572 ? data : (m1IrqStatReg_ & 0x08) | (statReg_ & ~0x08);
573
574 m0Irq_.statRegChange(data, eventTimes_(MODE0_IRQ), cc, ppu_.cgb());
575 }
576
lycRegChange(const unsigned data,const unsigned long cc)577 void LCD::lycRegChange(const unsigned data, const unsigned long cc)
578 {
579 unsigned const old = lycIrq_.lycReg();
580
581 if (data == old)
582 return;
583
584 if (cc >= eventTimes_.nextEventTime())
585 update(cc);
586
587 m0Irq_.lycRegChange(data, eventTimes_(MODE0_IRQ), cc, isDoubleSpeed(), ppu_.cgb());
588 lycIrq_.lycRegChange(data, ppu_.lyCounter(), cc);
589
590 if (!(ppu_.lcdc() & 0x80))
591 return;
592
593 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
594
595 int const timeToNextLy = ppu_.lyCounter().time() - cc;
596 unsigned is_doublespeed = (unsigned)isDoubleSpeed();
597
598 if ((statReg_ & 0x40) && data < 154
599 && (ppu_.lyCounter().ly() < 144
600 ? !(statReg_ & 0x08) || cc < m0TimeOfCurrentLine(cc) || timeToNextLy <= 4 << ppu_.cgb()
601 : !(statReg_ & 0x10) || (ppu_.lyCounter().ly() == 153 && timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)))
602 {
603 LyCnt lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
604
605 if (lycCmp.timeToNextLy <= 4 << ppu_.cgb())
606 {
607 lycCmp.ly = old != lycCmp.ly || (lycCmp.timeToNextLy <= 4 && ppu_.cgb() && !(bool)is_doublespeed)
608 ? (lycCmp.ly == 153 ? 0 : lycCmp.ly + 1)
609 : 0xFF; // simultaneous ly/lyc inc. lyc flag never goes low -> no trigger.
610 }
611
612 if (data == lycCmp.ly)
613 {
614 if (ppu_.cgb() && !(bool)is_doublespeed)
615 eventTimes_.setm<ONESHOT_LCDSTATIRQ>(cc + 5);
616 else
617 eventTimes_.flagIrq(2);
618 }
619 }
620 }
621
getStat(const unsigned lycReg,const unsigned long cc)622 unsigned LCD::getStat(const unsigned lycReg, const unsigned long cc)
623 {
624 unsigned stat = 0;
625
626 if (ppu_.lcdc() & 0x80)
627 {
628 if (cc >= eventTimes_.nextEventTime())
629 update(cc);
630
631 int const timeToNextLy = ppu_.lyCounter().time() - cc;
632 unsigned is_doublespeed = (unsigned)isDoubleSpeed();
633
634 if (ppu_.lyCounter().ly() > 143)
635 {
636 if (ppu_.lyCounter().ly() < 153 || timeToNextLy > 4 - is_doublespeed * 4)
637 stat = 1;
638 }
639 else
640 {
641 unsigned const lineCycles = 456 - (timeToNextLy >> is_doublespeed);
642
643 if (lineCycles < 80)
644 {
645 if (!ppu_.inactivePeriodAfterDisplayEnable(cc))
646 stat = 2;
647 }
648 else if (cc + is_doublespeed - ppu_.cgb() + 2 < m0TimeOfCurrentLine(cc))
649 stat = 3;
650 }
651
652 LyCnt const lycCmp = getLycCmpLy(ppu_.lyCounter(), cc);
653
654 if (lycReg == lycCmp.ly && lycCmp.timeToNextLy > 4 - is_doublespeed * 4)
655 stat |= 4;
656 }
657
658 return stat;
659 }
660
doMode2IrqEvent()661 inline void LCD::doMode2IrqEvent()
662 {
663 const unsigned ly = eventTimes_(LY_COUNT) - eventTimes_(MODE2_IRQ) < 8
664 ? (ppu_.lyCounter().ly() == 153 ? 0 : ppu_.lyCounter().ly() + 1)
665 : ppu_.lyCounter().ly();
666
667 if ((ly != 0 || !(m2IrqStatReg_ & 0x10)) &&
668 (!(m2IrqStatReg_ & 0x40) || (lycIrq_.lycReg() != 0 ? ly != (lycIrq_.lycReg() + 1U) : ly > 1)))
669 eventTimes_.flagIrq(2);
670
671 m2IrqStatReg_ = statReg_;
672
673 if (!(statReg_ & 0x08))
674 {
675 unsigned long nextTime = eventTimes_(MODE2_IRQ) + ppu_.lyCounter().lineTime();
676
677 if (ly == 0)
678 nextTime -= 4;
679 else if (ly == 143)
680 nextTime += ppu_.lyCounter().lineTime() * 10 + 4;
681
682 eventTimes_.setm<MODE2_IRQ>(nextTime);
683 }
684 else
685 eventTimes_.setm<MODE2_IRQ>(eventTimes_(MODE2_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
686 }
687
event()688 inline void LCD::event()
689 {
690 switch (eventTimes_.nextEvent())
691 {
692 case MEM_EVENT:
693 switch (eventTimes_.nextMemEvent())
694 {
695 case MODE1_IRQ:
696 eventTimes_.flagIrq((m1IrqStatReg_ & 0x18) == 0x10 ? 3 : 1);
697 m1IrqStatReg_ = statReg_;
698 eventTimes_.setm<MODE1_IRQ>(eventTimes_(MODE1_IRQ) + (70224 << (unsigned)isDoubleSpeed()));
699 break;
700
701 case LYC_IRQ:
702 {
703 unsigned char ifreg = 0;
704 lycIrq_.doEvent(&ifreg, ppu_.lyCounter());
705 eventTimes_.flagIrq(ifreg);
706 eventTimes_.setm<LYC_IRQ>(lycIrq_.time());
707 }
708 break;
709 case SPRITE_MAP:
710 eventTimes_.setm<SPRITE_MAP>(ppu_.doSpriteMapEvent(eventTimes_(SPRITE_MAP)));
711 mode3CyclesChange();
712 break;
713 case HDMA_REQ:
714 eventTimes_.flagHdmaReq();
715 nextM0Time_.predictNextM0Time(ppu_);
716 eventTimes_.setm<HDMA_REQ>(hdmaTimeFromM0Time(nextM0Time_.predictedNextM0Time(), isDoubleSpeed()));
717 break;
718 case MODE2_IRQ:
719 doMode2IrqEvent();
720 break;
721 case MODE0_IRQ:
722 {
723 unsigned char ifreg = 0;
724 m0Irq_.doEvent(&ifreg, ppu_.lyCounter().ly(), statReg_, lycIrq_.lycReg());
725 eventTimes_.flagIrq(ifreg);
726 }
727
728 eventTimes_.setm<MODE0_IRQ>((statReg_ & 0x08)
729 ? m0IrqTimeFromXpos166Time(ppu_.predictedNextXposTime(166), ppu_.cgb(), isDoubleSpeed())
730 : static_cast<unsigned long>(disabled_time));
731 break;
732 case ONESHOT_LCDSTATIRQ:
733 eventTimes_.flagIrq(2);
734 eventTimes_.setm<ONESHOT_LCDSTATIRQ>(disabled_time);
735 break;
736 case ONESHOT_UPDATEWY2:
737 ppu_.updateWy2();
738 mode3CyclesChange();
739 eventTimes_.setm<ONESHOT_UPDATEWY2>(disabled_time);
740 break;
741 }
742 break;
743 case LY_COUNT:
744 ppu_.doLyCountEvent();
745 eventTimes_.set<LY_COUNT>(ppu_.lyCounter().time());
746 break;
747 }
748 }
749
update(const unsigned long cycleCounter)750 void LCD::update(const unsigned long cycleCounter)
751 {
752 if (!(ppu_.lcdc() & 0x80))
753 return;
754
755 while (cycleCounter >= eventTimes_.nextEventTime())
756 {
757 ppu_.update(eventTimes_.nextEventTime());
758 event();
759 }
760
761 ppu_.update(cycleCounter);
762 }
763
764 }
765