Documentation ¶
Overview ¶
Package video implements pixel generation for the emulated TIA. Pixel generation is conceptually divided into six areas, implemented as types. These are:
Playfield Player 0 and Player 1 Missile 0 and Missile 1 Ball
Collectively we can refer to these as the playfield and sprites (even though the VCS sprites are nothing like what we now think of sprites, it is a useful appellation none-the-less).
The video subsystem is ticked along with the TIA every video cycle. The playfield is closely related to the TIA's HSYNC and is not ticked separately. The sprites are ticked depending on the state of the TIA's HBLANK signal; it also depends on whether HMOVE has been recently latched in the TIA and whether the sprite has completed any horizontal movement. For this reason the video sub-system and the sprites are initialised with references to the HBLANK signal and the HMOVE latch.
Reading of TIA memory is divided into six different Update*() functions. The timing of when TIA memory is read and registers updated is important and dividing the update functions in this manner helps. The TIA package handles these timings.
The three sprite categories, player, missile and ball, all have common features but have been implemented to be completely separate from one another. The exception is the enclockifier type used by both missiles and the ball.
All sprites keep track of their own phase clocks and position counters. Delayed side effects only occur when the sprite itself is ticked and so each sprite also has an instance of Ticker from the future package.
A significant difference to the description in Andrew Towers' document, "Atari 2600 TIA Hardware Notes", is how HMOVE counters are handled. In Towers' description of the hardware, the HMOVE latch, the counters and the signal line to the sprite are all intertwined. In the emulation this is almost turned inside out with the sprite maintaining its own counter and ticking (include HMOVE stuffing ticks) only when required.
Somewhere during the cycle the video sub-system will decide on what the pixel output should be (in this context we mean VCS clock-wide pixels). That is, we're deciding what the colour of all TV pixels for the duration of the video cycle should be.
The timing of this decision is critical: it must happen before some register updates but after others. Note that the pixel color decision is distinct from sending the color signal to the TV (which is handled by the TIA) package). Sending of the color signal always happens at the very end of the video cycle.
To effectively make the pixel color decision, the video sub-system at the same time processes the pixel "priority". For convenience, pixel collisions are also set at this time.
Index ¶
- Constants
- Variables
- type BallSprite
- type CollisionEvent
- type Collisions
- type Element
- type MissileSprite
- type PlayerSprite
- type Playfield
- func (pf *Playfield) Label() string
- func (pf *Playfield) Plumb(tia tia)
- func (pf *Playfield) SetCTRLPF(value uint8)
- func (pf *Playfield) SetPF0(v uint8)
- func (pf *Playfield) SetPF1(v uint8)
- func (pf *Playfield) SetPF2(v uint8)
- func (pf *Playfield) Snapshot() *Playfield
- func (pf *Playfield) String() string
- type ScreenRegion
- type TV
- type Video
- func (vd *Video) Pixel()
- func (vd *Video) Plumb(instance *instance.Instance, mem chipbus.Memory, tv TV, ...)
- func (vd *Video) PrepareSpritesForHMOVE()
- func (vd *Video) RSYNC(adjustment int)
- func (vd *Video) Snapshot() *Video
- func (vd *Video) Tick()
- func (vd *Video) UpdateCTRLPF()
- func (vd *Video) UpdateColor(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdateNUSIZ(num int, fromMissile bool)
- func (vd *Video) UpdatePlayfield(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdatePlayfieldColor(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdateSpriteHMOVE(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdateSpritePixels(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdateSpritePositioning(data chipbus.ChangedRegister) bool
- func (vd *Video) UpdateSpriteVariations(data chipbus.ChangedRegister) bool
Constants ¶
const ( CTRLPFPriorityMask = 0x04 CTRLPFScoremodeMask = 0x02 CTRLPFReflectedMask = 0x01 REFPxMask = 0x08 VDELPxMask = 0x01 RESMPxMask = 0x02 ENAxxMask = 0x02 HMxxMask = 0xf0 NUSIZxCopiesMask = 0x07 NUSIZxSizeMask = 0x03 )
Not all bits in TIA graphic registers are used. The following masks can be be used to keep only the relevant bits from the value that has been written to the register.
const RegionWidth = 20
the number of color clocks (playfield pixels) per left/right region.
Variables ¶
var BallSizes = []string{
"single size",
"double size",
"quad size",
"double-quad size",
}
BallSizes maps ball size values to descriptions of those sizes.
var MissileCopies = []string{
"one copy",
"two copies [close]",
"two copies [med]",
"three copies [close]",
"two copies [wide]",
"one copy",
"three copies [med]",
"one copy",
}
MissileCopies maps missile copies values to descriptions of those values.
var MissileSizes = []string{
"single width",
"double width",
"quadruple width",
"doubt-quad width",
}
MissileSizes maps missile sizes values to descriptions of those values.
var PlayerSizes = []string{
"one copy",
"two copies [close]",
"two copies [med]",
"three copies [close]",
"two copies [wide]",
"double size",
"three copies [med]",
"quad size",
}
PlayerSizes maps player size and copies values to descriptions of those values.
Functions ¶
This section is empty.
Types ¶
type BallSprite ¶ added in v0.7.1
type BallSprite struct { MoreHMOVE bool Hmove uint8 ResetPixel int HmovedPixel int // the ball color should match the color of the playfield foreground. // however, for debugging purposes it is sometimes useful to use different // colors, so this is not a pointer to playfield.ForegroundColor, as you // might expect Color uint8 // for convenience we store the raw CTRLPF register value and the // normalised size bits Ctrlpf uint8 Size uint8 VerticalDelay bool Enabled bool EnabledDelay bool // outputting of pixels is handled by the ball/missile enclockifier Enclockifier enclockifier // contains filtered or unexported fields }
BallSprite represents the moveable ball sprite in the VCS graphical display.
func (*BallSprite) Label ¶ added in v0.7.1
func (bs *BallSprite) Label() string
Label returns an appropriate name for the sprite.
func (*BallSprite) Plumb ¶ added in v0.7.1
func (bs *BallSprite) Plumb(tia tia)
Plumb changes into ball sprite.
func (*BallSprite) SetCTRLPF ¶ added in v0.7.1
func (bs *BallSprite) SetCTRLPF(value uint8)
func (*BallSprite) Snapshot ¶ added in v0.7.1
func (bs *BallSprite) Snapshot() *BallSprite
Snapshot creates a copy of the ball in its current state.
func (*BallSprite) String ¶ added in v0.7.1
func (bs *BallSprite) String() string
type CollisionEvent ¶ added in v0.8.0
type CollisionEvent uint16
CollisionEvent is an emulator specific value that records the collision events that occurred in the immediately preceding videocycle.
The VCS doesn't care about this and the collision registers instead record all collisions since the last CXCLR, which can be many hundreds of videocycles later. For debugging purposes however, it can be quite useful to know what collisions occurred on a single videocycle one.
The trick is to do this as efficiently as possible. Collision event is therefore a bitmask that is reset() every videocycle and the bit set for each collision that occurs during the collision tick().
It seems clumsy and it probably is, but it's the most efficient way I can think of right now. Certainly, it postpones the interpretation of the event in the form of a String() to when it is actually needed.
Note that multiple collisions can occur in a single videocycle. If this wasn't the case we could simplify the CollisionEvent type but as it is we need to cater for all circumstances.
func (CollisionEvent) IsCXCLR ¶ added in v0.8.0
func (col CollisionEvent) IsCXCLR() bool
IsCleared returns true if CollisionEvent is CXCLR.
func (CollisionEvent) IsNothing ¶ added in v0.8.0
func (col CollisionEvent) IsNothing() bool
IsNothing returns true if no new collision event occurred.
func (CollisionEvent) String ¶ added in v0.8.0
func (col CollisionEvent) String() string
String returns a string representation of a CollisionEvent.
type Collisions ¶ added in v0.2.1
type Collisions struct { // LastVideoCycle records the combination of collision bits for the most recent // video cycle. Facilitates production of string information. LastVideoCycle CollisionEvent // contains filtered or unexported fields }
Collisions represents the various collision registers in the VCS.
func (*Collisions) Clear ¶ added in v0.2.1
func (col *Collisions) Clear()
Clear all bits in the collision registers.
func (*Collisions) Plumb ¶ added in v0.7.1
func (col *Collisions) Plumb(mem chipbus.Memory)
Plumb a new ChipBus into the collision system.
func (*Collisions) Snapshot ¶ added in v0.7.1
func (col *Collisions) Snapshot() *Collisions
Snapshot creates a copy of the Collisions sub-system in its current state.
type Element ¶ added in v0.2.1
type Element int
Element is used to record from which video sub-system the pixel was generated, taking video priority into account.
type MissileSprite ¶ added in v0.7.1
type MissileSprite struct { MoreHMOVE bool Hmove uint8 ResetPixel int HmovedPixel int Color uint8 // equal to missile color Enabled bool ResetToPlayer bool // for convenience we split the NUSIZ register into size and copies Nusiz uint8 Size uint8 Copies uint8 // outputting of pixels is handled by the ball/missile enclockifier. // equivalent to the ScanCounter used by the player sprites Enclockifier enclockifier // contains filtered or unexported fields }
MissileSprite represents a moveable missile sprite in the VCS graphical display. The VCS has two missile sprites.
func (*MissileSprite) Label ¶ added in v0.7.1
func (ms *MissileSprite) Label() string
Label returns an appropriate name for the sprite.
func (*MissileSprite) Plumb ¶ added in v0.7.1
func (ms *MissileSprite) Plumb(tia tia)
Plumb changes into missile sprite.
func (*MissileSprite) SetNUSIZ ¶ added in v0.7.1
func (ms *MissileSprite) SetNUSIZ(value uint8)
SetNUSIZ is called when the NUSIZ register changes. It should also be used to set the NUSIZ value from a debugger for immediate effect.
func (*MissileSprite) Snapshot ¶ added in v0.7.1
func (ms *MissileSprite) Snapshot() *MissileSprite
Snapshot creates a copy of the missile in its current state.
func (*MissileSprite) String ¶ added in v0.7.1
func (ms *MissileSprite) String() string
type PlayerSprite ¶ added in v0.7.1
type PlayerSprite struct { // horizontal movement MoreHMOVE bool Hmove uint8 // the pixel at which the sprite was reset. in the case of the ball and // missile sprites the scan counter starts at the ResetPixel. for the // player sprite however, there is additional latching to consider. rather // than introducing an additional variable keeping track of the start // pixel, the ResetPixel is modified according to the player sprite's // current NUSIZ. ResetPixel int // the pixel at which the sprite was reset plus any HMOVE modification see // prepareForHMOVE() for a note on the presentation of HmovedPixel HmovedPixel int // player sprite attributes Color uint8 // equal to missile color Reflected bool VerticalDelay bool // which gfx register we use depends on the value of vertical delay GfxDataNew uint8 GfxDataOld uint8 // for convenience we store the raw NUSIZ value and the significant size // and copy bits Nusiz uint8 // the raw value from the NUSIZ register SizeAndCopies uint8 // just the three left-most bits // ScanCounter implements the "graphics scan counter" as described in // TIA_HW_Notes.txt: // // "The Player Graphics Scan Counters are 3-bit binary ripple counters // attached to the player objects, used to determine which pixel // of the player is currently being drawn by generating a 3-bit // source pixel address. These are the only binary ripple counters // in the TIA." // // equivalent to the Enclockifier used by the ball and missile sprites ScanCounter scanCounter // contains filtered or unexported fields }
PlayerSprite represents a moveable player sprite in the VCS graphical display. The VCS has two player sprites.
func (*PlayerSprite) Label ¶ added in v0.7.1
func (ps *PlayerSprite) Label() string
Label returns an appropriate name for the sprite.
func (*PlayerSprite) Plumb ¶ added in v0.7.1
func (ps *PlayerSprite) Plumb(tia tia)
Plumb changes into player sprite.
func (*PlayerSprite) SetNUSIZ ¶ added in v0.7.1
func (ps *PlayerSprite) SetNUSIZ(value uint8)
SetNUSIZ is called when the NUSIZ register changes, after a delay. It should also be used to set the NUSIZ value from a debugger for immediate effect. Setting the value directly will upset reset/hmove pixel information.
func (*PlayerSprite) SetVerticalDelay ¶ added in v0.7.1
func (ps *PlayerSprite) SetVerticalDelay(vdelay bool)
SetVerticalDelay bit also alters which gfx registers is being used. Debuggers should use this function to set the delay bit rather than setting it directly.
func (*PlayerSprite) Snapshot ¶ added in v0.7.1
func (ps *PlayerSprite) Snapshot() *PlayerSprite
Snapshot creates a copy of the player sprite in its current state.
func (*PlayerSprite) String ¶ added in v0.7.1
func (ps *PlayerSprite) String() string
type Playfield ¶ added in v0.7.1
type Playfield struct { // the color for the when playfield is on/off ForegroundColor uint8 BackgroundColor uint8 // RegularData and ReflectedData are updated on every call to the // SetPF*() functions // // Data is (re)pointed to either RegularData or ReflectedData whenever // SetPF*() is called and on the screen region boundaries. // // RegionLeft always uses RegularData and RegionRight uses either // RegularDat or ReflectedData depending on the state of the reflected bit // at either: // - the start of the region // - when PF bits are changed RegularData []bool ReflectedData []bool Data *[]bool // knowing what the left and right regions look like at any given time is // useful for debugging. for the emulation, the Data field is sufficient. LeftData *[]bool RightData *[]bool // the data field is a combination of three segments: PF0, pf1 and pf2. // these represent the three registers in VCS memory but we don't actually // use then, except in the String() functions PF0 uint8 PF1 uint8 PF2 uint8 // for convenience we store the raw CTRLPF register value and the // normalised control bits specific to the playfield Ctrlpf uint8 Reflected bool Priority bool Scoremode bool // Region keeps track of which part of the screen we're currently in Region ScreenRegion // Idx is the index into the data field - interpreted depending on // screenRegion and reflection settings Idx int // contains filtered or unexported fields }
Playfield represnets the static playfield and background, the non-sprite areas of the graphical display.
func (*Playfield) Plumb ¶ added in v0.7.1
func (pf *Playfield) Plumb(tia tia)
Plumb changes into playfield.
type ScreenRegion ¶
type ScreenRegion int
ScreenRegion notes which part of the screen is currently being drawn.
const ( RegionOffScreen ScreenRegion = iota RegionLeft RegionRight )
List of valid ScreenRegions.
type TV ¶ added in v0.16.0
type TV interface {
GetCoords() coords.TelevisionCoords
}
TV defines the television functions required by the Video type(s).
type Video ¶
type Video struct { // collision matrix Collisions *Collisions // playfield Playfield *Playfield // sprite objects Player0 *PlayerSprite Player1 *PlayerSprite Missile0 *MissileSprite Missile1 *MissileSprite Ball *BallSprite // LastElement records from which TIA video sub-system the most recent // pixel was generated, taking priority into account. see Pixel() function // for details LastElement Element // color of Video output PixelColor uint8 // contains filtered or unexported fields }
Video contains all the components of the video sub-system of the VCS TIA chip.
func NewVideo ¶
func NewVideo(instance *instance.Instance, mem chipbus.Memory, tv TV, pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter, hblank *bool, hmove *hmove.Hmove) *Video
NewVideo is the preferred method of initialisation for the Video sub-system.
The playfield type requires access access to the TIA's phaseclock and polyucounter and is used to decide which part of the playfield is to be drawn.
The sprites meanwhile require access to the television. This is for generating information about the sprites reset position - a debugging only requirement but of no performance related consequeunce.
The references to the TIA's HBLANK state and whether HMOVE is latched, are required to tune the delays experienced by the various sprite events (eg. reset position).
func (*Video) Pixel ¶
func (vd *Video) Pixel()
Pixel returns the color of the pixel at the current clock and also sets the collision registers. It will default to returning the background color if no sprite or playfield pixel is present.
func (*Video) Plumb ¶ added in v0.7.1
func (vd *Video) Plumb(instance *instance.Instance, mem chipbus.Memory, tv TV, pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter, hblank *bool, hmove *hmove.Hmove)
Plumb ChipBus into TIA/Video components. Update pointers that refer to parent TIA.
func (*Video) PrepareSpritesForHMOVE ¶
func (vd *Video) PrepareSpritesForHMOVE()
PrepareSpritesForHMOVE should be called whenever HMOVE is triggered.
func (*Video) RSYNC ¶
RSYNC adjusts the debugging information of the sprites when an RSYNC is triggered.
func (*Video) Snapshot ¶ added in v0.7.1
Snapshot creates a copy of the Video sub-system in its current state.
func (*Video) Tick ¶
func (vd *Video) Tick()
Tick moves all video elements forward one video cycle. This is the conceptual equivalent of the hardware MOTCK line.
func (*Video) UpdateCTRLPF ¶
func (vd *Video) UpdateCTRLPF()
UpdateCTRLPF should be called whenever any of the individual components of the CTRPF are altered. For example, if Playfield.Reflected is altered, then this function should be called so that the CTRLPF value is set to reflect the alteration.
This is only of use to debuggers. It's never required in normal operation of the emulator.
func (*Video) UpdateColor ¶
func (vd *Video) UpdateColor(data chipbus.ChangedRegister) bool
UpdateColor checks TIA memory for changes to color registers.
See UpdatePlayfieldColor() also.
Returns true if ChipData has *not* been serviced.
func (*Video) UpdateNUSIZ ¶
UpdateNUSIZ should be called whenever the player/missile size/copies information is altered. This function updates the NUSIZ value to reflect the changes whilst maintaining the other NUSIZ bits.
This is only of use to debuggers. It's never required in normal operation of the emulator.
func (*Video) UpdatePlayfield ¶
func (vd *Video) UpdatePlayfield(data chipbus.ChangedRegister) bool
UpdatePlayfield checks TIA memory for new playfield data. Note that CTRLPF is serviced in UpdateSpriteVariations().
Returns true if ChipData has *not* been serviced.
func (*Video) UpdatePlayfieldColor ¶ added in v0.10.1
func (vd *Video) UpdatePlayfieldColor(data chipbus.ChangedRegister) bool
UpdatePlayfieldColor checks TIA memory for changes to playfield color registers.
Separate from the UpdateColor() function because some TIA revisions (or sometimes for some other reason eg.RGB mod) are slower when updating the playfield color register than the other registers.
Returns true if ChipData has *not* been serviced.
func (*Video) UpdateSpriteHMOVE ¶
func (vd *Video) UpdateSpriteHMOVE(data chipbus.ChangedRegister) bool
UpdateSpriteHMOVE checks TIA memory for changes in sprite HMOVE settings.
Returns true if ChipData has *not* been serviced.
func (*Video) UpdateSpritePixels ¶
func (vd *Video) UpdateSpritePixels(data chipbus.ChangedRegister) bool
UpdateSpritePixels checks TIA memory for attribute changes that *must* occur after a call to Pixel().
Returns true if ChipData has *not* been serviced.
func (*Video) UpdateSpritePositioning ¶
func (vd *Video) UpdateSpritePositioning(data chipbus.ChangedRegister) bool
UpdateSpritePositioning checks TIA memory for strobing of reset registers.
Returns true if ChipData has *not* been serviced.
func (*Video) UpdateSpriteVariations ¶
func (vd *Video) UpdateSpriteVariations(data chipbus.ChangedRegister) bool
UpdateSpriteVariations checks TIA memory for writes to registers that affect how sprite pixels are output. Note that CTRLPF is serviced here rather than in UpdatePlayfield(), because it affects the ball sprite.
Returns true if ChipData has *not* been serviced.