aboutsummaryrefslogtreecommitdiff
path: root/tests/ppu_render_test.go
diff options
context:
space:
mode:
authorcrecher <crecher@crecher.net>2026-10-12 22:08:09 +0200
committercrecher <crecher@crecher.net>2026-10-12 22:08:09 +0200
commit96ff6d8eda2ba434682909f4ff4ce350d78b2273 (patch)
treefc1982c54420254ef82c588b742d7ecacfbe7d48 /tests/ppu_render_test.go
parent84801d18b9885c6d70914c67a76d8ac8e3b2b20d (diff)
downloadgones-96ff6d8eda2ba434682909f4ff4ce350d78b2273.tar.gz
gones-96ff6d8eda2ba434682909f4ff4ce350d78b2273.zip
ppu: add background rendering
cmd/gones: add sdl2 frontend
Diffstat (limited to 'tests/ppu_render_test.go')
-rw-r--r--tests/ppu_render_test.go160
1 files changed, 160 insertions, 0 deletions
diff --git a/tests/ppu_render_test.go b/tests/ppu_render_test.go
new file mode 100644
index 0000000..a6602b9
--- /dev/null
+++ b/tests/ppu_render_test.go
@@ -0,0 +1,160 @@
+package tests
+
+import (
+ "testing"
+
+ "crecher.net/gones/v2/cart"
+ "crecher.net/gones/v2/ppu"
+)
+
+const (
+ black = 0xFF000000 // palette entry $0F
+ white = 0xFFECEEEC // $30
+ red = 0xFF982220 // $16
+ blue = 0xFF4C9AEC // $21
+)
+
+// writes bytes through $2006/$2007 starting at addr, then points v back at 0
+func writeVRAM(p *ppu.PPU, addr uint16, data ...uint8) {
+ p.WriteRegister(0x2006, uint8(addr>>8))
+ p.WriteRegister(0x2006, uint8(addr))
+ for _, b := range data {
+ p.WriteRegister(0x2007, b)
+ }
+ p.WriteRegister(0x2006, 0)
+ p.WriteRegister(0x2006, 0)
+}
+
+// ticks two full frames so scroll registers have been copied from t at least once
+func runFrames(p *ppu.PPU) {
+ for i := 0; i < 2*341*262; i++ {
+ p.Tick()
+ }
+}
+
+func pixel(p *ppu.PPU, x, y int) uint32 {
+ return p.Framebuffer()[y*ppu.ScreenWidth+x]
+}
+
+// tile 1: row 0 is color 1 across, row 1 is color 2 across
+// palette 0: backdrop black, 1 = white, 2 = red. palette 1 color 1 = blue
+func newSceneWithTile1() *ppu.PPU {
+ p, _ := newTestPPU(cart.Horizontal)
+ writeVRAM(p, 0x0010, 0xFF, 0x00)
+ writeVRAM(p, 0x0018, 0x00, 0xFF)
+ writeVRAM(p, 0x3F00, 0x0F, 0x30, 0x16)
+ writeVRAM(p, 0x3F05, 0x21)
+ return p
+}
+
+func TestBackgroundDrawsBackdropWhenDisabled(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x2000, 1)
+ runFrames(p)
+ if got := pixel(p, 0, 0); got != black {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(black))
+ }
+}
+
+func TestBackgroundTilePixels(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x2000, 1)
+ p.WriteRegister(0x2001, 0x0A) // background + left 8 pixels
+ runFrames(p)
+
+ tests := []struct {
+ x, y int
+ want uint32
+ }{
+ {0, 0, white},
+ {7, 0, white},
+ {8, 0, black}, // next tile is tile 0, blank
+ {0, 1, red},
+ {7, 1, red},
+ {0, 2, black}, // rows 2-7 of tile 1 are empty
+ }
+ for _, tt := range tests {
+ if got := pixel(p, tt.x, tt.y); got != tt.want {
+ t.Errorf("pixel(%d,%d) = %#08x, want %#08x", tt.x, tt.y, got, tt.want)
+ }
+ }
+}
+
+func TestBackgroundAttributeSelectsPalette(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x2000, 1, 1, 1, 1)
+ writeVRAM(p, 0x23C0, 0x01) // top-left 16x16 quadrant uses palette 1
+ p.WriteRegister(0x2001, 0x0A)
+ runFrames(p)
+
+ if got := pixel(p, 0, 0); got != blue {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(blue))
+ }
+ if got := pixel(p, 15, 0); got != blue {
+ t.Errorf("pixel(15,0) = %#08x, want %#08x", got, uint32(blue))
+ }
+ if got := pixel(p, 16, 0); got != white {
+ t.Errorf("pixel(16,0) = %#08x, want %#08x (next quadrant, palette 0)", got, uint32(white))
+ }
+}
+
+func TestBackgroundLeftClip(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x2000, 1, 1)
+ p.WriteRegister(0x2001, 0x08) // background on, left 8 pixels hidden
+ runFrames(p)
+
+ if got := pixel(p, 0, 0); got != black {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x (clipped)", got, uint32(black))
+ }
+ if got := pixel(p, 8, 0); got != white {
+ t.Errorf("pixel(8,0) = %#08x, want %#08x", got, uint32(white))
+ }
+}
+
+func TestBackgroundFineXScroll(t *testing.T) {
+ p, _ := newTestPPU(cart.Horizontal)
+ writeVRAM(p, 0x0010, 0x01, 0x00) // only the rightmost pixel of row 0 is set
+ writeVRAM(p, 0x3F00, 0x0F, 0x30)
+ writeVRAM(p, 0x2000, 1)
+ p.WriteRegister(0x2005, 7)
+ p.WriteRegister(0x2005, 0)
+ p.WriteRegister(0x2001, 0x0A)
+ runFrames(p)
+
+ if got := pixel(p, 0, 0); got != white {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(white))
+ }
+ if got := pixel(p, 1, 0); got != black {
+ t.Errorf("pixel(1,0) = %#08x, want %#08x", got, uint32(black))
+ }
+}
+
+func TestBackgroundCoarseXScroll(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x2003, 1) // tile 1 in the 4th column
+ p.WriteRegister(0x2005, 24) // coarse x = 3
+ p.WriteRegister(0x2005, 0)
+ p.WriteRegister(0x2001, 0x0A)
+ runFrames(p)
+
+ if got := pixel(p, 0, 0); got != white {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(white))
+ }
+ if got := pixel(p, 8, 0); got != black {
+ t.Errorf("pixel(8,0) = %#08x, want %#08x", got, uint32(black))
+ }
+}
+
+func TestBackgroundPatternTableSelect(t *testing.T) {
+ p := newSceneWithTile1()
+ writeVRAM(p, 0x1018, 0xFF) // tile 1 in the high pattern table, row 0 high plane only = color 2
+ writeVRAM(p, 0x2000, 1)
+ p.WriteRegister(0x2000, 0x10)
+ p.WriteRegister(0x2001, 0x0A)
+ runFrames(p)
+
+ if got := pixel(p, 0, 0); got != red {
+ t.Errorf("pixel(0,0) = %#08x, want %#08x", got, uint32(red))
+ }
+}