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 "github.com/bnema/zerowrap"
14
15 vk "github.com/bnema/purego-vulkan/vulkan"
16)
17
18// Renderer is owned by one goroutine; its methods must not be called concurrently.
19type Renderer struct {
20 width, height int
21 // queuePriority is the global priority the queue got: realtime, high
22 // or default.
23 queuePriority string
24 instance vk.Instance
25 id *vk.InstanceDispatch
26 device vk.Device
27 dd *vk.DeviceDispatch
28 queue vk.Queue
29 // own is the SDR composition image; HDR uses hdrOwn and PQ targets.
30 own target
31 hdrOwn target // linear BT.709 in units of SDR reference white
32 targets []*target
33 current int
34 renderMods []uint64
35 hdrMods []uint64
36 hdrNits float64
37 hdr hdrPass
38 hdrError error
39 // modPlanes is the memory plane count of each probed target modifier
40 // (SDR and HDR): DCC modifiers have more than one.
41 modPlanes map[uint64]uint32
42 // log reports the chosen target modifier; the zero value discards.
43 log zerowrap.Logger
44 // hdrReadback makes exported HDR targets transfer sources, for GPU tests
45 // and headless --screenshot-raw (SetHDRReadback); never DRM.
46 hdrReadback bool
47 // virtual: the output has no display (headless), so ExportTargets
48 // accepts an empty modifier list for HDR (SetVirtualOutput).
49 virtual bool
50 physical vk.PhysicalDevice
51 // One converted cursor image is kept; changes replace it in bounded memory.
52 cursorCache cursorConversion
53 // lastHDRTarget is the index in targets of the exported PQ target the
54 // last HDR frame rendered into; -1 when the current targets hold none.
55 lastHDRTarget int
56 // last is the target of the last frame: what captures copy.
57 last *target
58 // captures are the readback slots (capture.go), allocated on the
59 // first capture; capturePass converts HDR frames into captureSDR.
60 captures []*captureSlot
61 capturePass hdrPass
62 captureSDR target
63 sync captureSync
64 pool vk.CommandPool
65 memory vk.PhysicalDeviceMemoryProperties
66 family uint32
67 // Frames in flight (frame.go): frame is the last frame recorded,
68 // submitted the last submitted, completed the last known finished.
69 slots [frameSlots]frameSlot
70 frame, submitted, completed uint64
71 deferred []deferredFree
72 // syncFD: frames export a sync file (else Render waits for them).
73 syncFD bool
74 // dmabuf is what clients may send; imports are their buffers by ID.
75 dmabuf ports.DMABufSupport
76 imports map[uint64]*imported
77 // pools are client wl_shm pools mapped by pool ID.
78 pools map[uint64]*mapping
79 // retired are pool mappings replaced this frame, unmapped once the
80 // frame's copies are done.
81 retired [][]byte
82 // shm are the GPU copies of wl_shm surfaces (shm.go); copied counts
83 // the bytes copied into them, for tests.
84 shm map[shmKey]*shmSurface
85 copied int
86 // shmRects is scratch for shmCopyFor.
87 shmRects []image.Rectangle
88 // marks date the frames for Trim's wall-clock eviction (trim.go).
89 marks []trimMark
90 // redrawn counts the target pixels drawn (TakeRedrawn).
91 redrawn int
92 // cursors are the exported cursor images (CursorBuffers).
93 cursors [2]*cursorImage
94 // compose draws every frame (compose.go); maxRange bounds one
95 // storage buffer binding.
96 compose composer
97 maxRange int
98 // Scratch belongs to the renderer goroutine; neither slice is published.
99 scratchDraws []draw
100 scratchCovers []image.Rectangle
101 damage damageRegion
102 damageSeen map[ports.WindowID]bool
103 damageDrawn map[ports.WindowID]heldWindow
104 frameDMAs []*imported
105 frameAcquires map[*imported]*os.File
106 frameWaits []vk.Semaphore
107 frameStages []vk.PipelineStageFlags
108}
109
110// priorityAttempts falls back to an ordinary queue when elevated priority is unavailable.
111func priorityAttempts(supported bool) []vk.QueueGlobalPriority {
112 if supported {
113 return []vk.QueueGlobalPriority{vk.QueueGlobalPriorityRealtime, vk.QueueGlobalPriorityHigh, 0}
114 }
115 return []vk.QueueGlobalPriority{0}
116}
117
118func checked(name string, result vk.Result) error {
119 if err := vk.Check(result); err != nil {
120 return fmt.Errorf("%s: %w", name, err)
121 }
122 return nil
123}
124
125func New(width, height int) (r *Renderer, err error) {
126 if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) {
127 return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height)
128 }
129 r = &Renderer{width: width, height: height, lastHDRTarget: -1, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}}
130 defer func() {
131 if err != nil {
132 r.Close()
133 r = nil
134 }
135 }()
136 if err = vk.Init(); err != nil {
137 return nil, fmt.Errorf("vk.Init: %w", err)
138 }
139 app := vk.ApplicationInfo{SType: vk.StructureTypeApplicationInfo, ApiVersion: vk.MakeVersion(1, 3, 0)}
140 info := vk.InstanceCreateInfo{SType: vk.StructureTypeInstanceCreateInfo, ApplicationInfo: &app}
141 if err = checked("vkCreateInstance", vk.Global().CreateInstance(&info, nil, &r.instance)); err != nil {
142 return
143 }
144 r.id, err = vk.LoadInstanceDispatch(r.instance)
145 if err != nil {
146 err = fmt.Errorf("LoadInstanceDispatch: %w", err)
147 return
148 }
149 var count uint32
150 if err = checked("vkEnumeratePhysicalDevices(count)", r.id.EnumeratePhysicalDevices(r.instance, &count, nil)); err != nil {
151 return
152 }
153 if count == 0 {
154 err = fmt.Errorf("no Vulkan 1.3 device")
155 return
156 }
157 devices := make([]vk.PhysicalDevice, count)
158 if err = checked("vkEnumeratePhysicalDevices", r.id.EnumeratePhysicalDevices(r.instance, &count, &devices[0])); err != nil {
159 return
160 }
161 index := -1
162 if value, ok := os.LookupEnv("NEFERWL_VK_DEVICE"); ok {
163 index, err = strconv.Atoi(value)
164 if err != nil || index < 0 || index >= int(count) {
165 err = fmt.Errorf("invalid NEFERWL_VK_DEVICE index %q", value)
166 return
167 }
168 }
169 var physical vk.PhysicalDevice
170 var family uint32
171 for i, device := range devices[:count] {
172 if index >= 0 && i != index {
173 continue
174 }
175 var properties vk.PhysicalDeviceProperties
176 r.id.GetPhysicalDeviceProperties(device, &properties)
177 if properties.ApiVersion < vk.MakeVersion(1, 3, 0) {
178 continue
179 }
180 var n uint32
181 r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil)
182 if n == 0 {
183 continue
184 }
185 families := make([]vk.QueueFamilyProperties, n)
186 r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, &families[0])
187 for j, f := range families[:n] {
188 if f.QueueCount > 0 && f.QueueFlags&vk.QueueGraphicsBit != 0 {
189 physical = device
190 family = uint32(j)
191 break
192 }
193 }
194 if physical != 0 {
195 break
196 }
197 }
198 if physical == 0 {
199 err = fmt.Errorf("no Vulkan 1.3 device with a graphics queue")
200 return
201 }
202 r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
203 var properties vk.PhysicalDeviceProperties
204 r.id.GetPhysicalDeviceProperties(physical, &properties)
205 r.maxRange = int(properties.Limits.MaxStorageBufferRange)
206 r.family = family
207 priority := float32(1)
208 qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
209 dynamicRendering := vk.PhysicalDeviceDynamicRenderingFeatures{SType: vk.StructureTypePhysicalDeviceDynamicRenderingFeatures, DynamicRendering: 1}
210 di := vk.DeviceCreateInfo{SType: vk.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&dynamicRendering), QueueCreateInfoCount: 1, QueueCreateInfos: &qi}
211 // dmabuf import needs every extension; without them clients use wl_shm.
212 var extNames [][]byte
213 var extPtrs []*byte
214 have := r.extensions(physical)
215 dmabufSupported := true
216 for _, e := range deviceExtensions {
217 if !have[e] {
218 dmabufSupported = false
219 }
220 }
221 if dmabufSupported {
222 for _, e := range deviceExtensions {
223 extNames = append(extNames, append([]byte(e), 0))
224 extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
225 }
226 di.EnabledExtensionCount = uint32(len(extPtrs))
227 di.PpEnabledExtensionNames = &extPtrs[0]
228 }
229 globalExt := ""
230 if have[vk.KHRGlobalPriorityExtensionName] {
231 globalExt = vk.KHRGlobalPriorityExtensionName
232 } else if have[vk.EXTGlobalPriorityExtensionName] {
233 globalExt = vk.EXTGlobalPriorityExtensionName
234 }
235 var global vk.DeviceQueueGlobalPriorityCreateInfo
236 chosen := "default"
237 for _, attempt := range priorityAttempts(globalExt != "") {
238 qi.Next = nil
239 count := len(extPtrs)
240 if attempt != 0 {
241 global = vk.DeviceQueueGlobalPriorityCreateInfo{SType: vk.StructureTypeDeviceQueueGlobalPriorityCreateInfo, GlobalPriority: attempt}
242 qi.Next = unsafe.Pointer(&global)
243 extNames = append(extNames, append([]byte(globalExt), 0))
244 extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
245 chosen = "realtime"
246 if attempt == vk.QueueGlobalPriorityHigh {
247 chosen = "high"
248 }
249 } else {
250 chosen = "default"
251 }
252 di.EnabledExtensionCount = uint32(len(extPtrs))
253 if len(extPtrs) > 0 {
254 di.PpEnabledExtensionNames = &extPtrs[0]
255 } else {
256 di.PpEnabledExtensionNames = nil
257 }
258 result := r.id.CreateDevice(physical, &di, nil, &r.device)
259 runtime.KeepAlive(global)
260 runtime.KeepAlive(qi)
261 runtime.KeepAlive(extNames)
262 runtime.KeepAlive(extPtrs)
263 extPtrs = extPtrs[:count]
264 extNames = extNames[:count]
265 if result == vk.Success {
266 err = nil
267 break
268 }
269 err = checked("vkCreateDevice", result)
270 if attempt == 0 {
271 return
272 }
273 }
274 r.queuePriority = chosen
275 r.dd, err = vk.LoadDeviceDispatch(r.id, r.device)
276 if err != nil {
277 err = fmt.Errorf("LoadDeviceDispatch: %w", err)
278 return
279 }
280 runtime.KeepAlive(extNames)
281 runtime.KeepAlive(extPtrs)
282 if dmabufSupported {
283 // Clients need the render node to allocate on the right GPU.
284 if sup := r.probeDMABuf(physical); sup.Device != 0 {
285 r.dmabuf = sup
286 r.probeRenderModifiers(physical)
287 r.physical = physical
288 }
289 // VK_KHR_external_semaphore_fd (enabled above): frames export
290 // their fence as a sync file.
291 r.syncFD = r.dd.HasGetSemaphoreFdKHR()
292 }
293 r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
294 extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1}
295 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 | vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
296 if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
297 return
298 }
299 var req vk.MemoryRequirements
300 r.dd.GetImageMemoryRequirements(r.device, r.own.image, &req)
301 var kind uint32
302 kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
303 if err != nil {
304 return
305 }
306 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
307 if err = checked("vkAllocateMemory(image)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.own.memory)); err != nil {
308 return
309 }
310 if err = checked("vkBindImageMemory", r.dd.BindImageMemory(r.device, r.own.image, r.own.memory, 0)); err != nil {
311 return
312 }
313 pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family}
314 if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil {
315 return
316 }
317 if err = r.createSlots(); err != nil {
318 return
319 }
320 if err = r.createView(&r.own); err != nil {
321 return
322 }
323 if err = r.createComposer(); err != nil {
324 return
325 }
326 runtime.KeepAlive(priority)
327 return
328}
329
330func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (uint32, error) {
331 for i := uint32(0); i < r.memory.MemoryTypeCount; i++ {
332 if bits&(uint32(1)<<i) != 0 && r.memory.MemoryTypes[i].PropertyFlags&props == props {
333 return i, nil
334 }
335 }
336 return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props)
337}
338
339// CopiedBytes reports cumulative wl_shm bytes copied into GPU storage.
340// It is diagnostic only and must be read by the renderer's owner goroutine.
341func (r *Renderer) CopiedBytes() int { return r.copied }
342
343// TakeRedrawn returns the target pixels drawn since the last call and
344// resets the count.
345func (r *Renderer) TakeRedrawn() int {
346 n := r.redrawn
347 r.redrawn = 0
348 return n
349}
350
351// SetLogger sets the logger (with its component field) for render target
352// events. Not on the port; the zero logger discards.
353func (r *Renderer) SetLogger(l zerowrap.Logger) { r.log = l }
354
355// QueuePriority is the global priority the queue got: realtime, high or
356// default.
357func (r *Renderer) QueuePriority() string { return r.queuePriority }
358
359// Pixels reads the last frame back through a capture slot, waiting for
360// the GPU on the calling goroutine (slot fence, no sync file needed):
361// headless screenshots and tests only. Nil when no slot is free (both
362// leased to captures) or the copy failed: never a silent black image.
363func (r *Renderer) Pixels() *image.RGBA {
364 cf, err := r.debugCapture()
365 if err != nil {
366 return nil
367 }
368 defer r.EndCapture(cf)
369 out := image.NewRGBA(image.Rect(0, 0, r.width, r.height))
370 if err := cf.Read(image.Rect(0, 0, r.width, r.height), out.Pix, out.Stride); err != nil {
371 return nil
372 }
373 for i := 0; i < len(out.Pix); i += 4 {
374 out.Pix[i], out.Pix[i+2] = out.Pix[i+2], out.Pix[i]
375 }
376 return out
377}
378
379// SetHDRReadback makes the exported HDR targets transfer sources so that
380// HDRPixels can read them (they are not in production: the DRM path never
381// sets it). Call before ExportTargets. Not on the port.
382func (r *Renderer) SetHDRReadback(on bool) { r.hdrReadback = on }
383
384// HDRPixels reads the last HDR frame's XRGB2101010 target back, each 10-bit
385// code scaled to 16 bits (c<<6 | c>>4), alpha opaque. It waits for the GPU:
386// debug only. Nil when the output is SDR, the target is not readable (no
387// SetHDRReadback), no HDR frame was rendered, every slot is leased or the
388// copy failed.
389func (r *Renderer) HDRPixels() *image.RGBA64 {
390 var out *image.RGBA64
391 err := r.readHDRTarget(func(words []uint32) {
392 out = image.NewRGBA64(image.Rect(0, 0, r.width, r.height))
393 for i, w := range words {
394 p := out.Pix[i*8 : i*8+8]
395 for ch, c := range [3]uint32{w >> 20 & 1023, w >> 10 & 1023, w & 1023} {
396 v := uint16(c<<6 | c>>4)
397 p[ch*2], p[ch*2+1] = byte(v>>8), byte(v)
398 }
399 p[6], p[7] = 0xff, 0xff
400 }
401 })
402 if err != nil {
403 return nil
404 }
405 return out
406}
407
408// Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE.
409const (
410 queueFamilyIgnored = ^uint32(0)
411 wholeSize = ^vk.DeviceSize(0)
412)
413
414func (r *Renderer) Close() {
415 if r == nil {
416 return
417 }
418 if r.dd != nil {
419 d := r.dd
420 if r.device != 0 {
421 _ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device))
422 r.idle()
423 }
424 r.dropTargets()
425 r.destroyHDR()
426 r.dropCursors()
427 for id, im := range r.imports {
428 r.release(im)
429 delete(r.imports, id)
430 }
431 r.frame += importTTL + 1
432 r.dropPools()
433 r.dropShm()
434 r.destroySlots()
435 r.destroyCaptures()
436 if r.pool != 0 {
437 d.DestroyCommandPool(r.device, r.pool, nil)
438 r.pool = 0
439 }
440 r.destroyComposer()
441 r.freeTarget(&r.hdrOwn)
442 r.freeTarget(&r.own)
443 d.DestroyDevice(r.device, nil)
444 r.device = 0
445 } else if r.device != 0 && r.id != nil {
446 if vk.VkDestroyDevice != nil {
447 vk.VkDestroyDevice(r.device, nil)
448 }
449 r.device = 0
450 }
451 if r.instance != 0 {
452 if r.id != nil {
453 r.id.DestroyInstance(r.instance, nil)
454 } else if vk.VkDestroyInstance != nil {
455 vk.VkDestroyInstance(r.instance, nil)
456 }
457 r.instance = 0
458 }
459}