1package vulkan
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "unsafe"
8
9 "github.com/bnema/neferwl/internal/ports"
10 vk "github.com/bnema/purego-vulkan/vulkan"
11)
12
13// Cursor images for the display's cursor plane: two linear B8G8R8A8
14// images in host-visible memory, exported as dmabufs. The CPU writes a
15// cursor image only when the cursor changes, never per frame.
16
17type cursorImage struct {
18 image vk.Image
19 memory vk.DeviceMemory
20 mapped unsafe.Pointer
21 pitch int
22 size int
23}
24
25// fourccARGB is DRM_FORMAT_ARGB8888: B8G8R8A8 in memory.
26const fourccARGB = 'A' | 'R'<<8 | '2'<<16 | '4'<<24
27
28// CursorBuffers allocates the two cursor images, replacing earlier ones.
29func (r *Renderer) CursorBuffers(size int) ([2]ports.DMABuf, error) {
30 var out [2]ports.DMABuf
31 r.dropCursors()
32 if r.dd.GetMemoryFdKHR == nil || len(r.dmabuf.Formats) == 0 || size <= 0 || size > 512 {
33 return out, errors.New("device cannot export cursor images")
34 }
35 for i := range r.cursors {
36 c, buf, err := r.cursorImage(size)
37 if err != nil {
38 r.dropCursors()
39 for _, b := range out[:i] {
40 b.Planes[0].File.Close()
41 }
42 return [2]ports.DMABuf{}, err
43 }
44 r.cursors[i], out[i] = c, buf
45 }
46 return out, nil
47}
48
49func (r *Renderer) cursorImage(size int) (*cursorImage, ports.DMABuf, error) {
50 d := r.dd
51 c := &cursorImage{size: size}
52 ok := false
53 defer func() {
54 if !ok {
55 r.freeCursor(c)
56 }
57 }()
58 // Exported images take an explicit DRM modifier: linear, as cursor
59 // planes scan out. The GPU never touches them (the CPU writes, KMS reads).
60 linear := uint64(0)
61 list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: 1, DrmFormatModifiers: &linear}
62 external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
63 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(size), Height: uint32(size), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageTransferSrcBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
64 if err := checked("vkCreateImage(cursor)", d.CreateImage(r.device, &ii, nil, &c.image)); err != nil {
65 return nil, ports.DMABuf{}, err
66 }
67 var req vk.MemoryRequirements
68 d.GetImageMemoryRequirements(r.device, c.image, &req)
69 kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit)
70 if err != nil {
71 return nil, ports.DMABuf{}, err
72 }
73 dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: c.image}
74 export := vk.ExportMemoryAllocateInfo{SType: vk.StructureTypeExportMemoryAllocateInfo, Next: unsafe.Pointer(&dedicated), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
75 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&export), AllocationSize: req.Size, MemoryTypeIndex: kind}
76 if err := checked("vkAllocateMemory(cursor)", d.AllocateMemory(r.device, &alloc, nil, &c.memory)); err != nil {
77 return nil, ports.DMABuf{}, err
78 }
79 if err := checked("vkBindImageMemory(cursor)", d.BindImageMemory(r.device, c.image, c.memory, 0)); err != nil {
80 return nil, ports.DMABuf{}, err
81 }
82 var layout vk.SubresourceLayout
83 sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
84 d.GetImageSubresourceLayout(r.device, c.image, &sub, &layout)
85 c.pitch = int(layout.RowPitch)
86 // Cursor planes scan out packed rows from the start of the buffer.
87 if c.pitch != size*4 || layout.Offset != 0 {
88 return nil, ports.DMABuf{}, fmt.Errorf("cursor image layout pitch %d offset %d, want pitch %d offset 0", c.pitch, layout.Offset, size*4)
89 }
90 if err := checked("vkMapMemory(cursor)", d.MapMemory(r.device, c.memory, 0, vk.DeviceSize(req.Size), 0, &c.mapped)); err != nil {
91 return nil, ports.DMABuf{}, err
92 }
93 c.mapped = unsafe.Add(c.mapped, layout.Offset)
94 clear(unsafe.Slice((*byte)(c.mapped), c.pitch*size))
95 var fd int32
96 get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: c.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
97 if err := checked("vkGetMemoryFdKHR(cursor)", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
98 return nil, ports.DMABuf{}, err
99 }
100 f := os.NewFile(uintptr(fd), "neferwl-cursor")
101 if f == nil {
102 return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
103 }
104 ok = true
105 return c, ports.DMABuf{Width: size, Height: size, Format: fourccARGB, Modifier: 0, Planes: []ports.DMABufPlane{{File: f, Offset: uint32(layout.Offset), Stride: uint32(layout.RowPitch)}}}, nil
106}
107
108// WriteCursor copies premultiplied ARGB8888 pixels (w×h, w*4 per row)
109// into cursor image i at its row pitch, cropped and cleared around.
110func (r *Renderer) WriteCursor(i int, pixels []byte, w, h int) error {
111 if i < 0 || i >= len(r.cursors) || r.cursors[i] == nil {
112 return errors.New("no cursor image")
113 }
114 if w < 0 || h < 0 || len(pixels) < w*h*4 {
115 return errors.New("short cursor image")
116 }
117 c := r.cursors[i]
118 mem := unsafe.Slice((*byte)(c.mapped), c.pitch*c.size)
119 clear(mem)
120 cw := min(w, c.size) * 4
121 for y := range min(h, c.size) {
122 copy(mem[y*c.pitch:y*c.pitch+cw], pixels[y*w*4:y*w*4+cw])
123 }
124 return nil
125}
126
127func (r *Renderer) freeCursor(c *cursorImage) {
128 if c.mapped != nil {
129 r.dd.UnmapMemory(r.device, c.memory)
130 c.mapped = nil
131 }
132 if c.image != 0 {
133 r.dd.DestroyImage(r.device, c.image, nil)
134 c.image = 0
135 }
136 if c.memory != 0 {
137 r.dd.FreeMemory(r.device, c.memory, nil)
138 c.memory = 0
139 }
140}
141
142func (r *Renderer) dropCursors() {
143 for i, c := range r.cursors {
144 if c != nil {
145 r.freeCursor(c)
146 r.cursors[i] = nil
147 }
148 }
149}