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