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 | #ifndef TIA_BALL |
19 | #define TIA_BALL |
20 | |
21 | #include "Serializable.hxx" |
22 | #include "bspf.hxx" |
23 | #include "TIAConstants.hxx" |
24 | |
25 | class TIA; |
26 | |
27 | class Ball : public Serializable |
28 | { |
29 | public: |
30 | |
31 | /** |
32 | The collision mask is injected at construction |
33 | */ |
34 | explicit Ball(uInt32 collisionMask); |
35 | |
36 | public: |
37 | |
38 | /** |
39 | Set the TIA instance |
40 | */ |
41 | void setTIA(TIA* tia) { myTIA = tia; } |
42 | |
43 | /** |
44 | Reset to initial state. |
45 | */ |
46 | void reset(); |
47 | |
48 | /** |
49 | ENABL write. |
50 | */ |
51 | void enabl(uInt8 value); |
52 | |
53 | /** |
54 | HMBL write. |
55 | */ |
56 | void hmbl(uInt8 value); |
57 | |
58 | /** |
59 | RESBL write. |
60 | */ |
61 | void resbl(uInt8 counter); |
62 | |
63 | /** |
64 | CTRLPF write. |
65 | */ |
66 | void ctrlpf(uInt8 value); |
67 | |
68 | /** |
69 | VDELBL write. |
70 | */ |
71 | void vdelbl(uInt8 value); |
72 | |
73 | /** |
74 | Enable / disable ball display (debugging only, not used during normal emulation). |
75 | */ |
76 | void toggleEnabled(bool enabled); |
77 | |
78 | /** |
79 | Enable / disable ball collisions (debugging only, not used during normal emulation). |
80 | */ |
81 | void toggleCollisions(bool enabled); |
82 | |
83 | /** |
84 | Set color PF. |
85 | */ |
86 | void setColor(uInt8 color); |
87 | |
88 | /** |
89 | Set the color used in "debug colors" mode. |
90 | */ |
91 | void setDebugColor(uInt8 color); |
92 | |
93 | /** |
94 | Enable "debug colors" mode. |
95 | */ |
96 | |
97 | void enableDebugColors(bool enabled); |
98 | |
99 | /** |
100 | Update internal state to use the color loss palette. |
101 | */ |
102 | void applyColorLoss(); |
103 | |
104 | /** |
105 | Switch to "inverted phase" mode. This mode emulates the phase shift |
106 | between movement and ordinary clock pulses that is exhibited by some |
107 | TIA revisions and that give rise to glitches like the infamous Cool |
108 | Aid Man bug on some Jr. models. |
109 | */ |
110 | void setInvertedPhaseClock(bool enable); |
111 | |
112 | /** |
113 | Start movement --- this is triggered by strobing HMOVE. |
114 | */ |
115 | void startMovement(); |
116 | |
117 | /** |
118 | Notify ball of line change. |
119 | */ |
120 | void nextLine(); |
121 | |
122 | /** |
123 | Is the ball visible? This is determined by looking at bit 15 |
124 | of the collision mask. |
125 | */ |
126 | bool isOn() const { return (collision & 0x8000); } |
127 | |
128 | /** |
129 | Get the current color. |
130 | */ |
131 | uInt8 getColor() const { return myColor; } |
132 | |
133 | /** |
134 | Shuffle the enabled flag. This is called in VDELBL mode when GRP1 is |
135 | written (with a delay of one cycle). |
136 | */ |
137 | void shuffleStatus(); |
138 | |
139 | /** |
140 | Calculate the sprite position from the counter. Used by the debugger only. |
141 | */ |
142 | uInt8 getPosition() const; |
143 | |
144 | /** |
145 | Set the counter and place the sprite at a specified position. Used by the debugger |
146 | only. |
147 | */ |
148 | void setPosition(uInt8 newPosition); |
149 | |
150 | /** |
151 | Get the "old" and "new" values of the enabled flag. Used by the debuggger only. |
152 | */ |
153 | bool getENABLOld() const { return myIsEnabledOld; } |
154 | bool getENABLNew() const { return myIsEnabledNew; } |
155 | |
156 | /** |
157 | Directly set the "old" value of the enabled flag. Used by the debugger only. |
158 | */ |
159 | void setENABLOld(bool enabled); |
160 | |
161 | /** |
162 | Serializable methods (see that class for more information). |
163 | */ |
164 | bool save(Serializer& out) const override; |
165 | bool load(Serializer& in) override; |
166 | |
167 | /** |
168 | Process a single movement tick. Inline for performance (implementation below). |
169 | */ |
170 | inline void movementTick(uInt32 clock, bool hblank); |
171 | |
172 | /** |
173 | Tick one color clock. Inline for performance (implementation below). |
174 | */ |
175 | inline void tick(bool isReceivingRegularClock = true); |
176 | |
177 | public: |
178 | |
179 | /** |
180 | 16 bit Collision mask. Each sprite is represented by a single bit in the mask |
181 | (1 = active, 0 = inactive). All other bits are always 1. The highest bit is |
182 | abused to store visibility (as the actual collision bit will always be zero |
183 | if collisions are disabled). |
184 | */ |
185 | uInt32 collision; |
186 | |
187 | /** |
188 | The movement flag. This corresponds to the state of the movement latch for |
189 | this sprite --- true while movement is active and ticks are still propagated |
190 | to the counters, false otherwise. |
191 | */ |
192 | bool isMoving; |
193 | |
194 | private: |
195 | |
196 | /** |
197 | Recalculate enabled / disabled state. This is not the same as the enabled / disabled |
198 | flag, but rather calculated from the flag and the corresponding debug setting. |
199 | */ |
200 | void updateEnabled(); |
201 | |
202 | /** |
203 | Recalculate ball color based on COLUPF, debug colors, color loss, etc. |
204 | */ |
205 | void applyColors(); |
206 | |
207 | private: |
208 | |
209 | /** |
210 | Offset of the render counter when rendering starts. Actual display starts at zero, |
211 | so this amounts to a delay. |
212 | */ |
213 | enum Count: Int8 { |
214 | renderCounterOffset = -4 |
215 | }; |
216 | |
217 | private: |
218 | |
219 | /** |
220 | Collision mask values for active / inactive states. Disabling collisions |
221 | will change those. |
222 | */ |
223 | uInt32 myCollisionMaskDisabled; |
224 | uInt32 myCollisionMaskEnabled; |
225 | |
226 | /** |
227 | Color value calculated by applyColors(). |
228 | */ |
229 | uInt8 myColor; |
230 | |
231 | /** |
232 | Color configured by COLUPF |
233 | */ |
234 | uInt8 myObjectColor; |
235 | |
236 | /** |
237 | Color for debug mode. |
238 | */ |
239 | uInt8 myDebugColor; |
240 | |
241 | /** |
242 | Debug mode enabled? |
243 | */ |
244 | bool myDebugEnabled; |
245 | |
246 | /** |
247 | "old" and "new" values of the enabled flag. |
248 | */ |
249 | bool myIsEnabledOld; |
250 | bool myIsEnabledNew; |
251 | |
252 | /** |
253 | Actual value of the enabled flag. Determined from the "old" and "new" values |
254 | VDEL, debug settings etc. |
255 | */ |
256 | bool myIsEnabled; |
257 | |
258 | /** |
259 | Is the sprite turned off in the debugger? |
260 | */ |
261 | bool myIsSuppressed; |
262 | |
263 | /** |
264 | Is VDEL active? |
265 | */ |
266 | bool myIsDelaying; |
267 | |
268 | /** |
269 | Is the ball sprite signal currently active? |
270 | */ |
271 | bool mySignalActive; |
272 | |
273 | /** |
274 | HMM clocks before movement stops. Changed by writing to HMBL. |
275 | */ |
276 | uInt8 myHmmClocks; |
277 | |
278 | /** |
279 | The sprite counter |
280 | */ |
281 | uInt8 myCounter; |
282 | |
283 | /** |
284 | Ball width, as configured by CTRLPF. |
285 | */ |
286 | uInt8 myWidth; |
287 | |
288 | /** |
289 | Effective width used for drawing. This is usually the same as myWidth, |
290 | but my differ in starfield mode. |
291 | */ |
292 | uInt8 myEffectiveWidth; |
293 | |
294 | /** |
295 | The value of the counter value at which the last movement tick occurred. This is |
296 | used for simulating the starfield pattern. |
297 | */ |
298 | uInt8 myLastMovementTick; |
299 | |
300 | /** |
301 | Are we currently rendering? This is latched when the counter hits it decode value, |
302 | or when RESBL is strobed. It is turned off once the render counter reaches its |
303 | maximum (i.e. when the sprite has been fully displayed). |
304 | */ |
305 | bool myIsRendering; |
306 | |
307 | /** |
308 | Rendering counter. It starts counting (below zero) when the counter hits the decode value, |
309 | and the actual signal becomes active once it reaches 0. |
310 | */ |
311 | Int8 myRenderCounter; |
312 | |
313 | /** |
314 | This memorizes a movement tick outside HBLANK in inverted clock mode. It is latched |
315 | durin ::movementTick() and processed during ::tick() where it inhibits the clock |
316 | pulse. |
317 | */ |
318 | bool myInvertedPhaseClock; |
319 | |
320 | /** |
321 | Use "inverted movement clock phase" mode? This emulates an idiosyncracy of several |
322 | newer TIA revisions (see the setter above for a deeper explanation). |
323 | */ |
324 | bool myUseInvertedPhaseClock; |
325 | |
326 | /** |
327 | TIA instance. Required for flushing the line cache and requesting collision updates. |
328 | */ |
329 | TIA* myTIA; |
330 | |
331 | private: |
332 | Ball() = delete; |
333 | Ball(const Ball&) = delete; |
334 | Ball(Ball&&) = delete; |
335 | Ball& operator=(const Ball&) = delete; |
336 | Ball& operator=(Ball&&); |
337 | }; |
338 | |
339 | // ############################################################################ |
340 | // Implementation |
341 | // ############################################################################ |
342 | |
343 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
344 | void Ball::movementTick(uInt32 clock, bool hblank) |
345 | { |
346 | myLastMovementTick = myCounter; |
347 | |
348 | // Stop movement once the number of clocks according to HMBL is reached |
349 | if (clock == myHmmClocks) |
350 | isMoving = false; |
351 | |
352 | if(isMoving) |
353 | { |
354 | // Process the tick if we are in hblank. Otherwise, the tick is either masked |
355 | // by an ordinary tick or merges two consecutive ticks into a single tick (inverted |
356 | // movement clock phase mode). |
357 | if (hblank) tick(false); |
358 | |
359 | // Track a tick outside hblank for later processing |
360 | myInvertedPhaseClock = !hblank; |
361 | } |
362 | } |
363 | |
364 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
365 | void Ball::tick(bool isReceivingRegularClock) |
366 | { |
367 | // If we are in inverted movement clock phase mode and a movement tick occurred, it |
368 | // will supress the tick. |
369 | if(myUseInvertedPhaseClock && myInvertedPhaseClock) |
370 | { |
371 | myInvertedPhaseClock = false; |
372 | return; |
373 | } |
374 | |
375 | // Turn on the signal if the render counter reaches the threshold |
376 | mySignalActive = myIsRendering && myRenderCounter >= 0; |
377 | |
378 | // Consider enabled status and the signal to determine visibility (as represented |
379 | // by the collision mask) |
380 | collision = (mySignalActive && myIsEnabled) ? myCollisionMaskEnabled : myCollisionMaskDisabled; |
381 | |
382 | // Regular clock pulse during movement -> starfield mode |
383 | bool starfieldEffect = isMoving && isReceivingRegularClock; |
384 | |
385 | // Decode value that triggers rendering |
386 | if (myCounter == 156) { |
387 | myIsRendering = true; |
388 | myRenderCounter = renderCounterOffset; |
389 | |
390 | // What follows is an effective description of ball width in starfield mode. |
391 | uInt8 starfieldDelta = (myCounter + TIAConstants::H_PIXEL - myLastMovementTick) % 4; |
392 | if (starfieldEffect && starfieldDelta == 3 && myWidth < 4) ++myRenderCounter; |
393 | |
394 | switch (starfieldDelta) { |
395 | case 3: |
396 | myEffectiveWidth = myWidth == 1 ? 2 : myWidth; |
397 | break; |
398 | |
399 | case 2: |
400 | myEffectiveWidth = 0; |
401 | break; |
402 | |
403 | default: |
404 | myEffectiveWidth = myWidth; |
405 | break; |
406 | } |
407 | |
408 | } else if (myIsRendering && ++myRenderCounter >= (starfieldEffect ? myEffectiveWidth : myWidth)) |
409 | myIsRendering = false; |
410 | |
411 | if (++myCounter >= TIAConstants::H_PIXEL) |
412 | myCounter = 0; |
413 | } |
414 | |
415 | #endif // TIA_BALL |
416 | |