stella/src/emucore/TIA.hxx

626 lines
20 KiB
C++

//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2014 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id$
//============================================================================
#ifndef TIA_HXX
#define TIA_HXX
class Console;
class Settings;
class Sound;
#include "bspf.hxx"
#include "Device.hxx"
#include "System.hxx"
#include "TIATables.hxx"
/**
This class is a device that emulates the Television Interface Adaptor
found in the Atari 2600 and 7800 consoles. The Television Interface
Adaptor is an integrated circuit designed to interface between an
eight bit microprocessor and a television video modulator. It converts
eight bit parallel data into serial outputs for the color, luminosity,
and composite sync required by a video modulator.
This class outputs the serial data into a frame buffer which can then
be displayed on screen.
@author Bradford W. Mott
@version $Id$
*/
class TIA : public Device
{
public:
friend class TIADebug;
friend class RiotDebug;
/**
Create a new TIA for the specified console
@param console The console the TIA is associated with
@param sound The sound object the TIA is associated with
@param settings The settings object for this TIA device
*/
TIA(Console& console, Sound& sound, Settings& settings);
/**
Destructor
*/
virtual ~TIA();
public:
/**
Reset device to its power-on state
*/
void reset();
/**
Reset frame to current YStart/Height properties
*/
void frameReset();
/**
Notification method invoked by the system right before the
system resets its cycle counter to zero. It may be necessary
to override this method for devices that remember cycle counts.
*/
void systemCyclesReset();
/**
Install TIA in the specified system. Invoked by the system
when the TIA is attached to it.
@param system The system the device should install itself in
*/
void install(System& system);
/**
Install TIA in the specified system and device. Invoked by
the system when the TIA is attached to it. All devices
which invoke this method take responsibility for chaining
requests back to *this* device.
@param system The system the device should install itself in
@param device The device responsible for this address space
*/
void install(System& system, Device& device);
/**
Save the current state of this device to the given Serializer.
@param out The Serializer object to use
@return False on any errors, else true
*/
bool save(Serializer& out) const;
/**
Load the current state of this device from the given Serializer.
@param in The Serializer object to use
@return False on any errors, else true
*/
bool load(Serializer& in);
/**
The following are very similar to save() and load(), except they
do a 'deeper' save of the display data itself.
Normally, the internal framebuffer doesn't need to be saved to
a state file, since the file already contains all the information
needed to re-create it, starting from scanline 0. In effect, when a
state is loaded, the framebuffer is empty, and the next call to
update() generates valid framebuffer data.
However, state files saved from the debugger need more information,
such as the exact state of the internal framebuffer itself *before*
we call update(), including if the display was in partial frame mode.
Essentially, a normal state save has 'frame resolution', whereas
the debugger state save has 'cycle resolution', and hence needs
more information. The methods below save/load this extra info,
and eliminate having to save approx. 50K to normal state files.
*/
bool saveDisplay(Serializer& out) const;
bool loadDisplay(Serializer& in);
/**
Get a descriptor for the device name (used in error checking).
@return The name of the object
*/
string name() const { return "TIA"; }
/**
Get the byte at the specified address
@return The byte at the specified address
*/
uInt8 peek(uInt16 address);
/**
Change the byte at the specified address to the given value
@param address The address where the value should be stored
@param value The value to be stored at the address
@return True if the poke changed the device address space, else false
*/
bool poke(uInt16 address, uInt8 value);
/**
This method should be called at an interval corresponding to the
desired frame rate to update the TIA. Invoking this method will update
the graphics buffer and generate the corresponding audio samples.
*/
void update();
/**
Answers the current frame buffer
@return Pointer to the current frame buffer
*/
uInt8* currentFrameBuffer() const
{ return myCurrentFrameBuffer + myFramePointerOffset; }
/**
Answers the previous frame buffer
@return Pointer to the previous frame buffer
*/
uInt8* previousFrameBuffer() const
{ return myPreviousFrameBuffer + myFramePointerOffset; }
/**
Answers the width and height of the frame buffer
*/
inline uInt32 width() const { return 160; }
inline uInt32 height() const { return myFrameHeight; }
inline uInt32 ystart() const { return myFrameYStart; }
/**
Changes the current Height/YStart properties.
Note that calls to these method(s) must be eventually followed by
::frameReset() for the changes to take effect.
*/
void setHeight(uInt32 height) { myFrameHeight = height; }
void setYStart(uInt32 ystart) { myFrameYStart = ystart; }
/**
Enables/disables auto-frame calculation. If enabled, the TIA
re-adjusts the framerate at regular intervals.
@param mode Whether to enable or disable all auto-frame calculation
*/
void enableAutoFrame(bool mode) { myAutoFrameEnabled = mode; }
/**
Enables/disables color-loss for PAL modes only.
@param mode Whether to enable or disable PAL color-loss mode
*/
void enableColorLoss(bool mode)
{ myColorLossEnabled = myFramerate <= 55 ? mode : false; }
/**
Answers whether this TIA runs at NTSC or PAL scanrates,
based on how many frames of out the total count are PAL frames.
*/
bool isPAL() const
{ return float(myPALFrameCounter) / myFrameCounter >= (25.0/60.0); }
/**
Answers the current color clock we've gotten to on this scanline.
@return The current color clock
*/
uInt32 clocksThisLine() const
{ return ((mySystem->cycles() * 3) - myClockWhenFrameStarted) % 228; }
/**
Answers the total number of scanlines the TIA generated in producing
the current frame buffer. For partial frames, this will be the
current scanline.
@return The total number of scanlines generated
*/
uInt32 scanlines() const
{ return ((mySystem->cycles() * 3) - myClockWhenFrameStarted) / 228; }
/**
Answers whether the TIA is currently in 'partial frame' mode
(we're in between a call of startFrame and endFrame).
@return If we're in partial frame mode
*/
bool partialFrame() const { return myPartialFrameFlag; }
/**
Answers the first scanline at which drawing occured in the last frame.
@return The starting scanline
*/
uInt32 startScanline() const { return myStartScanline; }
/**
Answers the current position of the virtual 'electron beam' used to
draw the TIA image. If not in partial frame mode, the position is
defined to be in the lower right corner (@ width/height of the screen).
Note that the coordinates are with respect to currentFrameBuffer(),
taking any YStart values into account.
@return The x/y coordinates of the scanline electron beam, and whether
it is in the visible/viewable area of the screen
*/
bool scanlinePos(uInt16& x, uInt16& y) const;
/**
Enables/disable/toggle the specified (or all) TIA bit(s). Note that
disabling a graphical object also disables its collisions.
@param mode 1/0 indicates on/off, and values greater than 1 mean
flip the bit from its current state
@return Whether the bit was enabled or disabled
*/
bool toggleBit(TIABit b, uInt8 mode = 2);
bool toggleBits();
/**
Enables/disable/toggle the specified (or all) TIA bit collision(s).
@param mode 1/0 indicates on/off, and values greater than 1 mean
flip the collision from its current state
@return Whether the collision was enabled or disabled
*/
bool toggleCollision(TIABit b, uInt8 mode = 2);
bool toggleCollisions();
/**
Toggle the display of HMOVE blanks.
@return Whether the HMOVE blanking was enabled or disabled
*/
bool toggleHMOVEBlank();
/**
Enables/disable/toggle 'fixed debug colors' mode.
@param mode 1/0 indicates on/off, otherwise flip from
its current state
@return Whether the mode was enabled or disabled
*/
bool toggleFixedColors(uInt8 mode = 2);
/**
Enable/disable/query state of 'undriven/floating TIA pins'.
@param mode 1/0 indicates on/off, otherwise return the current state
@return Whether the mode was enabled or disabled
*/
bool driveUnusedPinsRandom(uInt8 mode = 2);
#ifdef DEBUGGER_SUPPORT
/**
This method should be called to update the TIA with a new scanline.
*/
void updateScanline();
/**
This method should be called to update the TIA with a new partial
scanline by stepping one CPU instruction.
*/
void updateScanlineByStep();
/**
This method should be called to update the TIA with a new partial
scanline by tracing to target address.
*/
void updateScanlineByTrace(int target);
#endif
private:
/**
Enables/disables all TIABit bits. Note that disabling a graphical
object also disables its collisions.
@param mode Whether to enable or disable all bits
*/
void enableBits(bool mode);
/**
Enables/disables all TIABit collisions.
@param mode Whether to enable or disable all collisions
*/
void enableCollisions(bool mode);
// Update the current frame buffer to the specified color clock
void updateFrame(Int32 clock);
// Waste cycles until the current scanline is finished
void waitHorizontalSync();
// Reset horizontal sync counter
void waitHorizontalRSync();
// Clear both internal TIA buffers to black (palette color 0)
void clearBuffers();
// Set up bookkeeping for the next frame
void startFrame();
// Update bookkeeping at end of frame
void endFrame();
// Convert resistance from ports to dumped value
uInt8 dumpedInputPort(int resistance);
// Write the specified value to the HMOVE registers at the given clock
void pokeHMP0(uInt8 value, Int32 clock);
void pokeHMP1(uInt8 value, Int32 clock);
void pokeHMM0(uInt8 value, Int32 clock);
void pokeHMM1(uInt8 value, Int32 clock);
void pokeHMBL(uInt8 value, Int32 clock);
// Apply motion to registers when HMOVE is currently active
void applyActiveHMOVEMotion(int hpos, Int16& pos, Int32 motionClock);
// Apply motion to registers when HMOVE was previously active
void applyPreviousHMOVEMotion(int hpos, Int16& pos, uInt8 motion);
private:
// Console the TIA is associated with
Console& myConsole;
// Sound object the TIA is associated with
Sound& mySound;
// Settings object the TIA is associated with
Settings& mySettings;
// Pointer to the current frame buffer
uInt8* myCurrentFrameBuffer;
// Pointer to the previous frame buffer
uInt8* myPreviousFrameBuffer;
// Pointer to the next pixel that will be drawn in the current frame buffer
uInt8* myFramePointer;
// Indicates offset used by the exported frame buffer
// (the exported frame buffer is a vertical 'sliding window' of the actual buffer)
uInt32 myFramePointerOffset;
// Indicates the number of 'colour clocks' offset from the base
// frame buffer pointer
// (this is used when loading state files with a 'partial' frame)
uInt32 myFramePointerClocks;
// Indicated what scanline the frame should start being drawn at
uInt32 myFrameYStart;
// Indicates the height of the frame in scanlines
uInt32 myFrameHeight;
// Indicates offset in color clocks when display should stop
uInt32 myStopDisplayOffset;
// Indicates color clocks when the current frame began
Int32 myClockWhenFrameStarted;
// Indicates color clocks when frame should begin to be drawn
Int32 myClockStartDisplay;
// Indicates color clocks when frame should stop being drawn
Int32 myClockStopDisplay;
// Indicates color clocks when the frame was last updated
Int32 myClockAtLastUpdate;
// Indicates how many color clocks remain until the end of
// current scanline. This value is valid during the
// displayed portion of the frame.
Int32 myClocksToEndOfScanLine;
// Indicates the total number of scanlines generated by the last frame
uInt32 myScanlineCountForLastFrame;
// Indicates the maximum number of scanlines to be generated for a frame
uInt32 myMaximumNumberOfScanlines;
// Indicates potentially the first scanline at which drawing occurs
uInt32 myStartScanline;
// Color clock when VSYNC ending causes a new frame to be started
Int32 myVSYNCFinishClock;
uInt8 myVSYNC; // Holds the VSYNC register value
uInt8 myVBLANK; // Holds the VBLANK register value
uInt8 myNUSIZ0; // Number and size of player 0 and missle 0
uInt8 myNUSIZ1; // Number and size of player 1 and missle 1
uInt8 myPlayfieldPriorityAndScore;
uInt8 myPriorityEncoder[2][256];
uInt8 myColor[8];
uInt8 myFixedColor[8];
uInt8* myColorPtr;
uInt8 myCTRLPF; // Playfield control register
bool myREFP0; // Indicates if player 0 is being reflected
bool myREFP1; // Indicates if player 1 is being reflected
uInt32 myPF; // Playfield graphics (19-12:PF2 11-4:PF1 3-0:PF0)
uInt8 myGRP0; // Player 0 graphics register
uInt8 myGRP1; // Player 1 graphics register
uInt8 myDGRP0; // Player 0 delayed graphics register
uInt8 myDGRP1; // Player 1 delayed graphics register
bool myENAM0; // Indicates if missle 0 is enabled
bool myENAM1; // Indicates if missle 1 is enabled
bool myENABL; // Indicates if the ball is enabled
bool myDENABL; // Indicates if the vertically delayed ball is enabled
uInt8 myHMP0; // Player 0 horizontal motion register
uInt8 myHMP1; // Player 1 horizontal motion register
uInt8 myHMM0; // Missle 0 horizontal motion register
uInt8 myHMM1; // Missle 1 horizontal motion register
uInt8 myHMBL; // Ball horizontal motion register
bool myVDELP0; // Indicates if player 0 is being vertically delayed
bool myVDELP1; // Indicates if player 1 is being vertically delayed
bool myVDELBL; // Indicates if the ball is being vertically delayed
bool myRESMP0; // Indicates if missle 0 is reset to player 0
bool myRESMP1; // Indicates if missle 1 is reset to player 1
uInt16 myCollision; // Collision register
// Determines whether specified collisions are enabled or disabled
// The lower 16 bits are and'ed with the collision register to mask out
// any collisions we don't want to be processed
// The upper 16 bits are used to store which objects is currently
// enabled or disabled
// This is necessary since there are 15 collision combinations which
// are controlled by 6 objects
uInt32 myCollisionEnabledMask;
// Note that these position registers contain the color clock
// on which the object's serial output should begin (0 to 159)
Int16 myPOSP0; // Player 0 position register
Int16 myPOSP1; // Player 1 position register
Int16 myPOSM0; // Missle 0 position register
Int16 myPOSM1; // Missle 1 position register
Int16 myPOSBL; // Ball position register
// The color clocks elapsed so far for each of the graphical objects,
// as denoted by 'MOTCK' line described in A. Towers TIA Hardware Notes
Int32 myMotionClockP0;
Int32 myMotionClockP1;
Int32 myMotionClockM0;
Int32 myMotionClockM1;
Int32 myMotionClockBL;
// Indicates 'start' signal for each of the graphical objects as
// described in A. Towers TIA Hardware Notes
Int32 myStartP0;
Int32 myStartP1;
Int32 myStartM0;
Int32 myStartM1;
// Index into the player mask arrays indicating whether display
// of the first copy should be suppressed
uInt8 mySuppressP0;
uInt8 mySuppressP1;
// Latches for 'more motion required' as described in A. Towers TIA
// Hardware Notes
bool myHMP0mmr;
bool myHMP1mmr;
bool myHMM0mmr;
bool myHMM1mmr;
bool myHMBLmmr;
// Graphics for Player 0 that should be displayed. This will be
// reflected if the player is being reflected.
uInt8 myCurrentGRP0;
// Graphics for Player 1 that should be displayed. This will be
// reflected if the player is being reflected.
uInt8 myCurrentGRP1;
// It's VERY important that the BL, M0, M1, P0 and P1 current
// mask pointers are always on a uInt32 boundary. Otherwise,
// the TIA code will fail on a good number of CPUs.
const uInt8* myP0Mask;
const uInt8* myM0Mask;
const uInt8* myM1Mask;
const uInt8* myP1Mask;
const uInt8* myBLMask;
const uInt32* myPFMask;
// Audio values; only used by TIADebug
uInt8 myAUDV0, myAUDV1, myAUDC0, myAUDC1, myAUDF0, myAUDF1;
// Indicates when the dump for paddles was last set
Int32 myDumpDisabledCycle;
// Indicates if the dump is current enabled for the paddles
bool myDumpEnabled;
// Latches for INPT4 and INPT5
uInt8 myINPT4, myINPT5;
// Indicates if HMOVE blanks are currently or previously enabled,
// and at which horizontal position the HMOVE was initiated
Int32 myCurrentHMOVEPos;
Int32 myPreviousHMOVEPos;
bool myHMOVEBlankEnabled;
bool myAllowHMOVEBlanks;
// Indicates if unused TIA pins are randomly driven high or low
// Otherwise, they take on the value previously on the databus
bool myTIAPinsDriven;
// Bitmap of the objects that should be considered while drawing
uInt8 myEnabledObjects;
// Determines whether specified bits (from TIABit) are enabled or disabled
// This is and'ed with the enabled objects each scanline to mask out any
// objects we don't want to be processed
uInt8 myDisabledObjects;
// Indicates if color loss should be enabled or disabled. Color loss
// occurs on PAL (and maybe SECAM) systems when the previous frame
// contains an odd number of scanlines.
bool myColorLossEnabled;
// Indicates whether we're done with the current frame. poke() clears this
// when VSYNC is strobed or the max scanlines/frame limit is hit.
bool myPartialFrameFlag;
// Automatic framerate correction based on number of scanlines
bool myAutoFrameEnabled;
// Number of total frames displayed by this TIA
uInt32 myFrameCounter;
// Number of PAL frames displayed by this TIA
uInt32 myPALFrameCounter;
// The framerate currently in use by the Console
float myFramerate;
// Whether TIA bits/collisions are currently enabled/disabled
bool myBitsEnabled, myCollisionsEnabled;
private:
// Copy constructor and assignment operator not supported
TIA(const TIA&);
TIA& operator = (const TIA&);
};
#endif