Files
cabletest/fb.go
T

169 lines
3.7 KiB
Go
Raw Normal View History

package main
import (
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
const (
fbioGetVScreenInfo = 0x4600
fbioBlank = 0x4611
fbBlankUnblank = 0
)
// The variable screen info is a flat run of 40 u32s, so it is read as an array
// rather than a struct to sidestep any question of padding.
const (
viXres = 0
viYres = 1
viBitsPerPixel = 6
viRedOffset = 8
viGreenOffset = 11
viBlueOffset = 14
viScreenInfoLen = 40
)
type framebuffer struct {
file *os.File
mem []byte
back []byte
w int
h int
stride int
rShift uint
gShift uint
bShift uint
}
func openFramebuffer(path string) (*framebuffer, error) {
f, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return nil, err
}
var vi [viScreenInfoLen]uint32
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
fbioGetVScreenInfo, uintptr(unsafe.Pointer(&vi[0]))); errno != 0 {
f.Close()
return nil, fmt.Errorf("get screen info: %w", errno)
}
if vi[viBitsPerPixel] != 32 {
f.Close()
return nil, fmt.Errorf("only 32bpp supported, got %d", vi[viBitsPerPixel])
}
// Wake the panel; the console blanks it after a timeout.
unix.Syscall(unix.SYS_IOCTL, f.Fd(), fbioBlank, fbBlankUnblank)
stride, ok := readUint("/sys/class/graphics/fb0/stride")
if !ok {
f.Close()
return nil, fmt.Errorf("cannot read framebuffer stride")
}
fb := &framebuffer{
file: f,
w: int(vi[viXres]),
h: int(vi[viYres]),
stride: int(stride),
rShift: uint(vi[viRedOffset]),
gShift: uint(vi[viGreenOffset]),
bShift: uint(vi[viBlueOffset]),
}
fb.mem, err = unix.Mmap(int(f.Fd()), 0, fb.stride*fb.h,
unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
if err != nil {
f.Close()
return nil, fmt.Errorf("mmap: %w", err)
}
fb.back = make([]byte, fb.stride*fb.h)
return fb, nil
}
func (fb *framebuffer) close() {
unix.Munmap(fb.mem)
fb.file.Close()
}
func (fb *framebuffer) pixel(c rgb) uint32 {
return uint32(c.r)<<fb.rShift | uint32(c.g)<<fb.gShift | uint32(c.b)<<fb.bShift
}
type rgb struct {
r, g, b uint8
}
func (fb *framebuffer) fill(c rgb) {
v := fb.pixel(c)
row := make([]byte, fb.stride)
for x := 0; x+4 <= fb.stride; x += 4 {
row[x+0] = byte(v)
row[x+1] = byte(v >> 8)
row[x+2] = byte(v >> 16)
row[x+3] = byte(v >> 24)
}
for y := 0; y < fb.h; y++ {
copy(fb.back[y*fb.stride:], row)
}
}
func (fb *framebuffer) rect(x0, y0, w, h int, c rgb) {
v := fb.pixel(c)
for y := y0; y < y0+h; y++ {
if y < 0 || y >= fb.h {
continue
}
base := y * fb.stride
for x := x0; x < x0+w; x++ {
if x < 0 || x >= fb.w {
continue
}
o := base + x*4
fb.back[o+0] = byte(v)
fb.back[o+1] = byte(v >> 8)
fb.back[o+2] = byte(v >> 16)
fb.back[o+3] = byte(v >> 24)
}
}
}
// Blends src over the existing pixel, with cov as 0-255 coverage.
func (fb *framebuffer) blend(x, y int, c rgb, cov uint8) {
if x < 0 || y < 0 || x >= fb.w || y >= fb.h || cov == 0 {
return
}
o := y*fb.stride + x*4
if cov == 255 {
v := fb.pixel(c)
fb.back[o+0] = byte(v)
fb.back[o+1] = byte(v >> 8)
fb.back[o+2] = byte(v >> 16)
fb.back[o+3] = byte(v >> 24)
return
}
a := uint32(cov)
old := uint32(fb.back[o+0]) | uint32(fb.back[o+1])<<8 |
uint32(fb.back[o+2])<<16 | uint32(fb.back[o+3])<<24
orr := uint8(old >> fb.rShift)
og := uint8(old >> fb.gShift)
ob := uint8(old >> fb.bShift)
mix := rgb{
r: uint8((uint32(c.r)*a + uint32(orr)*(255-a)) / 255),
g: uint8((uint32(c.g)*a + uint32(og)*(255-a)) / 255),
b: uint8((uint32(c.b)*a + uint32(ob)*(255-a)) / 255),
}
v := fb.pixel(mix)
fb.back[o+0] = byte(v)
fb.back[o+1] = byte(v >> 8)
fb.back[o+2] = byte(v >> 16)
fb.back[o+3] = byte(v >> 24)
}
func (fb *framebuffer) flush() {
copy(fb.mem, fb.back)
}