Created
September 9, 2012 21:58
-
-
Save scottferg/3687553 to your computer and use it in GitHub Desktop.
Fubar PPU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"math" | |
) | |
const ( | |
StatusSpriteOverflow = iota | |
StatusSprite0Hit | |
StatusVblankStarted | |
MirroringVertical | |
MirroringHorizontal | |
MirroringSingleLower | |
MirroringSingleUpper | |
) | |
type SpriteData struct { | |
Tiles [256]Word | |
YCoordinates [256]Word | |
Attributes [256]Word | |
XCoordinates [256]Word | |
} | |
type Flags struct { | |
BaseNametableAddress Word | |
VramAddressInc Word | |
SpritePatternAddress Word | |
BackgroundPatternAddress Word | |
SpriteSize Word | |
MasterSlaveSel Word | |
NmiOnVblank Word | |
} | |
type Masks struct { | |
Grayscale bool | |
ShowBackgroundOnLeft bool | |
ShowSpritesOnLeft bool | |
ShowBackground bool | |
ShowSprites bool | |
IntensifyReds bool | |
IntensifyGreens bool | |
IntensifyBlues bool | |
} | |
type Registers struct { | |
Control Word | |
Mask Word | |
Status Word | |
OamAddress int | |
OamData Word | |
LoopyV int | |
LoopyT int | |
FineX int | |
Data Word | |
FirstWrite bool | |
} | |
type Ppu struct { | |
Registers | |
Flags | |
Masks | |
SpriteData | |
Vram [0xFFFF]Word | |
SpriteRam [0x100]Word | |
PaletteRam [0x20]Word | |
AttributeLocation [0x400]uint | |
AttributeShift [0x400]uint | |
Mirroring int | |
Framebuffer []int | |
Output chan []int | |
Cycle int | |
Scanline int | |
FirstLine int | |
} | |
func (p *Ppu) Init() chan []int { | |
p.FirstLine = 0 | |
p.FirstWrite = true | |
p.Output = make(chan []int) | |
p.Cycle = 0 | |
p.Scanline = -1 | |
for i, _ := range p.Vram { | |
p.Vram[i] = 0x00 | |
} | |
for i, _ := range p.SpriteRam { | |
p.SpriteRam[i] = 0x00 | |
} | |
for i, _ := range p.AttributeShift { | |
x := uint(i) | |
p.AttributeShift[i] = ((x >> 4) & 0x04) | (x & 0x02) | |
p.AttributeLocation[i] = ((x >> 2) & 0x07) | (((x >> 4) & 0x38) | 0x3C0) | |
} | |
p.Framebuffer = make([]int, 0xF000) | |
return p.Output | |
} | |
func (p *Ppu) writeNametableData(a int, v Word) { | |
switch p.Mirroring { | |
case MirroringVertical: | |
if a >= 0x2000 && a < 0x2400 { | |
p.Vram[a] = v | |
p.Vram[0x2800+(a-0x2000)] = v | |
} else if a >= 0x2800 && a < 0x2C00 { | |
p.Vram[a] = v | |
p.Vram[0x2000+(a-0x2800)] = v | |
} else if a >= 0x2400 && a < 0x2800 { | |
p.Vram[a] = v | |
p.Vram[0x2C00+(a-0x2400)] = v | |
} else if a >= 0x2C00 && a < 0x3000 { | |
p.Vram[a] = v | |
p.Vram[0x2400+(a-0x2C00)] = v | |
} | |
case MirroringHorizontal: | |
if a >= 0x2000 && a < 0x2400 { | |
p.Vram[a] = v | |
p.Vram[0x2400+(a-0x2000)] = v | |
} else if a >= 0x2400 && a < 0x2800 { | |
p.Vram[a] = v | |
p.Vram[0x2000+(a-0x2400)] = v | |
} else if a >= 0x2800 && a < 0x2C00 { | |
p.Vram[a] = v | |
p.Vram[0x2C00+(a-0x2800)] = v | |
} else if a >= 0x2C00 && a < 0x3000 { | |
p.Vram[a] = v | |
p.Vram[0x2800+(a-0x2C00)] = v | |
} | |
} | |
} | |
// Writes to mirrored regions of VRAM | |
func (p *Ppu) writeMirroredVram(a int, v Word) { | |
if a >= 0x3F00 && a < 0x3F20 { | |
// Palette table entries | |
p.PaletteRam[a-0x3F00] = v | |
} else if a >= 0x3F20 && a < 0x3F40 { | |
// Palette table entries | |
p.PaletteRam[a-0x3F20] = v | |
} else if a >= 0x3F40 && a < 0x3F80 { | |
// Palette table entries | |
p.PaletteRam[a-0x3F40] = v | |
} else if a >= 0x3F80 && a < 0x3FC0 { | |
// Palette table entries | |
p.PaletteRam[a-0x3F80] = v | |
} else { | |
p.Vram[a-0x1000] = v | |
} | |
} | |
func (p *Ppu) Step() { | |
switch { | |
case p.Scanline == 240: | |
// fmt.Println("Scanline 240") | |
// We're in VBlank | |
p.setStatus(StatusVblankStarted) | |
// Request NMI | |
cpu.RequestInterrupt(InterruptNmi) | |
// p.renderNametable(p.BaseNametableAddress) | |
p.renderSprites() | |
p.Output <- p.Framebuffer | |
p.Cycle = 0 | |
p.Scanline++ | |
case p.Scanline == 261: | |
// End of vblank | |
// fmt.Println("Scanline 261") | |
p.Scanline = -1 | |
p.Cycle = 0 | |
case p.Scanline < 240 && p.Scanline > 0: | |
// Render 1 row of 8x8 tiles | |
if p.Cycle == 341 { | |
// fmt.Println("End scanline") | |
p.Cycle = 0 | |
if p.Scanline%8 == 0 { | |
p.renderTileRow() | |
} | |
p.Scanline++ | |
} | |
// fmt.Println("Scanline 240") | |
case p.Scanline == -1: | |
// fmt.Println("Scanline -1") | |
if p.Cycle == 304 { | |
// Copy scroll latch into VRAMADDR register | |
p.LoopyV = p.LoopyT | |
} | |
} | |
if p.Cycle == 341 { | |
p.Cycle = 0 | |
p.Scanline++ | |
} | |
p.Cycle++ | |
} | |
// $2000 | |
func (p *Ppu) WriteControl(v Word) { | |
p.Control = v | |
// Control flag | |
// 7654 3210 | |
// |||| |||| | |
// |||| ||++- Base nametable address | |
// |||| || (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) | |
// |||| |+--- VRAM address increment per CPU read/write of PPUDATA | |
// |||| | (0: increment by 1, going across; 1: increment by 32, going down) | |
// |||| +---- Sprite pattern table address for 8x8 sprites | |
// |||| (0: $0000; 1: $1000; ignored in 8x16 mode) | |
// |||+------ Background pattern table address (0: $0000; 1: $1000) | |
// ||+------- Sprite size (0: 8x8; 1: 8x16) | |
// |+-------- PPU master/slave select (has no effect on the NES) | |
// +--------- Generate an NMI at the start of the | |
// vertical blanking interval (0: off; 1: on) | |
p.BaseNametableAddress = v & 0x03 | |
p.VramAddressInc = (v >> 2) & 0x01 | |
p.SpritePatternAddress = (v >> 3) & 0x01 | |
p.BackgroundPatternAddress = (v >> 4) & 0x01 | |
p.SpriteSize = (v >> 5) & 0x01 | |
p.NmiOnVblank = (v >> 7) & 0x01 | |
p.LoopyT = p.LoopyT & 0x73FF | |
p.LoopyT = p.LoopyT | ((int(v) & 0x03) << 10) | |
} | |
// $2001 | |
func (p *Ppu) WriteMask(v Word) { | |
p.Mask = v | |
// 76543210 | |
// |||||||| | |
// |||||||+- Grayscale (0: normal color; 1: produce a monochrome display) | |
// ||||||+-- 1: Show background in leftmost 8 pixels of screen; 0: Hide | |
// |||||+--- 1: Show sprites in leftmost 8 pixels of screen; 0: Hide | |
// ||||+---- 1: Show background | |
// |||+----- 1: Show sprites | |
// ||+------ Intensify reds (and darken other colors) | |
// |+------- Intensify greens (and darken other colors) | |
// +-------- Intensify blues (and darken other colors) | |
p.Grayscale = (v&0x01 == 0x01) | |
p.ShowBackgroundOnLeft = (((v >> 1) & 0x01) == 0x01) | |
p.ShowSpritesOnLeft = (((v >> 2) & 0x01) == 0x01) | |
p.ShowBackground = (((v >> 3) & 0x01) == 0x01) | |
p.ShowSprites = (((v >> 4) & 0x01) == 0x01) | |
p.IntensifyReds = (((v >> 5) & 0x01) == 0x01) | |
p.IntensifyGreens = (((v >> 6) & 0x01) == 0x01) | |
p.IntensifyBlues = (((v >> 7) & 0x01) == 0x01) | |
} | |
func (p *Ppu) clearStatus(s Word) { | |
current := Ram[0x2002] | |
switch s { | |
case StatusSpriteOverflow: | |
current = current & 0xDF | |
case StatusSprite0Hit: | |
current = current & 0xBF | |
case StatusVblankStarted: | |
current = current & 0x7F | |
} | |
Ram[0x2002] = current | |
} | |
func (p *Ppu) setStatus(s Word) { | |
current := Ram[0x2002] | |
switch s { | |
case StatusSpriteOverflow: | |
current = current | 0x20 | |
case StatusSprite0Hit: | |
current = current | 0x40 | |
case StatusVblankStarted: | |
current = current | 0x80 | |
} | |
Ram[0x2002] = current | |
} | |
// $2002 | |
func (p *Ppu) ReadStatus() (s Word, e error) { | |
p.FirstWrite = true | |
s = Ram[0x2002] | |
// Clear VBlank flag | |
p.clearStatus(StatusVblankStarted) | |
return | |
} | |
// $2003 | |
func (p *Ppu) WriteOamAddress(v Word) { | |
p.OamAddress = int(v) | |
} | |
// $2004 | |
func (p *Ppu) WriteOamData(v Word) { | |
p.OamData = v | |
} | |
// $4014 | |
func (p *Ppu) WriteDma(v Word) { | |
// Halt the CPU for 512 cycles | |
cpu.CyclesToWait = 512 | |
// Fill sprite RAM | |
addr := int(v) * 0x100 | |
for i := 0; i < 256; i++ { | |
d, _ := Ram.Read(addr + i) | |
p.SpriteRam[i] = d | |
p.updateBufferedSpriteMem(i, d) | |
} | |
} | |
func (p *Ppu) updateBufferedSpriteMem(a int, v Word) { | |
i := int(math.Floor(float64(a / 4))) | |
switch a % 4 { | |
case 0x0: | |
p.YCoordinates[i] = v | |
case 0x1: | |
p.Tiles[i] = v | |
case 0x2: | |
// Attribute | |
p.Attributes[i] = v | |
case 0x3: | |
p.XCoordinates[i] = v | |
} | |
} | |
// $2004 | |
func (p *Ppu) ReadOamData() (Word, error) { | |
return Ram[0x2004], nil | |
} | |
// $2005 | |
func (p *Ppu) WriteScroll(v Word) { | |
if p.FirstWrite { | |
p.LoopyT = p.LoopyT & 0x7FE0 | |
p.LoopyT = p.LoopyT | ((int(v) & 0xF8) >> 3) | |
p.FineX = int(v) & 0x07 | |
} else { | |
p.LoopyT = p.LoopyT & 0xC1F | |
p.LoopyT = p.LoopyT | (((int(v) & 0xF8) << 2) | ((int(v) & 0x07) << 12)) | |
} | |
p.FirstWrite = !p.FirstWrite | |
} | |
// $2006 | |
func (p *Ppu) WriteAddress(v Word) { | |
if p.FirstWrite { | |
p.LoopyT = p.LoopyT & 0xFF | |
p.LoopyT = p.LoopyT | ((int(v) & 0x3F) << 8) | |
} else { | |
p.LoopyT = p.LoopyT & 0x7F00 | |
p.LoopyT = p.LoopyT | int(v) | |
p.LoopyV = p.LoopyT | |
} | |
p.FirstWrite = !p.FirstWrite | |
} | |
// $2007 | |
func (p *Ppu) WriteData(v Word) { | |
if p.LoopyV > 0x3000 { | |
p.writeMirroredVram(p.LoopyV, v) | |
} else if p.LoopyV >= 0x2000 && p.LoopyV < 0x3000 { | |
// Nametable mirroring | |
p.writeNametableData(p.LoopyV, v) | |
} else { | |
p.Vram[p.LoopyV] = v | |
} | |
switch p.VramAddressInc { | |
case 0x01: | |
p.LoopyV = p.LoopyV + 0x20 | |
default: | |
p.LoopyV = p.LoopyV + 0x01 | |
} | |
} | |
// $2007 | |
func (p *Ppu) ReadData() (Word, error) { | |
return Ram[0x2007], nil | |
} | |
func (p *Ppu) sprPatternTableAddress(i int) int { | |
if p.SpriteSize&0x01 != 0x0 { | |
// 8x16 Sprites | |
var bank int | |
if i&0x01 != 0 { | |
bank = 0x1000 | |
} else { | |
bank = 0x0000 | |
} | |
return bank + ((int(i) >> 1) * 0x20) | |
} | |
// 8x8 Sprites | |
var a int | |
if p.SpritePatternAddress == 0x01 { | |
a = 0x1000 | |
} else { | |
a = 0x0 | |
} | |
return int(i)*0x10 + a | |
} | |
func (p *Ppu) bgPatternTableAddress(i Word) int { | |
var a int | |
if p.BackgroundPatternAddress == 0x01 { | |
a = 0x1000 | |
} else { | |
a = 0x0 | |
} | |
return int(i)*0x10 + a | |
} | |
func (p *Ppu) getNametableAddress(t Word) (a int) { | |
switch t { | |
case 0: | |
a = 0x2000 | |
case 1: | |
a = 0x2800 | |
case 2: | |
a = 0x2400 | |
case 3: | |
a = 0x2c00 | |
} | |
return | |
} | |
func (p *Ppu) renderNametable(table Word) { | |
a := p.getNametableAddress(table) | |
x := 0 | |
y := 0 | |
// Generates each tile and applies the palette | |
for i := a; i < a+0x3C0; i++ { | |
attrAddr := 0x23C0 | (p.LoopyV & 0xC00) | |
shift := p.AttributeShift[p.LoopyV&0x3FF] | |
attr := p.Vram[attrAddr+((i&0x1F)>>2)+((i&0x3E0)>>7)*8] | |
attr = (attr >> shift) & 0x03 | |
t := p.bgPatternTableAddress(p.Vram[i]) | |
p.decodePatternTile(t, x, y, p.bgPaletteEntry(attr), nil) | |
x += 8 | |
if x > 255 { | |
x = 0 | |
y += 8 | |
} | |
} | |
} | |
func (p *Ppu) renderTileRow() { | |
// Generates each tile and applies the palette | |
for x := 0; x < 32; x++ { | |
// for i := a; i < a+0x20; i++ { | |
attrAddr := 0x23C0 | (p.LoopyV & 0xC00) | |
shift := p.AttributeShift[p.LoopyV&0x3FF] | |
attr := p.Vram[attrAddr+((x&0x1F)>>2)+((x&0x3E0)>>7)*8] | |
attr = (attr >> shift) & 0x03 | |
t := p.bgPatternTableAddress(p.Vram[p.LoopyV+0x2000]) | |
p.decodePatternTile(t, x*8, p.Scanline-8, p.bgPaletteEntry(attr), nil) | |
// Flip bit 10 on wraparound | |
p.LoopyV++ | |
} | |
} | |
func (p *Ppu) decodePatternTile(t, x, y int, pal []Word, attr *Word) { | |
tile := p.Vram[t : t+16] | |
l := len(tile) | |
for i := 0; i < l/2; i++ { | |
var b uint | |
for b = 0; b < 8; b++ { | |
var xcoord int | |
if attr != nil && (*attr>>6)&0x1 != 0 { | |
xcoord = x + int(b) | |
} else { | |
xcoord = x + int(7-b) | |
} | |
var ycoord int | |
if attr != nil && (*attr>>7)&0x1 != 0 { | |
ycoord = y + int(7-b) | |
} else { | |
ycoord = y + i | |
} | |
fbRow := ycoord*256 + xcoord | |
// Store the bit 0/1 | |
pixel := (tile[i] >> b) & 0x01 | |
pixel += ((tile[i+8] >> b & 0x01) << 1) | |
trans := false | |
if attr != nil && pixel == 0 { | |
trans = true | |
} | |
// Set the color of the pixel in the buffer | |
if fbRow < 0xF000 && !trans { | |
p.Framebuffer[fbRow] = PaletteRgb[int(pal[pixel])] | |
} | |
} | |
} | |
} | |
func (p *Ppu) renderSprites() { | |
for i, t := range p.SpriteData.Tiles { | |
attrValue := p.Attributes[i] & 0x3 | |
if p.SpriteSize&0x01 != 0x0 { | |
// 8x16 Sprite | |
p.decodePatternTile(p.sprPatternTableAddress(int(t)), | |
int(p.XCoordinates[i]), | |
int(p.YCoordinates[i])+1, | |
p.sprPaletteEntry(uint(attrValue)), | |
&p.Attributes[i]) | |
} else { | |
p.decodePatternTile(p.sprPatternTableAddress(int(t)), | |
int(p.XCoordinates[i]), | |
int(p.YCoordinates[i])+1, | |
p.sprPaletteEntry(uint(attrValue)), | |
&p.Attributes[i]) | |
} | |
} | |
} | |
func (p *Ppu) bgPaletteEntry(a Word) (pal []Word) { | |
switch a { | |
case 0x0: | |
pal = []Word{ | |
p.PaletteRam[0x00], | |
p.PaletteRam[0x01], | |
p.PaletteRam[0x02], | |
p.PaletteRam[0x03], | |
} | |
case 0x1: | |
pal = []Word{ | |
p.PaletteRam[0x00], | |
p.PaletteRam[0x05], | |
p.PaletteRam[0x06], | |
p.PaletteRam[0x07], | |
} | |
case 0x2: | |
pal = []Word{ | |
p.PaletteRam[0x00], | |
p.PaletteRam[0x09], | |
p.PaletteRam[0x0A], | |
p.PaletteRam[0x0B], | |
} | |
case 0x3: | |
pal = []Word{ | |
p.PaletteRam[0x00], | |
p.PaletteRam[0x0D], | |
p.PaletteRam[0x0E], | |
p.PaletteRam[0x0F], | |
} | |
} | |
return | |
} | |
func (p *Ppu) sprPaletteEntry(a uint) (pal []Word) { | |
switch a { | |
case 0x0: | |
pal = []Word{ | |
p.PaletteRam[0x10], | |
p.PaletteRam[0x11], | |
p.PaletteRam[0x12], | |
p.PaletteRam[0x13], | |
} | |
case 0x1: | |
pal = []Word{ | |
p.PaletteRam[0x10], | |
p.PaletteRam[0x15], | |
p.PaletteRam[0x16], | |
p.PaletteRam[0x17], | |
} | |
case 0x2: | |
pal = []Word{ | |
p.PaletteRam[0x10], | |
p.PaletteRam[0x19], | |
p.PaletteRam[0x1A], | |
p.PaletteRam[0x1B], | |
} | |
case 0x3: | |
pal = []Word{ | |
p.PaletteRam[0x10], | |
p.PaletteRam[0x1D], | |
p.PaletteRam[0x1E], | |
p.PaletteRam[0x1F], | |
} | |
} | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment