cloud-game/nes/ppu.go
2019-03-31 23:54:02 +08:00

740 lines
17 KiB
Go

package nes
import (
"encoding/gob"
"image"
)
type PPU struct {
Memory // memory interface
console *Console // reference to parent object
Cycle int // 0-340
ScanLine int // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
Frame uint64 // frame counter
// storage variables
paletteData [32]byte
nameTableData [2048]byte
oamData [256]byte
front *image.RGBA
back *image.RGBA
// PPU registers
v uint16 // current vram address (15 bit)
t uint16 // temporary vram address (15 bit)
x byte // fine x scroll (3 bit)
w byte // write toggle (1 bit)
f byte // even/odd frame flag (1 bit)
register byte
// NMI flags
nmiOccurred bool
nmiOutput bool
nmiPrevious bool
nmiDelay byte
// background temporary variables
nameTableByte byte
attributeTableByte byte
lowTileByte byte
highTileByte byte
tileData uint64
// sprite temporary variables
spriteCount int
spritePatterns [8]uint32
spritePositions [8]byte
spritePriorities [8]byte
spriteIndexes [8]byte
// $2000 PPUCTRL
flagNameTable byte // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
flagIncrement byte // 0: add 1; 1: add 32
flagSpriteTable byte // 0: $0000; 1: $1000; ignored in 8x16 mode
flagBackgroundTable byte // 0: $0000; 1: $1000
flagSpriteSize byte // 0: 8x8; 1: 8x16
flagMasterSlave byte // 0: read EXT; 1: write EXT
// $2001 PPUMASK
flagGrayscale byte // 0: color; 1: grayscale
flagShowLeftBackground byte // 0: hide; 1: show
flagShowLeftSprites byte // 0: hide; 1: show
flagShowBackground byte // 0: hide; 1: show
flagShowSprites byte // 0: hide; 1: show
flagRedTint byte // 0: normal; 1: emphasized
flagGreenTint byte // 0: normal; 1: emphasized
flagBlueTint byte // 0: normal; 1: emphasized
// $2002 PPUSTATUS
flagSpriteZeroHit byte
flagSpriteOverflow byte
// $2003 OAMADDR
oamAddress byte
// $2007 PPUDATA
bufferedData byte // for buffered reads
}
func NewPPU(console *Console) *PPU {
ppu := PPU{Memory: NewPPUMemory(console), console: console}
ppu.front = image.NewRGBA(image.Rect(0, 0, 256, 240))
ppu.back = image.NewRGBA(image.Rect(0, 0, 256, 240))
ppu.Reset()
return &ppu
}
func (ppu *PPU) Save(encoder *gob.Encoder) error {
encoder.Encode(ppu.Cycle)
encoder.Encode(ppu.ScanLine)
encoder.Encode(ppu.Frame)
encoder.Encode(ppu.paletteData)
encoder.Encode(ppu.nameTableData)
encoder.Encode(ppu.oamData)
encoder.Encode(ppu.v)
encoder.Encode(ppu.t)
encoder.Encode(ppu.x)
encoder.Encode(ppu.w)
encoder.Encode(ppu.f)
encoder.Encode(ppu.register)
encoder.Encode(ppu.nmiOccurred)
encoder.Encode(ppu.nmiOutput)
encoder.Encode(ppu.nmiPrevious)
encoder.Encode(ppu.nmiDelay)
encoder.Encode(ppu.nameTableByte)
encoder.Encode(ppu.attributeTableByte)
encoder.Encode(ppu.lowTileByte)
encoder.Encode(ppu.highTileByte)
encoder.Encode(ppu.tileData)
encoder.Encode(ppu.spriteCount)
encoder.Encode(ppu.spritePatterns)
encoder.Encode(ppu.spritePositions)
encoder.Encode(ppu.spritePriorities)
encoder.Encode(ppu.spriteIndexes)
encoder.Encode(ppu.flagNameTable)
encoder.Encode(ppu.flagIncrement)
encoder.Encode(ppu.flagSpriteTable)
encoder.Encode(ppu.flagBackgroundTable)
encoder.Encode(ppu.flagSpriteSize)
encoder.Encode(ppu.flagMasterSlave)
encoder.Encode(ppu.flagGrayscale)
encoder.Encode(ppu.flagShowLeftBackground)
encoder.Encode(ppu.flagShowLeftSprites)
encoder.Encode(ppu.flagShowBackground)
encoder.Encode(ppu.flagShowSprites)
encoder.Encode(ppu.flagRedTint)
encoder.Encode(ppu.flagGreenTint)
encoder.Encode(ppu.flagBlueTint)
encoder.Encode(ppu.flagSpriteZeroHit)
encoder.Encode(ppu.flagSpriteOverflow)
encoder.Encode(ppu.oamAddress)
encoder.Encode(ppu.bufferedData)
return nil
}
func (ppu *PPU) Load(decoder *gob.Decoder) error {
decoder.Decode(&ppu.Cycle)
decoder.Decode(&ppu.ScanLine)
decoder.Decode(&ppu.Frame)
decoder.Decode(&ppu.paletteData)
decoder.Decode(&ppu.nameTableData)
decoder.Decode(&ppu.oamData)
decoder.Decode(&ppu.v)
decoder.Decode(&ppu.t)
decoder.Decode(&ppu.x)
decoder.Decode(&ppu.w)
decoder.Decode(&ppu.f)
decoder.Decode(&ppu.register)
decoder.Decode(&ppu.nmiOccurred)
decoder.Decode(&ppu.nmiOutput)
decoder.Decode(&ppu.nmiPrevious)
decoder.Decode(&ppu.nmiDelay)
decoder.Decode(&ppu.nameTableByte)
decoder.Decode(&ppu.attributeTableByte)
decoder.Decode(&ppu.lowTileByte)
decoder.Decode(&ppu.highTileByte)
decoder.Decode(&ppu.tileData)
decoder.Decode(&ppu.spriteCount)
decoder.Decode(&ppu.spritePatterns)
decoder.Decode(&ppu.spritePositions)
decoder.Decode(&ppu.spritePriorities)
decoder.Decode(&ppu.spriteIndexes)
decoder.Decode(&ppu.flagNameTable)
decoder.Decode(&ppu.flagIncrement)
decoder.Decode(&ppu.flagSpriteTable)
decoder.Decode(&ppu.flagBackgroundTable)
decoder.Decode(&ppu.flagSpriteSize)
decoder.Decode(&ppu.flagMasterSlave)
decoder.Decode(&ppu.flagGrayscale)
decoder.Decode(&ppu.flagShowLeftBackground)
decoder.Decode(&ppu.flagShowLeftSprites)
decoder.Decode(&ppu.flagShowBackground)
decoder.Decode(&ppu.flagShowSprites)
decoder.Decode(&ppu.flagRedTint)
decoder.Decode(&ppu.flagGreenTint)
decoder.Decode(&ppu.flagBlueTint)
decoder.Decode(&ppu.flagSpriteZeroHit)
decoder.Decode(&ppu.flagSpriteOverflow)
decoder.Decode(&ppu.oamAddress)
decoder.Decode(&ppu.bufferedData)
return nil
}
func (ppu *PPU) Reset() {
ppu.Cycle = 340
ppu.ScanLine = 240
ppu.Frame = 0
ppu.writeControl(0)
ppu.writeMask(0)
ppu.writeOAMAddress(0)
}
func (ppu *PPU) readPalette(address uint16) byte {
if address >= 16 && address%4 == 0 {
address -= 16
}
return ppu.paletteData[address]
}
func (ppu *PPU) writePalette(address uint16, value byte) {
if address >= 16 && address%4 == 0 {
address -= 16
}
ppu.paletteData[address] = value
}
func (ppu *PPU) readRegister(address uint16) byte {
switch address {
case 0x2002:
return ppu.readStatus()
case 0x2004:
return ppu.readOAMData()
case 0x2007:
return ppu.readData()
}
return 0
}
func (ppu *PPU) writeRegister(address uint16, value byte) {
ppu.register = value
switch address {
case 0x2000:
ppu.writeControl(value)
case 0x2001:
ppu.writeMask(value)
case 0x2003:
ppu.writeOAMAddress(value)
case 0x2004:
ppu.writeOAMData(value)
case 0x2005:
ppu.writeScroll(value)
case 0x2006:
ppu.writeAddress(value)
case 0x2007:
ppu.writeData(value)
case 0x4014:
ppu.writeDMA(value)
}
}
// $2000: PPUCTRL
func (ppu *PPU) writeControl(value byte) {
ppu.flagNameTable = (value >> 0) & 3
ppu.flagIncrement = (value >> 2) & 1
ppu.flagSpriteTable = (value >> 3) & 1
ppu.flagBackgroundTable = (value >> 4) & 1
ppu.flagSpriteSize = (value >> 5) & 1
ppu.flagMasterSlave = (value >> 6) & 1
ppu.nmiOutput = (value>>7)&1 == 1
ppu.nmiChange()
// t: ....BA.. ........ = d: ......BA
ppu.t = (ppu.t & 0xF3FF) | ((uint16(value) & 0x03) << 10)
}
// $2001: PPUMASK
func (ppu *PPU) writeMask(value byte) {
ppu.flagGrayscale = (value >> 0) & 1
ppu.flagShowLeftBackground = (value >> 1) & 1
ppu.flagShowLeftSprites = (value >> 2) & 1
ppu.flagShowBackground = (value >> 3) & 1
ppu.flagShowSprites = (value >> 4) & 1
ppu.flagRedTint = (value >> 5) & 1
ppu.flagGreenTint = (value >> 6) & 1
ppu.flagBlueTint = (value >> 7) & 1
}
// $2002: PPUSTATUS
func (ppu *PPU) readStatus() byte {
result := ppu.register & 0x1F
result |= ppu.flagSpriteOverflow << 5
result |= ppu.flagSpriteZeroHit << 6
if ppu.nmiOccurred {
result |= 1 << 7
}
ppu.nmiOccurred = false
ppu.nmiChange()
// w: = 0
ppu.w = 0
return result
}
// $2003: OAMADDR
func (ppu *PPU) writeOAMAddress(value byte) {
ppu.oamAddress = value
}
// $2004: OAMDATA (read)
func (ppu *PPU) readOAMData() byte {
return ppu.oamData[ppu.oamAddress]
}
// $2004: OAMDATA (write)
func (ppu *PPU) writeOAMData(value byte) {
ppu.oamData[ppu.oamAddress] = value
ppu.oamAddress++
}
// $2005: PPUSCROLL
func (ppu *PPU) writeScroll(value byte) {
if ppu.w == 0 {
// t: ........ ...HGFED = d: HGFED...
// x: CBA = d: .....CBA
// w: = 1
ppu.t = (ppu.t & 0xFFE0) | (uint16(value) >> 3)
ppu.x = value & 0x07
ppu.w = 1
} else {
// t: .CBA..HG FED..... = d: HGFEDCBA
// w: = 0
ppu.t = (ppu.t & 0x8FFF) | ((uint16(value) & 0x07) << 12)
ppu.t = (ppu.t & 0xFC1F) | ((uint16(value) & 0xF8) << 2)
ppu.w = 0
}
}
// $2006: PPUADDR
func (ppu *PPU) writeAddress(value byte) {
if ppu.w == 0 {
// t: ..FEDCBA ........ = d: ..FEDCBA
// t: .X...... ........ = 0
// w: = 1
ppu.t = (ppu.t & 0x80FF) | ((uint16(value) & 0x3F) << 8)
ppu.w = 1
} else {
// t: ........ HGFEDCBA = d: HGFEDCBA
// v = t
// w: = 0
ppu.t = (ppu.t & 0xFF00) | uint16(value)
ppu.v = ppu.t
ppu.w = 0
}
}
// $2007: PPUDATA (read)
func (ppu *PPU) readData() byte {
value := ppu.Read(ppu.v)
// emulate buffered reads
if ppu.v%0x4000 < 0x3F00 {
buffered := ppu.bufferedData
ppu.bufferedData = value
value = buffered
} else {
ppu.bufferedData = ppu.Read(ppu.v - 0x1000)
}
// increment address
if ppu.flagIncrement == 0 {
ppu.v += 1
} else {
ppu.v += 32
}
return value
}
// $2007: PPUDATA (write)
func (ppu *PPU) writeData(value byte) {
ppu.Write(ppu.v, value)
if ppu.flagIncrement == 0 {
ppu.v += 1
} else {
ppu.v += 32
}
}
// $4014: OAMDMA
func (ppu *PPU) writeDMA(value byte) {
cpu := ppu.console.CPU
address := uint16(value) << 8
for i := 0; i < 256; i++ {
ppu.oamData[ppu.oamAddress] = cpu.Read(address)
ppu.oamAddress++
address++
}
cpu.stall += 513
if cpu.Cycles%2 == 1 {
cpu.stall++
}
}
// NTSC Timing Helper Functions
func (ppu *PPU) incrementX() {
// increment hori(v)
// if coarse X == 31
if ppu.v&0x001F == 31 {
// coarse X = 0
ppu.v &= 0xFFE0
// switch horizontal nametable
ppu.v ^= 0x0400
} else {
// increment coarse X
ppu.v++
}
}
func (ppu *PPU) incrementY() {
// increment vert(v)
// if fine Y < 7
if ppu.v&0x7000 != 0x7000 {
// increment fine Y
ppu.v += 0x1000
} else {
// fine Y = 0
ppu.v &= 0x8FFF
// let y = coarse Y
y := (ppu.v & 0x03E0) >> 5
if y == 29 {
// coarse Y = 0
y = 0
// switch vertical nametable
ppu.v ^= 0x0800
} else if y == 31 {
// coarse Y = 0, nametable not switched
y = 0
} else {
// increment coarse Y
y++
}
// put coarse Y back into v
ppu.v = (ppu.v & 0xFC1F) | (y << 5)
}
}
func (ppu *PPU) copyX() {
// hori(v) = hori(t)
// v: .....F.. ...EDCBA = t: .....F.. ...EDCBA
ppu.v = (ppu.v & 0xFBE0) | (ppu.t & 0x041F)
}
func (ppu *PPU) copyY() {
// vert(v) = vert(t)
// v: .IHGF.ED CBA..... = t: .IHGF.ED CBA.....
ppu.v = (ppu.v & 0x841F) | (ppu.t & 0x7BE0)
}
func (ppu *PPU) nmiChange() {
nmi := ppu.nmiOutput && ppu.nmiOccurred
if nmi && !ppu.nmiPrevious {
// TODO: this fixes some games but the delay shouldn't have to be so
// long, so the timings are off somewhere
ppu.nmiDelay = 15
}
ppu.nmiPrevious = nmi
}
func (ppu *PPU) setVerticalBlank() {
ppu.front, ppu.back = ppu.back, ppu.front
ppu.nmiOccurred = true
ppu.nmiChange()
}
func (ppu *PPU) clearVerticalBlank() {
ppu.nmiOccurred = false
ppu.nmiChange()
}
func (ppu *PPU) fetchNameTableByte() {
v := ppu.v
address := 0x2000 | (v & 0x0FFF)
ppu.nameTableByte = ppu.Read(address)
}
func (ppu *PPU) fetchAttributeTableByte() {
v := ppu.v
address := 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)
shift := ((v >> 4) & 4) | (v & 2)
ppu.attributeTableByte = ((ppu.Read(address) >> shift) & 3) << 2
}
func (ppu *PPU) fetchLowTileByte() {
fineY := (ppu.v >> 12) & 7
table := ppu.flagBackgroundTable
tile := ppu.nameTableByte
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
ppu.lowTileByte = ppu.Read(address)
}
func (ppu *PPU) fetchHighTileByte() {
fineY := (ppu.v >> 12) & 7
table := ppu.flagBackgroundTable
tile := ppu.nameTableByte
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
ppu.highTileByte = ppu.Read(address + 8)
}
func (ppu *PPU) storeTileData() {
var data uint32
for i := 0; i < 8; i++ {
a := ppu.attributeTableByte
p1 := (ppu.lowTileByte & 0x80) >> 7
p2 := (ppu.highTileByte & 0x80) >> 6
ppu.lowTileByte <<= 1
ppu.highTileByte <<= 1
data <<= 4
data |= uint32(a | p1 | p2)
}
ppu.tileData |= uint64(data)
}
func (ppu *PPU) fetchTileData() uint32 {
return uint32(ppu.tileData >> 32)
}
func (ppu *PPU) backgroundPixel() byte {
if ppu.flagShowBackground == 0 {
return 0
}
data := ppu.fetchTileData() >> ((7 - ppu.x) * 4)
return byte(data & 0x0F)
}
func (ppu *PPU) spritePixel() (byte, byte) {
if ppu.flagShowSprites == 0 {
return 0, 0
}
for i := 0; i < ppu.spriteCount; i++ {
offset := (ppu.Cycle - 1) - int(ppu.spritePositions[i])
if offset < 0 || offset > 7 {
continue
}
offset = 7 - offset
color := byte((ppu.spritePatterns[i] >> byte(offset*4)) & 0x0F)
if color%4 == 0 {
continue
}
return byte(i), color
}
return 0, 0
}
func (ppu *PPU) renderPixel() {
x := ppu.Cycle - 1
y := ppu.ScanLine
background := ppu.backgroundPixel()
i, sprite := ppu.spritePixel()
if x < 8 && ppu.flagShowLeftBackground == 0 {
background = 0
}
if x < 8 && ppu.flagShowLeftSprites == 0 {
sprite = 0
}
b := background%4 != 0
s := sprite%4 != 0
var color byte
if !b && !s {
color = 0
} else if !b && s {
color = sprite | 0x10
} else if b && !s {
color = background
} else {
if ppu.spriteIndexes[i] == 0 && x < 255 {
ppu.flagSpriteZeroHit = 1
}
if ppu.spritePriorities[i] == 0 {
color = sprite | 0x10
} else {
color = background
}
}
c := Palette[ppu.readPalette(uint16(color))%64]
ppu.back.SetRGBA(x, y, c)
}
func (ppu *PPU) fetchSpritePattern(i, row int) uint32 {
tile := ppu.oamData[i*4+1]
attributes := ppu.oamData[i*4+2]
var address uint16
if ppu.flagSpriteSize == 0 {
if attributes&0x80 == 0x80 {
row = 7 - row
}
table := ppu.flagSpriteTable
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
} else {
if attributes&0x80 == 0x80 {
row = 15 - row
}
table := tile & 1
tile &= 0xFE
if row > 7 {
tile++
row -= 8
}
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
}
a := (attributes & 3) << 2
lowTileByte := ppu.Read(address)
highTileByte := ppu.Read(address + 8)
var data uint32
for i := 0; i < 8; i++ {
var p1, p2 byte
if attributes&0x40 == 0x40 {
p1 = (lowTileByte & 1) << 0
p2 = (highTileByte & 1) << 1
lowTileByte >>= 1
highTileByte >>= 1
} else {
p1 = (lowTileByte & 0x80) >> 7
p2 = (highTileByte & 0x80) >> 6
lowTileByte <<= 1
highTileByte <<= 1
}
data <<= 4
data |= uint32(a | p1 | p2)
}
return data
}
func (ppu *PPU) evaluateSprites() {
var h int
if ppu.flagSpriteSize == 0 {
h = 8
} else {
h = 16
}
count := 0
for i := 0; i < 64; i++ {
y := ppu.oamData[i*4+0]
a := ppu.oamData[i*4+2]
x := ppu.oamData[i*4+3]
row := ppu.ScanLine - int(y)
if row < 0 || row >= h {
continue
}
if count < 8 {
ppu.spritePatterns[count] = ppu.fetchSpritePattern(i, row)
ppu.spritePositions[count] = x
ppu.spritePriorities[count] = (a >> 5) & 1
ppu.spriteIndexes[count] = byte(i)
}
count++
}
if count > 8 {
count = 8
ppu.flagSpriteOverflow = 1
}
ppu.spriteCount = count
}
// tick updates Cycle, ScanLine and Frame counters
func (ppu *PPU) tick() {
if ppu.nmiDelay > 0 {
ppu.nmiDelay--
if ppu.nmiDelay == 0 && ppu.nmiOutput && ppu.nmiOccurred {
ppu.console.CPU.triggerNMI()
}
}
if ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0 {
if ppu.f == 1 && ppu.ScanLine == 261 && ppu.Cycle == 339 {
ppu.Cycle = 0
ppu.ScanLine = 0
ppu.Frame++
ppu.f ^= 1
return
}
}
ppu.Cycle++
if ppu.Cycle > 340 {
ppu.Cycle = 0
ppu.ScanLine++
if ppu.ScanLine > 261 {
ppu.ScanLine = 0
ppu.Frame++
ppu.f ^= 1
}
}
}
// Step executes a single PPU cycle
func (ppu *PPU) Step() {
ppu.tick()
renderingEnabled := ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0
preLine := ppu.ScanLine == 261
visibleLine := ppu.ScanLine < 240
// postLine := ppu.ScanLine == 240
renderLine := preLine || visibleLine
preFetchCycle := ppu.Cycle >= 321 && ppu.Cycle <= 336
visibleCycle := ppu.Cycle >= 1 && ppu.Cycle <= 256
fetchCycle := preFetchCycle || visibleCycle
// background logic
if renderingEnabled {
if visibleLine && visibleCycle {
ppu.renderPixel()
}
if renderLine && fetchCycle {
ppu.tileData <<= 4
switch ppu.Cycle % 8 {
case 1:
ppu.fetchNameTableByte()
case 3:
ppu.fetchAttributeTableByte()
case 5:
ppu.fetchLowTileByte()
case 7:
ppu.fetchHighTileByte()
case 0:
ppu.storeTileData()
}
}
if preLine && ppu.Cycle >= 280 && ppu.Cycle <= 304 {
ppu.copyY()
}
if renderLine {
if fetchCycle && ppu.Cycle%8 == 0 {
ppu.incrementX()
}
if ppu.Cycle == 256 {
ppu.incrementY()
}
if ppu.Cycle == 257 {
ppu.copyX()
}
}
}
// sprite logic
if renderingEnabled {
if ppu.Cycle == 257 {
if visibleLine {
ppu.evaluateSprites()
} else {
ppu.spriteCount = 0
}
}
}
// vblank logic
if ppu.ScanLine == 241 && ppu.Cycle == 1 {
ppu.setVerticalBlank()
}
if preLine && ppu.Cycle == 1 {
ppu.clearVerticalBlank()
ppu.flagSpriteZeroHit = 0
ppu.flagSpriteOverflow = 0
}
}