You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.2 KiB
146 lines
3.2 KiB
package imagedrm |
|
|
|
import ( |
|
"fmt" |
|
"github.com/kytart/godrm/pkg/drm" |
|
"github.com/kytart/godrm/pkg/mode" |
|
"launchpad.net/gommap" |
|
"os" |
|
) |
|
|
|
type Framebuffer struct { |
|
*mode.FB |
|
id uint32 |
|
data []byte |
|
} |
|
|
|
type Image struct { |
|
file *os.File |
|
modeset *mode.SimpleModeset |
|
displays []*Display |
|
} |
|
|
|
type Display struct { |
|
mode *mode.Modeset |
|
fb *Framebuffer |
|
savedCrtc *mode.Crtc |
|
} |
|
|
|
func NewImage(card int) (*Image, error) { |
|
file, err := drm.OpenCard(card) |
|
if err != nil { |
|
return nil, fmt.Errorf("open drm card: %w", err) |
|
} |
|
|
|
if !drm.HasDumbBuffer(file) { |
|
return nil, fmt.Errorf("drm device does not support dumb buffers") |
|
} |
|
|
|
modeset, err := mode.NewSimpleModeset(file) |
|
if err != nil { |
|
return nil, fmt.Errorf("create modeset: %w", err) |
|
} |
|
|
|
image := &Image{ |
|
file: file, |
|
modeset: modeset, |
|
} |
|
|
|
for _, mod := range modeset.Modesets { |
|
display, err := image.setupDisplay(mod) |
|
if err != nil { |
|
image.Close() |
|
return nil, fmt.Errorf("setup display: %w", err) |
|
} |
|
|
|
image.displays = append(image.displays, display) |
|
} |
|
|
|
return image, nil |
|
} |
|
|
|
func (i *Image) Close() error { |
|
for _, display := range i.displays { |
|
err := i.destroyFramebuffer(display) |
|
if err != nil { |
|
fmt.Println(err) |
|
} |
|
} |
|
return i.file.Close() |
|
} |
|
|
|
func (i *Image) createFramebuffer(dev *mode.Modeset) (*Framebuffer, error) { |
|
fb, err := mode.CreateFB(i.file, dev.Width, dev.Height, 32) |
|
if err != nil { |
|
return nil, fmt.Errorf("create framebuffer: %w", err) |
|
} |
|
|
|
fbID, err := mode.AddFB(i.file, dev.Width, dev.Height, 24, 32, fb.Pitch, fb.Handle) |
|
if err != nil { |
|
return nil, fmt.Errorf("create dumb buffer: %w", err) |
|
} |
|
|
|
offset, err := mode.MapDumb(i.file, fb.Handle) |
|
if err != nil { |
|
return nil, fmt.Errorf("map dumb: %w", err) |
|
} |
|
|
|
mmap, err := gommap.MapAt(0, i.file.Fd(), int64(offset), int64(fb.Size), gommap.PROT_READ|gommap.PROT_WRITE, gommap.MAP_SHARED) |
|
if err != nil { |
|
return nil, fmt.Errorf("mmap framebuffer: %w", err) |
|
} |
|
|
|
for i := uint64(0); i < fb.Size; i++ { |
|
mmap[i] = 0 |
|
} |
|
|
|
return &Framebuffer{ |
|
FB: fb, |
|
id: fbID, |
|
data: mmap, |
|
}, nil |
|
} |
|
|
|
func (i *Image) destroyFramebuffer(display *Display) error { |
|
err := gommap.MMap(display.fb.data).UnsafeUnmap() |
|
if err != nil { |
|
return fmt.Errorf("munmap memory: %w", err) |
|
} |
|
|
|
err = mode.RmFB(i.file, display.fb.id) |
|
if err != nil { |
|
return fmt.Errorf("remove frame buffer: %w", err) |
|
} |
|
|
|
err = mode.DestroyDumb(i.file, display.fb.Handle) |
|
if err != nil { |
|
return fmt.Errorf("destroy dumb buffer: %w", err) |
|
} |
|
|
|
return i.modeset.SetCrtc(display.mode, display.savedCrtc) |
|
} |
|
|
|
func (i *Image) setupDisplay(mod mode.Modeset) (*Display, error) { |
|
framebuf, err := i.createFramebuffer(&mod) |
|
if err != nil { |
|
return nil, fmt.Errorf("create framebuffer: %w", err) |
|
} |
|
|
|
// save current CRTC of this display to restore at exit |
|
savedCrtc, err := mode.GetCrtc(i.file, mod.Crtc) |
|
if err != nil { |
|
return nil, fmt.Errorf("get CRTC for connector %d: %w", mod.Conn, err) |
|
} |
|
|
|
// change the display |
|
err = mode.SetCrtc(i.file, mod.Crtc, framebuf.id, 0, 0, &mod.Conn, 1, &mod.Mode) |
|
if err != nil { |
|
return nil, fmt.Errorf("set CRTC for connector %d: %w", mod.Conn, err) |
|
} |
|
|
|
return &Display{ |
|
mode: &mod, |
|
fb: framebuf, |
|
savedCrtc: savedCrtc, |
|
}, nil |
|
}
|
|
|