1package gpu
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "math"
8 "unsafe"
9
10 "github.com/bnema/purego-vulkan/vulkan"
11 "golang.org/x/sys/unix"
12
13 "github.com/bnema/neferbar/internal/glyph"
14 "github.com/bnema/neferbar/internal/gpu/shaders"
15 "github.com/bnema/neferbar/internal/syncobj"
16)
17
18const (
19 atlasSize = 2048
20 // SlotCount is the number of DMA-BUF images: one on screen, one being
21 // shown or queued, one being drawn.
22 SlotCount = 3
23 // maxUploads bounds the glyphs rasterized by one Draw.
24 maxUploads = 256
25)
26
27// Cell is one grid cell as the renderer consumes it.
28type Cell struct {
29 Rune rune
30 FG, BG [3]uint8
31 Style glyph.Style
32}
33
34// instance is the per-cell vertex data (64 bytes, four vec4).
35type instance struct {
36 Rect [4]float32 // x, y, w, h in pixels
37 UV [4]float32 // atlas u0, v0, u1, v1
38 FG [4]float32
39 BG [4]float32
40}
41
42type glyphKey struct {
43 r rune
44 style glyph.Style
45}
46
47type slotState uint8
48
49const (
50 slotFree slotState = iota
51 slotShown
52)
53
54// slot is one DMA-BUF image with everything needed to draw and present it.
55type slot struct {
56 img *Image
57 pool vulkan.CommandPool
58 cmd vulkan.CommandBuffer
59 fence vulkan.Fence
60
61 instBuf, stageBuf vulkan.Buffer
62 instMem, stageMem vulkan.DeviceMemory
63 inst []instance // mapped
64 stage []byte // mapped
65
66 releaseHandle uint32
67 releaseFD int // syncobj fd, exported once
68 eventFD int // readable when releasePoint signals
69 releasePoint uint64
70 presented bool
71 state slotState
72
73 // Scratch argument structs: the bindings heap-allocate pointer arguments,
74 // so a call built from locals allocates on every frame.
75 begin vulkan.CommandBufferBeginInfo
76 barrier [1]vulkan.ImageMemoryBarrier2
77 dep vulkan.DependencyInfo
78 color vulkan.RenderingAttachmentInfo
79 render vulkan.RenderingInfo
80 cmdInfo vulkan.CommandBufferSubmitInfo
81 submit vulkan.SubmitInfo2
82 offset vulkan.DeviceSize
83 push [2]float32
84 regions []vulkan.BufferImageCopy
85 sets [1]vulkan.DescriptorSet
86 atlasSub vulkan.ImageSubresourceRange
87}
88
89// Frame describes one drawn slot for the Wayland layer. Fields stay valid
90// until the next Draw.
91type Frame struct {
92 Slot int
93 NewBuffer bool // import Image as a wl_buffer
94 Image *Image
95 AcquirePoint uint64
96 ReleasePoint uint64
97 ReleaseEventFD int // readable when ReleasePoint signals; stable per slot
98 ReleaseTimeline int // syncobj fd of the slot's release timeline; valid with NewBuffer
99}
100
101// Renderer draws a row of cells into a pool of DMA-BUF images.
102type Renderer struct {
103 dev *Device
104 node *syncobj.Node
105 face *glyph.Face
106
107 W, H int32
108 cols, maxCols int
109 yOff float32
110
111 slots [SlotCount]slot
112
113 acquireHandle uint32
114 acquireFD int
115 acquirePoint uint64
116
117 // atlas
118 atlasImg vulkan.Image
119 atlasMem vulkan.DeviceMemory
120 atlasView vulkan.ImageView
121 sampler vulkan.Sampler
122 setLayout vulkan.DescriptorSetLayout
123 descPool vulkan.DescriptorPool
124 descSet vulkan.DescriptorSet
125 pipeLayout vulkan.PipelineLayout
126 pipeline vulkan.Pipeline
127 atlasCols int
128 atlasRows int
129 glyphs map[glyphKey]uint32
130 nextGlyph uint32
131 atlasInited bool
132
133 uploadKeys []glyphKey // glyphs added during the current Draw
134 uploadIdx []uint32
135}
136
137// NewRenderer builds the pipeline, the atlas and the image pool for a w x h
138// target. node must be the render node of dev.
139func NewRenderer(dev *Device, node *syncobj.Node, mod Modifier, face *glyph.Face, w, h int32) (r *Renderer, err error) {
140 if face.CellH > int(h) {
141 return nil, fmt.Errorf("gpu: cell height %d exceeds bar height %d", face.CellH, h)
142 }
143 r = &Renderer{dev: dev, node: node, face: face, W: w, H: h, acquireFD: -1,
144 glyphs: make(map[glyphKey]uint32, 256), yOff: float32((int(h) - face.CellH) / 2)}
145 r.cols = (int(w) + face.CellW - 1) / face.CellW
146 r.maxCols = r.cols
147 for i := range r.slots {
148 r.slots[i].releaseFD, r.slots[i].eventFD = -1, -1
149 }
150 defer func() {
151 if err != nil {
152 r.Close()
153 r = nil
154 }
155 }()
156 if err = r.newAtlas(); err != nil {
157 return nil, err
158 }
159 if err = r.newPipeline(); err != nil {
160 return nil, err
161 }
162 if r.acquireHandle, err = node.Create(); err != nil {
163 return nil, err
164 }
165 if r.acquireFD, err = node.Export(r.acquireHandle); err != nil {
166 return nil, err
167 }
168 for i := range r.slots {
169 if err = r.newSlot(i, mod); err != nil {
170 return nil, err
171 }
172 }
173 // Glyph 0 is the blank cell. Printable ASCII is assigned now and uploaded
174 // with the first frame.
175 r.glyphs[glyphKey{' ', 0}] = 0
176 r.nextGlyph = 1
177 for c := rune(0x21); c < 0x7f; c++ {
178 r.glyphs[glyphKey{c, 0}] = r.nextGlyph
179 r.nextGlyph++
180 }
181 r.uploadKeys = make([]glyphKey, 0, maxUploads)
182 r.uploadIdx = make([]uint32, 0, maxUploads)
183 return r, nil
184}
185
186func (r *Renderer) buffer(size vulkan.DeviceSize, usage vulkan.BufferUsageFlags) (vulkan.Buffer, vulkan.DeviceMemory, unsafe.Pointer, error) {
187 d := r.dev
188 var buf vulkan.Buffer
189 ci := vulkan.BufferCreateInfo{SType: vulkan.StructureTypeBufferCreateInfo, Size: size, Usage: usage, SharingMode: vulkan.SharingModeExclusive}
190 if err := vulkan.Check(d.disp.CreateBuffer(d.logical, &ci, nil, &buf)); err != nil {
191 return 0, 0, nil, err
192 }
193 var req vulkan.MemoryRequirements
194 d.disp.GetBufferMemoryRequirements(d.logical, buf, &req)
195 idx, err := d.memoryType(req.MemoryTypeBits, vulkan.MemoryPropertyHostVisibleBit|vulkan.MemoryPropertyHostCoherentBit)
196 if err != nil {
197 d.disp.DestroyBuffer(d.logical, buf, nil)
198 return 0, 0, nil, err
199 }
200 var mem vulkan.DeviceMemory
201 ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: idx}
202 if err = vulkan.Check(d.disp.AllocateMemory(d.logical, &ai, nil, &mem)); err != nil {
203 d.disp.DestroyBuffer(d.logical, buf, nil)
204 return 0, 0, nil, err
205 }
206 fail := func(err error) (vulkan.Buffer, vulkan.DeviceMemory, unsafe.Pointer, error) {
207 d.disp.FreeMemory(d.logical, mem, nil)
208 d.disp.DestroyBuffer(d.logical, buf, nil)
209 return 0, 0, nil, err
210 }
211 if err = vulkan.Check(d.disp.BindBufferMemory(d.logical, buf, mem, 0)); err != nil {
212 return fail(err)
213 }
214 var ptr unsafe.Pointer
215 if err = vulkan.Check(d.disp.MapMemory(d.logical, mem, 0, size, 0, &ptr)); err != nil {
216 return fail(err)
217 }
218 return buf, mem, ptr, nil
219}
220
221func (r *Renderer) newSlot(i int, mod Modifier) (err error) {
222 d := r.dev
223 s := &r.slots[i]
224 if s.img, err = d.NewImage(r.W, r.H, mod); err != nil {
225 return err
226 }
227 pi := vulkan.CommandPoolCreateInfo{SType: vulkan.StructureTypeCommandPoolCreateInfo, QueueFamilyIndex: d.family, Flags: vulkan.CommandPoolCreateResetCommandBufferBit}
228 if err = vulkan.Check(d.disp.CreateCommandPool(d.logical, &pi, nil, &s.pool)); err != nil {
229 return err
230 }
231 ai := vulkan.CommandBufferAllocateInfo{SType: vulkan.StructureTypeCommandBufferAllocateInfo, CommandPool: s.pool, Level: vulkan.CommandBufferLevelPrimary, CommandBufferCount: 1}
232 if err = vulkan.Check(d.disp.AllocateCommandBuffers(d.logical, &ai, &s.cmd)); err != nil {
233 return err
234 }
235 fi := vulkan.FenceCreateInfo{SType: vulkan.StructureTypeFenceCreateInfo}
236 if err = vulkan.Check(d.disp.CreateFence(d.logical, &fi, nil, &s.fence)); err != nil {
237 return err
238 }
239 instBytes := vulkan.DeviceSize(r.maxCols) * vulkan.DeviceSize(unsafe.Sizeof(instance{}))
240 var p unsafe.Pointer
241 if s.instBuf, s.instMem, p, err = r.buffer(instBytes, vulkan.BufferUsageVertexBufferBit); err != nil {
242 return err
243 }
244 s.inst = unsafe.Slice((*instance)(p), r.maxCols)
245 stageBytes := maxUploads * r.face.CellW * r.face.CellH
246 if s.stageBuf, s.stageMem, p, err = r.buffer(vulkan.DeviceSize(stageBytes), vulkan.BufferUsageTransferSrcBit); err != nil {
247 return err
248 }
249 s.stage = unsafe.Slice((*byte)(p), stageBytes)
250 s.regions = make([]vulkan.BufferImageCopy, 0, maxUploads)
251 s.atlasSub = vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
252 if s.releaseHandle, err = r.node.Create(); err != nil {
253 return err
254 }
255 if s.releaseFD, err = r.node.Export(s.releaseHandle); err != nil {
256 return err
257 }
258 if s.eventFD, err = unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK); err != nil {
259 return err
260 }
261 return nil
262}
263
264func (r *Renderer) newAtlas() error {
265 d := r.dev
266 r.atlasCols = atlasSize / r.face.CellW
267 r.atlasRows = atlasSize / r.face.CellH
268 if r.atlasCols < 1 || r.atlasRows < 1 {
269 return fmt.Errorf("gpu: cell %dx%d does not fit the atlas", r.face.CellW, r.face.CellH)
270 }
271 ci := vulkan.ImageCreateInfo{
272 SType: vulkan.StructureTypeImageCreateInfo, ImageType: vulkan.ImageType2d, Format: vulkan.FormatR8Unorm,
273 Extent: vulkan.Extent3D{Width: atlasSize, Height: atlasSize, Depth: 1},
274 MipLevels: 1, ArrayLayers: 1, Samples: vulkan.SampleCount1Bit, Tiling: vulkan.ImageTilingOptimal,
275 Usage: vulkan.ImageUsageSampledBit | vulkan.ImageUsageTransferDstBit,
276 SharingMode: vulkan.SharingModeExclusive, InitialLayout: vulkan.ImageLayoutUndefined,
277 }
278 if err := vulkan.Check(d.disp.CreateImage(d.logical, &ci, nil, &r.atlasImg)); err != nil {
279 return fmt.Errorf("gpu: create atlas: %w", err)
280 }
281 var req vulkan.MemoryRequirements
282 d.disp.GetImageMemoryRequirements(d.logical, r.atlasImg, &req)
283 idx, err := d.memoryType(req.MemoryTypeBits, vulkan.MemoryPropertyDeviceLocalBit)
284 if err != nil {
285 return err
286 }
287 ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: idx}
288 if err = vulkan.Check(d.disp.AllocateMemory(d.logical, &ai, nil, &r.atlasMem)); err != nil {
289 return err
290 }
291 if err = vulkan.Check(d.disp.BindImageMemory(d.logical, r.atlasImg, r.atlasMem, 0)); err != nil {
292 return err
293 }
294 vi := vulkan.ImageViewCreateInfo{
295 SType: vulkan.StructureTypeImageViewCreateInfo, Image: r.atlasImg, ViewType: vulkan.ImageViewType2d, Format: vulkan.FormatR8Unorm,
296 SubresourceRange: vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1},
297 }
298 if err = vulkan.Check(d.disp.CreateImageView(d.logical, &vi, nil, &r.atlasView)); err != nil {
299 return err
300 }
301 si := vulkan.SamplerCreateInfo{
302 SType: vulkan.StructureTypeSamplerCreateInfo, MagFilter: vulkan.FilterNearest, MinFilter: vulkan.FilterNearest,
303 MipmapMode: vulkan.SamplerMipmapModeNearest,
304 AddressModeU: vulkan.SamplerAddressModeClampToEdge, AddressModeV: vulkan.SamplerAddressModeClampToEdge, AddressModeW: vulkan.SamplerAddressModeClampToEdge,
305 }
306 if err = vulkan.Check(d.disp.CreateSampler(d.logical, &si, nil, &r.sampler)); err != nil {
307 return err
308 }
309 b := vulkan.DescriptorSetLayoutBinding{DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vulkan.ShaderStageFragmentBit}
310 li := vulkan.DescriptorSetLayoutCreateInfo{SType: vulkan.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 1, Bindings: &b}
311 if err = vulkan.Check(d.disp.CreateDescriptorSetLayout(d.logical, &li, nil, &r.setLayout)); err != nil {
312 return err
313 }
314 ps := vulkan.DescriptorPoolSize{Type: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}
315 pi := vulkan.DescriptorPoolCreateInfo{SType: vulkan.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &ps}
316 if err = vulkan.Check(d.disp.CreateDescriptorPool(d.logical, &pi, nil, &r.descPool)); err != nil {
317 return err
318 }
319 ds := vulkan.DescriptorSetAllocateInfo{SType: vulkan.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: r.descPool, DescriptorSetCount: 1, SetLayouts: &r.setLayout}
320 if err = vulkan.Check(d.disp.AllocateDescriptorSets(d.logical, &ds, &r.descSet)); err != nil {
321 return err
322 }
323 info := vulkan.DescriptorImageInfo{Sampler: r.sampler, ImageView: r.atlasView, ImageLayout: vulkan.ImageLayoutShaderReadOnlyOptimal}
324 w := vulkan.WriteDescriptorSet{SType: vulkan.StructureTypeWriteDescriptorSet, DstSet: r.descSet, DescriptorCount: 1, DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, ImageInfo: &info}
325 d.disp.UpdateDescriptorSets(d.logical, 1, &w, 0, nil)
326 return nil
327}
328
329func (r *Renderer) shader(code []byte) (vulkan.ShaderModule, error) {
330 if len(code) < 20 || len(code)%4 != 0 || binary.LittleEndian.Uint32(code) != 0x07230203 {
331 return 0, errors.New("gpu: invalid embedded SPIR-V")
332 }
333 words := make([]uint32, len(code)/4)
334 for i := range words {
335 words[i] = le32(code[i*4:])
336 }
337 ci := vulkan.ShaderModuleCreateInfo{SType: vulkan.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(code)), Code: &words[0]}
338 var m vulkan.ShaderModule
339 if err := vulkan.Check(r.dev.disp.CreateShaderModule(r.dev.logical, &ci, nil, &m)); err != nil {
340 return 0, err
341 }
342 return m, nil
343}
344
345func (r *Renderer) newPipeline() error {
346 d := r.dev
347 pc := vulkan.PushConstantRange{StageFlags: vulkan.ShaderStageVertexBit, Size: 8}
348 li := vulkan.PipelineLayoutCreateInfo{SType: vulkan.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &r.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &pc}
349 if err := vulkan.Check(d.disp.CreatePipelineLayout(d.logical, &li, nil, &r.pipeLayout)); err != nil {
350 return err
351 }
352 vs, err := r.shader(shaders.CellVertex)
353 if err != nil {
354 return err
355 }
356 defer d.disp.DestroyShaderModule(d.logical, vs, nil)
357 fs, err := r.shader(shaders.CellFragment)
358 if err != nil {
359 return err
360 }
361 defer d.disp.DestroyShaderModule(d.logical, fs, nil)
362 entry := []byte("main\x00")
363 stages := [2]vulkan.PipelineShaderStageCreateInfo{
364 {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageVertexBit, Module: vs, Name: &entry[0]},
365 {SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fs, Name: &entry[0]},
366 }
367 var attrs [4]vulkan.VertexInputAttributeDescription
368 for i := range attrs {
369 attrs[i] = vulkan.VertexInputAttributeDescription{Location: uint32(i), Format: vulkan.FormatR32g32b32a32Sfloat, Offset: uint32(i) * 16}
370 }
371 bind := vulkan.VertexInputBindingDescription{Stride: uint32(unsafe.Sizeof(instance{})), InputRate: vulkan.VertexInputRateInstance}
372 vin := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo, VertexBindingDescriptionCount: 1, VertexBindingDescriptions: &bind, VertexAttributeDescriptionCount: 4, VertexAttributeDescriptions: &attrs[0]}
373 asm := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}
374 vp := vulkan.Viewport{Width: float32(r.W), Height: float32(r.H), MaxDepth: 1}
375 sc := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(r.W), Height: uint32(r.H)}}
376 vps := vulkan.PipelineViewportStateCreateInfo{SType: vulkan.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &vp, ScissorCount: 1, Scissors: &sc}
377 ras := vulkan.PipelineRasterizationStateCreateInfo{SType: vulkan.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vulkan.PolygonModeFill, CullMode: vulkan.CullModeNone, FrontFace: vulkan.FrontFaceCounterClockwise, LineWidth: 1}
378 ms := vulkan.PipelineMultisampleStateCreateInfo{SType: vulkan.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vulkan.SampleCount1Bit}
379 att := vulkan.PipelineColorBlendAttachmentState{ColorWriteMask: vulkan.ColorComponentRBit | vulkan.ColorComponentGBit | vulkan.ColorComponentBBit | vulkan.ColorComponentABit}
380 blend := vulkan.PipelineColorBlendStateCreateInfo{SType: vulkan.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &att}
381 format := vulkan.Format(vulkan.FormatB8g8r8a8Unorm)
382 rend := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
383 gi := vulkan.GraphicsPipelineCreateInfo{
384 SType: vulkan.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rend), StageCount: 2, Stages: &stages[0],
385 VertexInputState: &vin, InputAssemblyState: &asm, ViewportState: &vps, RasterizationState: &ras,
386 MultisampleState: &ms, ColorBlendState: &blend, Layout: r.pipeLayout,
387 }
388 if err = vulkan.Check(d.disp.CreateGraphicsPipelines(d.logical, 0, 1, &gi, nil, &r.pipeline)); err != nil {
389 return fmt.Errorf("gpu: create pipeline: %w", err)
390 }
391 return nil
392}
393
394// queueGlyph schedules k for upload to atlas index idx in the current Draw.
395func (r *Renderer) queueGlyph(k glyphKey, idx uint32) {
396 r.uploadKeys = append(r.uploadKeys, k)
397 r.uploadIdx = append(r.uploadIdx, idx)
398}
399
400// glyph returns the atlas index of k, assigning one (and queueing its upload)
401// on first use. An exhausted atlas maps to the blank glyph.
402func (r *Renderer) glyph(k glyphKey) uint32 {
403 if idx, ok := r.glyphs[k]; ok {
404 return idx
405 }
406 if int(r.nextGlyph) >= r.atlasCols*r.atlasRows || len(r.uploadKeys) >= maxUploads {
407 return 0
408 }
409 idx := r.nextGlyph
410 r.nextGlyph++
411 r.glyphs[k] = idx
412 r.queueGlyph(k, idx)
413 return idx
414}
415
416// pickSlot returns a free slot index, or -1.
417func (r *Renderer) pickSlot() int {
418 for i := range r.slots {
419 if r.slots[i].state == slotFree {
420 return i
421 }
422 }
423 return -1
424}
425
426// Draw renders cells (len(cells) <= cols) into a free slot, waits for the GPU
427// and signals the acquire point. It reports false when every image is still
428// held by the compositor. clear is the background for pixels outside cells.
429func (r *Renderer) Draw(cells []Cell, clear [3]uint8, out *Frame) (bool, error) {
430 n := r.pickSlot()
431 if n < 0 {
432 return false, nil
433 }
434 s := &r.slots[n]
435 if len(cells) > r.maxCols {
436 cells = cells[:r.maxCols]
437 }
438 r.uploadKeys, r.uploadIdx = r.uploadKeys[:0], r.uploadIdx[:0]
439 if !r.atlasInited {
440 r.queueGlyph(glyphKey{' ', 0}, 0)
441 for c := rune(0x21); c < 0x7f; c++ {
442 r.queueGlyph(glyphKey{c, 0}, r.glyphs[glyphKey{c, 0}])
443 }
444 }
445 cw, ch := float32(r.face.CellW), float32(r.face.CellH)
446 inv := float32(1) / atlasSize
447 for i := range cells {
448 c := &cells[i]
449 gi := r.glyph(glyphKey{c.Rune, c.Style})
450 gx, gy := float32(int(gi)%r.atlasCols)*cw, float32(int(gi)/r.atlasCols)*ch
451 in := &s.inst[i]
452 in.Rect = [4]float32{float32(i) * cw, r.yOff, cw, ch}
453 in.UV = [4]float32{gx * inv, gy * inv, (gx + cw) * inv, (gy + ch) * inv}
454 in.FG = [4]float32{float32(c.FG[0]) / 255, float32(c.FG[1]) / 255, float32(c.FG[2]) / 255, 1}
455 in.BG = [4]float32{float32(c.BG[0]) / 255, float32(c.BG[1]) / 255, float32(c.BG[2]) / 255, 1}
456 }
457 cellBytes := r.face.CellW * r.face.CellH
458 s.regions = s.regions[:0]
459 for i, k := range r.uploadKeys {
460 off := i * cellBytes
461 r.face.Rasterize(k.r, k.style, s.stage[off:off+cellBytes])
462 gi := int(r.uploadIdx[i])
463 s.regions = append(s.regions, vulkan.BufferImageCopy{
464 BufferOffset: vulkan.DeviceSize(off),
465 BufferRowLength: uint32(r.face.CellW),
466 ImageSubresource: vulkan.ImageSubresourceLayers{AspectMask: vulkan.ImageAspectColorBit, LayerCount: 1},
467 ImageOffset: vulkan.Offset3D{X: int32((gi % r.atlasCols) * r.face.CellW), Y: int32((gi / r.atlasCols) * r.face.CellH)},
468 ImageExtent: vulkan.Extent3D{Width: uint32(r.face.CellW), Height: uint32(r.face.CellH), Depth: 1},
469 })
470 }
471 if err := r.record(s, len(cells), clear); err != nil {
472 return false, err
473 }
474 d := r.dev
475 s.cmdInfo = vulkan.CommandBufferSubmitInfo{SType: vulkan.StructureTypeCommandBufferSubmitInfo, CommandBuffer: s.cmd}
476 s.submit = vulkan.SubmitInfo2{SType: vulkan.StructureTypeSubmitInfo2, CommandBufferInfoCount: 1, CommandBufferInfos: &s.cmdInfo}
477 if err := vulkan.Check(d.disp.QueueSubmit2(d.queue, 1, &s.submit, s.fence)); err != nil {
478 return false, fmt.Errorf("gpu: submit: %w", err)
479 }
480 // The draw is tiny: wait for it here, then signal the acquire point from
481 // the CPU. No sync-file import is needed.
482 if err := vulkan.Check(d.disp.WaitForFences(d.logical, 1, &s.fence, 1, math.MaxUint64)); err != nil {
483 return false, fmt.Errorf("gpu: wait fence: %w", err)
484 }
485 if err := vulkan.Check(d.disp.ResetFences(d.logical, 1, &s.fence)); err != nil {
486 return false, err
487 }
488 r.atlasInited = true
489 r.acquirePoint++
490 if err := r.node.Signal(r.acquireHandle, r.acquirePoint); err != nil {
491 return false, err
492 }
493 s.releasePoint++
494 if err := r.node.EventFD(s.releaseHandle, s.releasePoint, s.eventFD); err != nil {
495 return false, err
496 }
497 s.state = slotShown
498 *out = Frame{
499 Slot: n, NewBuffer: !s.presented, Image: s.img,
500 AcquirePoint: r.acquirePoint, ReleasePoint: s.releasePoint,
501 ReleaseEventFD: s.eventFD, ReleaseTimeline: s.releaseFD,
502 }
503 s.presented = true
504 return true, nil
505}
506
507func (r *Renderer) record(s *slot, count int, clearRGB [3]uint8) error {
508 d := r.dev
509 disp := d.disp
510 if err := vulkan.Check(disp.ResetCommandBuffer(s.cmd, 0)); err != nil {
511 return err
512 }
513 s.begin = vulkan.CommandBufferBeginInfo{SType: vulkan.StructureTypeCommandBufferBeginInfo, Flags: vulkan.CommandBufferUsageOneTimeSubmitBit}
514 if err := vulkan.Check(disp.BeginCommandBuffer(s.cmd, &s.begin)); err != nil {
515 return err
516 }
517 s.dep = vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: &s.barrier[0]}
518 barrier := &s.barrier[0]
519 if len(s.regions) > 0 || !r.atlasInited {
520 old := vulkan.ImageLayout(vulkan.ImageLayoutShaderReadOnlyOptimal)
521 srcStage, srcAccess := vulkan.PipelineStageFlags2(vulkan.PipelineStage2FragmentShaderBit), vulkan.AccessFlags2(vulkan.Access2ShaderSampledReadBit)
522 if !r.atlasInited {
523 old, srcStage, srcAccess = vulkan.ImageLayoutUndefined, vulkan.PipelineStage2None, 0
524 }
525 *barrier = vulkan.ImageMemoryBarrier2{
526 SType: vulkan.StructureTypeImageMemoryBarrier2, SrcStageMask: srcStage, SrcAccessMask: srcAccess,
527 DstStageMask: vulkan.PipelineStage2AllTransferBit, DstAccessMask: vulkan.Access2TransferWriteBit,
528 OldLayout: old, NewLayout: vulkan.ImageLayoutTransferDstOptimal,
529 SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: r.atlasImg, SubresourceRange: s.atlasSub,
530 }
531 disp.CmdPipelineBarrier2(s.cmd, &s.dep)
532 if !r.atlasInited {
533 var zero vulkan.ClearColorValue
534 disp.CmdClearColorImage(s.cmd, r.atlasImg, vulkan.ImageLayoutTransferDstOptimal, &zero, 1, &s.atlasSub)
535 barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2AllTransferBit, vulkan.Access2TransferWriteBit
536 barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal
537 disp.CmdPipelineBarrier2(s.cmd, &s.dep)
538 }
539 if len(s.regions) > 0 {
540 disp.CmdCopyBufferToImage(s.cmd, s.stageBuf, r.atlasImg, vulkan.ImageLayoutTransferDstOptimal, uint32(len(s.regions)), &s.regions[0])
541 }
542 barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2AllTransferBit, vulkan.Access2TransferWriteBit
543 barrier.DstStageMask, barrier.DstAccessMask = vulkan.PipelineStage2FragmentShaderBit, vulkan.Access2ShaderSampledReadBit
544 barrier.OldLayout, barrier.NewLayout = vulkan.ImageLayoutTransferDstOptimal, vulkan.ImageLayoutShaderReadOnlyOptimal
545 disp.CmdPipelineBarrier2(s.cmd, &s.dep)
546 }
547 old := vulkan.ImageLayout(vulkan.ImageLayoutGeneral)
548 if !s.img.used {
549 old = vulkan.ImageLayoutUndefined
550 }
551 *barrier = vulkan.ImageMemoryBarrier2{
552 SType: vulkan.StructureTypeImageMemoryBarrier2, SrcStageMask: vulkan.PipelineStage2None,
553 DstStageMask: vulkan.PipelineStage2ColorAttachmentOutputBit, DstAccessMask: vulkan.Access2ColorAttachmentWriteBit,
554 OldLayout: old, NewLayout: vulkan.ImageLayoutColorAttachmentOptimal,
555 SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: s.img.image, SubresourceRange: s.atlasSub,
556 }
557 disp.CmdPipelineBarrier2(s.cmd, &s.dep)
558 var cv vulkan.ClearValue
559 cv[0], cv[1], cv[2], cv[3] = math.Float32bits(float32(clearRGB[0])/255), math.Float32bits(float32(clearRGB[1])/255), math.Float32bits(float32(clearRGB[2])/255), math.Float32bits(1)
560 s.color = vulkan.RenderingAttachmentInfo{
561 SType: vulkan.StructureTypeRenderingAttachmentInfo, ImageView: s.img.view, ImageLayout: vulkan.ImageLayoutColorAttachmentOptimal,
562 LoadOp: vulkan.AttachmentLoadOpClear, StoreOp: vulkan.AttachmentStoreOpStore, ClearValue: cv,
563 }
564 s.render = vulkan.RenderingInfo{
565 SType: vulkan.StructureTypeRenderingInfo,
566 RenderArea: vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(r.W), Height: uint32(r.H)}},
567 LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &s.color,
568 }
569 disp.CmdBeginRendering(s.cmd, &s.render)
570 disp.CmdBindPipeline(s.cmd, vulkan.PipelineBindPointGraphics, r.pipeline)
571 s.sets[0] = r.descSet
572 disp.CmdBindDescriptorSets(s.cmd, vulkan.PipelineBindPointGraphics, r.pipeLayout, 0, 1, &s.sets[0], 0, nil)
573 s.push = [2]float32{float32(r.W), float32(r.H)}
574 disp.CmdPushConstants(s.cmd, r.pipeLayout, vulkan.ShaderStageVertexBit, 0, 8, unsafe.Pointer(&s.push))
575 s.offset = 0
576 disp.CmdBindVertexBuffers(s.cmd, 0, 1, &s.instBuf, &s.offset)
577 if count > 0 {
578 disp.CmdDraw(s.cmd, 6, uint32(count), 0, 0)
579 }
580 disp.CmdEndRendering(s.cmd)
581 // Leave the image in GENERAL for the compositor.
582 barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2ColorAttachmentOutputBit, vulkan.Access2ColorAttachmentWriteBit
583 barrier.DstStageMask, barrier.DstAccessMask = vulkan.PipelineStage2AllCommandsBit, vulkan.Access2MemoryReadBit
584 barrier.OldLayout, barrier.NewLayout = vulkan.ImageLayoutColorAttachmentOptimal, vulkan.ImageLayoutGeneral
585 disp.CmdPipelineBarrier2(s.cmd, &s.dep)
586 s.img.used = true
587 return vulkan.Check(disp.EndCommandBuffer(s.cmd))
588}
589
590// Released handles a readable release eventfd of slot: when the compositor's
591// release point has signaled, the image may be drawn again.
592func (r *Renderer) Released(slotIdx int) error {
593 if slotIdx < 0 || slotIdx >= SlotCount {
594 return nil
595 }
596 s := &r.slots[slotIdx]
597 var counter [8]byte
598 if _, err := unix.Read(s.eventFD, counter[:]); err != nil && !errors.Is(err, unix.EAGAIN) {
599 return fmt.Errorf("gpu: read release eventfd: %w", err)
600 }
601 if s.state != slotShown {
602 return nil
603 }
604 point, err := r.node.Query(s.releaseHandle)
605 if err != nil {
606 return err
607 }
608 if point >= s.releasePoint {
609 s.state = slotFree
610 }
611 return nil
612}
613
614// AcquireTimelineFD is the syncobj fd of the shared acquire timeline.
615func (r *Renderer) AcquireTimelineFD() int { return r.acquireFD }
616
617// Cols is the number of cells that fit the width, rounding up.
618func (r *Renderer) Cols() int { return r.cols }
619
620// Close waits for the GPU and frees everything. The compositor's imports keep
621// their own kernel references.
622func (r *Renderer) Close() {
623 if r == nil {
624 return
625 }
626 d := r.dev
627 _ = d.WaitIdle()
628 for i := range r.slots {
629 s := &r.slots[i]
630 if s.eventFD >= 0 {
631 _ = unix.Close(s.eventFD)
632 }
633 if s.releaseFD >= 0 {
634 _ = unix.Close(s.releaseFD)
635 }
636 if s.releaseHandle != 0 {
637 _ = r.node.Destroy(s.releaseHandle)
638 }
639 for _, b := range [...]struct {
640 buf vulkan.Buffer
641 mem vulkan.DeviceMemory
642 }{{s.instBuf, s.instMem}, {s.stageBuf, s.stageMem}} {
643 if b.mem != 0 {
644 d.disp.UnmapMemory(d.logical, b.mem)
645 d.disp.FreeMemory(d.logical, b.mem, nil)
646 }
647 if b.buf != 0 {
648 d.disp.DestroyBuffer(d.logical, b.buf, nil)
649 }
650 }
651 if s.fence != 0 {
652 d.disp.DestroyFence(d.logical, s.fence, nil)
653 }
654 if s.pool != 0 {
655 d.disp.DestroyCommandPool(d.logical, s.pool, nil)
656 }
657 s.img.Close()
658 }
659 if r.acquireFD >= 0 {
660 _ = unix.Close(r.acquireFD)
661 }
662 if r.acquireHandle != 0 {
663 _ = r.node.Destroy(r.acquireHandle)
664 }
665 if r.pipeline != 0 {
666 d.disp.DestroyPipeline(d.logical, r.pipeline, nil)
667 }
668 if r.pipeLayout != 0 {
669 d.disp.DestroyPipelineLayout(d.logical, r.pipeLayout, nil)
670 }
671 if r.descPool != 0 {
672 d.disp.DestroyDescriptorPool(d.logical, r.descPool, nil)
673 }
674 if r.setLayout != 0 {
675 d.disp.DestroyDescriptorSetLayout(d.logical, r.setLayout, nil)
676 }
677 if r.sampler != 0 {
678 d.disp.DestroySampler(d.logical, r.sampler, nil)
679 }
680 if r.atlasView != 0 {
681 d.disp.DestroyImageView(d.logical, r.atlasView, nil)
682 }
683 if r.atlasImg != 0 {
684 d.disp.DestroyImage(d.logical, r.atlasImg, nil)
685 }
686 if r.atlasMem != 0 {
687 d.disp.FreeMemory(d.logical, r.atlasMem, nil)
688 }
689}