Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gbadisplay: add simple driver for the GameBoy Advance display #620

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions gbadisplay/gbadisplay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Package gbadisplay implements a simple driver for the GameBoy Advance
// display.
package gbadisplay

import (
"device/gba"
"errors"
"image/color"
"runtime/volatile"
"unsafe"

"tinygo.org/x/drivers/pixel"
)

// Image buffer type used by the GameBoy Advance.
type Image = pixel.Image[pixel.RGB555]

const (
displayWidth = 240
displayHeight = 160
)

var (
errOutOfBounds = errors.New("rectangle coordinates outside display area")
)

type Device struct{}

// New returns a new GameBoy Advance display object.
func New() Device {
return Device{}
}

var displayFrameBuffer = (*[160 * 240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))

type Config struct {
// TODO: add more display modes here.
}

// Configure the display as a regular 15bpp framebuffer.
func (d Device) Configure(config Config) {
// Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2.
gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<<gba.DISPCNT_BGMODE_Pos |
gba.DISPCNT_SCREENDISPLAY_BG2_ENABLE<<gba.DISPCNT_SCREENDISPLAY_BG2_Pos)
}

// Size returns the fixed size of this display.
func (d Device) Size() (x, y int16) {
return displayWidth, displayHeight
}

// Display is a no-op: the display framebuffer is modified directly.
func (d Device) Display() error {
// Nothing to do here.
return nil
}

// SetPixel changes the pixel at (x, y) to the given color.
func (d Device) SetPixel(x, y int16, c color.RGBA) {
if x < 0 || y < 0 || x >= displayWidth || y > displayHeight {
// Out of bounds, so ignore.
return
}
val := pixel.NewColor[pixel.RGB555](c.R, c.G, c.B)
displayFrameBuffer[(int(y))*240+int(x)].Set(uint16(val))
}

// DrawBitmap updates the rectangle at (x, y) to the image stored in buf.
func (d Device) DrawBitmap(x, y int16, buf Image) error {
width, height := buf.Size()
if x < 0 || y < 0 || int(x)+width > displayWidth || int(y)+height > displayHeight {
return errOutOfBounds
}

// TODO: try to do a 4-byte memcpy if possible. That should significantly
// speed up the copying of this image.
for bufY := 0; bufY < int(height); bufY++ {
for bufX := 0; bufX < int(width); bufX++ {
val := buf.Get(bufX, bufY)
displayFrameBuffer[(int(y)+bufY)*240+int(x)+bufX].Set(uint16(val))
}
}

return nil
}
33 changes: 32 additions & 1 deletion pixel/pixel.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// particular display. Each pixel is at least 1 byte in size.
// The color format is sRGB (or close to it) in all cases.
type Color interface {
RGB888 | RGB565BE | RGB444BE
RGB888 | RGB565BE | RGB555 | RGB444BE

BaseColor
}
Expand Down Expand Up @@ -46,6 +46,8 @@ func NewColor[T Color](r, g, b uint8) T {
return any(NewRGB888(r, g, b)).(T)
case RGB565BE:
return any(NewRGB565BE(r, g, b)).(T)
case RGB555:
return any(NewRGB555(r, g, b)).(T)
case RGB444BE:
return any(NewRGB444BE(r, g, b)).(T)
default:
Expand Down Expand Up @@ -140,6 +142,35 @@ func (c RGB565BE) RGBA() color.RGBA {
return color
}

// Color format used on the GameBoy Advance among others.
//
// Colors are stored as native endian values, with bits 0bbbbbgg_gggrrrrr (red
// is least significant, blue is most significant).
type RGB555 uint16

func NewRGB555(r, g, b uint8) RGB555 {
return RGB555(r)>>3 | (RGB555(g)>>3)<<5 | (RGB555(b)>>3)<<10
}

func (c RGB555) BitsPerPixel() int {
// 15 bits per pixel, but there are 16 bits when stored
return 16
}

func (c RGB555) RGBA() color.RGBA {
color := color.RGBA{
R: uint8(c>>10) << 3,
G: uint8(c>>5) << 3,
B: uint8(c) << 3,
A: 255,
}
// Correct color rounding, so that 0xff roundtrips back to 0xff.
color.R |= color.R >> 5
color.G |= color.G >> 5
color.B |= color.B >> 5
return color
}

// Color format that is supported by the ST7789 for example.
// It may be a bit faster to use than RGB565BE on very slow SPI buses.
//
Expand Down