1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #include "TIA.hxx"
19 #include "M6502.hxx"
20 #include "M6532.hxx"
21 #include "Control.hxx"
22 #include "Paddles.hxx"
23 #include "DelayQueueIteratorImpl.hxx"
24 #include "TIAConstants.hxx"
25 #include "frame-manager/FrameManager.hxx"
26 #include "AudioQueue.hxx"
27 #include "DispatchResult.hxx"
28 #include "Base.hxx"
29 
30 enum CollisionMask: uInt32 {
31   player0   = 0b0111110000000000,
32   player1   = 0b0100001111000000,
33   missile0  = 0b0010001000111000,
34   missile1  = 0b0001000100100110,
35   ball      = 0b0000100010010101,
36   playfield = 0b0000010001001011
37 };
38 
39 enum Delay: uInt8 {
40   hmove = 6,
41   pf = 2,
42   grp = 1,
43   shufflePlayer = 1,
44   shuffleBall = 1,
45   hmp = 2,
46   hmm = 2,
47   hmbl = 2,
48   hmclr = 2,
49   refp = 1,
50   enabl = 1,
51   enam = 1,
52   vblank = 1
53 };
54 
55 enum ResxCounter: uInt8 {
56   hblank = 159,
57   lateHblank = 158,
58   frame = 157
59 };
60 
61 // This parameter still has room for tuning. If we go lower than 73, long005 will show
62 // a slight artifact (still have to crosscheck on real hardware), if we go lower than
63 // 70, the G.I. Joe will show an artifact (hole in roof).
64 static constexpr uInt8 resxLateHblankThreshold = TIAConstants::H_CYCLES - 3;
65 
66 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA(ConsoleIO & console,const ConsoleTimingProvider & timingProvider,Settings & settings)67 TIA::TIA(ConsoleIO& console, const ConsoleTimingProvider& timingProvider,
68          Settings& settings)
69   : myConsole{console},
70     myTimingProvider{timingProvider},
71     mySettings{settings},
72     myPlayfield{~CollisionMask::playfield & 0x7FFF},
73     myMissile0{~CollisionMask::missile0 & 0x7FFF},
74     myMissile1{~CollisionMask::missile1 & 0x7FFF},
75     myPlayer0{~CollisionMask::player0 & 0x7FFF},
76     myPlayer1{~CollisionMask::player1 & 0x7FFF},
77     myBall{~CollisionMask::ball & 0x7FFF}
78 {
79   myBackground.setTIA(this);
80   myPlayfield.setTIA(this);
81   myPlayer0.setTIA(this);
82   myPlayer1.setTIA(this);
83   myMissile0.setTIA(this);
84   myMissile1.setTIA(this);
85   myBall.setTIA(this);
86 
87   initialize();
88 }
89 
90 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setFrameManager(AbstractFrameManager * frameManager)91 void TIA::setFrameManager(AbstractFrameManager* frameManager)
92 {
93   clearFrameManager();
94 
95   myFrameManager = frameManager;
96 
97   myFrameManager->setHandlers(
98     [this] () {
99       onFrameStart();
100     },
101     [this] () {
102       onFrameComplete();
103     }
104   );
105 
106   myFrameManager->enableJitter(myEnableJitter);
107   myFrameManager->setJitterFactor(myJitterFactor);
108 }
109 
110 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAudioQueue(const shared_ptr<AudioQueue> & queue)111 void TIA::setAudioQueue(const shared_ptr<AudioQueue>& queue)
112 {
113   myAudio.setAudioQueue(queue);
114 }
115 
116 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAudioRewindMode(bool enable)117 void TIA::setAudioRewindMode(bool enable)
118 {
119   myAudio.setAudioRewindMode(enable);
120 }
121 
122 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
clearFrameManager()123 void TIA::clearFrameManager()
124 {
125   if (!myFrameManager) return;
126 
127   myFrameManager->clearHandlers();
128 
129   myFrameManager = nullptr;
130 }
131 
132 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
initialize()133 void TIA::initialize()
134 {
135   myHctr = 0;
136   myMovementInProgress = false;
137   myExtendedHblank = false;
138   myMovementClock = 0;
139   myPriority = Priority::normal;
140   myHstate = HState::blank;
141   myCollisionMask = 0;
142   myLinesSinceChange = 0;
143   myCollisionUpdateRequired = myCollisionUpdateScheduled = false;
144   myColorLossEnabled = myColorLossActive = false;
145   myColorHBlank = 0;
146   myLastCycle = 0;
147   mySubClock = 0;
148   myHctrDelta = 0;
149   myXAtRenderingStart = 0;
150 
151   myShadowRegisters.fill(0);
152 
153   myBackground.reset();
154   myPlayfield.reset();
155   myMissile0.reset();
156   myMissile1.reset();
157   myPlayer0.reset();
158   myPlayer1.reset();
159   myBall.reset();
160 
161   myInput0.reset();
162   myInput1.reset();
163 
164   myAudio.reset();
165 
166   myTimestamp = 0;
167   for (AnalogReadout& analogReadout : myAnalogReadouts)
168     analogReadout.reset(myTimestamp);
169 
170   myDelayQueue.reset();
171 
172 #ifdef DEBUGGER_SUPPORT
173   myCyclesAtFrameStart = 0;
174   myFrameWsyncCycles = 0;
175 #endif
176 
177   if (myFrameManager)
178     myFrameManager->reset();
179 
180   myFrontBufferScanlines = myFrameBufferScanlines = 0;
181 
182   myFramesSinceLastRender = 0;
183 
184   // Blank the various framebuffers; they may contain graphical garbage
185   myBackBuffer.fill(0);
186   myFrontBuffer.fill(0);
187   myFramebuffer.fill(0);
188 
189   applyDeveloperSettings();
190 
191   // Must be done last, after all other items have reset
192   bool devSettings = mySettings.getBool("dev.settings");
193   setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
194   enableFixedColors(mySettings.getBool(devSettings ? "dev.debugcolors" : "plr.debugcolors"));
195 
196 #ifdef DEBUGGER_SUPPORT
197   createAccessArrays();
198 #endif // DEBUGGER_SUPPORT
199 }
200 
201 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()202 void TIA::reset()
203 {
204   // Simply call initialize(); mostly to get around calling a virtual method
205   // from the constructor
206   initialize();
207 
208   if(myRandomize && !mySystem->autodetectMode())
209   {
210     for(uInt32 i = 0; i < 0x4000; ++i)
211     {
212       uInt16 address = mySystem->randGenerator().next() & 0x3F;
213 
214       if(address <= 0x2F)
215       {
216         poke(address, mySystem->randGenerator().next());
217         cycle(1 + (mySystem->randGenerator().next() & 7)); // process delay queue
218       }
219     }
220     cycle(76); // just to be sure :)
221   }
222 }
223 
224 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
install(System & system)225 void TIA::install(System& system)
226 {
227   installDelegate(system, *this);
228 }
229 
230 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
installDelegate(System & system,Device & device)231 void TIA::installDelegate(System& system, Device& device)
232 {
233   // Remember which system I'm installed in
234   mySystem = &system;
235 
236   // All accesses are to the given device
237   System::PageAccess access(&device, System::PageAccessType::READWRITE);
238 
239   // Map all peek/poke to mirrors of TIA address space to this class
240   // That is, all mirrors of ($00 - $3F) in the lower 4K of the 2600
241   // address space are mapped here
242   for(uInt16 addr = 0; addr < 0x1000; addr += System::PAGE_SIZE)
243     if((addr & TIA_BIT) == 0x0000)
244       mySystem->setPageAccess(addr, access);
245 
246   mySystem->m6502().setOnHaltCallback(
247     [this] () {
248       onHalt();
249     }
250   );
251 }
252 
253 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save(Serializer & out) const254 bool TIA::save(Serializer& out) const
255 {
256   try
257   {
258     if(!myDelayQueue.save(out))   return false;
259     if(!myFrameManager->save(out)) return false;
260 
261     if(!myBackground.save(out)) return false;
262     if(!myPlayfield.save(out))  return false;
263     if(!myMissile0.save(out))   return false;
264     if(!myMissile1.save(out))   return false;
265     if(!myPlayer0.save(out))    return false;
266     if(!myPlayer1.save(out))    return false;
267     if(!myBall.save(out))       return false;
268     if(!myAudio.save(out))      return false;
269 
270     for (const AnalogReadout& analogReadout : myAnalogReadouts)
271       if(!analogReadout.save(out)) return false;
272 
273     if(!myInput0.save(out)) return false;
274     if(!myInput1.save(out)) return false;
275 
276     out.putInt(int(myHstate));
277 
278     out.putInt(myHctr);
279     out.putInt(myHctrDelta);
280     out.putInt(myXAtRenderingStart);
281 
282     out.putBool(myCollisionUpdateRequired);
283     out.putBool(myCollisionUpdateScheduled);
284     out.putInt(myCollisionMask);
285 
286     out.putInt(myMovementClock);
287     out.putBool(myMovementInProgress);
288     out.putBool(myExtendedHblank);
289 
290     out.putInt(myLinesSinceChange);
291 
292     out.putInt(int(myPriority));
293 
294     out.putByte(mySubClock);
295     out.putLong(myLastCycle);
296 
297     out.putByte(mySpriteEnabledBits);
298     out.putByte(myCollisionsEnabledBits);
299 
300     out.putByte(myColorHBlank);
301 
302     out.putLong(myTimestamp);
303 
304     out.putByteArray(myShadowRegisters.data(), myShadowRegisters.size());
305 
306   #ifdef DEBUGGER_SUPPORT
307     out.putLong(myCyclesAtFrameStart);
308     out.putLong(myFrameWsyncCycles);
309   #endif
310 
311     out.putInt(myFrameBufferScanlines);
312     out.putInt(myFrontBufferScanlines);
313 
314     out.putByte(myPFBitsDelay);
315     out.putByte(myPFColorDelay);
316     out.putByte(myBKColorDelay);
317     out.putByte(myPlSwapDelay);
318   }
319   catch(...)
320   {
321     cerr << "ERROR: TIA::save" << endl;
322     return false;
323   }
324 
325   return true;
326 }
327 
328 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
load(Serializer & in)329 bool TIA::load(Serializer& in)
330 {
331   try
332   {
333     if(!myDelayQueue.load(in))   return false;
334     if(!myFrameManager->load(in)) return false;
335 
336     if(!myBackground.load(in)) return false;
337     if(!myPlayfield.load(in))  return false;
338     if(!myMissile0.load(in))   return false;
339     if(!myMissile1.load(in))   return false;
340     if(!myPlayer0.load(in))    return false;
341     if(!myPlayer1.load(in))    return false;
342     if(!myBall.load(in))       return false;
343     if(!myAudio.load(in))       return false;
344 
345     for (AnalogReadout& analogReadout : myAnalogReadouts)
346       if(!analogReadout.load(in)) return false;
347 
348     if(!myInput0.load(in)) return false;
349     if(!myInput1.load(in)) return false;
350 
351     myHstate = HState(in.getInt());
352 
353     myHctr = in.getInt();
354     myHctrDelta = in.getInt();
355     myXAtRenderingStart = in.getInt();
356 
357     myCollisionUpdateRequired = in.getBool();
358     myCollisionUpdateScheduled = in.getBool();
359     myCollisionMask = in.getInt();
360 
361     myMovementClock = in.getInt();
362     myMovementInProgress = in.getBool();
363     myExtendedHblank = in.getBool();
364 
365     myLinesSinceChange = in.getInt();
366 
367     myPriority = Priority(in.getInt());
368 
369     mySubClock = in.getByte();
370     myLastCycle = in.getLong();
371 
372     mySpriteEnabledBits = in.getByte();
373     myCollisionsEnabledBits = in.getByte();
374 
375     myColorHBlank = in.getByte();
376 
377     myTimestamp = in.getLong();
378 
379     in.getByteArray(myShadowRegisters.data(), myShadowRegisters.size());
380 
381   #ifdef DEBUGGER_SUPPORT
382     myCyclesAtFrameStart = in.getLong();
383     myFrameWsyncCycles = in.getLong();
384   #endif
385 
386     myFrameBufferScanlines = in.getInt();
387     myFrontBufferScanlines = in.getInt();
388 
389     myPFBitsDelay = in.getByte();
390     myPFColorDelay = in.getByte();
391     myBKColorDelay = in.getByte();
392     myPlSwapDelay = in.getByte();
393 
394     // Re-apply dev settings
395     applyDeveloperSettings();
396   }
397   catch(...)
398   {
399     cerr << "ERROR: TIA::load" << endl;
400     return false;
401   }
402 
403   return true;
404 }
405 
406 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bindToControllers()407 void TIA::bindToControllers()
408 {
409   myConsole.leftController().setOnAnalogPinUpdateCallback(
410     [this] (Controller::AnalogPin pin) {
411       updateEmulation();
412 
413       switch (pin) {
414         case Controller::AnalogPin::Five:
415           updateAnalogReadout(1);
416           break;
417 
418         case Controller::AnalogPin::Nine:
419           updateAnalogReadout(0);
420           break;
421       }
422     }
423   );
424 
425   myConsole.rightController().setOnAnalogPinUpdateCallback(
426     [this] (Controller::AnalogPin pin) {
427       updateEmulation();
428 
429       switch (pin) {
430         case Controller::AnalogPin::Five:
431           updateAnalogReadout(3);
432           break;
433 
434         case Controller::AnalogPin::Nine:
435           updateAnalogReadout(2);
436           break;
437       }
438     }
439   );
440 
441   for (uInt8 i = 0; i < 4; ++i)
442     updateAnalogReadout(i);
443 }
444 
445 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
peek(uInt16 address)446 uInt8 TIA::peek(uInt16 address)
447 {
448   updateEmulation();
449 
450   // Start with all bits disabled
451   // In some cases both D7 and D6 are used; in other cases only D7 is used
452   uInt8 result = 0b0000000;
453 
454   switch (address & 0x0F) {
455     case CXM0P:
456       result = collCXM0P() & 0b11000000;
457       break;
458 
459     case CXM1P:
460       result = collCXM1P() & 0b11000000;
461       break;
462 
463     case CXP0FB:
464       result = collCXP0FB() & 0b11000000;
465       break;
466 
467     case CXP1FB:
468       result = collCXP1FB() & 0b11000000;
469       break;
470 
471     case CXM0FB:
472       result = collCXM0FB() & 0b11000000;
473       break;
474 
475     case CXM1FB:
476       result = collCXM1FB() & 0b11000000;
477       break;
478 
479     case CXPPMM:
480       result = collCXPPMM() & 0b11000000;
481       break;
482 
483     case CXBLPF:
484       result = collCXBLPF() & 0b10000000;
485       break;
486 
487     case INPT0:
488       updateAnalogReadout(0);
489       result = myAnalogReadouts[0].inpt(myTimestamp) & 0b10000000;
490       break;
491 
492     case INPT1:
493       updateAnalogReadout(1);
494       result = myAnalogReadouts[1].inpt(myTimestamp) & 0b10000000;
495       break;
496 
497     case INPT2:
498       updateAnalogReadout(2);
499       result = myAnalogReadouts[2].inpt(myTimestamp) & 0b10000000;
500       break;
501 
502     case INPT3:
503       updateAnalogReadout(3);
504       result = myAnalogReadouts[3].inpt(myTimestamp) & 0b10000000;
505       break;
506 
507     case INPT4:
508       result = myInput0.inpt(!myConsole.leftController().read(Controller::DigitalPin::Six))
509           & 0b10000000;
510       break;
511 
512     case INPT5:
513       result = myInput1.inpt(!myConsole.rightController().read(Controller::DigitalPin::Six))
514           & 0b10000000;
515       break;
516 
517     default:
518       break;
519   }
520 
521   // Bits D5 .. D0 are floating
522   // The options are either to use the last databus value, or use random data
523   return result | ((!myTIAPinsDriven ? mySystem->getDataBusState() :
524     mySystem->randGenerator().next()) & 0b00111111);
525 }
526 
527 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
poke(uInt16 address,uInt8 value)528 bool TIA::poke(uInt16 address, uInt8 value)
529 {
530   updateEmulation();
531 
532   address &= 0x3F;
533 
534   switch (address)
535   {
536     case WSYNC:
537       mySystem->m6502().requestHalt();
538       break;
539 
540     case RSYNC:
541       flushLineCache();
542       applyRsync();
543       myShadowRegisters[address] = value;
544       break;
545 
546     case VSYNC:
547       myFrameManager->setVsync(value & 0x02);
548       myShadowRegisters[address] = value;
549       break;
550 
551     case VBLANK:
552       myInput0.vblank(value);
553       myInput1.vblank(value);
554 
555       for (AnalogReadout& analogReadout : myAnalogReadouts)
556         analogReadout.vblank(value, myTimestamp);
557       updateDumpPorts(value);
558 
559       myDelayQueue.push(VBLANK, value, Delay::vblank);
560 
561       break;
562 
563     case AUDV0:
564     {
565       myAudio.channel0().audv(value);
566       myShadowRegisters[address] = value;
567     #ifdef DEBUGGER_SUPPORT
568       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
569       if(dataAddr)
570         mySystem->setAccessFlags(dataAddr, Device::AUD);
571     #endif
572       break;
573     }
574 
575     case AUDV1:
576     {
577       myAudio.channel1().audv(value);
578       myShadowRegisters[address] = value;
579     #ifdef DEBUGGER_SUPPORT
580       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
581       if(dataAddr)
582         mySystem->setAccessFlags(dataAddr, Device::AUD);
583     #endif
584       break;
585     }
586 
587     case AUDF0:
588     {
589       myAudio.channel0().audf(value);
590       myShadowRegisters[address] = value;
591     #ifdef DEBUGGER_SUPPORT
592       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
593       if(dataAddr)
594         mySystem->setAccessFlags(dataAddr, Device::AUD);
595     #endif
596       break;
597     }
598 
599     case AUDF1:
600     {
601       myAudio.channel1().audf(value);
602       myShadowRegisters[address] = value;
603     #ifdef DEBUGGER_SUPPORT
604       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
605       if(dataAddr)
606         mySystem->setAccessFlags(dataAddr, Device::AUD);
607     #endif
608       break;
609     }
610 
611     case AUDC0:
612     {
613       myAudio.channel0().audc(value);
614       myShadowRegisters[address] = value;
615     #ifdef DEBUGGER_SUPPORT
616       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
617       if(dataAddr)
618         mySystem->setAccessFlags(dataAddr, Device::AUD);
619     #endif
620       break;
621     }
622 
623     case AUDC1:
624     {
625       myAudio.channel1().audc(value);
626       myShadowRegisters[address] = value;
627     #ifdef DEBUGGER_SUPPORT
628       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
629       if(dataAddr)
630         mySystem->setAccessFlags(dataAddr, Device::AUD);
631     #endif
632       break;
633     }
634 
635     case HMOVE:
636       myDelayQueue.push(HMOVE, value, Delay::hmove);
637       break;
638 
639     case COLUBK:
640     {
641       value &= 0xFE;
642       if(myBKColorDelay)
643         myDelayQueue.push(COLUBK, value, 1);
644       else
645       {
646         myBackground.setColor(value);
647         myShadowRegisters[address] = value;
648       }
649     #ifdef DEBUGGER_SUPPORT
650       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
651       if(dataAddr)
652         mySystem->setAccessFlags(dataAddr, Device::BCOL);
653     #endif
654       break;
655     }
656 
657     case COLUP0:
658     {
659       value &= 0xFE;
660       myPlayfield.setColorP0(value);
661       myMissile0.setColor(value);
662       myPlayer0.setColor(value);
663       myShadowRegisters[address] = value;
664     #ifdef DEBUGGER_SUPPORT
665       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
666       if(dataAddr)
667         mySystem->setAccessFlags(dataAddr, Device::COL);
668     #endif
669       break;
670     }
671 
672     case COLUP1:
673     {
674       value &= 0xFE;
675       myPlayfield.setColorP1(value);
676       myMissile1.setColor(value);
677       myPlayer1.setColor(value);
678       myShadowRegisters[address] = value;
679     #ifdef DEBUGGER_SUPPORT
680       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
681       if(dataAddr)
682         mySystem->setAccessFlags(dataAddr, Device::COL);
683     #endif
684       break;
685     }
686 
687     case CTRLPF:
688       flushLineCache();
689       myPriority = (value & 0x04) ? Priority::pfp :
690                    (value & 0x02) ? Priority::score : Priority::normal;
691       myPlayfield.ctrlpf(value);
692       myBall.ctrlpf(value);
693       myShadowRegisters[address] = value;
694       break;
695 
696     case COLUPF:
697     {
698       flushLineCache();
699       value &= 0xFE;
700       if(myPFColorDelay)
701         myDelayQueue.push(COLUPF, value, 1);
702       else
703       {
704         myPlayfield.setColor(value);
705         myBall.setColor(value);
706         myShadowRegisters[address] = value;
707       }
708     #ifdef DEBUGGER_SUPPORT
709       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
710       if(dataAddr)
711         mySystem->setAccessFlags(dataAddr, Device::PCOL);
712     #endif
713       break;
714     }
715 
716     case PF0:
717     {
718       myDelayQueue.push(PF0, value, myPFBitsDelay);
719     #ifdef DEBUGGER_SUPPORT
720       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
721       if(dataAddr)
722         mySystem->setAccessFlags(dataAddr, Device::PGFX);
723     #endif
724       break;
725     }
726 
727     case PF1:
728     {
729       myDelayQueue.push(PF1, value, myPFBitsDelay);
730     #ifdef DEBUGGER_SUPPORT
731       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
732       if(dataAddr)
733         mySystem->setAccessFlags(dataAddr, Device::PGFX);
734     #endif
735       break;
736     }
737 
738     case PF2:
739     {
740       myDelayQueue.push(PF2, value, myPFBitsDelay);
741     #ifdef DEBUGGER_SUPPORT
742       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
743       if(dataAddr)
744         mySystem->setAccessFlags(dataAddr, Device::PGFX);
745     #endif
746       break;
747     }
748 
749     case ENAM0:
750       myDelayQueue.push(ENAM0, value, Delay::enam);
751       break;
752 
753     case ENAM1:
754       myDelayQueue.push(ENAM1, value, Delay::enam);
755       break;
756 
757     case RESM0:
758       flushLineCache();
759       myMissile0.resm(resxCounter(), myHstate == HState::blank);
760       myShadowRegisters[address] = value;
761       break;
762 
763     case RESM1:
764       flushLineCache();
765       myMissile1.resm(resxCounter(), myHstate == HState::blank);
766       myShadowRegisters[address] = value;
767       break;
768 
769     case RESMP0:
770       myMissile0.resmp(value, myPlayer0);
771       myShadowRegisters[address] = value;
772       break;
773 
774     case RESMP1:
775       myMissile1.resmp(value, myPlayer1);
776       myShadowRegisters[address] = value;
777       break;
778 
779     case NUSIZ0:
780       flushLineCache();
781       myMissile0.nusiz(value);
782       myPlayer0.nusiz(value, myHstate == HState::blank);
783       myShadowRegisters[address] = value;
784       break;
785 
786     case NUSIZ1:
787       flushLineCache();
788       myMissile1.nusiz(value);
789       myPlayer1.nusiz(value, myHstate == HState::blank);
790       myShadowRegisters[address] = value;
791       break;
792 
793     case HMM0:
794       myDelayQueue.push(HMM0, value, Delay::hmm);
795       break;
796 
797     case HMM1:
798       myDelayQueue.push(HMM1, value, Delay::hmm);
799       break;
800 
801     case HMCLR:
802       myDelayQueue.push(HMCLR, value, Delay::hmclr);
803       break;
804 
805     case GRP0:
806     {
807       myDelayQueue.push(GRP0, value, Delay::grp);
808       myDelayQueue.push(DummyRegisters::shuffleP1, 0, myPlSwapDelay);
809     #ifdef DEBUGGER_SUPPORT
810       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
811       if(dataAddr)
812         mySystem->setAccessFlags(dataAddr, Device::GFX);
813     #endif
814       break;
815     }
816 
817     case GRP1:
818     {
819       myDelayQueue.push(GRP1, value, Delay::grp);
820       myDelayQueue.push(DummyRegisters::shuffleP0, 0, myPlSwapDelay);
821       myDelayQueue.push(DummyRegisters::shuffleBL, 0, Delay::shuffleBall);
822     #ifdef DEBUGGER_SUPPORT
823       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
824       if(dataAddr)
825         mySystem->setAccessFlags(dataAddr, Device::GFX);
826     #endif
827       break;
828     }
829 
830     case RESP0:
831       flushLineCache();
832       myPlayer0.resp(resxCounter());
833       myShadowRegisters[address] = value;
834       break;
835 
836     case RESP1:
837       flushLineCache();
838       myPlayer1.resp(resxCounter());
839       myShadowRegisters[address] = value;
840       break;
841 
842     case REFP0:
843       myDelayQueue.push(REFP0, value, Delay::refp);
844       break;
845 
846     case REFP1:
847       myDelayQueue.push(REFP1, value, Delay::refp);
848       break;
849 
850     case VDELP0:
851       myPlayer0.vdelp(value);
852       myShadowRegisters[address] = value;
853       break;
854 
855     case VDELP1:
856       myPlayer1.vdelp(value);
857       myShadowRegisters[address] = value;
858       break;
859 
860     case HMP0:
861       myDelayQueue.push(HMP0, value, Delay::hmp);
862       break;
863 
864     case HMP1:
865       myDelayQueue.push(HMP1, value, Delay::hmp);
866       break;
867 
868     case ENABL:
869       myDelayQueue.push(ENABL, value, Delay::enabl);
870       break;
871 
872     case RESBL:
873       flushLineCache();
874       myBall.resbl(resxCounter());
875       myShadowRegisters[address] = value;
876       break;
877 
878     case VDELBL:
879       myBall.vdelbl(value);
880       myShadowRegisters[address] = value;
881       break;
882 
883     case HMBL:
884       myDelayQueue.push(HMBL, value, Delay::hmbl);
885       break;
886 
887     case CXCLR:
888       flushLineCache();
889       myCollisionMask = 0;
890       myShadowRegisters[address] = value;
891       break;
892 
893     default:
894       break;
895   }
896 
897   return true;
898 }
899 
900 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
saveDisplay(Serializer & out) const901 bool TIA::saveDisplay(Serializer& out) const
902 {
903   try
904   {
905     out.putByteArray(myFramebuffer.data(), myFramebuffer.size());
906     out.putByteArray(myBackBuffer.data(), myBackBuffer.size());
907     out.putByteArray(myFrontBuffer.data(), myFrontBuffer.size());
908     out.putInt(myFramesSinceLastRender);
909   }
910   catch(...)
911   {
912     cerr << "ERROR: TIA::saveDisplay" << endl;
913     return false;
914   }
915 
916   return true;
917 }
918 
919 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
loadDisplay(Serializer & in)920 bool TIA::loadDisplay(Serializer& in)
921 {
922   try
923   {
924     // Reset frame buffer pointer and data
925     in.getByteArray(myFramebuffer.data(), myFramebuffer.size());
926     in.getByteArray(myBackBuffer.data(), myBackBuffer.size());
927     in.getByteArray(myFrontBuffer.data(), myFrontBuffer.size());
928     myFramesSinceLastRender = in.getInt();
929   }
930   catch(...)
931   {
932     cerr << "ERROR: TIA::loadDisplay" << endl;
933     return false;
934   }
935 
936   return true;
937 }
938 
939 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
applyDeveloperSettings()940 void TIA::applyDeveloperSettings()
941 {
942   bool devSettings = mySettings.getBool("dev.settings");
943   if(devSettings)
944   {
945     bool custom = BSPF::equalsIgnoreCase("custom", mySettings.getString("dev.tia.type"));
946 
947     setPlInvertedPhaseClock(custom
948                             ? mySettings.getBool("dev.tia.plinvphase")
949                             : BSPF::equalsIgnoreCase("koolaidman", mySettings.getString("dev.tia.type")));
950     setMsInvertedPhaseClock(custom
951                             ? mySettings.getBool("dev.tia.msinvphase")
952                             : BSPF::equalsIgnoreCase("cosmicark", mySettings.getString("dev.tia.type")));
953     setBlInvertedPhaseClock(custom ? mySettings.getBool("dev.tia.blinvphase") : false);
954     setPFBitsDelay(custom
955                    ? mySettings.getBool("dev.tia.delaypfbits")
956                    : BSPF::equalsIgnoreCase("pesco", mySettings.getString("dev.tia.type")));
957     setPFColorDelay(custom
958                     ? mySettings.getBool("dev.tia.delaypfcolor")
959                     : BSPF::equalsIgnoreCase("quickstep", mySettings.getString("dev.tia.type")));
960     setBKColorDelay(custom
961                     ? mySettings.getBool("dev.tia.delaybkcolor")
962                     : BSPF::equalsIgnoreCase("indy500", mySettings.getString("dev.tia.type")));
963     setPlSwapDelay(custom
964                    ? mySettings.getBool("dev.tia.delayplswap")
965                    : BSPF::equalsIgnoreCase("heman", mySettings.getString("dev.tia.type")));
966     setBlSwapDelay(custom ? mySettings.getBool("dev.tia.delayblswap") : false);
967   }
968   else
969   {
970     setPlInvertedPhaseClock(false);
971     setMsInvertedPhaseClock(false);
972     setBlInvertedPhaseClock(false);
973     setPFBitsDelay(false);
974     setPFColorDelay(false);
975     setBKColorDelay(false);
976     setPlSwapDelay(false);
977     setBlSwapDelay(false);
978   }
979 
980   myRandomize = mySettings.getBool(devSettings ? "dev.tiarandom" : "plr.tiarandom");
981   myTIAPinsDriven = devSettings ? mySettings.getBool("dev.tiadriven") : false;
982 
983   myEnableJitter = mySettings.getBool(devSettings ? "dev.tv.jitter" : "plr.tv.jitter");
984   myJitterFactor = mySettings.getInt(devSettings ? "dev.tv.jitter_recovery" : "plr.tv.jitter_recovery");
985 
986   if(myFrameManager)
987     enableColorLoss(mySettings.getBool(devSettings ? "dev.colorloss" : "plr.colorloss"));
988 }
989 
990 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
update(DispatchResult & result,uInt64 maxCycles)991 void TIA::update(DispatchResult& result, uInt64 maxCycles)
992 {
993   mySystem->m6502().execute(maxCycles, result);
994 
995   updateEmulation();
996 }
997 
998 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
renderToFrameBuffer()999 void TIA::renderToFrameBuffer()
1000 {
1001   if (myFramesSinceLastRender == 0) return;
1002 
1003   myFramesSinceLastRender = 0;
1004 
1005   myFramebuffer = myFrontBuffer;
1006 
1007   myFrameBufferScanlines = myFrontBufferScanlines;
1008 }
1009 
1010 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
clearFrameBuffer()1011 void TIA::clearFrameBuffer()
1012 {
1013   myFramebuffer.fill(0);
1014   myFrontBuffer.fill(0);
1015 }
1016 
1017 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
update(uInt64 maxCycles)1018 void TIA::update(uInt64 maxCycles)
1019 {
1020   DispatchResult dispatchResult;
1021 
1022   update(dispatchResult, maxCycles);
1023 }
1024 
1025 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
enableColorLoss(bool enabled)1026 bool TIA::enableColorLoss(bool enabled)
1027 {
1028   bool allowColorLoss = myTimingProvider() == ConsoleTiming::pal;
1029 
1030   if(allowColorLoss && enabled)
1031   {
1032     myColorLossEnabled = true;
1033     myColorLossActive = myFrameManager->scanlinesLastFrame() & 0x1;
1034   }
1035   else
1036   {
1037     myColorLossEnabled = myColorLossActive = false;
1038 
1039     myMissile0.applyColorLoss();
1040     myMissile1.applyColorLoss();
1041     myPlayer0.applyColorLoss();
1042     myPlayer1.applyColorLoss();
1043     myBall.applyColorLoss();
1044     myPlayfield.applyColorLoss();
1045     myBackground.applyColorLoss();
1046   }
1047 
1048   return allowColorLoss;
1049 }
1050 
1051 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
electronBeamPos(uInt32 & x,uInt32 & y) const1052 bool TIA::electronBeamPos(uInt32& x, uInt32& y) const
1053 {
1054   uInt8 clocks = clocksThisLine();
1055 
1056   x = (clocks < TIAConstants::H_BLANK_CLOCKS) ? 0 : clocks - TIAConstants::H_BLANK_CLOCKS;
1057   y = myFrameManager->getY();
1058 
1059   return isRendering();
1060 }
1061 
1062 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleBit(TIABit b,uInt8 mode)1063 bool TIA::toggleBit(TIABit b, uInt8 mode)
1064 {
1065   uInt8 mask;
1066 
1067   switch (mode) {
1068     case 0:
1069       mask = 0;
1070       break;
1071 
1072     case 1:
1073       mask = b;
1074       break;
1075 
1076     case 2:
1077       mask = (~mySpriteEnabledBits & b);
1078       break;
1079 
1080     default:
1081       mask = (mySpriteEnabledBits & b);
1082       break;
1083   }
1084 
1085   mySpriteEnabledBits = (mySpriteEnabledBits & ~b) | mask;
1086 
1087   myMissile0.toggleEnabled(mySpriteEnabledBits & TIABit::M0Bit);
1088   myMissile1.toggleEnabled(mySpriteEnabledBits & TIABit::M1Bit);
1089   myPlayer0.toggleEnabled(mySpriteEnabledBits & TIABit::P0Bit);
1090   myPlayer1.toggleEnabled(mySpriteEnabledBits & TIABit::P1Bit);
1091   myBall.toggleEnabled(mySpriteEnabledBits & TIABit::BLBit);
1092   myPlayfield.toggleEnabled(mySpriteEnabledBits & TIABit::PFBit);
1093 
1094   return mask;
1095 }
1096 
1097 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleBits(bool toggle)1098 bool TIA::toggleBits(bool toggle)
1099 {
1100   toggleBit(TIABit(0xFF), toggle
1101                           ? mySpriteEnabledBits > 0 ? 0 : 1
1102                           : mySpriteEnabledBits);
1103 
1104   return mySpriteEnabledBits;
1105 }
1106 
1107 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollision(TIABit b,uInt8 mode)1108 bool TIA::toggleCollision(TIABit b, uInt8 mode)
1109 {
1110   uInt8 mask;
1111 
1112   switch (mode) {
1113     case 0:
1114       mask = 0;
1115       break;
1116 
1117     case 1:
1118       mask = b;
1119       break;
1120 
1121     case 2:
1122       mask = (~myCollisionsEnabledBits & b);
1123       break;
1124 
1125     default:
1126       mask = (myCollisionsEnabledBits & b);
1127       break;
1128   }
1129 
1130   myCollisionsEnabledBits = (myCollisionsEnabledBits & ~b) | mask;
1131 
1132   myMissile0.toggleCollisions(myCollisionsEnabledBits & TIABit::M0Bit);
1133   myMissile1.toggleCollisions(myCollisionsEnabledBits & TIABit::M1Bit);
1134   myPlayer0.toggleCollisions(myCollisionsEnabledBits & TIABit::P0Bit);
1135   myPlayer1.toggleCollisions(myCollisionsEnabledBits & TIABit::P1Bit);
1136   myBall.toggleCollisions(myCollisionsEnabledBits & TIABit::BLBit);
1137   myPlayfield.toggleCollisions(myCollisionsEnabledBits & TIABit::PFBit);
1138 
1139   return mask;
1140 }
1141 
1142 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollisions(bool toggle)1143 bool TIA::toggleCollisions(bool toggle)
1144 {
1145   toggleCollision(TIABit(0xFF), toggle
1146                                 ? myCollisionsEnabledBits > 0 ? 0 : 1
1147                                 : myCollisionsEnabledBits);
1148 
1149   return myCollisionsEnabledBits;
1150 }
1151 
1152 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
enableFixedColors(bool enable)1153 bool TIA::enableFixedColors(bool enable)
1154 {
1155   int timing = myTimingProvider() == ConsoleTiming::ntsc ? 0
1156     : myTimingProvider() == ConsoleTiming::pal ? 1 : 2;
1157 
1158   myMissile0.setDebugColor(myFixedColorPalette[timing][FixedObject::M0]);
1159   myMissile1.setDebugColor(myFixedColorPalette[timing][FixedObject::M1]);
1160   myPlayer0.setDebugColor(myFixedColorPalette[timing][FixedObject::P0]);
1161   myPlayer1.setDebugColor(myFixedColorPalette[timing][FixedObject::P1]);
1162   myBall.setDebugColor(myFixedColorPalette[timing][FixedObject::BL]);
1163   myPlayfield.setDebugColor(myFixedColorPalette[timing][FixedObject::PF]);
1164   myBackground.setDebugColor(myFixedColorPalette[timing][FixedObject::BK]);
1165 
1166   myMissile0.enableDebugColors(enable);
1167   myMissile1.enableDebugColors(enable);
1168   myPlayer0.enableDebugColors(enable);
1169   myPlayer1.enableDebugColors(enable);
1170   myBall.enableDebugColors(enable);
1171   myPlayfield.enableDebugColors(enable);
1172   myBackground.enableDebugColors(enable);
1173   myColorHBlank = enable ? FixedColor::HBLANK_WHITE : 0x00;
1174 
1175   return enable;
1176 }
1177 
1178 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setFixedColorPalette(const string & colors)1179 bool TIA::setFixedColorPalette(const string& colors)
1180 {
1181   string s = colors;
1182   sort(s.begin(), s.end());
1183   if(s != "bgopry")
1184     return false;
1185 
1186   for(int i = 0; i < 6; ++i)
1187   {
1188     switch(colors[i])
1189     {
1190       case 'r':
1191         myFixedColorPalette[0][i] = FixedColor::NTSC_RED;
1192         myFixedColorPalette[1][i] = FixedColor::PAL_RED;
1193         myFixedColorPalette[2][i] = FixedColor::SECAM_RED;
1194         myFixedColorNames[i] = "Red   ";
1195         break;
1196       case 'o':
1197         myFixedColorPalette[0][i] = FixedColor::NTSC_ORANGE;
1198         myFixedColorPalette[1][i] = FixedColor::PAL_ORANGE;
1199         myFixedColorPalette[2][i] = FixedColor::SECAM_ORANGE;
1200         myFixedColorNames[i] = "Orange";
1201         break;
1202       case 'y':
1203         myFixedColorPalette[0][i] = FixedColor::NTSC_YELLOW;
1204         myFixedColorPalette[1][i] = FixedColor::PAL_YELLOW;
1205         myFixedColorPalette[2][i] = FixedColor::SECAM_YELLOW;
1206         myFixedColorNames[i] = "Yellow";
1207         break;
1208       case 'g':
1209         myFixedColorPalette[0][i] = FixedColor::NTSC_GREEN;
1210         myFixedColorPalette[1][i] = FixedColor::PAL_GREEN;
1211         myFixedColorPalette[2][i] = FixedColor::SECAM_GREEN;
1212         myFixedColorNames[i] = "Green ";
1213         break;
1214       case 'b':
1215         myFixedColorPalette[0][i] = FixedColor::NTSC_BLUE;
1216         myFixedColorPalette[1][i] = FixedColor::PAL_BLUE;
1217         myFixedColorPalette[2][i] = FixedColor::SECAM_BLUE;
1218         myFixedColorNames[i] = "Blue  ";
1219         break;
1220       case 'p':
1221         myFixedColorPalette[0][i] = FixedColor::NTSC_PURPLE;
1222         myFixedColorPalette[1][i] = FixedColor::PAL_PURPLE;
1223         myFixedColorPalette[2][i] = FixedColor::SECAM_PURPLE;
1224         myFixedColorNames[i] = "Purple";
1225         break;
1226     }
1227   }
1228   myFixedColorPalette[0][TIA::BK] = FixedColor::NTSC_GREY;
1229   myFixedColorPalette[1][TIA::BK] = FixedColor::PAL_GREY;
1230   myFixedColorPalette[2][TIA::BK] = FixedColor::SECAM_GREY;
1231 
1232   // If already in fixed debug colours mode, update the current palette
1233   if(usingFixedColors())
1234     enableFixedColors(true);
1235 
1236   return true;
1237 }
1238 
1239 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
driveUnusedPinsRandom(uInt8 mode)1240 bool TIA::driveUnusedPinsRandom(uInt8 mode)
1241 {
1242   // If mode is 0 or 1, use it as a boolean (off or on)
1243   // Otherwise, return the state
1244   if (mode == 0 || mode == 1)
1245     myTIAPinsDriven = bool(mode);
1246 
1247   return myTIAPinsDriven;
1248 }
1249 
1250 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleJitter(uInt8 mode)1251 bool TIA::toggleJitter(uInt8 mode)
1252 {
1253   switch (mode) {
1254     case 0:
1255       myEnableJitter = false;
1256       break;
1257 
1258     case 1:
1259       myEnableJitter = true;
1260       break;
1261 
1262     case 2:
1263       myEnableJitter = !myEnableJitter;
1264       break;
1265 
1266     case 3:
1267       break;
1268 
1269     default:
1270       throw runtime_error("invalid argument for toggleJitter");
1271   }
1272 
1273   if (myFrameManager) myFrameManager->enableJitter(myEnableJitter);
1274 
1275   return myEnableJitter;
1276 }
1277 
1278 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setJitterRecoveryFactor(Int32 factor)1279 void TIA::setJitterRecoveryFactor(Int32 factor)
1280 {
1281   myJitterFactor = factor;
1282 
1283   if (myFrameManager) myFrameManager->setJitterFactor(myJitterFactor);
1284 }
1285 
1286 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
delayQueueIterator() const1287 shared_ptr<DelayQueueIterator> TIA::delayQueueIterator() const
1288 {
1289   return make_shared<DelayQueueIteratorImpl<delayQueueLength, delayQueueSize>>(
1290     myDelayQueue
1291   );
1292 }
1293 
1294 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateScanline()1295 TIA& TIA::updateScanline()
1296 {
1297   // Update frame by one scanline at a time
1298   uInt32 line = scanlines();
1299   while (line == scanlines() && mySystem->m6502().execute(1));
1300 
1301   return *this;
1302 }
1303 
1304 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateScanlineByStep()1305 TIA& TIA::updateScanlineByStep()
1306 {
1307   // Update frame by one CPU instruction/color clock
1308   mySystem->m6502().execute(1);
1309 
1310   return *this;
1311 }
1312 
1313 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
registerValue(uInt8 reg) const1314 uInt8 TIA::registerValue(uInt8 reg) const
1315 {
1316   return reg < 64 ? myShadowRegisters[reg] : 0;
1317 }
1318 
1319 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateEmulation()1320 void TIA::updateEmulation()
1321 {
1322   const uInt64 systemCycles = mySystem->cycles();
1323 
1324   if (mySubClock > TIAConstants::CYCLE_CLOCKS - 1)
1325     throw runtime_error("subclock exceeds range");
1326 
1327   const uInt32 cyclesToRun = TIAConstants::CYCLE_CLOCKS * uInt32(systemCycles - myLastCycle) + mySubClock;
1328 
1329   mySubClock = 0;
1330   myLastCycle = systemCycles;
1331 
1332   cycle(cyclesToRun);
1333 }
1334 
1335 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
onFrameStart()1336 void TIA::onFrameStart()
1337 {
1338   myXAtRenderingStart = 0;
1339 #ifdef DEBUGGER_SUPPORT
1340   myFrameWsyncCycles = 0;
1341   mySystem->m6532().resetTimReadCylces();
1342 #endif
1343 
1344   // Check for colour-loss emulation
1345   if (myColorLossEnabled)
1346   {
1347     // Only activate it when necessary, since changing colours in
1348     // the graphical object forces the TIA cached line to be flushed
1349     if (myFrameManager->scanlineParityChanged())
1350     {
1351       myColorLossActive = myFrameManager->scanlinesLastFrame() & 0x1;
1352 
1353       myMissile0.applyColorLoss();
1354       myMissile1.applyColorLoss();
1355       myPlayer0.applyColorLoss();
1356       myPlayer1.applyColorLoss();
1357       myBall.applyColorLoss();
1358       myPlayfield.applyColorLoss();
1359       myBackground.applyColorLoss();
1360     }
1361   }
1362 }
1363 
1364 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
onFrameComplete()1365 void TIA::onFrameComplete()
1366 {
1367   mySystem->m6502().stop();
1368 #ifdef DEBUGGER_SUPPORT
1369   myCyclesAtFrameStart = mySystem->cycles();
1370 #endif
1371 
1372   if (myXAtRenderingStart > 0)
1373     std::fill_n(myBackBuffer.begin(), myXAtRenderingStart, 0);
1374 
1375   // Blank out any extra lines not drawn this frame
1376   const Int32 missingScanlines = myFrameManager->missingScanlines();
1377   if (missingScanlines > 0)
1378     std::fill_n(myBackBuffer.begin() + TIAConstants::H_PIXEL * myFrameManager->getY(), missingScanlines * TIAConstants::H_PIXEL, 0);
1379 
1380   myFrontBuffer = myBackBuffer;
1381 
1382   myFrontBufferScanlines = scanlinesLastFrame();
1383 
1384   ++myFramesSinceLastRender;
1385 }
1386 
1387 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
onHalt()1388 void TIA::onHalt()
1389 {
1390   mySubClock += (TIAConstants::H_CLOCKS - myHctr) % TIAConstants::H_CLOCKS;
1391   mySystem->incrementCycles(mySubClock / TIAConstants::CYCLE_CLOCKS);
1392 #ifdef DEBUGGER_SUPPORT
1393   myFrameWsyncCycles += 3 + mySubClock / TIAConstants::CYCLE_CLOCKS;
1394 #endif
1395   mySubClock %= TIAConstants::CYCLE_CLOCKS;
1396 }
1397 
1398 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cycle(uInt32 colorClocks)1399 void TIA::cycle(uInt32 colorClocks)
1400 {
1401   for (uInt32 i = 0; i < colorClocks; ++i)
1402   {
1403     myDelayQueue.execute(
1404       [this] (uInt8 address, uInt8 value) {delayedWrite(address, value);}
1405     );
1406 
1407     myCollisionUpdateRequired = myCollisionUpdateScheduled;
1408     myCollisionUpdateScheduled = false;
1409 
1410     if (myLinesSinceChange < 2) {
1411       tickMovement();
1412 
1413       if (myHstate == HState::blank)
1414         tickHblank();
1415       else
1416         tickHframe();
1417 
1418       if (myCollisionUpdateRequired && !myFrameManager->vblank()) updateCollision();
1419     }
1420 
1421     if (++myHctr >= TIAConstants::H_CLOCKS)
1422       nextLine();
1423 
1424     #ifdef SOUND_SUPPORT
1425       myAudio.tick();
1426     #endif
1427 
1428     ++myTimestamp;
1429   }
1430 }
1431 
1432 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tickMovement()1433 void TIA::tickMovement()
1434 {
1435   if (!myMovementInProgress) return;
1436 
1437   if ((myHctr & 0x03) == 0) {
1438     const bool hblank = myHstate == HState::blank;
1439     uInt8 movementCounter = myMovementClock > 15 ? 0 : myMovementClock;
1440 
1441     myMissile0.movementTick(movementCounter, myHctr, hblank);
1442     myMissile1.movementTick(movementCounter, myHctr, hblank);
1443     myPlayer0.movementTick(movementCounter, hblank);
1444     myPlayer1.movementTick(movementCounter, hblank);
1445     myBall.movementTick(movementCounter, hblank);
1446 
1447     myMovementInProgress =
1448       myMissile0.isMoving ||
1449       myMissile1.isMoving ||
1450       myPlayer0.isMoving  ||
1451       myPlayer1.isMoving  ||
1452       myBall.isMoving;
1453 
1454     myCollisionUpdateRequired = myCollisionUpdateRequired || myMovementInProgress;
1455 
1456     ++myMovementClock;
1457   }
1458 }
1459 
1460 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tickHblank()1461 void TIA::tickHblank()
1462 {
1463   switch (myHctr) {
1464     case 0:
1465       myExtendedHblank = false;
1466       break;
1467 
1468     case TIAConstants::H_BLANK_CLOCKS - 1:
1469       if (!myExtendedHblank) myHstate = HState::frame;
1470       break;
1471 
1472     case TIAConstants::H_BLANK_CLOCKS + 7:
1473       if (myExtendedHblank) myHstate = HState::frame;
1474       break;
1475   }
1476 
1477   if (myExtendedHblank && myHctr > TIAConstants::H_BLANK_CLOCKS - 1) myPlayfield.tick(myHctr - TIAConstants::H_BLANK_CLOCKS - myHctrDelta);
1478 }
1479 
1480 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tickHframe()1481 void TIA::tickHframe()
1482 {
1483   const uInt32 y = myFrameManager->getY();
1484   const uInt32 x = myHctr - TIAConstants::H_BLANK_CLOCKS - myHctrDelta;
1485 
1486   myCollisionUpdateRequired = true;
1487 
1488   myPlayfield.tick(x);
1489   myMissile0.tick(myHctr);
1490   myMissile1.tick(myHctr);
1491   myPlayer0.tick();
1492   myPlayer1.tick();
1493   myBall.tick();
1494 
1495   if (myFrameManager->isRendering())
1496     renderPixel(x, y);
1497 }
1498 
1499 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
applyRsync()1500 void TIA::applyRsync()
1501 {
1502   const uInt32 x = myHctr > TIAConstants::H_BLANK_CLOCKS ? myHctr - TIAConstants::H_BLANK_CLOCKS : 0;
1503 
1504   myHctrDelta = TIAConstants::H_CLOCKS - 3 - myHctr;
1505   if (myFrameManager->isRendering())
1506     std::fill_n(myBackBuffer.begin() + myFrameManager->getY() * TIAConstants::H_PIXEL + x, TIAConstants::H_PIXEL - x, 0);
1507 
1508   myHctr = TIAConstants::H_CLOCKS - 3;
1509 }
1510 
1511 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nextLine()1512 void TIA::nextLine()
1513 {
1514   if (myLinesSinceChange >= 2) {
1515     cloneLastLine();
1516   }
1517 
1518   myHctr = 0;
1519 
1520   if (!myMovementInProgress && myLinesSinceChange < 2) ++myLinesSinceChange;
1521 
1522   myHstate = HState::blank;
1523   myHctrDelta = 0;
1524 
1525   myFrameManager->nextLine();
1526   myMissile0.nextLine();
1527   myMissile1.nextLine();
1528   myPlayer0.nextLine();
1529   myPlayer1.nextLine();
1530   myBall.nextLine();
1531   myPlayfield.nextLine();
1532 
1533   if (myFrameManager->isRendering() && myFrameManager->getY() == 0) flushLineCache();
1534 
1535   mySystem->m6502().clearHaltRequest();
1536 }
1537 
1538 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cloneLastLine()1539 void TIA::cloneLastLine()
1540 {
1541   const auto y = myFrameManager->getY();
1542 
1543   if (!myFrameManager->isRendering() || y == 0) return;
1544 
1545   std::copy_n(myBackBuffer.begin() + (y-1) * TIAConstants::H_PIXEL, TIAConstants::H_PIXEL,
1546       myBackBuffer.begin() + y * TIAConstants::H_PIXEL);
1547 }
1548 
1549 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
scheduleCollisionUpdate()1550 void TIA::scheduleCollisionUpdate()
1551 {
1552   myCollisionUpdateScheduled = true;
1553 }
1554 
1555 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateCollision()1556 void TIA::updateCollision()
1557 {
1558   myCollisionMask |= (
1559     myPlayer0.collision &
1560     myPlayer1.collision &
1561     myMissile0.collision &
1562     myMissile1.collision &
1563     myBall.collision &
1564     myPlayfield.collision
1565   );
1566 }
1567 
1568 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
renderPixel(uInt32 x,uInt32 y)1569 void TIA::renderPixel(uInt32 x, uInt32 y)
1570 {
1571   if (x >= TIAConstants::H_PIXEL) return;
1572 
1573   uInt8 color = 0;
1574 
1575   if (!myFrameManager->vblank())
1576   {
1577     switch (myPriority)
1578     {
1579       case Priority::pfp:  // CTRLPF D2=1, D1=ignored
1580         // Playfield has priority so ScoreBit isn't used
1581         // Priority from highest to lowest:
1582         //   BL/PF => P0/M0 => P1/M1 => BK
1583         if (myPlayfield.isOn())       color = myPlayfield.getColor();
1584         else if (myBall.isOn())       color = myBall.getColor();
1585         else if (myPlayer0.isOn())    color = myPlayer0.getColor();
1586         else if (myMissile0.isOn())   color = myMissile0.getColor();
1587         else if (myPlayer1.isOn())    color = myPlayer1.getColor();
1588         else if (myMissile1.isOn())   color = myMissile1.getColor();
1589         else                          color = myBackground.getColor();
1590         break;
1591 
1592       case Priority::score:  // CTRLPF D2=0, D1=1
1593         // Formally we have (priority from highest to lowest)
1594         //   PF/P0/M0 => P1/M1 => BL => BK
1595         // for the first half and
1596         //   P0/M0 => PF/P1/M1 => BL => BK
1597         // for the second half. However, the first ordering is equivalent
1598         // to the second (PF has the same color as P0/M0), so we can just
1599         // write
1600         if (myPlayer0.isOn())         color = myPlayer0.getColor();
1601         else if (myMissile0.isOn())   color = myMissile0.getColor();
1602         else if (myPlayfield.isOn())  color = myPlayfield.getColor();
1603         else if (myPlayer1.isOn())    color = myPlayer1.getColor();
1604         else if (myMissile1.isOn())   color = myMissile1.getColor();
1605         else if (myBall.isOn())       color = myBall.getColor();
1606         else                          color = myBackground.getColor();
1607         break;
1608 
1609       case Priority::normal:  // CTRLPF D2=0, D1=0
1610         // Priority from highest to lowest:
1611         //   P0/M0 => P1/M1 => BL/PF => BK
1612         if (myPlayer0.isOn())         color = myPlayer0.getColor();
1613         else if (myMissile0.isOn())   color = myMissile0.getColor();
1614         else if (myPlayer1.isOn())    color = myPlayer1.getColor();
1615         else if (myMissile1.isOn())   color = myMissile1.getColor();
1616         else if (myPlayfield.isOn())  color = myPlayfield.getColor();
1617         else if (myBall.isOn())       color = myBall.getColor();
1618         else                          color = myBackground.getColor();
1619         break;
1620     }
1621   }
1622 
1623   myBackBuffer[y * TIAConstants::H_PIXEL + x] = color;
1624 }
1625 
1626 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
flushLineCache()1627 void TIA::flushLineCache()
1628 {
1629   const bool wasCaching = myLinesSinceChange >= 2;
1630 
1631   myLinesSinceChange = 0;
1632 
1633   if (wasCaching) {
1634     const auto rewindCycles = myHctr;
1635 
1636     for (myHctr = 0; myHctr < rewindCycles; ++myHctr) {
1637       if (myHstate == HState::blank)
1638         tickHblank();
1639       else
1640         tickHframe();
1641     }
1642   }
1643 }
1644 
1645 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
clearHmoveComb()1646 void TIA::clearHmoveComb()
1647 {
1648   if (myFrameManager->isRendering() && myHstate == HState::blank)
1649     std::fill_n(myBackBuffer.begin() + myFrameManager->getY() * TIAConstants::H_PIXEL, 8, myColorHBlank);
1650 }
1651 
1652 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setPFBitsDelay(bool delayed)1653 void TIA::setPFBitsDelay(bool delayed)
1654 {
1655   myPFBitsDelay = delayed ? Delay::pf + 1 : Delay::pf;
1656 }
1657 
1658 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setPFColorDelay(bool delayed)1659 void TIA::setPFColorDelay(bool delayed)
1660 {
1661   myPFColorDelay = delayed ? 1 : 0;
1662 }
1663 
1664 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setBKColorDelay(bool delayed)1665 void TIA::setBKColorDelay(bool delayed)
1666 {
1667   myBKColorDelay = delayed ? 1 : 0;
1668 }
1669 
1670 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setPlSwapDelay(bool delayed)1671 void TIA::setPlSwapDelay(bool delayed)
1672 {
1673   myPlSwapDelay = delayed ? Delay::shufflePlayer + 1 : Delay::shufflePlayer;
1674 }
1675 
1676 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setBlSwapDelay(bool delayed)1677 void TIA::setBlSwapDelay(bool delayed)
1678 {
1679   myBlSwapDelay = delayed ? Delay::shuffleBall + 1 : Delay::shuffleBall;
1680 }
1681 
1682 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setPlInvertedPhaseClock(bool enable)1683 void TIA::setPlInvertedPhaseClock(bool enable)
1684 {
1685   myPlayer0.setInvertedPhaseClock(enable);
1686   myPlayer1.setInvertedPhaseClock(enable);
1687 }
1688 
1689 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setMsInvertedPhaseClock(bool enable)1690 void TIA::setMsInvertedPhaseClock(bool enable)
1691 {
1692   myMissile0.setInvertedPhaseClock(enable);
1693   myMissile1.setInvertedPhaseClock(enable);
1694 }
1695 
1696 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setBlInvertedPhaseClock(bool enable)1697 void TIA::setBlInvertedPhaseClock(bool enable)
1698 {
1699   myBall.setInvertedPhaseClock(enable);
1700 }
1701 
1702 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
delayedWrite(uInt8 address,uInt8 value)1703 void TIA::delayedWrite(uInt8 address, uInt8 value)
1704 {
1705   if (address < 64)
1706     myShadowRegisters[address] = value;
1707 
1708   switch (address)
1709   {
1710     case VBLANK:
1711       flushLineCache();
1712       myFrameManager->setVblank(value & 0x02);
1713       break;
1714 
1715     case HMOVE:
1716       flushLineCache();
1717 
1718       myMovementClock = 0;
1719       myMovementInProgress = true;
1720 
1721       if (!myExtendedHblank) {
1722         clearHmoveComb();
1723         myExtendedHblank = true;
1724       }
1725 
1726       myMissile0.startMovement();
1727       myMissile1.startMovement();
1728       myPlayer0.startMovement();
1729       myPlayer1.startMovement();
1730       myBall.startMovement();
1731       break;
1732 
1733     case PF0:
1734       myPlayfield.pf0(value);
1735       break;
1736 
1737     case PF1:
1738       myPlayfield.pf1(value);
1739       break;
1740 
1741     case PF2:
1742       myPlayfield.pf2(value);
1743       break;
1744 
1745     case COLUBK:
1746       myBackground.setColor(value);
1747       break;
1748 
1749     case COLUPF:
1750       myPlayfield.setColor(value);
1751       myBall.setColor(value);
1752       break;
1753 
1754     case HMM0:
1755       myMissile0.hmm(value);
1756       break;
1757 
1758     case HMM1:
1759       myMissile1.hmm(value);
1760       break;
1761 
1762     case HMCLR:
1763       // We must update the shadow registers for each HM object too
1764       myMissile0.hmm(0);  myShadowRegisters[HMM0] = 0;
1765       myMissile1.hmm(0);  myShadowRegisters[HMM1] = 0;
1766       myPlayer0.hmp(0);   myShadowRegisters[HMP0] = 0;
1767       myPlayer1.hmp(0);   myShadowRegisters[HMP1] = 0;
1768       myBall.hmbl(0);     myShadowRegisters[HMBL] = 0;
1769       break;
1770 
1771     case GRP0:
1772       myPlayer0.grp(value);
1773       break;
1774 
1775     case GRP1:
1776       myPlayer1.grp(value);
1777       break;
1778 
1779     case DummyRegisters::shuffleP0:
1780       myPlayer0.shufflePatterns();
1781       break;
1782 
1783     case DummyRegisters::shuffleP1:
1784       myPlayer1.shufflePatterns();
1785       break;
1786 
1787     case DummyRegisters::shuffleBL:
1788       myBall.shuffleStatus();
1789       break;
1790 
1791     case HMP0:
1792       myPlayer0.hmp(value);
1793       break;
1794 
1795     case HMP1:
1796       myPlayer1.hmp(value);
1797       break;
1798 
1799     case HMBL:
1800       myBall.hmbl(value);
1801       break;
1802 
1803     case REFP0:
1804       myPlayer0.refp(value);
1805       break;
1806 
1807     case REFP1:
1808       myPlayer1.refp(value);
1809       break;
1810 
1811     case ENABL:
1812       myBall.enabl(value);
1813       break;
1814 
1815     case ENAM0:
1816       myMissile0.enam(value);
1817       break;
1818 
1819     case ENAM1:
1820       myMissile1.enam(value);
1821       break;
1822 
1823     default:
1824       break;
1825   }
1826 }
1827 
1828 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateAnalogReadout(uInt8 idx)1829 void TIA::updateAnalogReadout(uInt8 idx)
1830 {
1831   AnalogReadout::Connection connection;
1832   switch (idx) {
1833     case 0:
1834       connection = myConsole.leftController().read(Controller::AnalogPin::Nine);
1835       break;
1836 
1837     case 1:
1838       connection = myConsole.leftController().read(Controller::AnalogPin::Five);
1839       break;
1840 
1841     case 2:
1842       connection = myConsole.rightController().read(Controller::AnalogPin::Nine);
1843       break;
1844 
1845     case 3:
1846       connection = myConsole.rightController().read(Controller::AnalogPin::Five);
1847       break;
1848 
1849     default:
1850       throw runtime_error("invalid analog input");
1851   }
1852 
1853   myAnalogReadouts[idx].update(
1854     connection,
1855     myTimestamp,
1856     myTimingProvider()
1857   );
1858 }
1859 
1860 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
resxCounter()1861 uInt8 TIA::resxCounter()
1862 {
1863   return myHstate == HState::blank ?
1864     (myHctr >= resxLateHblankThreshold ? ResxCounter::lateHblank : ResxCounter::hblank) : ResxCounter::frame;
1865 }
1866 
1867 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXM0P() const1868 uInt8 TIA::collCXM0P() const
1869 {
1870   return (
1871     ((myCollisionMask & CollisionMask::missile0 & CollisionMask::player0) ? 0x40 : 0) |
1872     ((myCollisionMask & CollisionMask::missile0 & CollisionMask::player1) ? 0x80 : 0)
1873   );
1874 }
1875 
1876 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXM1P() const1877 uInt8 TIA::collCXM1P() const
1878 {
1879   return (
1880     ((myCollisionMask & CollisionMask::missile1 & CollisionMask::player1) ? 0x40 : 0) |
1881     ((myCollisionMask & CollisionMask::missile1 & CollisionMask::player0) ? 0x80 : 0)
1882   );
1883 }
1884 
1885 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXP0FB() const1886 uInt8 TIA::collCXP0FB() const
1887 {
1888   return (
1889     ((myCollisionMask & CollisionMask::player0 & CollisionMask::ball) ? 0x40 : 0) |
1890     ((myCollisionMask & CollisionMask::player0 & CollisionMask::playfield) ? 0x80 : 0)
1891   );
1892 }
1893 
1894 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXP1FB() const1895 uInt8 TIA::collCXP1FB() const
1896 {
1897   return (
1898     ((myCollisionMask & CollisionMask::player1 & CollisionMask::ball) ? 0x40 : 0) |
1899     ((myCollisionMask & CollisionMask::player1 & CollisionMask::playfield) ? 0x80 : 0)
1900   );
1901 }
1902 
1903 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXM0FB() const1904 uInt8 TIA::collCXM0FB() const
1905 {
1906   return (
1907     ((myCollisionMask & CollisionMask::missile0 & CollisionMask::ball) ? 0x40 : 0) |
1908     ((myCollisionMask & CollisionMask::missile0 & CollisionMask::playfield) ? 0x80 : 0)
1909   );
1910 }
1911 
1912 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXM1FB() const1913 uInt8 TIA::collCXM1FB() const
1914 {
1915   return (
1916     ((myCollisionMask & CollisionMask::missile1 & CollisionMask::ball) ? 0x40 : 0) |
1917     ((myCollisionMask & CollisionMask::missile1 & CollisionMask::playfield) ? 0x80 : 0)
1918   );
1919 }
1920 
1921 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXPPMM() const1922 uInt8 TIA::collCXPPMM() const
1923 {
1924   return (
1925     ((myCollisionMask & CollisionMask::missile0 & CollisionMask::missile1) ? 0x40 : 0) |
1926     ((myCollisionMask & CollisionMask::player0 & CollisionMask::player1) ? 0x80 : 0)
1927   );
1928 }
1929 
1930 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
collCXBLPF() const1931 uInt8 TIA::collCXBLPF() const
1932 {
1933   return (myCollisionMask & CollisionMask::ball & CollisionMask::playfield) ? 0x80 : 0;
1934 }
1935 
1936 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP0PF()1937 void TIA::toggleCollP0PF()
1938 {
1939   myCollisionMask ^= (CollisionMask::player0 & CollisionMask::playfield);
1940 }
1941 
1942 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP0BL()1943 void TIA::toggleCollP0BL()
1944 {
1945   myCollisionMask ^= (CollisionMask::player0 & CollisionMask::ball);
1946 }
1947 
1948 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP0M1()1949 void TIA::toggleCollP0M1()
1950 {
1951   myCollisionMask ^= (CollisionMask::player0 & CollisionMask::missile1);
1952 }
1953 
1954 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP0M0()1955 void TIA::toggleCollP0M0()
1956 {
1957   myCollisionMask ^= (CollisionMask::player0 & CollisionMask::missile0);
1958 }
1959 
1960 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP0P1()1961 void TIA::toggleCollP0P1()
1962 {
1963   myCollisionMask ^= (CollisionMask::player0 & CollisionMask::player1);
1964 }
1965 
1966 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP1PF()1967 void TIA::toggleCollP1PF()
1968 {
1969   myCollisionMask ^= (CollisionMask::player1 & CollisionMask::playfield);
1970 }
1971 
1972 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP1BL()1973 void TIA::toggleCollP1BL()
1974 {
1975   myCollisionMask ^= (CollisionMask::player1 & CollisionMask::ball);
1976 }
1977 
1978 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP1M1()1979 void TIA::toggleCollP1M1()
1980 {
1981   myCollisionMask ^= (CollisionMask::player1 & CollisionMask::missile1);
1982 }
1983 
1984 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollP1M0()1985 void TIA::toggleCollP1M0()
1986 {
1987   myCollisionMask ^= (CollisionMask::player1 & CollisionMask::missile0);
1988 }
1989 
1990 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollM0PF()1991 void TIA::toggleCollM0PF()
1992 {
1993   myCollisionMask ^= (CollisionMask::missile0 & CollisionMask::playfield);
1994 }
1995 
1996 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollM0BL()1997 void TIA::toggleCollM0BL()
1998 {
1999   myCollisionMask ^= (CollisionMask::missile0 & CollisionMask::ball);
2000 }
2001 
2002 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollM0M1()2003 void TIA::toggleCollM0M1()
2004 {
2005   myCollisionMask ^= (CollisionMask::missile0 & CollisionMask::missile1);
2006 }
2007 
2008 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollM1PF()2009 void TIA::toggleCollM1PF()
2010 {
2011   myCollisionMask ^= (CollisionMask::missile1 & CollisionMask::playfield);
2012 }
2013 
2014 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollM1BL()2015 void TIA::toggleCollM1BL()
2016 {
2017   myCollisionMask ^= (CollisionMask::missile1 & CollisionMask::ball);
2018 }
2019 
2020 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollBLPF()2021 void TIA::toggleCollBLPF()
2022 {
2023   myCollisionMask ^= (CollisionMask::ball & CollisionMask::playfield);
2024 }
2025 
2026 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateDumpPorts(uInt8 value)2027 void TIA::updateDumpPorts(uInt8 value)
2028 {
2029   bool newIsDumped = value & 0x80;
2030 
2031   if(myArePortsDumped != newIsDumped)
2032   {
2033     myArePortsDumped = newIsDumped;
2034     myDumpPortsCycles = mySystem->cycles();
2035   }
2036 }
2037 
2038 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
dumpPortsCycles()2039 Int64 TIA::dumpPortsCycles()
2040 {
2041   return mySystem->cycles() - myDumpPortsCycles;
2042 }
2043 
2044 #ifdef DEBUGGER_SUPPORT
2045 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
createAccessArrays()2046 void TIA::createAccessArrays()
2047 {
2048   myAccessBase.fill(Device::NONE);
2049   myAccessCounter.fill(0);
2050   myAccessDelay.fill(TIA_DELAY);
2051 }
2052 
2053 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getAccessFlags(uInt16 address) const2054 Device::AccessFlags TIA::getAccessFlags(uInt16 address) const
2055 {
2056   return myAccessBase[address & TIA_MASK];
2057 }
2058 
2059 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAccessFlags(uInt16 address,Device::AccessFlags flags)2060 void TIA::setAccessFlags(uInt16 address, Device::AccessFlags flags)
2061 {
2062   // ignore none flag
2063   if (flags != Device::NONE) {
2064     if (flags == Device::WRITE) {
2065       // the first two write accesses are assumed as initialization
2066       if (myAccessDelay[address & TIA_MASK])
2067         myAccessDelay[address & TIA_MASK]--;
2068       else
2069         myAccessBase[address & TIA_MASK] |= flags;
2070     } else
2071       myAccessBase[address & TIA_READ_MASK] |= flags;
2072   }
2073 }
2074 
2075 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
increaseAccessCounter(uInt16 address,bool isWrite)2076 void TIA::increaseAccessCounter(uInt16 address, bool isWrite)
2077 {
2078   if(isWrite)
2079   {
2080     // the first two write accesses are assumed as initialization
2081     if(myAccessDelay[address & TIA_MASK])
2082       myAccessDelay[address & TIA_MASK]--;
2083     else
2084       myAccessCounter[address & TIA_MASK]++;
2085   }
2086   else
2087     myAccessCounter[TIA_SIZE + (address & TIA_READ_MASK)]++;
2088 }
2089 
2090 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getAccessCounters() const2091 string TIA::getAccessCounters() const
2092 {
2093   ostringstream out;
2094 
2095   out << "TIA reads:\n";
2096   for(uInt16 addr = 0x00; addr < TIA_READ_SIZE; ++addr)
2097     out << Common::Base::HEX4 << addr << ","
2098     << Common::Base::toString(myAccessCounter[TIA_SIZE + addr], Common::Base::Fmt::_10_8) << ", ";
2099   out << "\n";
2100   out << "TIA writes:\n";
2101   for(uInt16 addr = 0x00; addr < TIA_SIZE; ++addr)
2102     out << Common::Base::HEX4 << addr << ","
2103     << Common::Base::toString(myAccessCounter[addr], Common::Base::Fmt::_10_8) << ", ";
2104   out << "\n";
2105 
2106   return out.str();
2107 }
2108 #endif // DEBUGGER_SUPPORT
2109