From e260768039ec6f4a1376777fcb72082600679014 Mon Sep 17 00:00:00 2001 From: ptrcnull Date: Sat, 4 Dec 2021 07:08:00 +0100 Subject: [PATCH] feat: Initial commit --- README.md | 5 ++ drm.go | 145 +++++++++++++++++++++++++++++++++++++++ examples/jpeg/glenda.jpg | Bin 0 -> 5339 bytes examples/jpeg/main.go | 39 +++++++++++ go.mod | 8 +++ go.sum | 6 ++ image.go | 51 ++++++++++++++ 7 files changed, 254 insertions(+) create mode 100644 README.md create mode 100644 drm.go create mode 100644 examples/jpeg/glenda.jpg create mode 100644 examples/jpeg/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 image.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a76f3c --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# imagedrm + +A simple library that wraps [godrm](https://github.com/kytart/godrm) as [draw.Image](https://pkg.go.dev/image/draw#Image) + +See example usage in `examples/`. diff --git a/drm.go b/drm.go new file mode 100644 index 0000000..401e787 --- /dev/null +++ b/drm.go @@ -0,0 +1,145 @@ +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() (*Image, error) { + file, err := drm.OpenCard(0) + if err != nil { + return nil, fmt.Errorf("open drm card: %w", err) + } + defer file.Close() + + 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 { + var err error + for _, display := range i.displays { + err = i.destroyFramebuffer(display) + } + return err +} + +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 +} diff --git a/examples/jpeg/glenda.jpg b/examples/jpeg/glenda.jpg new file mode 100644 index 0000000000000000000000000000000000000000..14b1944336a5890721ce66feb5d4c33175a4c6f8 GIT binary patch literal 5339 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 ua*~t89J> 24), + R: uint8((val & 0x00ff0000) >> 16), + G: uint8((val & 0x0000ff00) >> 8), + B: uint8(val & 0x000000ff), + } +} + +func (i *Image) Set(x, y int, c color.Color) { + display := i.displays[0] + + r, g, b, a := c.RGBA() + val := (a << 24) | (r << 16) | (g << 8) | b + + offset := (display.fb.Pitch * uint32(y)) + (uint32(x) * 4) + *(*uint32)(unsafe.Pointer(&display.fb.data[offset])) = val +} + +var _ draw.Image = (*Image)(nil)