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-2014 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 // $Id: TIA.cxx 2838 2014-01-17 23:34:03Z stephena $
18 //============================================================================
19 
20 #include <cassert>
21 #include <cstdlib>
22 #include <cstring>
23 
24 #include "bspf.hxx"
25 
26 #ifdef DEBUGGER_SUPPORT
27   #include "CartDebug.hxx"
28 #endif
29 
30 #include "Console.hxx"
31 #include "Control.hxx"
32 #include "Device.hxx"
33 #include "M6502.hxx"
34 #include "Settings.hxx"
35 #include "Sound.hxx"
36 #include "System.hxx"
37 #include "TIATables.hxx"
38 
39 #include "TIA.hxx"
40 
41 #define HBLANK 68
42 
43 #define CLAMP_POS(reg) if(reg < 0) { reg += 160; }  reg %= 160;
44 
45 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA(Console & console,Sound & sound,Settings & settings)46 TIA::TIA(Console& console, Sound& sound, Settings& settings)
47   : myConsole(console),
48     mySound(sound),
49     mySettings(settings),
50     myFrameYStart(34),
51     myFrameHeight(210),
52     myMaximumNumberOfScanlines(262),
53     myStartScanline(0),
54     myColorLossEnabled(false),
55     myPartialFrameFlag(false),
56     myAutoFrameEnabled(false),
57     myFrameCounter(0),
58     myPALFrameCounter(0),
59     myBitsEnabled(true),
60     myCollisionsEnabled(true)
61 
62 {
63   // Allocate buffers for two frame buffers
64   myCurrentFrameBuffer = new uInt8[160 * 320];
65   myPreviousFrameBuffer = new uInt8[160 * 320];
66 
67   // Make sure all TIA bits are enabled
68   enableBits(true);
69 
70   // Turn off debug colours (this also sets up the PriorityEncoder)
71   toggleFixedColors(0);
72 
73   // Compute all of the mask tables
74   TIATables::computeAllTables();
75 
76   // Zero audio registers
77   myAUDV0 = myAUDV1 = myAUDF0 = myAUDF1 = myAUDC0 = myAUDC1 = 0;
78 
79   // Should undriven pins be randomly pulled high or low?
80   myTIAPinsDriven = mySettings.getBool("tiadriven");
81 }
82 
83 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
~TIA()84 TIA::~TIA()
85 {
86   delete[] myCurrentFrameBuffer;
87   delete[] myPreviousFrameBuffer;
88 }
89 
90 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()91 void TIA::reset()
92 {
93   // Reset the sound device
94   mySound.reset();
95 
96   // Currently no objects are enabled or selectively disabled
97   myEnabledObjects = 0;
98   myDisabledObjects = 0xFF;
99   myAllowHMOVEBlanks = true;
100 
101   // Some default values for the registers
102   myVSYNC = myVBLANK = 0;
103   myNUSIZ0 = myNUSIZ1 = 0;
104   myColor[P0Color] = myColor[P1Color] = myColor[PFColor] = myColor[BKColor] = 0;
105   myColor[M0Color] = myColor[M1Color] = myColor[BLColor] = myColor[HBLANKColor] = 0;
106 
107   myPlayfieldPriorityAndScore = 0;
108   myCTRLPF = 0;
109   myREFP0 = myREFP1 = false;
110   myPF = 0;
111   myGRP0 = myGRP1 = myDGRP0 = myDGRP1 = 0;
112   myENAM0 = myENAM1 = myENABL = myDENABL = false;
113   myHMP0 = myHMP1 = myHMM0 = myHMM1 = myHMBL = 0;
114   myVDELP0 = myVDELP1 = myVDELBL = myRESMP0 = myRESMP1 = false;
115   myCollision = 0;
116   myCollisionEnabledMask = 0xFFFFFFFF;
117   myPOSP0 = myPOSP1 = myPOSM0 = myPOSM1 = myPOSBL = 0;
118 
119   // Some default values for the "current" variables
120   myCurrentGRP0 = 0;
121   myCurrentGRP1 = 0;
122 
123   myMotionClockP0 = 0;
124   myMotionClockP1 = 0;
125   myMotionClockM0 = 0;
126   myMotionClockM1 = 0;
127   myMotionClockBL = 0;
128 
129   mySuppressP0 = mySuppressP1 = 0;
130 
131   myHMP0mmr = myHMP1mmr = myHMM0mmr = myHMM1mmr = myHMBLmmr = false;
132 
133   myCurrentHMOVEPos = myPreviousHMOVEPos = 0x7FFFFFFF;
134   myHMOVEBlankEnabled = false;
135 
136   enableBits(true);
137 
138   myDumpEnabled = false;
139   myDumpDisabledCycle = 0;
140   myINPT4 = myINPT5 = 0x80;
141 
142   myFrameCounter = myPALFrameCounter = 0;
143   myScanlineCountForLastFrame = 0;
144 
145   myP0Mask = &TIATables::PxMask[0][0][0];
146   myP1Mask = &TIATables::PxMask[0][0][0];
147   myM0Mask = &TIATables::MxMask[0][0][0];
148   myM1Mask = &TIATables::MxMask[0][0][0];
149   myBLMask = &TIATables::BLMask[0][0];
150   myPFMask = TIATables::PFMask[0];
151 
152   // Recalculate the size of the display
153   toggleFixedColors(0);
154   frameReset();
155 }
156 
157 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
frameReset()158 void TIA::frameReset()
159 {
160   // Clear frame buffers
161   clearBuffers();
162 
163   // Reset pixel pointer and drawing flag
164   myFramePointer = myCurrentFrameBuffer;
165 
166   // Calculate color clock offsets for starting and stopping frame drawing
167   // Note that although we always start drawing at scanline zero, the
168   // framebuffer that is exposed outside the class actually starts at 'ystart'
169   myFramePointerOffset = 160 * myFrameYStart;
170 
171   //myAutoFrameEnabled = (mySettings.getFloat("framerate") <= 0);
172   myAutoFrameEnabled = false;
173   myFramerate = myConsole.getFramerate();
174 
175   if(myFramerate > 55.0)  // NTSC
176   {
177     myFixedColor[P0Color]     = 0x30;
178     myFixedColor[P1Color]     = 0x16;
179     myFixedColor[M0Color]     = 0x38;
180     myFixedColor[M1Color]     = 0x12;
181     myFixedColor[BLColor]     = 0x7e;
182     myFixedColor[PFColor]     = 0x76;
183     myFixedColor[BKColor]     = 0x0a;
184     myFixedColor[HBLANKColor] = 0x0e;
185     myColorLossEnabled = false;
186     myMaximumNumberOfScanlines = 290;
187   }
188   else
189   {
190     myFixedColor[P0Color]     = 0x62;
191     myFixedColor[P1Color]     = 0x26;
192     myFixedColor[M0Color]     = 0x68;
193     myFixedColor[M1Color]     = 0x2e;
194     myFixedColor[BLColor]     = 0xde;
195     myFixedColor[PFColor]     = 0xd8;
196     myFixedColor[BKColor]     = 0x1c;
197     myFixedColor[HBLANKColor] = 0x0e;
198     myColorLossEnabled = mySettings.getBool("colorloss");
199     myMaximumNumberOfScanlines = 342;
200   }
201 
202   // NTSC screens will process at least 262 scanlines,
203   // while PAL will have at least 312
204   // In any event, at most 320 lines can be processed
205   uInt32 scanlines = myFrameYStart + myFrameHeight;
206   if(myMaximumNumberOfScanlines == 290)
207     scanlines = MAX(scanlines, 262u);  // NTSC
208   else
209     scanlines = MAX(scanlines, 312u);  // PAL
210   myStopDisplayOffset = 228 * MIN(scanlines, 320u);
211 
212   // Reasonable values to start and stop the current frame drawing
213   myClockWhenFrameStarted = mySystem->cycles() * 3;
214   myClockStartDisplay = myClockWhenFrameStarted;
215   myClockStopDisplay = myClockWhenFrameStarted + myStopDisplayOffset;
216   myClockAtLastUpdate = myClockWhenFrameStarted;
217   myClocksToEndOfScanLine = 228;
218   myVSYNCFinishClock = 0x7FFFFFFF;
219 }
220 
221 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
systemCyclesReset()222 void TIA::systemCyclesReset()
223 {
224   // Get the current system cycle
225   uInt32 cycles = mySystem->cycles();
226 
227   // Adjust the sound cycle indicator
228   mySound.adjustCycleCounter(-1 * cycles);
229 
230   // Adjust the dump cycle
231   myDumpDisabledCycle -= cycles;
232 
233   // Get the current color clock the system is using
234   uInt32 clocks = cycles * 3;
235 
236   // Adjust the clocks by this amount since we're reseting the clock to zero
237   myClockWhenFrameStarted -= clocks;
238   myClockStartDisplay -= clocks;
239   myClockStopDisplay -= clocks;
240   myClockAtLastUpdate -= clocks;
241   myVSYNCFinishClock -= clocks;
242 }
243 
244 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
install(System & system)245 void TIA::install(System& system)
246 {
247   install(system, *this);
248 }
249 
250 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
install(System & system,Device & device)251 void TIA::install(System& system, Device& device)
252 {
253   // Remember which system I'm installed in
254   mySystem = &system;
255 
256   uInt16 shift = mySystem->pageShift();
257   mySystem->resetCycles();
258 
259   // All accesses are to the given device
260   System::PageAccess access(0, 0, 0, &device, System::PA_READWRITE);
261 
262   // We're installing in a 2600 system
263   for(uInt32 i = 0; i < 8192; i += (1 << shift))
264     if((i & 0x1080) == 0x0000)
265       mySystem->setPageAccess(i >> shift, access);
266 }
267 
268 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save(Serializer & out) const269 bool TIA::save(Serializer& out) const
270 {
271   const string& device = name();
272 
273   try
274   {
275     out.putString(device);
276 
277     out.putInt(myClockWhenFrameStarted);
278     out.putInt(myClockStartDisplay);
279     out.putInt(myClockStopDisplay);
280     out.putInt(myClockAtLastUpdate);
281     out.putInt(myClocksToEndOfScanLine);
282     out.putInt(myScanlineCountForLastFrame);
283     out.putInt(myVSYNCFinishClock);
284 
285     out.putByte(myEnabledObjects);
286     out.putByte(myDisabledObjects);
287 
288     out.putByte(myVSYNC);
289     out.putByte(myVBLANK);
290     out.putByte(myNUSIZ0);
291     out.putByte(myNUSIZ1);
292 
293     out.putByteArray(myColor, 8);
294 
295     out.putByte(myCTRLPF);
296     out.putByte(myPlayfieldPriorityAndScore);
297     out.putBool(myREFP0);
298     out.putBool(myREFP1);
299     out.putInt(myPF);
300     out.putByte(myGRP0);
301     out.putByte(myGRP1);
302     out.putByte(myDGRP0);
303     out.putByte(myDGRP1);
304     out.putBool(myENAM0);
305     out.putBool(myENAM1);
306     out.putBool(myENABL);
307     out.putBool(myDENABL);
308     out.putByte(myHMP0);
309     out.putByte(myHMP1);
310     out.putByte(myHMM0);
311     out.putByte(myHMM1);
312     out.putByte(myHMBL);
313     out.putBool(myVDELP0);
314     out.putBool(myVDELP1);
315     out.putBool(myVDELBL);
316     out.putBool(myRESMP0);
317     out.putBool(myRESMP1);
318     out.putShort(myCollision);
319     out.putInt(myCollisionEnabledMask);
320     out.putByte(myCurrentGRP0);
321     out.putByte(myCurrentGRP1);
322 
323     out.putBool(myDumpEnabled);
324     out.putInt(myDumpDisabledCycle);
325 
326     out.putShort(myPOSP0);
327     out.putShort(myPOSP1);
328     out.putShort(myPOSM0);
329     out.putShort(myPOSM1);
330     out.putShort(myPOSBL);
331 
332     out.putInt(myMotionClockP0);
333     out.putInt(myMotionClockP1);
334     out.putInt(myMotionClockM0);
335     out.putInt(myMotionClockM1);
336     out.putInt(myMotionClockBL);
337 
338     out.putInt(myStartP0);
339     out.putInt(myStartP1);
340     out.putInt(myStartM0);
341     out.putInt(myStartM1);
342 
343     out.putByte(mySuppressP0);
344     out.putByte(mySuppressP1);
345 
346     out.putBool(myHMP0mmr);
347     out.putBool(myHMP1mmr);
348     out.putBool(myHMM0mmr);
349     out.putBool(myHMM1mmr);
350     out.putBool(myHMBLmmr);
351 
352     out.putInt(myCurrentHMOVEPos);
353     out.putInt(myPreviousHMOVEPos);
354     out.putBool(myHMOVEBlankEnabled);
355 
356     out.putInt(myFrameCounter);
357     out.putInt(myPALFrameCounter);
358 
359     // Save the sound sample stuff ...
360     mySound.save(out);
361   }
362   catch(...)
363   {
364     cerr << "ERROR: TIA::save" << endl;
365     return false;
366   }
367 
368   return true;
369 }
370 
371 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
load(Serializer & in)372 bool TIA::load(Serializer& in)
373 {
374   const string& device = name();
375 
376   try
377   {
378     if(in.getString() != device)
379       return false;
380 
381     myClockWhenFrameStarted = (Int32) in.getInt();
382     myClockStartDisplay = (Int32) in.getInt();
383     myClockStopDisplay = (Int32) in.getInt();
384     myClockAtLastUpdate = (Int32) in.getInt();
385     myClocksToEndOfScanLine = (Int32) in.getInt();
386     myScanlineCountForLastFrame = in.getInt();
387     myVSYNCFinishClock = (Int32) in.getInt();
388 
389     myEnabledObjects = in.getByte();
390     myDisabledObjects = in.getByte();
391 
392     myVSYNC = in.getByte();
393     myVBLANK = in.getByte();
394     myNUSIZ0 = in.getByte();
395     myNUSIZ1 = in.getByte();
396 
397     in.getByteArray(myColor, 8);
398 
399     myCTRLPF = in.getByte();
400     myPlayfieldPriorityAndScore = in.getByte();
401     myREFP0 = in.getBool();
402     myREFP1 = in.getBool();
403     myPF = in.getInt();
404     myGRP0 = in.getByte();
405     myGRP1 = in.getByte();
406     myDGRP0 = in.getByte();
407     myDGRP1 = in.getByte();
408     myENAM0 = in.getBool();
409     myENAM1 = in.getBool();
410     myENABL = in.getBool();
411     myDENABL = in.getBool();
412     myHMP0 = in.getByte();
413     myHMP1 = in.getByte();
414     myHMM0 = in.getByte();
415     myHMM1 = in.getByte();
416     myHMBL = in.getByte();
417     myVDELP0 = in.getBool();
418     myVDELP1 = in.getBool();
419     myVDELBL = in.getBool();
420     myRESMP0 = in.getBool();
421     myRESMP1 = in.getBool();
422     myCollision = in.getShort();
423     myCollisionEnabledMask = in.getInt();
424     myCurrentGRP0 = in.getByte();
425     myCurrentGRP1 = in.getByte();
426 
427     myDumpEnabled = in.getBool();
428     myDumpDisabledCycle = (Int32) in.getInt();
429 
430     myPOSP0 = (Int16) in.getShort();
431     myPOSP1 = (Int16) in.getShort();
432     myPOSM0 = (Int16) in.getShort();
433     myPOSM1 = (Int16) in.getShort();
434     myPOSBL = (Int16) in.getShort();
435 
436     myMotionClockP0 = (Int32) in.getInt();
437     myMotionClockP1 = (Int32) in.getInt();
438     myMotionClockM0 = (Int32) in.getInt();
439     myMotionClockM1 = (Int32) in.getInt();
440     myMotionClockBL = (Int32) in.getInt();
441 
442     myStartP0 = (Int32) in.getInt();
443     myStartP1 = (Int32) in.getInt();
444     myStartM0 = (Int32) in.getInt();
445     myStartM1 = (Int32) in.getInt();
446 
447     mySuppressP0 = in.getByte();
448     mySuppressP1 = in.getByte();
449 
450     myHMP0mmr = in.getBool();
451     myHMP1mmr = in.getBool();
452     myHMM0mmr = in.getBool();
453     myHMM1mmr = in.getBool();
454     myHMBLmmr = in.getBool();
455 
456     myCurrentHMOVEPos = (Int32) in.getInt();
457     myPreviousHMOVEPos = (Int32) in.getInt();
458     myHMOVEBlankEnabled = in.getBool();
459 
460     myFrameCounter = in.getInt();
461     myPALFrameCounter = in.getInt();
462 
463     // Load the sound sample stuff ...
464     mySound.load(in);
465 
466     // Reset TIA bits to be on
467     enableBits(true);
468     toggleFixedColors(0);
469     myAllowHMOVEBlanks = true;
470   }
471   catch(...)
472   {
473     cerr << "ERROR: TIA::load" << endl;
474     return false;
475   }
476 
477   return true;
478 }
479 
480 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
saveDisplay(Serializer & out) const481 bool TIA::saveDisplay(Serializer& out) const
482 {
483   try
484   {
485     out.putBool(myPartialFrameFlag);
486     out.putInt(myFramePointerClocks);
487     out.putByteArray(myCurrentFrameBuffer, 160*320);
488   }
489   catch(...)
490   {
491     cerr << "ERROR: TIA::saveDisplay" << endl;
492     return false;
493   }
494 
495   return true;
496 }
497 
498 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
loadDisplay(Serializer & in)499 bool TIA::loadDisplay(Serializer& in)
500 {
501   try
502   {
503     myPartialFrameFlag = in.getBool();
504     myFramePointerClocks = in.getInt();
505 
506     // Reset frame buffer pointer and data
507     clearBuffers();
508     myFramePointer = myCurrentFrameBuffer;
509     in.getByteArray(myCurrentFrameBuffer, 160*320);
510     memcpy(myPreviousFrameBuffer, myCurrentFrameBuffer, 160*320);
511 
512     // If we're in partial frame mode, make sure to re-create the screen
513     // as it existed when the state was saved
514     if(myPartialFrameFlag)
515       myFramePointer += myFramePointerClocks;
516   }
517   catch(...)
518   {
519     cerr << "ERROR: TIA::loadDisplay" << endl;
520     return false;
521   }
522 
523   return true;
524 }
525 
526 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
update()527 void TIA::update()
528 {
529   // if we've finished a frame, start a new one
530   if(!myPartialFrameFlag)
531     startFrame();
532 
533   // Partial frame flag starts out true here. When then 6502 strobes VSYNC,
534   // TIA::poke() will set this flag to false, so we'll know whether the
535   // frame got finished or interrupted by the debugger hitting a break/trap.
536   myPartialFrameFlag = true;
537 
538   // Execute instructions until frame is finished, or a breakpoint/trap hits
539   mySystem->m6502().execute(25000);
540 
541   // TODO: have code here that handles errors....
542 
543   endFrame();
544 }
545 
546 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
startFrame()547 inline void TIA::startFrame()
548 {
549   // This stuff should only happen at the beginning of a new frame.
550   uInt8* tmp = myCurrentFrameBuffer;
551   myCurrentFrameBuffer = myPreviousFrameBuffer;
552   myPreviousFrameBuffer = tmp;
553 
554   // Remember the number of clocks which have passed on the current scanline
555   // so that we can adjust the frame's starting clock by this amount.  This
556   // is necessary since some games position objects during VSYNC and the
557   // TIA's internal counters are not reset by VSYNC.
558   uInt32 clocks = ((mySystem->cycles() * 3) - myClockWhenFrameStarted) % 228;
559 
560   // Ask the system to reset the cycle count so it doesn't overflow
561   mySystem->resetCycles();
562 
563   // Setup clocks that'll be used for drawing this frame
564   myClockWhenFrameStarted = -1 * clocks;
565   myClockStartDisplay = myClockWhenFrameStarted;
566   myClockStopDisplay = myClockWhenFrameStarted + myStopDisplayOffset;
567   myClockAtLastUpdate = myClockStartDisplay;
568   myClocksToEndOfScanLine = 228;
569 
570   // Reset frame buffer pointer
571   myFramePointer = myCurrentFrameBuffer;
572   myFramePointerClocks = 0;
573 
574   // If color loss is enabled then update the color registers based on
575   // the number of scanlines in the last frame that was generated
576   if(myColorLossEnabled)
577   {
578     if(myScanlineCountForLastFrame & 0x01)
579     {
580       myColor[P0Color] |= 0x01;
581       myColor[P1Color] |= 0x01;
582       myColor[PFColor] |= 0x01;
583       myColor[BKColor] |= 0x01;
584       myColor[M0Color] |= 0x01;
585       myColor[M1Color] |= 0x01;
586       myColor[BLColor] |= 0x01;
587     }
588     else
589     {
590       myColor[P0Color] &= 0xfe;
591       myColor[P1Color] &= 0xfe;
592       myColor[PFColor] &= 0xfe;
593       myColor[BKColor] &= 0xfe;
594       myColor[M0Color] &= 0xfe;
595       myColor[M1Color] &= 0xfe;
596       myColor[BLColor] &= 0xfe;
597     }
598   }
599   myStartScanline = 0;
600 
601   // Stats counters
602   myFrameCounter++;
603   if(myScanlineCountForLastFrame >= 287)
604     myPALFrameCounter++;
605 }
606 
607 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
endFrame()608 inline void TIA::endFrame()
609 {
610   uInt32 currentlines = scanlines();
611 
612   // The TIA may generate frames that are 'invisible' to TV (they complete
613   // before the first visible scanline)
614   // Such 'short' frames can't simply be eliminated, since they're running
615   // code at that point; however, they are not shown at all, otherwise the
616   // double-buffering of the video output will get confused
617   if(currentlines <= myStartScanline)
618   {
619     // Skip display of this frame, as if it wasn't generated at all
620     startFrame();
621     myFrameCounter--;  // This frame doesn't contribute to frame count
622     return;
623   }
624 
625   // Compute the number of scanlines in the frame
626   uInt32 previousCount = myScanlineCountForLastFrame;
627   myScanlineCountForLastFrame = currentlines;
628 
629   // The following handle cases where scanlines either go too high or too
630   // low compared to the previous frame, in which case certain portions
631   // of the framebuffer are cleared to zero (black pixels)
632   // Due to the FrameBuffer class (potentially) doing dirty-rectangle
633   // updates, each internal buffer must be set slightly differently,
634   // otherwise they won't know anything has changed
635   // Hence, the front buffer is set to pixel 0, and the back to pixel 1
636 
637   // Did we generate too many scanlines?
638   // (usually caused by VBLANK/VSYNC taking too long or not occurring at all)
639   // If so, blank entire viewable area
640   if(myScanlineCountForLastFrame > myMaximumNumberOfScanlines+1)
641   {
642     myScanlineCountForLastFrame = myMaximumNumberOfScanlines;
643     if(previousCount < myMaximumNumberOfScanlines)
644     {
645       memset(myCurrentFrameBuffer, 0, 160 * 320);
646       memset(myPreviousFrameBuffer, 1, 160 * 320);
647     }
648   }
649   // Did the number of scanlines decrease?
650   // If so, blank scanlines that weren't rendered this frame
651   else if(myScanlineCountForLastFrame < previousCount &&
652           myScanlineCountForLastFrame < 320 && previousCount < 320)
653   {
654     uInt32 offset = myScanlineCountForLastFrame * 160,
655            stride = (previousCount - myScanlineCountForLastFrame) * 160;
656     memset(myCurrentFrameBuffer + offset, 0, stride);
657     memset(myPreviousFrameBuffer + offset, 1, stride);
658   }
659 
660   // Recalculate framerate. attempting to auto-correct for scanline 'jumps'
661   if(myAutoFrameEnabled)
662   {
663     myFramerate = (myScanlineCountForLastFrame > 285 ? 15600.0 : 15720.0) /
664                    myScanlineCountForLastFrame;
665     myConsole.setFramerate(myFramerate);
666 
667     // Adjust end-of-frame pointer
668     // We always accommodate the highest # of scanlines, up to the maximum
669     // size of the buffer (currently, 320 lines)
670     uInt32 offset = 228 * myScanlineCountForLastFrame;
671     if(offset > myStopDisplayOffset && offset < 228 * 320)
672       myStopDisplayOffset = offset;
673   }
674 }
675 
676 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
scanlinePos(uInt16 & x,uInt16 & y) const677 bool TIA::scanlinePos(uInt16& x, uInt16& y) const
678 {
679   if(myPartialFrameFlag)
680   {
681     // We only care about the scanline position when it's in the viewable area
682     if(myFramePointerClocks >= myFramePointerOffset)
683     {
684       x = (myFramePointerClocks - myFramePointerOffset) % 160;
685       y = (myFramePointerClocks - myFramePointerOffset) / 160;
686       return true;
687     }
688     else
689     {
690       x = 0;
691       y = 0;
692       return false;
693     }
694   }
695   else
696   {
697     x = width();
698     y = height();
699     return false;
700   }
701 }
702 
703 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
enableBits(bool mode)704 void TIA::enableBits(bool mode)
705 {
706   toggleBit(P0Bit, mode ? 1 : 0);
707   toggleBit(P1Bit, mode ? 1 : 0);
708   toggleBit(M0Bit, mode ? 1 : 0);
709   toggleBit(M1Bit, mode ? 1 : 0);
710   toggleBit(BLBit, mode ? 1 : 0);
711   toggleBit(PFBit, mode ? 1 : 0);
712 }
713 
714 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleBit(TIABit b,uInt8 mode)715 bool TIA::toggleBit(TIABit b, uInt8 mode)
716 {
717   // If mode is 0 or 1, use it as a boolean (off or on)
718   // Otherwise, flip the state
719   bool on = (mode == 0 || mode == 1) ? bool(mode) : !(myDisabledObjects & b);
720   if(on)  myDisabledObjects |= b;
721   else    myDisabledObjects &= ~b;
722 
723   return on;
724 }
725 
726 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleBits()727 bool TIA::toggleBits()
728 {
729   myBitsEnabled = !myBitsEnabled;
730   enableBits(myBitsEnabled);
731   return myBitsEnabled;
732 }
733 
734 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
enableCollisions(bool mode)735 void TIA::enableCollisions(bool mode)
736 {
737   toggleCollision(P0Bit, mode ? 1 : 0);
738   toggleCollision(P1Bit, mode ? 1 : 0);
739   toggleCollision(M0Bit, mode ? 1 : 0);
740   toggleCollision(M1Bit, mode ? 1 : 0);
741   toggleCollision(BLBit, mode ? 1 : 0);
742   toggleCollision(PFBit, mode ? 1 : 0);
743 }
744 
745 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollision(TIABit b,uInt8 mode)746 bool TIA::toggleCollision(TIABit b, uInt8 mode)
747 {
748   uInt16 enabled = myCollisionEnabledMask >> 16;
749 
750   // If mode is 0 or 1, use it as a boolean (off or on)
751   // Otherwise, flip the state
752   bool on = (mode == 0 || mode == 1) ? bool(mode) : !(enabled & b);
753   if(on)  enabled |= b;
754   else    enabled &= ~b;
755 
756   // Assume all collisions are on, then selectively turn the desired ones off
757   uInt16 mask = 0xffff;
758   if(!(enabled & P0Bit))
759     mask &= ~(Cx_M0P0 | Cx_M1P0 | Cx_P0PF | Cx_P0BL | Cx_P0P1);
760   if(!(enabled & P1Bit))
761     mask &= ~(Cx_M0P1 | Cx_M1P1 | Cx_P1PF | Cx_P1BL | Cx_P0P1);
762   if(!(enabled & M0Bit))
763     mask &= ~(Cx_M0P0 | Cx_M0P1 | Cx_M0PF | Cx_M0BL | Cx_M0M1);
764   if(!(enabled & M1Bit))
765     mask &= ~(Cx_M1P0 | Cx_M1P1 | Cx_M1PF | Cx_M1BL | Cx_M0M1);
766   if(!(enabled & BLBit))
767     mask &= ~(Cx_P0BL | Cx_P1BL | Cx_M0BL | Cx_M1BL | Cx_BLPF);
768   if(!(enabled & PFBit))
769     mask &= ~(Cx_P0PF | Cx_P1PF | Cx_M0PF | Cx_M1PF | Cx_BLPF);
770 
771   // Now combine the masks
772   myCollisionEnabledMask = (enabled << 16) | mask;
773 
774   return on;
775 }
776 
777 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleCollisions()778 bool TIA::toggleCollisions()
779 {
780   myCollisionsEnabled = !myCollisionsEnabled;
781   enableCollisions(myCollisionsEnabled);
782   return myCollisionsEnabled;
783 }
784 
785 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleHMOVEBlank()786 bool TIA::toggleHMOVEBlank()
787 {
788   myAllowHMOVEBlanks = myAllowHMOVEBlanks ? false : true;
789   return myAllowHMOVEBlanks;
790 }
791 
792 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
toggleFixedColors(uInt8 mode)793 bool TIA::toggleFixedColors(uInt8 mode)
794 {
795   // If mode is 0 or 1, use it as a boolean (off or on)
796   // Otherwise, flip the state
797   bool on = (mode == 0 || mode == 1) ? bool(mode) :
798             (myColorPtr == myColor ? true : false);
799   if(on)  myColorPtr = myFixedColor;
800   else    myColorPtr = myColor;
801 
802   // Set PriorityEncoder
803   // This needs to be done here, since toggling debug colours also changes
804   // how colours are interpreted in PF 'score' mode
805   for(uInt16 x = 0; x < 2; ++x)
806   {
807     for(uInt16 enabled = 0; enabled < 256; ++enabled)
808     {
809       if(enabled & PriorityBit)
810       {
811         // Priority from highest to lowest:
812         //   PF/BL => P0/M0 => P1/M1 => BK
813         uInt8 color = BKColor;
814 
815         if((enabled & M1Bit) != 0)
816           color = M1Color;
817         if((enabled & P1Bit) != 0)
818           color = P1Color;
819         if((enabled & M0Bit) != 0)
820           color = M0Color;
821         if((enabled & P0Bit) != 0)
822           color = P0Color;
823         if((enabled & BLBit) != 0)
824           color = BLColor;
825         if((enabled & PFBit) != 0)
826           color = PFColor;  // NOTE: Playfield has priority so ScoreBit isn't used
827 
828         myPriorityEncoder[x][enabled] = color;
829       }
830       else
831       {
832         // Priority from highest to lowest:
833         //   P0/M0 => P1/M1 => PF/BL => BK
834         uInt8 color = BKColor;
835 
836         if((enabled & BLBit) != 0)
837           color = BLColor;
838         if((enabled & PFBit) != 0)
839           color = (!on && (enabled & ScoreBit)) ? ((x == 0) ? P0Color : P1Color) : PFColor;
840         if((enabled & M1Bit) != 0)
841           color = M1Color;
842         if((enabled & P1Bit) != 0)
843           color = P1Color;
844         if((enabled & M0Bit) != 0)
845           color = M0Color;
846         if((enabled & P0Bit) != 0)
847           color = P0Color;
848 
849         myPriorityEncoder[x][enabled] = color;
850       }
851     }
852   }
853 
854   return on;
855 }
856 
857 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
driveUnusedPinsRandom(uInt8 mode)858 bool TIA::driveUnusedPinsRandom(uInt8 mode)
859 {
860   // If mode is 0 or 1, use it as a boolean (off or on)
861   // Otherwise, return the state
862   if(mode == 0 || mode == 1)
863   {
864     myTIAPinsDriven = bool(mode);
865     mySettings.setValue("tiadriven", myTIAPinsDriven);
866   }
867   return myTIAPinsDriven;
868 }
869 
870 #ifdef DEBUGGER_SUPPORT
871 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateScanline()872 void TIA::updateScanline()
873 {
874   // Start a new frame if the old one was finished
875   if(!myPartialFrameFlag)
876     startFrame();
877 
878   // true either way:
879   myPartialFrameFlag = true;
880 
881   int totalClocks = (mySystem->cycles() * 3) - myClockWhenFrameStarted;
882   int endClock = ((totalClocks + 228) / 228) * 228;
883 
884   int clock;
885   do {
886     mySystem->m6502().execute(1);
887     clock = mySystem->cycles() * 3;
888     updateFrame(clock);
889   } while(clock < endClock);
890 
891   // if we finished the frame, get ready for the next one
892   if(!myPartialFrameFlag)
893     endFrame();
894 }
895 
896 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateScanlineByStep()897 void TIA::updateScanlineByStep()
898 {
899   // Start a new frame if the old one was finished
900   if(!myPartialFrameFlag)
901     startFrame();
902 
903   // true either way:
904   myPartialFrameFlag = true;
905 
906   // Update frame by one CPU instruction/color clock
907   mySystem->m6502().execute(1);
908   updateFrame(mySystem->cycles() * 3);
909 
910   // if we finished the frame, get ready for the next one
911   if(!myPartialFrameFlag)
912     endFrame();
913 }
914 
915 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateScanlineByTrace(int target)916 void TIA::updateScanlineByTrace(int target)
917 {
918   // Start a new frame if the old one was finished
919   if(!myPartialFrameFlag)
920     startFrame();
921 
922   // true either way:
923   myPartialFrameFlag = true;
924 
925   while(mySystem->m6502().getPC() != target)
926   {
927     mySystem->m6502().execute(1);
928     updateFrame(mySystem->cycles() * 3);
929   }
930 
931   // if we finished the frame, get ready for the next one
932   if(!myPartialFrameFlag)
933     endFrame();
934 }
935 #endif
936 
937 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateFrame(Int32 clock)938 void TIA::updateFrame(Int32 clock)
939 {
940   // See if we've already updated this portion of the screen
941   if((clock < myClockStartDisplay) ||
942      (myClockAtLastUpdate >= myClockStopDisplay) ||
943      (myClockAtLastUpdate >= clock))
944     return;
945 
946   // Truncate the number of cycles to update to the stop display point
947   if(clock > myClockStopDisplay)
948     clock = myClockStopDisplay;
949 
950   // Determine how many scanlines to process
951   // It's easier to think about this in scanlines rather than color clocks
952   uInt32 startLine = (myClockAtLastUpdate - myClockWhenFrameStarted) / 228;
953   uInt32 endLine = (clock - myClockWhenFrameStarted) / 228;
954 
955   // Update frame one scanline at a time
956   for(uInt32 line = startLine; line <= endLine; ++line)
957   {
958     // Only check for inter-line changes after the current scanline
959     // The ideas for much of the following code was inspired by MESS
960     // (used with permission from Wilbert Pol)
961     if(line != startLine)
962     {
963       // We're no longer concerned with previously issued HMOVE's
964       myPreviousHMOVEPos = 0x7FFFFFFF;
965       bool posChanged = false;
966 
967       // Apply pending motion clocks from a HMOVE initiated during the scanline
968       if(myCurrentHMOVEPos != 0x7FFFFFFF)
969       {
970         if(myCurrentHMOVEPos >= 97 && myCurrentHMOVEPos < 157)
971         {
972           myPOSP0 -= myMotionClockP0;  if(myPOSP0 < 0) myPOSP0 += 160;
973           myPOSP1 -= myMotionClockP1;  if(myPOSP1 < 0) myPOSP1 += 160;
974           myPOSM0 -= myMotionClockM0;  if(myPOSM0 < 0) myPOSM0 += 160;
975           myPOSM1 -= myMotionClockM1;  if(myPOSM1 < 0) myPOSM1 += 160;
976           myPOSBL -= myMotionClockBL;  if(myPOSBL < 0) myPOSBL += 160;
977 
978           myPreviousHMOVEPos = myCurrentHMOVEPos;
979         }
980         // Indicate that the HMOVE has been completed
981         myCurrentHMOVEPos = 0x7FFFFFFF;
982         posChanged = true;
983       }
984 
985       // Apply extra clocks for 'more motion required/mmr'
986       if(myHMP0mmr) { myPOSP0 -= 17;  if(myPOSP0 < 0) myPOSP0 += 160;  posChanged = true; }
987       if(myHMP1mmr) { myPOSP1 -= 17;  if(myPOSP1 < 0) myPOSP1 += 160;  posChanged = true; }
988       if(myHMM0mmr) { myPOSM0 -= 17;  if(myPOSM0 < 0) myPOSM0 += 160;  posChanged = true; }
989       if(myHMM1mmr) { myPOSM1 -= 17;  if(myPOSM1 < 0) myPOSM1 += 160;  posChanged = true; }
990       if(myHMBLmmr) { myPOSBL -= 17;  if(myPOSBL < 0) myPOSBL += 160;  posChanged = true; }
991 
992       // Scanline change, so reset PF mask based on current CTRLPF reflection state
993       myPFMask = TIATables::PFMask[myCTRLPF & 0x01];
994 
995       // TODO - handle changes to player timing
996       if(posChanged)
997       {
998       }
999     }
1000 
1001     // Compute the number of clocks we're going to update
1002     Int32 clocksToUpdate = 0;
1003 
1004     // Remember how many clocks we are from the left side of the screen
1005     Int32 clocksFromStartOfScanLine = 228 - myClocksToEndOfScanLine;
1006 
1007     // See if we're updating more than the current scanline
1008     if(clock > (myClockAtLastUpdate + myClocksToEndOfScanLine))
1009     {
1010       // Yes, we have more than one scanline to update so finish current one
1011       clocksToUpdate = myClocksToEndOfScanLine;
1012       myClocksToEndOfScanLine = 228;
1013       myClockAtLastUpdate += clocksToUpdate;
1014     }
1015     else
1016     {
1017       // No, so do as much of the current scanline as possible
1018       clocksToUpdate = clock - myClockAtLastUpdate;
1019       myClocksToEndOfScanLine -= clocksToUpdate;
1020       myClockAtLastUpdate = clock;
1021     }
1022 
1023     Int32 startOfScanLine = HBLANK;
1024 
1025     // Skip over as many horizontal blank clocks as we can
1026     if(clocksFromStartOfScanLine < startOfScanLine)
1027     {
1028       uInt32 tmp;
1029 
1030       if((startOfScanLine - clocksFromStartOfScanLine) < clocksToUpdate)
1031         tmp = startOfScanLine - clocksFromStartOfScanLine;
1032       else
1033         tmp = clocksToUpdate;
1034 
1035       clocksFromStartOfScanLine += tmp;
1036       clocksToUpdate -= tmp;
1037     }
1038 
1039     // Remember frame pointer in case HMOVE blanks need to be handled
1040     uInt8* oldFramePointer = myFramePointer;
1041 
1042     // Update as much of the scanline as we can
1043     if(clocksToUpdate != 0)
1044     {
1045       // Calculate the ending frame pointer value
1046       uInt8* ending = myFramePointer + clocksToUpdate;
1047       myFramePointerClocks += clocksToUpdate;
1048 
1049       // See if we're in the vertical blank region
1050       if(myVBLANK & 0x02)
1051       {
1052         memset(myFramePointer, 0, clocksToUpdate);
1053       }
1054       // Handle all other possible combinations
1055       else
1056       {
1057         // Update masks
1058         myP0Mask = &TIATables::PxMask[mySuppressP0]
1059             [myNUSIZ0 & 0x07][160 - (myPOSP0 & 0xFF)];
1060         myP1Mask = &TIATables::PxMask[mySuppressP1]
1061             [myNUSIZ1 & 0x07][160 - (myPOSP1 & 0xFF)];
1062         myBLMask = &TIATables::BLMask[(myCTRLPF & 0x30) >> 4]
1063             [160 - (myPOSBL & 0xFF)];
1064 
1065         // TODO - 08-27-2009: Simulate the weird effects of Cosmic Ark and
1066         // Stay Frosty.  The movement itself is well understood, but there
1067         // also seems to be some widening and blanking occurring as well.
1068         // This doesn't properly emulate the effect at a low level; it only
1069         // simulates the behaviour as visually seen in the aforementioned
1070         // ROMs.  Other ROMs may break this simulation; more testing is
1071         // required to figure out what's really going on here.
1072         if(myHMM0mmr)
1073         {
1074           switch(myPOSM0 % 4)
1075           {
1076             case 3:
1077               // Stretch this missle so it's 2 pixels wide and shifted one
1078               // pixel to the left
1079               myM0Mask = &TIATables::MxMask[myNUSIZ0 & 0x07]
1080                   [((myNUSIZ0 & 0x30) >> 4)|1][160 - ((myPOSM0-1) & 0xFF)];
1081               break;
1082             case 2:
1083               // Missle is disabled on this line
1084               myM0Mask = &TIATables::DisabledMask[0];
1085               break;
1086             default:
1087               myM0Mask = &TIATables::MxMask[myNUSIZ0 & 0x07]
1088                   [(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFF)];
1089               break;
1090           }
1091         }
1092         else
1093           myM0Mask = &TIATables::MxMask[myNUSIZ0 & 0x07]
1094               [(myNUSIZ0 & 0x30) >> 4][160 - (myPOSM0 & 0xFF)];
1095         if(myHMM1mmr)
1096         {
1097           switch(myPOSM1 % 4)
1098           {
1099             case 3:
1100               // Stretch this missle so it's 2 pixels wide and shifted one
1101               // pixel to the left
1102               myM1Mask = &TIATables::MxMask[myNUSIZ1 & 0x07]
1103                   [((myNUSIZ1 & 0x30) >> 4)|1][160 - ((myPOSM1-1) & 0xFF)];
1104               break;
1105             case 2:
1106               // Missle is disabled on this line
1107               myM1Mask = &TIATables::DisabledMask[0];
1108               break;
1109             default:
1110               myM1Mask = &TIATables::MxMask[myNUSIZ1 & 0x07]
1111                   [(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFF)];
1112               break;
1113           }
1114         }
1115         else
1116           myM1Mask = &TIATables::MxMask[myNUSIZ1 & 0x07]
1117               [(myNUSIZ1 & 0x30) >> 4][160 - (myPOSM1 & 0xFF)];
1118 
1119         uInt8 enabledObjects = myEnabledObjects & myDisabledObjects;
1120         uInt32 hpos = clocksFromStartOfScanLine - HBLANK;
1121         for(; myFramePointer < ending; ++myFramePointer, ++hpos)
1122         {
1123           uInt8 enabled = ((enabledObjects & PFBit) &&
1124                            (myPF & myPFMask[hpos])) ? PFBit : 0;
1125 
1126           if((enabledObjects & BLBit) && myBLMask[hpos])
1127             enabled |= BLBit;
1128 
1129           if((enabledObjects & P1Bit) && (myCurrentGRP1 & myP1Mask[hpos]))
1130             enabled |= P1Bit;
1131 
1132           if((enabledObjects & M1Bit) && myM1Mask[hpos])
1133             enabled |= M1Bit;
1134 
1135           if((enabledObjects & P0Bit) && (myCurrentGRP0 & myP0Mask[hpos]))
1136             enabled |= P0Bit;
1137 
1138           if((enabledObjects & M0Bit) && myM0Mask[hpos])
1139             enabled |= M0Bit;
1140 
1141           myCollision |= TIATables::CollisionMask[enabled];
1142           *myFramePointer = myColorPtr[myPriorityEncoder[hpos < 80 ? 0 : 1]
1143               [enabled | myPlayfieldPriorityAndScore]];
1144         }
1145       }
1146       myFramePointer = ending;
1147     }
1148 
1149     // Handle HMOVE blanks if they are enabled
1150     if(myHMOVEBlankEnabled && (startOfScanLine < HBLANK + 8) &&
1151         (clocksFromStartOfScanLine < (HBLANK + 8)))
1152     {
1153       Int32 blanks = (HBLANK + 8) - clocksFromStartOfScanLine;
1154       memset(oldFramePointer, myColorPtr[HBLANKColor], blanks);
1155 
1156       if((clocksToUpdate + clocksFromStartOfScanLine) >= (HBLANK + 8))
1157         myHMOVEBlankEnabled = false;
1158     }
1159 
1160 // TODO - this needs to be updated to actually do as the comment suggests
1161 #if 1
1162     // See if we're at the end of a scanline
1163     if(myClocksToEndOfScanLine == 228)
1164     {
1165       // TODO - 01-21-99: These should be reset right after the first copy
1166       // of the player has passed.  However, for now we'll just reset at the
1167       // end of the scanline since the other way would be too slow.
1168       mySuppressP0 = mySuppressP1 = 0;
1169     }
1170 #endif
1171   }
1172 }
1173 
1174 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
waitHorizontalSync()1175 inline void TIA::waitHorizontalSync()
1176 {
1177   uInt32 cyclesToEndOfLine = 76 - ((mySystem->cycles() -
1178       (myClockWhenFrameStarted / 3)) % 76);
1179 
1180   if(cyclesToEndOfLine < 76)
1181     mySystem->incrementCycles(cyclesToEndOfLine);
1182 }
1183 
1184 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
waitHorizontalRSync()1185 inline void TIA::waitHorizontalRSync()
1186 {
1187   // 02-23-2013: RSYNC has now been updated to work correctly with
1188   // Extra-Terrestrials. Fatal Run also uses RSYNC (in its VSYNC routine),
1189   // and the NTSC prototype now displays 262 scanlines instead of 261.
1190   // What is not emulated correctly is the "real time" effects. For example
1191   // the VSYNC signal may not be 3 complete scanlines, although Stella will
1192   // now count it as such.
1193   //
1194   // There are two extreme cases to demonstrate this "real time" variance
1195   // effect over a proper three line VSYNC. 3*76 = 228 cycles properly needed:
1196   //
1197   // ======  SHORT TIME CASE  ======
1198   //
1199   //     lda    #3      ;2  @67
1200   //     sta    VSYNC   ;3  @70      vsync starts
1201   //     sta    RSYNC   ;3  @73  +3
1202   //     sta    WSYNC   ;3  @76  +6
1203   // ------------------------------
1204   //     sta    WSYNC   ;3  @76  +82
1205   // ------------------------------
1206   //     lda    #0      ;2  @2   +84
1207   //     sta    VSYNC                vsync ends
1208   //
1209   // ======  LONG TIME CASE  ======
1210   //
1211   //    lda    #3      ;2  @70
1212   //    sta    VSYNC   ;3  @73      vsync starts
1213   //    sta    RSYNC   ;3  @74  +3
1214   //    sta    WSYNC   ;3  @..  +81  2 cycles are added to previous line, and then
1215   //                                 WSYNC halts the new line delaying 78 cycles total!
1216   //------------------------------
1217   //    sta    WSYNC   ;3  @76  +157
1218   //------------------------------
1219   //    lda    #0      ;2  @2   +159
1220   //    sta    VSYNC                vsync ends
1221 
1222   // The significance of the 'magic numbers' below is as follows (thanks to
1223   // Eckhard Stolberg and Omegamatrix for explanation and implementation)
1224   //
1225   // Objects always get positioned three pixels further to the right after a
1226   // WSYNC than they do after a RSYNC, but this is to be expected.  Triggering
1227   // WSYNC will halt the CPU until the horizontal sync counter wraps around to zero.
1228   // Triggering RSYNC will reset the horizontal sync counter to zero immediately.
1229   // But the warp-around will actually happen after one more cycle of this counter.
1230   // Since the horizontal sync counter counts once every 4 pixels, one more CPU
1231   // cycle occurs before the counter warps around to zero. Therefore the positioning
1232   // code will hit RESPx one cycle sooner after a RSYNC than after a WSYNC.
1233 
1234   uInt32 cyclesToEndOfLine = 76 - ((mySystem->cycles() -
1235       (myClockWhenFrameStarted / 3)) % 76);
1236 
1237   mySystem->incrementCycles(cyclesToEndOfLine-1);
1238 }
1239 
1240 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
clearBuffers()1241 void TIA::clearBuffers()
1242 {
1243   memset(myCurrentFrameBuffer, 0, 160 * 320);
1244   memset(myPreviousFrameBuffer, 0, 160 * 320);
1245 }
1246 
1247 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
dumpedInputPort(int resistance)1248 inline uInt8 TIA::dumpedInputPort(int resistance)
1249 {
1250   if(resistance == Controller::minimumResistance)
1251   {
1252     return 0x80;
1253   }
1254   else if((resistance == Controller::maximumResistance) || myDumpEnabled)
1255   {
1256     return 0x00;
1257   }
1258   else
1259   {
1260     // Constant here is derived from '1.6 * 0.01e-6 * 228 / 3'
1261     uInt32 needed = (uInt32)
1262       (1.216e-6 * resistance * myScanlineCountForLastFrame * myFramerate);
1263     if((mySystem->cycles() - myDumpDisabledCycle) > needed)
1264       return 0x80;
1265     else
1266       return 0x00;
1267   }
1268   return 0x00;
1269 }
1270 
1271 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
peek(uInt16 addr)1272 uInt8 TIA::peek(uInt16 addr)
1273 {
1274   // Update frame to current color clock before we look at anything!
1275   updateFrame(mySystem->cycles() * 3);
1276 
1277   // If pins are undriven, we start with the last databus value
1278   // Otherwise, there is some randomness injected into the mix
1279   // In either case, we start out with D7 and D6 disabled (the only
1280   // valid bits in a TIA read), and selectively enable them
1281   uInt8 value = 0x3F & (!myTIAPinsDriven ? mySystem->getDataBusState() :
1282                         mySystem->getDataBusState(0xFF));
1283   uInt16 collision = myCollision & (uInt16)myCollisionEnabledMask;
1284 
1285   switch(addr & 0x000f)
1286   {
1287     case CXM0P:
1288       value |= ((collision & Cx_M0P1) ? 0x80 : 0x00) |
1289                ((collision & Cx_M0P0) ? 0x40 : 0x00);
1290       break;
1291 
1292     case CXM1P:
1293       value |= ((collision & Cx_M1P0) ? 0x80 : 0x00) |
1294                ((collision & Cx_M1P1) ? 0x40 : 0x00);
1295       break;
1296 
1297     case CXP0FB:
1298       value |= ((collision & Cx_P0PF) ? 0x80 : 0x00) |
1299                ((collision & Cx_P0BL) ? 0x40 : 0x00);
1300       break;
1301 
1302     case CXP1FB:
1303       value |= ((collision & Cx_P1PF) ? 0x80 : 0x00) |
1304                ((collision & Cx_P1BL) ? 0x40 : 0x00);
1305       break;
1306 
1307     case CXM0FB:
1308       value |= ((collision & Cx_M0PF) ? 0x80 : 0x00) |
1309                ((collision & Cx_M0BL) ? 0x40 : 0x00);
1310       break;
1311 
1312     case CXM1FB:
1313       value |= ((collision & Cx_M1PF) ? 0x80 : 0x00) |
1314                ((collision & Cx_M1BL) ? 0x40 : 0x00);
1315       break;
1316 
1317     case CXBLPF:
1318       value = (value & 0x7F) | ((collision & Cx_BLPF) ? 0x80 : 0x00);
1319       break;
1320 
1321     case CXPPMM:
1322       value |= ((collision & Cx_P0P1) ? 0x80 : 0x00) |
1323                ((collision & Cx_M0M1) ? 0x40 : 0x00);
1324       break;
1325 
1326     case INPT0:
1327       value = (value & 0x7F) |
1328         dumpedInputPort(myConsole.controller(Controller::Left).read(Controller::Nine));
1329       break;
1330 
1331     case INPT1:
1332       value = (value & 0x7F) |
1333         dumpedInputPort(myConsole.controller(Controller::Left).read(Controller::Five));
1334       break;
1335 
1336     case INPT2:
1337       value = (value & 0x7F) |
1338         dumpedInputPort(myConsole.controller(Controller::Right).read(Controller::Nine));
1339       break;
1340 
1341     case INPT3:
1342       value = (value & 0x7F) |
1343         dumpedInputPort(myConsole.controller(Controller::Right).read(Controller::Five));
1344       break;
1345 
1346     case INPT4:
1347     {
1348       uInt8 button = (myConsole.controller(Controller::Left).read(Controller::Six) ? 0x80 : 0x00);
1349       myINPT4 = (myVBLANK & 0x40) ? (myINPT4 & button) : button;
1350 
1351       value = (value & 0x7F) | myINPT4;
1352       break;
1353     }
1354 
1355     case INPT5:
1356     {
1357       uInt8 button = (myConsole.controller(Controller::Right).read(Controller::Six) ? 0x80 : 0x00);
1358       myINPT5 = (myVBLANK & 0x40) ? (myINPT5 & button) : button;
1359 
1360       value = (value & 0x7F) | myINPT5;
1361       break;
1362     }
1363 
1364     default:
1365       // This shouldn't happen, but if it does, we essentially just
1366       // return the last databus value with bits D6 and D7 zeroed out
1367       break;
1368   }
1369   return value;
1370 }
1371 
1372 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
poke(uInt16 addr,uInt8 value)1373 bool TIA::poke(uInt16 addr, uInt8 value)
1374 {
1375   addr = addr & 0x003f;
1376 
1377   Int32 clock = mySystem->cycles() * 3;
1378   Int16 delay = TIATables::PokeDelay[addr];
1379 
1380   // See if this is a poke to a PF register
1381   if(delay == -1)
1382   {
1383     static uInt32 d[4] = {4, 5, 2, 3};
1384     Int32 x = ((clock - myClockWhenFrameStarted) % 228);
1385     delay = d[(x / 3) & 3];
1386   }
1387 
1388   // Update frame to current CPU cycle before we make any changes!
1389   updateFrame(clock + delay);
1390 
1391   // If a VSYNC hasn't been generated in time go ahead and end the frame
1392   if(((clock - myClockWhenFrameStarted) / 228) >= (Int32)myMaximumNumberOfScanlines)
1393   {
1394     mySystem->m6502().stop();
1395     myPartialFrameFlag = false;
1396   }
1397 
1398   switch(addr)
1399   {
1400     case VSYNC:    // Vertical sync set-clear
1401     {
1402       myVSYNC = value;
1403 
1404       if(myVSYNC & 0x02)
1405       {
1406         // Indicate when VSYNC should be finished.  This should really
1407         // be 3 * 228 according to Atari's documentation, however, some
1408         // games don't supply the full 3 scanlines of VSYNC.
1409         myVSYNCFinishClock = clock + 228;
1410       }
1411       else if(!(myVSYNC & 0x02) && (clock >= myVSYNCFinishClock))
1412       {
1413         // We're no longer interested in myVSYNCFinishClock
1414         myVSYNCFinishClock = 0x7FFFFFFF;
1415 
1416         // Since we're finished with the frame tell the processor to halt
1417         mySystem->m6502().stop();
1418         myPartialFrameFlag = false;
1419       }
1420       break;
1421     }
1422 
1423     case VBLANK:  // Vertical blank set-clear
1424     {
1425       // Is the dump to ground path being set for I0, I1, I2, and I3?
1426       if(!(myVBLANK & 0x80) && (value & 0x80))
1427       {
1428         myDumpEnabled = true;
1429       }
1430       // Is the dump to ground path being removed from I0, I1, I2, and I3?
1431       else if((myVBLANK & 0x80) && !(value & 0x80))
1432       {
1433         myDumpEnabled = false;
1434         myDumpDisabledCycle = mySystem->cycles();
1435       }
1436 
1437       // Are the latches for I4 and I5 being reset?
1438       if (!(myVBLANK & 0x40))
1439         myINPT4 = myINPT5 = 0x80;
1440 
1441       // Check for the first scanline at which VBLANK is disabled.
1442       // Usually, this will be the first scanline to start drawing.
1443       if(myStartScanline == 0 && !(value & 0x10))
1444         myStartScanline = scanlines();
1445 
1446       myVBLANK = value;
1447       break;
1448     }
1449 
1450     case WSYNC:   // Wait for leading edge of HBLANK
1451     {
1452       // It appears that the 6507 only halts during a read cycle so
1453       // we test here for follow-on writes which should be ignored as
1454       // far as halting the processor is concerned.
1455       //
1456       // TODO - 08-30-2006: This halting isn't correct since it's
1457       // still halting on the original write.  The 6507 emulation
1458       // should be expanded to include a READY line.
1459       if(mySystem->m6502().lastAccessWasRead())
1460       {
1461         // Tell the cpu to waste the necessary amount of time
1462         waitHorizontalSync();
1463       }
1464       break;
1465     }
1466 
1467     case RSYNC:   // Reset horizontal sync counter
1468     {
1469       waitHorizontalRSync();
1470       break;
1471     }
1472 
1473     case NUSIZ0:  // Number-size of player-missle 0
1474     {
1475       // TODO - 08-11-2009: determine correct delay instead of always
1476       //                    using '8' in TIATables::PokeDelay
1477       updateFrame(clock + 8);
1478 
1479       myNUSIZ0 = value;
1480       mySuppressP0 = 0;
1481       break;
1482     }
1483 
1484     case NUSIZ1:  // Number-size of player-missle 1
1485     {
1486       // TODO - 08-11-2009: determine correct delay instead of always
1487       //                    using '8' in TIATables::PokeDelay
1488       updateFrame(clock + 8);
1489 
1490       myNUSIZ1 = value;
1491       mySuppressP1 = 0;
1492       break;
1493     }
1494 
1495     case COLUP0:  // Color-Luminance Player 0
1496     {
1497       uInt8 color = value & 0xfe;
1498       if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
1499         color |= 0x01;
1500 
1501       myColor[P0Color] = myColor[M0Color] = color;
1502       break;
1503     }
1504 
1505     case COLUP1:  // Color-Luminance Player 1
1506     {
1507       uInt8 color = value & 0xfe;
1508       if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
1509         color |= 0x01;
1510 
1511       myColor[P1Color] = myColor[M1Color] = color;
1512       break;
1513     }
1514 
1515     case COLUPF:  // Color-Luminance Playfield
1516     {
1517       uInt8 color = value & 0xfe;
1518       if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
1519         color |= 0x01;
1520 
1521       myColor[PFColor] = myColor[BLColor] = color;
1522       break;
1523     }
1524 
1525     case COLUBK:  // Color-Luminance Background
1526     {
1527       uInt8 color = value & 0xfe;
1528       if(myColorLossEnabled && (myScanlineCountForLastFrame & 0x01))
1529         color |= 0x01;
1530 
1531       myColor[BKColor] = color;
1532       break;
1533     }
1534 
1535     case CTRLPF:  // Control Playfield, Ball size, Collisions
1536     {
1537       myCTRLPF = value;
1538 
1539       // The playfield priority and score bits from the control register
1540       // are accessed when the frame is being drawn.  We precompute the
1541       // necessary value here so we can save time while drawing.
1542       myPlayfieldPriorityAndScore = ((myCTRLPF & 0x06) << 5);
1543 
1544       // Update the playfield mask based on reflection state if
1545       // we're still on the left hand side of the playfield
1546       if(((clock - myClockWhenFrameStarted) % 228) < (68 + 79))
1547         myPFMask = TIATables::PFMask[myCTRLPF & 0x01];
1548 
1549       break;
1550     }
1551 
1552     case REFP0:   // Reflect Player 0
1553     {
1554       // See if the reflection state of the player is being changed
1555       if(((value & 0x08) && !myREFP0) || (!(value & 0x08) && myREFP0))
1556       {
1557         myREFP0 = (value & 0x08);
1558         myCurrentGRP0 = TIATables::GRPReflect[myCurrentGRP0];
1559       }
1560       break;
1561     }
1562 
1563     case REFP1:   // Reflect Player 1
1564     {
1565       // See if the reflection state of the player is being changed
1566       if(((value & 0x08) && !myREFP1) || (!(value & 0x08) && myREFP1))
1567       {
1568         myREFP1 = (value & 0x08);
1569         myCurrentGRP1 = TIATables::GRPReflect[myCurrentGRP1];
1570       }
1571       break;
1572     }
1573 
1574     case PF0:     // Playfield register byte 0
1575     {
1576       myPF = (myPF & 0x000FFFF0) | ((value >> 4) & 0x0F);
1577 
1578       if(myPF == 0)
1579         myEnabledObjects &= ~PFBit;
1580       else
1581         myEnabledObjects |= PFBit;
1582 
1583     #ifdef DEBUGGER_SUPPORT
1584       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
1585       if(dataAddr)
1586         mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
1587     #endif
1588       break;
1589     }
1590 
1591     case PF1:     // Playfield register byte 1
1592     {
1593       myPF = (myPF & 0x000FF00F) | ((uInt32)value << 4);
1594 
1595       if(myPF == 0)
1596         myEnabledObjects &= ~PFBit;
1597       else
1598         myEnabledObjects |= PFBit;
1599 
1600     #ifdef DEBUGGER_SUPPORT
1601       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
1602       if(dataAddr)
1603         mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
1604     #endif
1605       break;
1606     }
1607 
1608     case PF2:     // Playfield register byte 2
1609     {
1610       myPF = (myPF & 0x00000FFF) | ((uInt32)value << 12);
1611 
1612       if(myPF == 0)
1613         myEnabledObjects &= ~PFBit;
1614       else
1615         myEnabledObjects |= PFBit;
1616 
1617     #ifdef DEBUGGER_SUPPORT
1618       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
1619       if(dataAddr)
1620         mySystem->setAccessFlags(dataAddr, CartDebug::PGFX);
1621     #endif
1622       break;
1623     }
1624 
1625     case RESP0:   // Reset Player 0
1626     {
1627       Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
1628       Int16 newx;
1629 
1630       // Check if HMOVE is currently active
1631       if(myCurrentHMOVEPos != 0x7FFFFFFF)
1632       {
1633         newx = hpos < 7 ? 3 : ((hpos + 5) % 160);
1634         // If HMOVE is active, adjust for any remaining horizontal move clocks
1635         applyActiveHMOVEMotion(hpos, newx, myMotionClockP0);
1636       }
1637       else
1638       {
1639         newx = hpos < -2 ? 3 : ((hpos + 5) % 160);
1640         applyPreviousHMOVEMotion(hpos, newx, myHMP0);
1641       }
1642       if(myPOSP0 != newx)
1643       {
1644         // TODO - update player timing
1645 
1646         // Find out under what condition the player is being reset
1647         delay = TIATables::PxPosResetWhen[myNUSIZ0 & 7][myPOSP0][newx];
1648 
1649         switch(delay)
1650         {
1651           // Player is being reset during the display of one of its copies
1652           case 1:
1653             // TODO - 08-20-2009: determine whether we really need to update
1654             // the frame here, and also come up with a way to eliminate the
1655             // 200KB PxPosResetWhen table.
1656             updateFrame(clock + 11);
1657             mySuppressP0 = 1;
1658             break;
1659 
1660           // Player is being reset in neither the delay nor display section
1661           case 0:
1662             mySuppressP0 = 1;
1663             break;
1664 
1665           // Player is being reset during the delay section of one of its copies
1666           case -1:
1667             mySuppressP0 = 0;
1668             break;
1669         }
1670         myPOSP0 = newx;
1671       }
1672       break;
1673     }
1674 
1675     case RESP1:   // Reset Player 1
1676     {
1677       Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
1678       Int16 newx;
1679 
1680       // Check if HMOVE is currently active
1681       if(myCurrentHMOVEPos != 0x7FFFFFFF)
1682       {
1683         newx = hpos < 7 ? 3 : ((hpos + 5) % 160);
1684         // If HMOVE is active, adjust for any remaining horizontal move clocks
1685         applyActiveHMOVEMotion(hpos, newx, myMotionClockP1);
1686       }
1687       else
1688       {
1689         newx = hpos < -2 ? 3 : ((hpos + 5) % 160);
1690         applyPreviousHMOVEMotion(hpos, newx, myHMP1);
1691       }
1692       if(myPOSP1 != newx)
1693       {
1694         // TODO - update player timing
1695 
1696         // Find out under what condition the player is being reset
1697         delay = TIATables::PxPosResetWhen[myNUSIZ1 & 7][myPOSP1][newx];
1698 
1699         switch(delay)
1700         {
1701           // Player is being reset during the display of one of its copies
1702           case 1:
1703             // TODO - 08-20-2009: determine whether we really need to update
1704             // the frame here, and also come up with a way to eliminate the
1705             // 200KB PxPosResetWhen table.
1706             updateFrame(clock + 11);
1707             mySuppressP1 = 1;
1708             break;
1709 
1710           // Player is being reset in neither the delay nor display section
1711           case 0:
1712             mySuppressP1 = 1;
1713             break;
1714 
1715           // Player is being reset during the delay section of one of its copies
1716           case -1:
1717             mySuppressP1 = 0;
1718             break;
1719         }
1720         myPOSP1 = newx;
1721       }
1722       break;
1723     }
1724 
1725     case RESM0:   // Reset Missle 0
1726     {
1727       Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
1728       Int16 newx;
1729 
1730       // Check if HMOVE is currently active
1731       if(myCurrentHMOVEPos != 0x7FFFFFFF)
1732       {
1733         newx = hpos < 7 ? 2 : ((hpos + 4) % 160);
1734         // If HMOVE is active, adjust for any remaining horizontal move clocks
1735         applyActiveHMOVEMotion(hpos, newx, myMotionClockM0);
1736       }
1737       else
1738       {
1739         newx = hpos < -1 ? 2 : ((hpos + 4) % 160);
1740         applyPreviousHMOVEMotion(hpos, newx, myHMM0);
1741       }
1742       if(newx != myPOSM0)
1743       {
1744         myPOSM0 = newx;
1745       }
1746       break;
1747     }
1748 
1749     case RESM1:   // Reset Missle 1
1750     {
1751       Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
1752       Int16 newx;
1753 
1754       // Check if HMOVE is currently active
1755       if(myCurrentHMOVEPos != 0x7FFFFFFF)
1756       {
1757         newx = hpos < 7 ? 2 : ((hpos + 4) % 160);
1758         // If HMOVE is active, adjust for any remaining horizontal move clocks
1759         applyActiveHMOVEMotion(hpos, newx, myMotionClockM1);
1760       }
1761       else
1762       {
1763         newx = hpos < -1 ? 2 : ((hpos + 4) % 160);
1764         applyPreviousHMOVEMotion(hpos, newx, myHMM1);
1765       }
1766       if(newx != myPOSM1)
1767       {
1768         myPOSM1 = newx;
1769       }
1770       break;
1771     }
1772 
1773     case RESBL:   // Reset Ball
1774     {
1775       Int32 hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
1776 
1777       // Check if HMOVE is currently active
1778       if(myCurrentHMOVEPos != 0x7FFFFFFF)
1779       {
1780         myPOSBL = hpos < 7 ? 2 : ((hpos + 4) % 160);
1781         // If HMOVE is active, adjust for any remaining horizontal move clocks
1782         applyActiveHMOVEMotion(hpos, myPOSBL, myMotionClockBL);
1783       }
1784       else
1785       {
1786         myPOSBL = hpos < 0 ? 2 : ((hpos + 4) % 160);
1787         applyPreviousHMOVEMotion(hpos, myPOSBL, myHMBL);
1788       }
1789       break;
1790     }
1791 
1792     case AUDC0:   // Audio control 0
1793     {
1794       myAUDC0 = value & 0x0f;
1795       mySound.set(addr, value, mySystem->cycles());
1796       break;
1797     }
1798 
1799     case AUDC1:   // Audio control 1
1800     {
1801       myAUDC1 = value & 0x0f;
1802       mySound.set(addr, value, mySystem->cycles());
1803       break;
1804     }
1805 
1806     case AUDF0:   // Audio frequency 0
1807     {
1808       myAUDF0 = value & 0x1f;
1809       mySound.set(addr, value, mySystem->cycles());
1810       break;
1811     }
1812 
1813     case AUDF1:   // Audio frequency 1
1814     {
1815       myAUDF1 = value & 0x1f;
1816       mySound.set(addr, value, mySystem->cycles());
1817       break;
1818     }
1819 
1820     case AUDV0:   // Audio volume 0
1821     {
1822       myAUDV0 = value & 0x0f;
1823       mySound.set(addr, value, mySystem->cycles());
1824       break;
1825     }
1826 
1827     case AUDV1:   // Audio volume 1
1828     {
1829       myAUDV1 = value & 0x0f;
1830       mySound.set(addr, value, mySystem->cycles());
1831       break;
1832     }
1833 
1834     case GRP0:    // Graphics Player 0
1835     {
1836       // Set player 0 graphics
1837       myGRP0 = value;
1838 
1839       // Copy player 1 graphics into its delayed register
1840       myDGRP1 = myGRP1;
1841 
1842       // Get the "current" data for GRP0 base on delay register and reflect
1843       uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
1844       myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
1845 
1846       // Get the "current" data for GRP1 base on delay register and reflect
1847       uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
1848       myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
1849 
1850       // Set enabled object bits
1851       if(myCurrentGRP0 != 0)
1852         myEnabledObjects |= P0Bit;
1853       else
1854         myEnabledObjects &= ~P0Bit;
1855 
1856       if(myCurrentGRP1 != 0)
1857         myEnabledObjects |= P1Bit;
1858       else
1859         myEnabledObjects &= ~P1Bit;
1860 
1861     #ifdef DEBUGGER_SUPPORT
1862       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
1863       if(dataAddr)
1864         mySystem->setAccessFlags(dataAddr, CartDebug::GFX);
1865     #endif
1866       break;
1867     }
1868 
1869     case GRP1:    // Graphics Player 1
1870     {
1871       // Set player 1 graphics
1872       myGRP1 = value;
1873 
1874       // Copy player 0 graphics into its delayed register
1875       myDGRP0 = myGRP0;
1876 
1877       // Copy ball graphics into its delayed register
1878       myDENABL = myENABL;
1879 
1880       // Get the "current" data for GRP0 base on delay register
1881       uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
1882       myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
1883 
1884       // Get the "current" data for GRP1 base on delay register
1885       uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
1886       myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
1887 
1888       // Set enabled object bits
1889       if(myCurrentGRP0 != 0)
1890         myEnabledObjects |= P0Bit;
1891       else
1892         myEnabledObjects &= ~P0Bit;
1893 
1894       if(myCurrentGRP1 != 0)
1895         myEnabledObjects |= P1Bit;
1896       else
1897         myEnabledObjects &= ~P1Bit;
1898 
1899       if(myVDELBL ? myDENABL : myENABL)
1900         myEnabledObjects |= BLBit;
1901       else
1902         myEnabledObjects &= ~BLBit;
1903 
1904     #ifdef DEBUGGER_SUPPORT
1905       uInt16 dataAddr = mySystem->m6502().lastDataAddressForPoke();
1906       if(dataAddr)
1907         mySystem->setAccessFlags(dataAddr, CartDebug::GFX);
1908     #endif
1909       break;
1910     }
1911 
1912     case ENAM0:   // Enable Missile 0 graphics
1913     {
1914       myENAM0 = value & 0x02;
1915 
1916       if(myENAM0 && !myRESMP0)
1917         myEnabledObjects |= M0Bit;
1918       else
1919         myEnabledObjects &= ~M0Bit;
1920       break;
1921     }
1922 
1923     case ENAM1:   // Enable Missile 1 graphics
1924     {
1925       myENAM1 = value & 0x02;
1926 
1927       if(myENAM1 && !myRESMP1)
1928         myEnabledObjects |= M1Bit;
1929       else
1930         myEnabledObjects &= ~M1Bit;
1931       break;
1932     }
1933 
1934     case ENABL:   // Enable Ball graphics
1935     {
1936       myENABL = value & 0x02;
1937 
1938       if(myVDELBL ? myDENABL : myENABL)
1939         myEnabledObjects |= BLBit;
1940       else
1941         myEnabledObjects &= ~BLBit;
1942 
1943       break;
1944     }
1945 
1946     case HMP0:    // Horizontal Motion Player 0
1947     {
1948       pokeHMP0(value, clock);
1949       break;
1950     }
1951 
1952     case HMP1:    // Horizontal Motion Player 1
1953     {
1954       pokeHMP1(value, clock);
1955       break;
1956     }
1957 
1958     case HMM0:    // Horizontal Motion Missle 0
1959     {
1960       pokeHMM0(value, clock);
1961       break;
1962     }
1963 
1964     case HMM1:    // Horizontal Motion Missle 1
1965     {
1966       pokeHMM1(value, clock);
1967       break;
1968     }
1969 
1970     case HMBL:    // Horizontal Motion Ball
1971     {
1972       pokeHMBL(value, clock);
1973       break;
1974     }
1975 
1976     case VDELP0:  // Vertical Delay Player 0
1977     {
1978       myVDELP0 = value & 0x01;
1979 
1980       uInt8 grp0 = myVDELP0 ? myDGRP0 : myGRP0;
1981       myCurrentGRP0 = myREFP0 ? TIATables::GRPReflect[grp0] : grp0;
1982 
1983       if(myCurrentGRP0 != 0)
1984         myEnabledObjects |= P0Bit;
1985       else
1986         myEnabledObjects &= ~P0Bit;
1987       break;
1988     }
1989 
1990     case VDELP1:  // Vertical Delay Player 1
1991     {
1992       myVDELP1 = value & 0x01;
1993 
1994       uInt8 grp1 = myVDELP1 ? myDGRP1 : myGRP1;
1995       myCurrentGRP1 = myREFP1 ? TIATables::GRPReflect[grp1] : grp1;
1996 
1997       if(myCurrentGRP1 != 0)
1998         myEnabledObjects |= P1Bit;
1999       else
2000         myEnabledObjects &= ~P1Bit;
2001       break;
2002     }
2003 
2004     case VDELBL:  // Vertical Delay Ball
2005     {
2006       myVDELBL = value & 0x01;
2007 
2008       if(myVDELBL ? myDENABL : myENABL)
2009         myEnabledObjects |= BLBit;
2010       else
2011         myEnabledObjects &= ~BLBit;
2012       break;
2013     }
2014 
2015     case RESMP0:  // Reset missle 0 to player 0
2016     {
2017       if(myRESMP0 && !(value & 0x02))
2018       {
2019         uInt16 middle = 4;
2020         switch(myNUSIZ0 & 0x07)
2021         {
2022           // 1-pixel delay is taken care of in TIATables::PxMask
2023           case 0x05: middle = 8;  break;  // double size
2024           case 0x07: middle = 16; break;  // quad size
2025         }
2026         myPOSM0 = myPOSP0 + middle;
2027         if(myCurrentHMOVEPos != 0x7FFFFFFF)
2028         {
2029           myPOSM0 -= (8 - myMotionClockP0);
2030           myPOSM0 += (8 - myMotionClockM0);
2031         }
2032         CLAMP_POS(myPOSM0);
2033       }
2034       myRESMP0 = value & 0x02;
2035 
2036       if(myENAM0 && !myRESMP0)
2037         myEnabledObjects |= M0Bit;
2038       else
2039         myEnabledObjects &= ~M0Bit;
2040 
2041       break;
2042     }
2043 
2044     case RESMP1:  // Reset missle 1 to player 1
2045     {
2046       if(myRESMP1 && !(value & 0x02))
2047       {
2048         uInt16 middle = 4;
2049         switch(myNUSIZ1 & 0x07)
2050         {
2051           // 1-pixel delay is taken care of in TIATables::PxMask
2052           case 0x05: middle = 8;  break;  // double size
2053           case 0x07: middle = 16; break;  // quad size
2054         }
2055         myPOSM1 = myPOSP1 + middle;
2056         if(myCurrentHMOVEPos != 0x7FFFFFFF)
2057         {
2058           myPOSM1 -= (8 - myMotionClockP1);
2059           myPOSM1 += (8 - myMotionClockM1);
2060         }
2061         CLAMP_POS(myPOSM1);
2062       }
2063       myRESMP1 = value & 0x02;
2064 
2065       if(myENAM1 && !myRESMP1)
2066         myEnabledObjects |= M1Bit;
2067       else
2068         myEnabledObjects &= ~M1Bit;
2069       break;
2070     }
2071 
2072     case HMOVE:   // Apply horizontal motion
2073     {
2074       int hpos = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2075       myCurrentHMOVEPos = hpos;
2076 
2077       // See if we need to enable the HMOVE blank bug
2078       myHMOVEBlankEnabled = myAllowHMOVEBlanks ?
2079         TIATables::HMOVEBlankEnableCycles[((clock - myClockWhenFrameStarted) % 228) / 3] : false;
2080 
2081       // Do we have to undo some of the already applied cycles from an
2082       // active graphics latch?
2083       if(hpos + HBLANK < 17 * 4)
2084       {
2085         Int16 cycle_fix = 17 - ((hpos + HBLANK + 7) / 4);
2086         if(myHMP0mmr)  myPOSP0 = (myPOSP0 + cycle_fix) % 160;
2087         if(myHMP1mmr)  myPOSP1 = (myPOSP1 + cycle_fix) % 160;
2088         if(myHMM0mmr)  myPOSM0 = (myPOSM0 + cycle_fix) % 160;
2089         if(myHMM1mmr)  myPOSM1 = (myPOSM1 + cycle_fix) % 160;
2090         if(myHMBLmmr)  myPOSBL = (myPOSBL + cycle_fix) % 160;
2091       }
2092       myHMP0mmr = myHMP1mmr = myHMM0mmr = myHMM1mmr = myHMBLmmr = false;
2093 
2094       // Can HMOVE activities be ignored?
2095       if(hpos >= -5 && hpos < 97 )
2096       {
2097         myMotionClockP0 = 0;
2098         myMotionClockP1 = 0;
2099         myMotionClockM0 = 0;
2100         myMotionClockM1 = 0;
2101         myMotionClockBL = 0;
2102         myHMOVEBlankEnabled = false;
2103         myCurrentHMOVEPos = 0x7FFFFFFF;
2104         break;
2105       }
2106 
2107       myMotionClockP0 = (myHMP0 ^ 0x80) >> 4;
2108       myMotionClockP1 = (myHMP1 ^ 0x80) >> 4;
2109       myMotionClockM0 = (myHMM0 ^ 0x80) >> 4;
2110       myMotionClockM1 = (myHMM1 ^ 0x80) >> 4;
2111       myMotionClockBL = (myHMBL ^ 0x80) >> 4;
2112 
2113       // Adjust number of graphics motion clocks for active display
2114       if(hpos >= 97 && hpos < 151)
2115       {
2116         Int16 skip_motclks = (160 - myCurrentHMOVEPos - 6) >> 2;
2117         myMotionClockP0 -= skip_motclks;
2118         myMotionClockP1 -= skip_motclks;
2119         myMotionClockM0 -= skip_motclks;
2120         myMotionClockM1 -= skip_motclks;
2121         myMotionClockBL -= skip_motclks;
2122         if(myMotionClockP0 < 0)  myMotionClockP0 = 0;
2123         if(myMotionClockP1 < 0)  myMotionClockP1 = 0;
2124         if(myMotionClockM0 < 0)  myMotionClockM0 = 0;
2125         if(myMotionClockM1 < 0)  myMotionClockM1 = 0;
2126         if(myMotionClockBL < 0)  myMotionClockBL = 0;
2127       }
2128 
2129       if(hpos >= -56 && hpos < -5)
2130       {
2131         Int16 max_motclks = (7 - (myCurrentHMOVEPos + 5)) >> 2;
2132         if(myMotionClockP0 > max_motclks)  myMotionClockP0 = max_motclks;
2133         if(myMotionClockP1 > max_motclks)  myMotionClockP1 = max_motclks;
2134         if(myMotionClockM0 > max_motclks)  myMotionClockM0 = max_motclks;
2135         if(myMotionClockM1 > max_motclks)  myMotionClockM1 = max_motclks;
2136         if(myMotionClockBL > max_motclks)  myMotionClockBL = max_motclks;
2137       }
2138 
2139       // Apply horizontal motion
2140       if(hpos < -5 || hpos >= 157)
2141       {
2142         myPOSP0 += 8 - myMotionClockP0;
2143         myPOSP1 += 8 - myMotionClockP1;
2144         myPOSM0 += 8 - myMotionClockM0;
2145         myPOSM1 += 8 - myMotionClockM1;
2146         myPOSBL += 8 - myMotionClockBL;
2147       }
2148 
2149       // Make sure positions are in range
2150       CLAMP_POS(myPOSP0);
2151       CLAMP_POS(myPOSP1);
2152       CLAMP_POS(myPOSM0);
2153       CLAMP_POS(myPOSM1);
2154       CLAMP_POS(myPOSBL);
2155 
2156       // TODO - handle late HMOVE's
2157       mySuppressP0 = mySuppressP1 = 0;
2158       break;
2159     }
2160 
2161     case HMCLR:   // Clear horizontal motion registers
2162     {
2163       pokeHMP0(0, clock);
2164       pokeHMP1(0, clock);
2165       pokeHMM0(0, clock);
2166       pokeHMM1(0, clock);
2167       pokeHMBL(0, clock);
2168       break;
2169     }
2170 
2171     case CXCLR:   // Clear collision latches
2172     {
2173       myCollision = 0;
2174       break;
2175     }
2176 
2177     default:
2178     {
2179 #ifdef DEBUG_ACCESSES
2180       cerr << "BAD TIA Poke: " << hex << addr << endl;
2181 #endif
2182       break;
2183     }
2184   }
2185   return true;
2186 }
2187 
2188 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2189 // Note that the following methods to change the horizontal motion registers
2190 // are not completely accurate.  We should be taking care of the following
2191 // explanation from A. Towers Hardware Notes:
2192 //
2193 //   Much more interesting is this: if the counter has not yet
2194 //   reached the value in HMxx (or has reached it but not yet
2195 //   commited the comparison) and a value with at least one bit
2196 //   in common with all remaining internal counter states is
2197 //   written (zeros or ones), the stopping condition will never be
2198 //   reached and the object will be moved a full 15 pixels left.
2199 //   In addition to this, the HMOVE will complete without clearing
2200 //   the "more movement required" latch, and so will continue to send
2201 //   an additional clock signal every 4 CLK (during visible and
2202 //   non-visible parts of the scanline) until another HMOVE operation
2203 //   clears the latch. The HMCLR command does not reset these latches.
2204 //
2205 // This condition is what causes the 'starfield effect' in Cosmic Ark,
2206 // and the 'snow' in Stay Frosty.  Ideally, we'd trace the counter and
2207 // do a compare every colour clock, updating the horizontal positions
2208 // when applicable.  We can save time by cheating, and noting that the
2209 // effect only occurs for 'magic numbers' 0x70 and 0x80.
2210 //
2211 // Most of the ideas in these methods come from MESS.
2212 // (used with permission from Wilbert Pol)
2213 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pokeHMP0(uInt8 value,Int32 clock)2214 void TIA::pokeHMP0(uInt8 value, Int32 clock)
2215 {
2216   value &= 0xF0;
2217   if(myHMP0 == value)
2218     return;
2219 
2220   int hpos  = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2221 
2222   // Check if HMOVE is currently active
2223   if(myCurrentHMOVEPos != 0x7FFFFFFF &&
2224      hpos < MIN(myCurrentHMOVEPos + 6 + myMotionClockP0 * 4, 7))
2225   {
2226     Int32 newMotion = (value ^ 0x80) >> 4;
2227     // Check if new horizontal move can still be applied normally
2228     if(newMotion > myMotionClockP0 ||
2229        hpos <= MIN(myCurrentHMOVEPos + 6 + newMotion * 4, 7))
2230     {
2231       myPOSP0 -= (newMotion - myMotionClockP0);
2232       myMotionClockP0 = newMotion;
2233     }
2234     else
2235     {
2236       myPOSP0 -= (15 - myMotionClockP0);
2237       myMotionClockP0 = 15;
2238       if(value != 0x70 && value != 0x80)
2239         myHMP0mmr = true;
2240     }
2241     CLAMP_POS(myPOSP0);
2242     // TODO - adjust player timing
2243   }
2244   myHMP0 = value;
2245 }
2246 
2247 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pokeHMP1(uInt8 value,Int32 clock)2248 void TIA::pokeHMP1(uInt8 value, Int32 clock)
2249 {
2250   value &= 0xF0;
2251   if(myHMP1 == value)
2252     return;
2253 
2254   int hpos  = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2255 
2256   // Check if HMOVE is currently active
2257   if(myCurrentHMOVEPos != 0x7FFFFFFF &&
2258      hpos < MIN(myCurrentHMOVEPos + 6 + myMotionClockP1 * 4, 7))
2259   {
2260     Int32 newMotion = (value ^ 0x80) >> 4;
2261     // Check if new horizontal move can still be applied normally
2262     if(newMotion > myMotionClockP1 ||
2263        hpos <= MIN(myCurrentHMOVEPos + 6 + newMotion * 4, 7))
2264     {
2265       myPOSP1 -= (newMotion - myMotionClockP1);
2266       myMotionClockP1 = newMotion;
2267     }
2268     else
2269     {
2270       myPOSP1 -= (15 - myMotionClockP1);
2271       myMotionClockP1 = 15;
2272       if(value != 0x70 && value != 0x80)
2273         myHMP1mmr = true;
2274     }
2275     CLAMP_POS(myPOSP1);
2276     // TODO - adjust player timing
2277   }
2278   myHMP1 = value;
2279 }
2280 
2281 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pokeHMM0(uInt8 value,Int32 clock)2282 void TIA::pokeHMM0(uInt8 value, Int32 clock)
2283 {
2284   value &= 0xF0;
2285   if(myHMM0 == value)
2286     return;
2287 
2288   int hpos  = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2289 
2290   // Check if HMOVE is currently active
2291   if(myCurrentHMOVEPos != 0x7FFFFFFF &&
2292      hpos < MIN(myCurrentHMOVEPos + 6 + myMotionClockM0 * 4, 7))
2293   {
2294     Int32 newMotion = (value ^ 0x80) >> 4;
2295     // Check if new horizontal move can still be applied normally
2296     if(newMotion > myMotionClockM0 ||
2297        hpos <= MIN(myCurrentHMOVEPos + 6 + newMotion * 4, 7))
2298     {
2299       myPOSM0 -= (newMotion - myMotionClockM0);
2300       myMotionClockM0 = newMotion;
2301     }
2302     else
2303     {
2304       myPOSM0 -= (15 - myMotionClockM0);
2305       myMotionClockM0 = 15;
2306       if(value != 0x70 && value != 0x80)
2307         myHMM0mmr = true;
2308     }
2309     CLAMP_POS(myPOSM0);
2310   }
2311   myHMM0 = value;
2312 }
2313 
2314 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pokeHMM1(uInt8 value,Int32 clock)2315 void TIA::pokeHMM1(uInt8 value, Int32 clock)
2316 {
2317   value &= 0xF0;
2318   if(myHMM1 == value)
2319     return;
2320 
2321   int hpos  = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2322 
2323   // Check if HMOVE is currently active
2324   if(myCurrentHMOVEPos != 0x7FFFFFFF &&
2325      hpos < MIN(myCurrentHMOVEPos + 6 + myMotionClockM1 * 4, 7))
2326   {
2327     Int32 newMotion = (value ^ 0x80) >> 4;
2328     // Check if new horizontal move can still be applied normally
2329     if(newMotion > myMotionClockM1 ||
2330        hpos <= MIN(myCurrentHMOVEPos + 6 + newMotion * 4, 7))
2331     {
2332       myPOSM1 -= (newMotion - myMotionClockM1);
2333       myMotionClockM1 = newMotion;
2334     }
2335     else
2336     {
2337       myPOSM1 -= (15 - myMotionClockM1);
2338       myMotionClockM1 = 15;
2339       if(value != 0x70 && value != 0x80)
2340         myHMM1mmr = true;
2341     }
2342     CLAMP_POS(myPOSM1);
2343   }
2344   myHMM1 = value;
2345 }
2346 
2347 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pokeHMBL(uInt8 value,Int32 clock)2348 void TIA::pokeHMBL(uInt8 value, Int32 clock)
2349 {
2350   value &= 0xF0;
2351   if(myHMBL == value)
2352     return;
2353 
2354   int hpos  = (clock - myClockWhenFrameStarted) % 228 - HBLANK;
2355 
2356   // Check if HMOVE is currently active
2357   if(myCurrentHMOVEPos != 0x7FFFFFFF &&
2358      hpos < MIN(myCurrentHMOVEPos + 6 + myMotionClockBL * 4, 7))
2359   {
2360     Int32 newMotion = (value ^ 0x80) >> 4;
2361     // Check if new horizontal move can still be applied normally
2362     if(newMotion > myMotionClockBL ||
2363        hpos <= MIN(myCurrentHMOVEPos + 6 + newMotion * 4, 7))
2364     {
2365       myPOSBL -= (newMotion - myMotionClockBL);
2366       myMotionClockBL = newMotion;
2367     }
2368     else
2369     {
2370       myPOSBL -= (15 - myMotionClockBL);
2371       myMotionClockBL = 15;
2372       if(value != 0x70 && value != 0x80)
2373         myHMBLmmr = true;
2374     }
2375     CLAMP_POS(myPOSBL);
2376   }
2377   myHMBL = value;
2378 }
2379 
2380 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2381 // The following two methods apply extra clocks when a horizontal motion
2382 // register (HMxx) is modified during an HMOVE, before waiting for the
2383 // documented time of at least 24 CPU cycles.  The applicable explanation
2384 // from A. Towers Hardware Notes is as follows:
2385 //
2386 //   In theory then the side effects of modifying the HMxx registers
2387 //   during HMOVE should be quite straight-forward. If the internal
2388 //   counter has not yet reached the value in HMxx, a new value greater
2389 //   than this (in 0-15 terms) will work normally. Conversely, if
2390 //   the counter has already reached the value in HMxx, new values
2391 //   will have no effect because the latch will have been cleared.
2392 //
2393 // Most of the ideas in these methods come from MESS.
2394 // (used with permission from Wilbert Pol)
2395 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
applyActiveHMOVEMotion(int hpos,Int16 & pos,Int32 motionClock)2396 inline void TIA::applyActiveHMOVEMotion(int hpos, Int16& pos, Int32 motionClock)
2397 {
2398   if(hpos < MIN(myCurrentHMOVEPos + 6 + 16 * 4, 7))
2399   {
2400     Int32 decrements_passed = (hpos - (myCurrentHMOVEPos + 4)) >> 2;
2401     pos += 8;
2402     if((motionClock - decrements_passed) > 0)
2403     {
2404       pos -= (motionClock - decrements_passed);
2405       if(pos < 0)  pos += 160;
2406     }
2407   }
2408 }
2409 
2410 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
applyPreviousHMOVEMotion(int hpos,Int16 & pos,uInt8 motion)2411 inline void TIA::applyPreviousHMOVEMotion(int hpos, Int16& pos, uInt8 motion)
2412 {
2413   if(myPreviousHMOVEPos != 0x7FFFFFFF)
2414   {
2415     uInt8 motclk = (motion ^ 0x80) >> 4;
2416     if(hpos <= myPreviousHMOVEPos - 228 + 5 + motclk * 4)
2417     {
2418       uInt8 motclk_passed = (hpos - (myPreviousHMOVEPos - 228 + 6)) >> 2;
2419       pos -= (motclk - motclk_passed);
2420     }
2421   }
2422 }
2423 
2424 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA(const TIA & c)2425 TIA::TIA(const TIA& c)
2426   : myConsole(c.myConsole),
2427     mySound(c.mySound),
2428     mySettings(c.mySettings)
2429 {
2430   assert(false);
2431 }
2432 
2433 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
operator =(const TIA &)2434 TIA& TIA::operator = (const TIA&)
2435 {
2436   assert(false);
2437   return *this;
2438 }
2439