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