Render the live status to the framebuffer in Atkinson Hyperlegible Mono
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user