board/board-gameboy-advance.go

162 lines
3.4 KiB
Go
Raw Permalink Normal View History

2024-07-31 03:14:52 +03:00
//go:build gameboyadvance
package board
import (
"device/gba"
"errors"
"math/bits"
"runtime/volatile"
"time"
"unsafe"
"tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
)
const (
Name = "gameboy-advance"
)
var (
Power = dummyBattery{state: UnknownBattery}
Sensors = baseSensors{}
Display = mainDisplay{}
Buttons = &gbaButtons{}
)
type mainDisplay struct{}
func (d mainDisplay) PPI() int {
return 99
}
func (d mainDisplay) Configure() Displayer[pixel.RGB555] {
// 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)
return gbaDisplay{}
}
func (d mainDisplay) MaxBrightness() int {
return 0
}
func (d mainDisplay) SetBrightness(level int) {
// The display doesn't have a backlight.
}
func (d mainDisplay) WaitForVBlank(time.Duration) {
// Wait until the VBlank flag is set.
// TODO: sleep until the next VBlank instead of busy waiting.
// (See VBlankIntrWait)
for gba.DISP.DISPSTAT.Get()&(1<<gba.DISPSTAT_VBLANK_Pos) == 0 {
}
}
func (d mainDisplay) ConfigureTouch() TouchInput {
return noTouch{}
}
type gbaDisplay struct{}
var displayFrameBuffer = (*[160 * 240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))
var errOutOfBounds = errors.New("rectangle coordinates outside display area")
const (
displayWidth = 240
displayHeight = 160
)
func (d gbaDisplay) Size() (x, y int16) {
return displayWidth, displayHeight
}
func (d gbaDisplay) Display() error {
// Nothing to do here.
return nil
}
func (d gbaDisplay) DrawBitmap(x, y int16, buf pixel.Image[pixel.RGB555]) 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
}
func (d gbaDisplay) Sleep(sleepEnabled bool) error {
return nil // nothign to do here
}
var errNoRotation = errors.New("error: SetRotation isn't supported")
func (d gbaDisplay) Rotation() drivers.Rotation {
return drivers.Rotation0
}
func (d gbaDisplay) SetRotation(rotation drivers.Rotation) error {
return errNoRotation
}
type gbaButtons struct {
state uint16
previousState uint16
}
func (b *gbaButtons) Configure() {
// nothing to configure
}
func (b *gbaButtons) ReadInput() {
b.state = gba.KEY.INPUT.Get() ^ 0x3ff
}
var codes = [16]Key{
KeyA,
KeyB,
KeySelect,
KeyStart,
KeyRight,
KeyLeft,
KeyUp,
KeyDown,
KeyR,
KeyL,
}
func (b *gbaButtons) NextEvent() KeyEvent {
// The xor between the previous state and the current state is the buttons
// that changed.
change := b.state ^ b.previousState
if change == 0 {
return NoKeyEvent
}
// Find the index of the button with the lowest index that changed state.
index := bits.TrailingZeros32(uint32(change))
e := KeyEvent(codes[index])
if b.state&(1<<index) == 0 {
// The button state change was from 1 to 0, so it was released.
e |= keyReleased
}
// This button event was read, so mark it as such.
// By toggling the bit, the bit will be set to the value that is currently
// in b.state.
b.previousState ^= (1 << index)
return e
}