video

package
v0.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2021 License: GPL-3.0, GPL-3.0 Imports: 9 Imported by: 0

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

View Source
const (
	CollisionMask       uint8 = 0xc0
	CollisionCXBLPFMask uint8 = 0x80
)

Collisions registers do not use all their bits only the top two bits, or in the case of CXBLPF the top bit only.

View Source
const RegionWidth = 20

the number of color clocks (playfield pixels) per left/right region.

Variables

View Source
var BallSizes = []string{
	"single size",
	"double size",
	"quad size",
	"double-quad size",
}

BallSizes maps ball size values to descriptions of those sizes.

View Source
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.

View Source
var MissileSizes = []string{
	"single width",
	"double width",
	"quadruple width",
	"doubt-quad width",
}

MissileSizes maps missile sizes values to descriptions of those values.

View Source
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(hblank *bool, hmoveLatch *bool)

Plumb a new ChipBus into the 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 {

	// top two bits are significant
	CXM0P  uint8
	CXM1P  uint8
	CXP0FB uint8
	CXP1FB uint8
	CXM0FB uint8
	CXM1FB uint8
	CXPPMM uint8

	// only top bit is significant
	CXBLPF uint8

	// 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 bus.ChipBus)

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.

const (
	ElementBackground Element = iota
	ElementBall
	ElementPlayfield
	ElementPlayer0
	ElementPlayer1
	ElementMissile0
	ElementMissile1
)

List of valid Element Signals.

func (*Element) String added in v0.2.1

func (e *Element) String() string

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(hblank *bool, hmoveLatch *bool)

Plumb a new ChipBus into the 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(hblank *bool, hmoveLatch *bool)

Plumb a new ChipBus into the 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) Label added in v0.7.1

func (pf *Playfield) Label() string

Label returns an appropriate name for playfield.

func (*Playfield) Plumb added in v0.7.1

func (pf *Playfield) Plumb(pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter)

Plumb a new ChipBus into the Playfield.

func (*Playfield) SetCTRLPF added in v0.7.1

func (pf *Playfield) SetCTRLPF(value uint8)

func (*Playfield) SetPF0 added in v0.7.1

func (pf *Playfield) SetPF0(v uint8)

SetPF0 sets the playfield PF0 bits.

func (*Playfield) SetPF1 added in v0.7.1

func (pf *Playfield) SetPF1(v uint8)

SetPF1 sets the playfield PF1 bits.

func (*Playfield) SetPF2 added in v0.7.1

func (pf *Playfield) SetPF2(v uint8)

SetPF2 sets the playfield PF2 bits.

func (*Playfield) Snapshot added in v0.7.1

func (pf *Playfield) Snapshot() *Playfield

Snapshot creates a copy of the Video Playfield in its current state.

func (*Playfield) String added in v0.7.1

func (pf *Playfield) String() string

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 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

	// optimisation flags
	OptReusePixel       bool
	OptNoCollisionCheck bool
	// contains filtered or unexported fields
}

Video contains all the components of the video sub-system of the VCS TIA chip.

func NewVideo

func NewVideo(mem bus.ChipBus, tv signal.TelevisionSprite, pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter, hblank *bool, hmoveLatch *bool) *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(mem bus.ChipBus, pclk *phaseclock.PhaseClock, hsync *polycounter.Polycounter, hblank *bool, hmoveLatch *bool)

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

func (vd *Video) RSYNC(adjustment int)

RSYNC adjusts the debugging information of the sprites when an RSYNC is triggered.

func (*Video) Snapshot added in v0.7.1

func (vd *Video) Snapshot() *Video

Snapshot creates a copy of the Video sub-system in its current state.

func (*Video) Tick

func (vd *Video) Tick(isHmove bool, hmoveCt uint8)

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 bus.ChipData) bool

UpdateColor checks TIA memory for changes to color registers.

Returns true if memory.ChipData has not been serviced.

func (*Video) UpdateNUSIZ

func (vd *Video) UpdateNUSIZ(num int, fromMissile bool)

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 bus.ChipData) 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) UpdateSpriteHMOVE

func (vd *Video) UpdateSpriteHMOVE(data bus.ChipData) 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 bus.ChipData) bool

UpdateSpritePixels checks TIA memory for attribute changes that *must* occur after a call to Pixel().

Returns true if memory.ChipData has not been serviced.

func (*Video) UpdateSpritePositioning

func (vd *Video) UpdateSpritePositioning(data bus.ChipData) bool

UpdateSpritePositioning checks TIA memory for strobing of reset registers.

Returns true if memory.ChipData has not been serviced.

func (*Video) UpdateSpriteVariations

func (vd *Video) UpdateSpriteVariations(data bus.ChipData) 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 memory.ChipData has not been serviced.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL