1package vulkan
2
3import (
4 "fmt"
5 "image"
6 "math"
7 "os"
8 "runtime"
9 "strconv"
10 "unsafe"
11
12 "github.com/bnema/neferwl/internal/ports"
13
14 vk "github.com/bnema/purego-vulkan/vulkan"
15)
16
17// Renderer is owned by one goroutine; its methods must not be called concurrently.
18type Renderer struct {
19 width, height int
20 instance vk.Instance
21 id *vk.InstanceDispatch
22 device vk.Device
23 dd *vk.DeviceDispatch
24 queue vk.Queue
25 // own is the target until ExportTargets; targets are exported images
26 // (targets.go), current the one the next frame draws into.
27 own target
28 targets []*target
29 current int
30 renderMods []uint64
31 // last is the target of the last frame; readback copies it into
32 // buffer only when Pixels asks (stale until then).
33 last *target
34 readBack bool
35 buffer vk.Buffer
36 bufferMemory vk.DeviceMemory
37 mapped unsafe.Pointer
38 pool vk.CommandPool
39 memory vk.PhysicalDeviceMemoryProperties
40 family uint32
41 // Frames in flight (frame.go): frame is the last frame recorded,
42 // submitted the last submitted, completed the last known finished.
43 slots [frameSlots]frameSlot
44 frame, submitted, completed uint64
45 deferred []deferredFree
46 // syncFD: frames export a sync file (else Render waits for them).
47 syncFD bool
48 // dmabuf is what clients may send; imports are their buffers by ID.
49 dmabuf ports.DMABufSupport
50 imports map[uint64]*imported
51 // pools are client wl_shm pools mapped by pool ID.
52 pools map[uint64]*mapping
53 // retired are pool mappings replaced this frame, unmapped once the
54 // frame's copies are done.
55 retired [][]byte
56 // shm are the GPU copies of wl_shm surfaces (shm.go); copied counts
57 // the bytes copied into them, for tests.
58 shm map[shmKey]*shmSurface
59 copied int
60 // redrawn counts the target pixels drawn, for tests.
61 redrawn int
62 // cursors are the exported cursor images (CursorBuffers).
63 cursors [2]*cursorImage
64 // compose draws every frame (compose.go); maxRange bounds one
65 // storage buffer binding.
66 compose composer
67 maxRange int
68}
69
70func checked(name string, result vk.Result) error {
71 if err := vk.Check(result); err != nil {
72 return fmt.Errorf("%s: %w", name, err)
73 }
74 return nil
75}
76
77func New(width, height int) (r *Renderer, err error) {
78 if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) {
79 return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height)
80 }
81 r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}}
82 defer func() {
83 if err != nil {
84 r.Close()
85 r = nil
86 }
87 }()
88 if err = vk.Init(); err != nil {
89 return nil, fmt.Errorf("vk.Init: %w", err)
90 }
91 app := vk.ApplicationInfo{SType: vk.StructureTypeApplicationInfo, ApiVersion: vk.MakeVersion(1, 3, 0)}
92 info := vk.InstanceCreateInfo{SType: vk.StructureTypeInstanceCreateInfo, ApplicationInfo: &app}
93 if err = checked("vkCreateInstance", vk.Global().CreateInstance(&info, nil, &r.instance)); err != nil {
94 return
95 }
96 r.id, err = vk.LoadInstanceDispatch(r.instance)
97 if err != nil {
98 err = fmt.Errorf("LoadInstanceDispatch: %w", err)
99 return
100 }
101 var count uint32
102 if err = checked("vkEnumeratePhysicalDevices(count)", r.id.EnumeratePhysicalDevices(r.instance, &count, nil)); err != nil {
103 return
104 }
105 if count == 0 {
106 err = fmt.Errorf("no Vulkan 1.3 device")
107 return
108 }
109 devices := make([]vk.PhysicalDevice, count)
110 if err = checked("vkEnumeratePhysicalDevices", r.id.EnumeratePhysicalDevices(r.instance, &count, &devices[0])); err != nil {
111 return
112 }
113 index := -1
114 if value, ok := os.LookupEnv("NEFERWL_VK_DEVICE"); ok {
115 index, err = strconv.Atoi(value)
116 if err != nil || index < 0 || index >= int(count) {
117 err = fmt.Errorf("invalid NEFERWL_VK_DEVICE index %q", value)
118 return
119 }
120 }
121 var physical vk.PhysicalDevice
122 var family uint32
123 for i, device := range devices[:count] {
124 if index >= 0 && i != index {
125 continue
126 }
127 var properties vk.PhysicalDeviceProperties
128 r.id.GetPhysicalDeviceProperties(device, &properties)
129 if properties.ApiVersion < vk.MakeVersion(1, 3, 0) {
130 continue
131 }
132 var n uint32
133 r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil)
134 if n == 0 {
135 continue
136 }
137 families := make([]vk.QueueFamilyProperties, n)
138 r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, &families[0])
139 for j, f := range families[:n] {
140 if f.QueueCount > 0 && f.QueueFlags&vk.QueueGraphicsBit != 0 {
141 physical = device
142 family = uint32(j)
143 break
144 }
145 }
146 if physical != 0 {
147 break
148 }
149 }
150 if physical == 0 {
151 err = fmt.Errorf("no Vulkan 1.3 device with a graphics queue")
152 return
153 }
154 r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
155 var properties vk.PhysicalDeviceProperties
156 r.id.GetPhysicalDeviceProperties(physical, &properties)
157 r.maxRange = int(properties.Limits.MaxStorageBufferRange)
158 r.family = family
159 priority := float32(1)
160 qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
161 dynamicRendering := vk.PhysicalDeviceDynamicRenderingFeatures{SType: vk.StructureTypePhysicalDeviceDynamicRenderingFeatures, DynamicRendering: 1}
162 di := vk.DeviceCreateInfo{SType: vk.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&dynamicRendering), QueueCreateInfoCount: 1, QueueCreateInfos: &qi}
163 // dmabuf import needs every extension; without them clients use wl_shm.
164 var extNames [][]byte
165 var extPtrs []*byte
166 if r.hasExtensions(physical) {
167 for _, e := range deviceExtensions {
168 extNames = append(extNames, append([]byte(e), 0))
169 extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
170 }
171 di.EnabledExtensionCount = uint32(len(extPtrs))
172 di.PpEnabledExtensionNames = &extPtrs[0]
173 }
174 if err = checked("vkCreateDevice", r.id.CreateDevice(physical, &di, nil, &r.device)); err != nil {
175 return
176 }
177 r.dd, err = vk.LoadDeviceDispatch(r.id, r.device)
178 if err != nil {
179 err = fmt.Errorf("LoadDeviceDispatch: %w", err)
180 return
181 }
182 runtime.KeepAlive(extNames)
183 runtime.KeepAlive(extPtrs)
184 if len(extPtrs) > 0 {
185 // Clients need the render node to allocate on the right GPU.
186 if sup := r.probeDMABuf(physical); sup.Device != 0 {
187 r.dmabuf = sup
188 r.probeRenderModifiers(physical)
189 }
190 // VK_KHR_external_semaphore_fd (enabled above): frames export
191 // their fence as a sync file.
192 r.syncFD = r.dd.GetSemaphoreFdKHR != nil
193 }
194 r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
195 extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1}
196 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: targetUsage, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
197 if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
198 return
199 }
200 var req vk.MemoryRequirements
201 r.dd.GetImageMemoryRequirements(r.device, r.own.image, &req)
202 var kind uint32
203 kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
204 if err != nil {
205 return
206 }
207 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
208 if err = checked("vkAllocateMemory(image)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.own.memory)); err != nil {
209 return
210 }
211 if err = checked("vkBindImageMemory", r.dd.BindImageMemory(r.device, r.own.image, r.own.memory, 0)); err != nil {
212 return
213 }
214 size := vk.DeviceSize(width) * vk.DeviceSize(height) * 4
215 bi := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: size, Usage: vk.BufferUsageTransferDstBit, SharingMode: vk.SharingModeExclusive}
216 if err = checked("vkCreateBuffer", r.dd.CreateBuffer(r.device, &bi, nil, &r.buffer)); err != nil {
217 return
218 }
219 r.dd.GetBufferMemoryRequirements(r.device, r.buffer, &req)
220 // Readback (screenshots) reads this buffer: uncached memory is ~100x slower.
221 kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit|vk.MemoryPropertyHostCachedBit)
222 if err != nil {
223 kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit)
224 }
225 if err != nil {
226 return
227 }
228 alloc = vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
229 if err = checked("vkAllocateMemory(buffer)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.bufferMemory)); err != nil {
230 return
231 }
232 if err = checked("vkBindBufferMemory", r.dd.BindBufferMemory(r.device, r.buffer, r.bufferMemory, 0)); err != nil {
233 return
234 }
235 if err = checked("vkMapMemory", r.dd.MapMemory(r.device, r.bufferMemory, 0, size, 0, &r.mapped)); err != nil {
236 return
237 }
238 pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family}
239 if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil {
240 return
241 }
242 if err = r.createSlots(); err != nil {
243 return
244 }
245 if err = r.createView(&r.own); err != nil {
246 return
247 }
248 if err = r.createComposer(); err != nil {
249 return
250 }
251 runtime.KeepAlive(priority)
252 return
253}
254
255func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (uint32, error) {
256 for i := uint32(0); i < r.memory.MemoryTypeCount; i++ {
257 if bits&(uint32(1)<<i) != 0 && r.memory.MemoryTypes[i].PropertyFlags&props == props {
258 return i, nil
259 }
260 }
261 return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props)
262}
263
264func (r *Renderer) Pixels() *image.RGBA {
265 out := image.NewRGBA(image.Rect(0, 0, r.width, r.height))
266 if r.mapped == nil || r.readback() != nil {
267 return out
268 }
269 src := unsafe.Slice((*byte)(r.mapped), len(out.Pix))
270 for i := 0; i < len(src); i += 4 {
271 // The output is opaque: x-format client buffers leave alpha undefined.
272 out.Pix[i], out.Pix[i+1], out.Pix[i+2], out.Pix[i+3] = src[i+2], src[i+1], src[i], 255
273 }
274 return out
275}
276
277// Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE.
278const (
279 queueFamilyIgnored = ^uint32(0)
280 wholeSize = ^vk.DeviceSize(0)
281)
282
283// readback copies the last frame into the host buffer, once per frame
284// (screenshots only).
285func (r *Renderer) readback() error {
286 if r.readBack || r.last == nil || r.last.image == 0 {
287 return nil
288 }
289 // The copy reads the frame: wait for it, and keep the ring ordered.
290 if err := r.waitFrame(r.submitted); err != nil {
291 return err
292 }
293 t := r.last
294 err := r.oneShot(func(cmd vk.CommandBuffer) {
295 d := r.dd
296 b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: t.image, SubresourceRange: colorRange}
297 if t.exported {
298 b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family
299 }
300 d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b)
301 region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}}
302 d.CmdCopyImageToBuffer(cmd, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, ®ion)
303 // Make the copy visible to host reads of the mapped buffer.
304 hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize}
305 d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil)
306 if t.exported {
307 b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral
308 b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0
309 b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT
310 d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b)
311 }
312 })
313 if err != nil {
314 return err
315 }
316 if !t.exported {
317 t.layout = vk.ImageLayoutTransferSrcOptimal
318 }
319 r.readBack = true
320 return nil
321}
322
323func (r *Renderer) Close() {
324 if r == nil {
325 return
326 }
327 if r.dd != nil {
328 d := r.dd
329 if r.device != 0 {
330 _ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device))
331 r.idle()
332 }
333 r.dropTargets()
334 r.dropCursors()
335 for id, im := range r.imports {
336 r.release(im)
337 delete(r.imports, id)
338 }
339 r.frame += importTTL + 1
340 r.dropPools()
341 r.dropShm()
342 r.destroySlots()
343 if r.pool != 0 {
344 d.DestroyCommandPool(r.device, r.pool, nil)
345 r.pool = 0
346 }
347 if r.mapped != nil {
348 d.UnmapMemory(r.device, r.bufferMemory)
349 r.mapped = nil
350 }
351 if r.buffer != 0 {
352 d.DestroyBuffer(r.device, r.buffer, nil)
353 r.buffer = 0
354 }
355 if r.bufferMemory != 0 {
356 d.FreeMemory(r.device, r.bufferMemory, nil)
357 r.bufferMemory = 0
358 }
359 r.destroyComposer()
360 r.freeTarget(&r.own)
361 d.DestroyDevice(r.device, nil)
362 r.device = 0
363 } else if r.device != 0 && r.id != nil {
364 if vk.VkDestroyDevice != nil {
365 vk.VkDestroyDevice(r.device, nil)
366 }
367 r.device = 0
368 }
369 if r.instance != 0 {
370 if r.id != nil {
371 r.id.DestroyInstance(r.instance, nil)
372 } else if vk.VkDestroyInstance != nil {
373 vk.VkDestroyInstance(r.instance, nil)
374 }
375 r.instance = 0
376 }
377}