1//go:build linux
2
3package vkdevice
4
5import (
6 "fmt"
7 "math"
8 "unsafe"
9
10 "github.com/bnema/nefergui/internal/text"
11 "github.com/bnema/purego-vulkan/vulkan"
12)
13
14// Frame records into its own command buffer and signals a binary semaphore.
15// Do not reset the command buffer or destroy the semaphore until its GPU fence
16// completes. The compositor's release point is an independent ownership gate.
17type Frame struct {
18 device *Device
19 Pool vulkan.CommandPool
20 Commands vulkan.CommandBuffer
21 Done vulkan.Fence
22 Signal *BinaryFence
23 View vulkan.ImageView
24 submitted bool
25 Readback *Readback
26 instances *instanceBuffer
27 glyphStaging *instanceBuffer
28 imageStaging *instanceBuffer
29 imageCopies []imageCopy
30 imageSerial uint64
31 atlasRecorded bool
32 fallbackRecorded bool
33 // signalExported is set once the acquire SYNC_FD was exported. Exporting
34 // with copy transference resets the binary semaphore, so Signal may be
35 // signaled again after the frame's fence completes.
36 signalExported bool
37 scratch frameScratch
38}
39
40// frameScratch holds the Vulkan info structs and out-parameters of one
41// recording and submission. The purego bindings force every pointer argument
42// to the heap, so building these per call allocated each frame. A Frame is
43// used by a single owner and its structs are only read by the driver during
44// the call that receives them (the command buffer copies recorded state), so
45// they are rebuilt and reused on every call.
46type frameScratch struct {
47 begin vulkan.CommandBufferBeginInfo
48 barrier vulkan.ImageMemoryBarrier2
49 dependency vulkan.DependencyInfo
50 attachment vulkan.RenderingAttachmentInfo
51 rendering vulkan.RenderingInfo
52 sets [2]vulkan.DescriptorSet
53 screen [2]float32
54 offset vulkan.DeviceSize
55 cmd vulkan.CommandBufferSubmitInfo
56 signal vulkan.SemaphoreSubmitInfo
57 waiting vulkan.SemaphoreSubmitInfo
58 submit vulkan.SubmitInfo2
59 mapped unsafe.Pointer
60}
61
62func (d *Device) NewFrame(img *Image) (f *Frame, err error) {
63 f = &Frame{device: d}
64 defer func() {
65 if err != nil {
66 f.Close()
67 }
68 }()
69 pool := vulkan.CommandPoolCreateInfo{SType: vulkan.StructureTypeCommandPoolCreateInfo, QueueFamilyIndex: d.QueueFamily, Flags: vulkan.CommandPoolCreateResetCommandBufferBit}
70 if err = vulkan.Check(d.Dispatch.CreateCommandPool(d.Logical, &pool, nil, &f.Pool)); err != nil {
71 return nil, err
72 }
73 allocate := vulkan.CommandBufferAllocateInfo{SType: vulkan.StructureTypeCommandBufferAllocateInfo, CommandPool: f.Pool, Level: vulkan.CommandBufferLevelPrimary, CommandBufferCount: 1}
74 if err = vulkan.Check(d.Dispatch.AllocateCommandBuffers(d.Logical, &allocate, &f.Commands)); err != nil {
75 return nil, err
76 }
77 view := vulkan.ImageViewCreateInfo{SType: vulkan.StructureTypeImageViewCreateInfo, Image: img.Image, ViewType: vulkan.ImageViewType2d, Format: vulkan.FormatB8g8r8a8Unorm, SubresourceRange: vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}}
78 if err = vulkan.Check(d.Dispatch.CreateImageView(d.Logical, &view, nil, &f.View)); err != nil {
79 return nil, err
80 }
81 fence := vulkan.FenceCreateInfo{SType: vulkan.StructureTypeFenceCreateInfo}
82 if err = vulkan.Check(d.Dispatch.CreateFence(d.Logical, &fence, nil, &f.Done)); err != nil {
83 return nil, err
84 }
85 f.Signal, err = d.NewBinaryFence(true)
86 if err != nil {
87 return nil, err
88 }
89 return f, nil
90}
91
92func (f *Frame) Ready() (bool, error) {
93 if !f.submitted {
94 return true, nil
95 }
96 r := f.device.Dispatch.WaitForFences(f.device.Logical, 1, &f.Done, 1, 0)
97 if r == vulkan.Timeout {
98 return false, nil
99 }
100 if err := vulkan.Check(r); err != nil {
101 return false, err
102 }
103 return true, nil
104}
105
106// RecordList records an ordered list of physical-pixel quads. Reuse is gated
107// by Ready, including any instance-buffer growth.
108func (f *Frame) RecordList(img *Image, pipe *Pipeline, width, height int32, instances []Instance, uploads []text.Upload, batches []Batch, initial, capture bool) error {
109 return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches)
110}
111
112func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch) (err error) {
113 if width <= 0 || height <= 0 || pipe == nil {
114 return fmt.Errorf("invalid render dimensions or pipeline")
115 }
116 ready, err := f.Ready()
117 if err != nil || !ready {
118 return fmt.Errorf("command buffer still in use: ready=%v err=%v", ready, err)
119 }
120 f.settleImages()
121 if f.submitted {
122 if err = vulkan.Check(f.device.Dispatch.ResetFences(f.device.Logical, 1, &f.Done)); err != nil {
123 return err
124 }
125 if err = vulkan.Check(f.device.Dispatch.ResetCommandBuffer(f.Commands, 0)); err != nil {
126 return err
127 }
128 // The fence is signaled, so the previous submission finished. Its
129 // semaphore is reusable only if the SYNC_FD export consumed the signal;
130 // otherwise it is still signaled and must be replaced.
131 if !f.signalExported {
132 f.Signal.Close()
133 f.Signal, err = f.device.NewBinaryFence(true)
134 if err != nil {
135 return err
136 }
137 }
138 f.submitted = false
139 }
140 if f.device.atlas == nil {
141 return fmt.Errorf("list atlas not initialized")
142 }
143 if err := f.stageGlyphs(uploads); err != nil {
144 return err
145 }
146 if f.device.images != nil {
147 defer func() {
148 if err != nil {
149 f.device.images.abort(f)
150 }
151 }()
152 batches, err = f.device.images.resolve(f, batches)
153 if err != nil {
154 return err
155 }
156 }
157 if len(instances) > 0 {
158 if err := f.uploadInstances(instances); err != nil {
159 return err
160 }
161 }
162 sc := &f.scratch
163 sc.begin = vulkan.CommandBufferBeginInfo{SType: vulkan.StructureTypeCommandBufferBeginInfo, Flags: vulkan.CommandBufferUsageOneTimeSubmitBit}
164 d := f.device.Dispatch
165 if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &sc.begin)); err != nil {
166 return err
167 }
168 f.atlasRecorded = !f.device.atlas.initialized
169 // Glyph uploads, a new image texture or the first frame each need
170 // transfers before rendering: an image added without new glyphs must
171 // still be uploaded.
172 if len(uploads) != 0 || len(f.imageCopies) != 0 || !f.device.atlas.initialized || !f.device.images.fallbackReady {
173 var staging vulkan.Buffer
174 if len(uploads) != 0 {
175 staging = f.glyphStaging.Buffer
176 }
177 f.device.atlas.recordUploads(f.Commands, staging, uploads)
178 if !f.device.images.fallbackReady {
179 f.recordFallback()
180 }
181 f.recordImages()
182 }
183 rangeInfo := vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
184 old := vulkan.ImageLayout(vulkan.ImageLayoutGeneral)
185 if initial {
186 old = vulkan.ImageLayoutUndefined
187 }
188 barrier, dependency := &sc.barrier, &sc.dependency
189 *barrier = vulkan.ImageMemoryBarrier2{SType: vulkan.StructureTypeImageMemoryBarrier2, SrcStageMask: vulkan.PipelineStage2None, DstStageMask: vulkan.PipelineStage2ColorAttachmentOutputBit, DstAccessMask: vulkan.Access2ColorAttachmentWriteBit, OldLayout: old, NewLayout: vulkan.ImageLayoutColorAttachmentOptimal, SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: img.Image, SubresourceRange: rangeInfo}
190 *dependency = vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: barrier}
191 d.CmdPipelineBarrier2(f.Commands, dependency)
192 clear := vulkan.ClearValue{}
193 sc.attachment = vulkan.RenderingAttachmentInfo{SType: vulkan.StructureTypeRenderingAttachmentInfo, ImageView: f.View, ImageLayout: vulkan.ImageLayoutColorAttachmentOptimal, LoadOp: vulkan.AttachmentLoadOpClear, StoreOp: vulkan.AttachmentStoreOpStore, ClearValue: clear}
194 area := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}
195 sc.rendering = vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &sc.attachment}
196 d.CmdBeginRendering(f.Commands, &sc.rendering)
197 d.CmdBindPipeline(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Handle)
198 // The fragment shader statically references both descriptor sets even
199 // for non-image draws. Bind a valid initialized image set before any
200 // draw; each image batch then replaces set 1 in painter's order.
201 sc.sets = [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}
202 d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sc.sets[0], 0, nil)
203 sc.screen = [2]float32{float32(width), float32(height)}
204 d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&sc.screen))
205 if len(instances) > 0 {
206 sc.offset = 0
207 d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &sc.offset)
208 for _, batch := range batches {
209 if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {
210 return fmt.Errorf("invalid list batch range")
211 }
212 if batch.Source != nil {
213 if batch.Texture == nil {
214 return fmt.Errorf("image batch has no GPU texture")
215 }
216 d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
217 }
218 d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)
219 }
220 }
221 d.CmdEndRendering(f.Commands)
222 if capture && f.Readback != nil {
223 barrier.SrcStageMask = vulkan.PipelineStage2ColorAttachmentOutputBit
224 barrier.SrcAccessMask = vulkan.Access2ColorAttachmentWriteBit
225 barrier.DstStageMask = vulkan.PipelineStage2TransferBit
226 barrier.DstAccessMask = vulkan.Access2TransferReadBit
227 barrier.OldLayout = vulkan.ImageLayoutColorAttachmentOptimal
228 barrier.NewLayout = vulkan.ImageLayoutTransferSrcOptimal
229 d.CmdPipelineBarrier2(f.Commands, dependency)
230 f.Readback.record(f.Commands, img.Image)
231 barrier.SrcStageMask = vulkan.PipelineStage2TransferBit
232 barrier.SrcAccessMask = vulkan.Access2TransferReadBit
233 barrier.OldLayout = vulkan.ImageLayoutTransferSrcOptimal
234 } else {
235 barrier.SrcStageMask = vulkan.PipelineStage2ColorAttachmentOutputBit
236 barrier.SrcAccessMask = vulkan.Access2ColorAttachmentWriteBit
237 barrier.OldLayout = vulkan.ImageLayoutColorAttachmentOptimal
238 }
239 barrier.DstStageMask = vulkan.PipelineStage2AllCommandsBit
240 barrier.DstAccessMask = vulkan.Access2MemoryReadBit
241 barrier.NewLayout = vulkan.ImageLayoutGeneral
242 d.CmdPipelineBarrier2(f.Commands, dependency)
243 return vulkan.Check(d.EndCommandBuffer(f.Commands))
244}
245
246// Submit has no CPU wait: the next render waits on a temporarily imported
247// release SYNC_FD, and the compositor waits on the exported acquire SYNC_FD.
248func (f *Frame) Submit(wait *BinaryFence) error {
249 sc := &f.scratch
250 sc.cmd = vulkan.CommandBufferSubmitInfo{SType: vulkan.StructureTypeCommandBufferSubmitInfo, CommandBuffer: f.Commands}
251 sc.signal = vulkan.SemaphoreSubmitInfo{SType: vulkan.StructureTypeSemaphoreSubmitInfo, Semaphore: f.Signal.Semaphore, StageMask: vulkan.PipelineStage2AllCommandsBit}
252 sc.submit = vulkan.SubmitInfo2{SType: vulkan.StructureTypeSubmitInfo2, CommandBufferInfoCount: 1, CommandBufferInfos: &sc.cmd, SignalSemaphoreInfoCount: 1, SignalSemaphoreInfos: &sc.signal}
253 if wait != nil {
254 sc.waiting = vulkan.SemaphoreSubmitInfo{SType: vulkan.StructureTypeSemaphoreSubmitInfo, Semaphore: wait.Semaphore, StageMask: vulkan.PipelineStage2AllCommandsBit}
255 sc.submit.WaitSemaphoreInfoCount = 1
256 sc.submit.WaitSemaphoreInfos = &sc.waiting
257 }
258 if err := vulkan.Check(f.device.Dispatch.QueueSubmit2(f.device.Queue, 1, &sc.submit, f.Done)); err != nil {
259 // Only this recording's unsubmitted image state is released; a
260 // previous submission's serial was completed by record.
261 if f.device.images != nil && f.imageSerial != 0 {
262 f.device.images.abort(f)
263 }
264 return err
265 }
266 f.submitted = true
267 f.signalExported = false
268 if f.device.images != nil && f.imageSerial != 0 {
269 f.device.images.inflight[f] = struct{}{}
270 }
271 if f.atlasRecorded {
272 f.device.atlas.initialized = true
273 }
274 if f.fallbackRecorded {
275 f.device.images.fallbackReady = true
276 }
277 return nil
278}
279
280// ExportSignal exports the submitted frame's acquire SYNC_FD. The export
281// resets the semaphore, which lets the next recording of this frame reuse it.
282func (f *Frame) ExportSignal() (int, error) {
283 fd, err := f.Signal.Export()
284 if err == nil {
285 f.signalExported = true
286 }
287 return fd, err
288}
289
290// settleImages runs once the frame is Ready. A submitted frame's serial is
291// completed and its upload list forgotten, so a later failed submission can
292// never abort textures that submitted work used. A recorded but unsubmitted
293// frame is aborted: nothing on the GPU references its new textures.
294func (f *Frame) settleImages() {
295 if f.device.images == nil || f.imageSerial == 0 {
296 return
297 }
298 if f.submitted {
299 delete(f.device.images.inflight, f)
300 f.device.images.complete(f.imageSerial)
301 f.imageSerial = 0
302 f.imageCopies = f.imageCopies[:0]
303 return
304 }
305 f.device.images.abort(f)
306}
307
308func (f *Frame) Wait() error {
309 if !f.submitted {
310 return nil
311 }
312 return vulkan.Check(f.device.Dispatch.WaitForFences(f.device.Logical, 1, &f.Done, 1, math.MaxUint64))
313}
314func (f *Frame) Close() {
315 if f == nil || f.device == nil {
316 return
317 }
318 _ = f.Wait()
319 d := f.device.Dispatch
320 if f.Signal != nil {
321 f.Signal.Close()
322 f.Signal = nil
323 }
324 if f.Done != 0 {
325 d.DestroyFence(f.device.Logical, f.Done, nil)
326 f.Done = 0
327 }
328 if f.View != 0 {
329 d.DestroyImageView(f.device.Logical, f.View, nil)
330 f.View = 0
331 }
332 if f.Pool != 0 {
333 d.DestroyCommandPool(f.device.Logical, f.Pool, nil)
334 f.Pool = 0
335 }
336 if f.instances != nil {
337 f.instances.Close()
338 f.instances = nil
339 }
340 if f.glyphStaging != nil {
341 f.glyphStaging.Close()
342 f.glyphStaging = nil
343 }
344 if f.imageStaging != nil {
345 f.imageStaging.Close()
346 f.imageStaging = nil
347 }
348 // After Wait: complete submitted work, abort a recorded-but-unsubmitted list.
349 f.settleImages()
350 if f.Readback != nil {
351 f.Readback.Close()
352 f.Readback = nil
353 }
354}