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}
34
35func (d *Device) NewFrame(img *Image) (f *Frame, err error) {
36 f = &Frame{device: d}
37 defer func() {
38 if err != nil {
39 f.Close()
40 }
41 }()
42 pool := vulkan.CommandPoolCreateInfo{SType: vulkan.StructureTypeCommandPoolCreateInfo, QueueFamilyIndex: d.QueueFamily, Flags: vulkan.CommandPoolCreateResetCommandBufferBit}
43 if err = vulkan.Check(d.Dispatch.CreateCommandPool(d.Logical, &pool, nil, &f.Pool)); err != nil {
44 return nil, err
45 }
46 allocate := vulkan.CommandBufferAllocateInfo{SType: vulkan.StructureTypeCommandBufferAllocateInfo, CommandPool: f.Pool, Level: vulkan.CommandBufferLevelPrimary, CommandBufferCount: 1}
47 if err = vulkan.Check(d.Dispatch.AllocateCommandBuffers(d.Logical, &allocate, &f.Commands)); err != nil {
48 return nil, err
49 }
50 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}}
51 if err = vulkan.Check(d.Dispatch.CreateImageView(d.Logical, &view, nil, &f.View)); err != nil {
52 return nil, err
53 }
54 fence := vulkan.FenceCreateInfo{SType: vulkan.StructureTypeFenceCreateInfo}
55 if err = vulkan.Check(d.Dispatch.CreateFence(d.Logical, &fence, nil, &f.Done)); err != nil {
56 return nil, err
57 }
58 f.Signal, err = d.NewBinaryFence(true)
59 if err != nil {
60 return nil, err
61 }
62 return f, nil
63}
64
65func (f *Frame) Ready() (bool, error) {
66 if !f.submitted {
67 return true, nil
68 }
69 r := f.device.Dispatch.WaitForFences(f.device.Logical, 1, &f.Done, 1, 0)
70 if r == vulkan.Timeout {
71 return false, nil
72 }
73 if err := vulkan.Check(r); err != nil {
74 return false, err
75 }
76 return true, nil
77}
78
79func (f *Frame) Record(img *Image, pipe *Pipeline, width, height int32, rect Rect, initial bool, capture bool) error {
80 return f.record(img, pipe, width, height, initial, capture, nil, nil, nil, &rect)
81}
82
83// RecordList records an ordered list of physical-pixel quads. Reuse is gated
84// by Ready, including any instance-buffer growth.
85func (f *Frame) RecordList(img *Image, pipe *Pipeline, width, height int32, instances []Instance, uploads []text.Upload, batches []Batch, initial, capture bool) error {
86 return f.record(img, pipe, width, height, initial, capture, instances, uploads, batches, nil)
87}
88
89func (f *Frame) record(img *Image, pipe *Pipeline, width, height int32, initial, capture bool, instances []Instance, uploads []text.Upload, batches []Batch, rect *Rect) (err error) {
90 if width <= 0 || height <= 0 || pipe == nil {
91 return fmt.Errorf("invalid render dimensions or pipeline")
92 }
93 ready, err := f.Ready()
94 if err != nil || !ready {
95 return fmt.Errorf("command buffer still in use: ready=%v err=%v", ready, err)
96 }
97 f.settleImages()
98 if f.submitted {
99 if err = vulkan.Check(f.device.Dispatch.ResetFences(f.device.Logical, 1, &f.Done)); err != nil {
100 return err
101 }
102 if err = vulkan.Check(f.device.Dispatch.ResetCommandBuffer(f.Commands, 0)); err != nil {
103 return err
104 }
105 f.Signal.Close()
106 f.Signal, err = f.device.NewBinaryFence(true)
107 if err != nil {
108 return err
109 }
110 f.submitted = false
111 }
112 if rect == nil && f.device.atlas == nil {
113 return fmt.Errorf("list atlas not initialized")
114 }
115 if rect == nil {
116 if err := f.stageGlyphs(uploads); err != nil {
117 return err
118 }
119 }
120 if rect == nil && f.device.images != nil {
121 defer func() {
122 if err != nil {
123 f.device.images.abort(f)
124 }
125 }()
126 batches, err = f.device.images.resolve(f, batches)
127 if err != nil {
128 return err
129 }
130 }
131 if rect == nil && len(instances) > 0 {
132 if err := f.uploadInstances(instances); err != nil {
133 return err
134 }
135 }
136 begin := vulkan.CommandBufferBeginInfo{SType: vulkan.StructureTypeCommandBufferBeginInfo, Flags: vulkan.CommandBufferUsageOneTimeSubmitBit}
137 d := f.device.Dispatch
138 if err = vulkan.Check(d.BeginCommandBuffer(f.Commands, &begin)); err != nil {
139 return err
140 }
141 f.atlasRecorded = rect == nil && !f.device.atlas.initialized
142 if rect == nil {
143 var staging vulkan.Buffer
144 if len(uploads) != 0 {
145 staging = f.glyphStaging.Buffer
146 }
147 f.device.atlas.recordUploads(f.Commands, staging, uploads)
148 if !f.device.images.fallbackReady {
149 f.recordFallback()
150 }
151 f.recordImages()
152 }
153 rangeInfo := vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
154 old := vulkan.ImageLayout(vulkan.ImageLayoutGeneral)
155 if initial {
156 old = vulkan.ImageLayoutUndefined
157 }
158 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}
159 dependency := vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: &barrier}
160 d.CmdPipelineBarrier2(f.Commands, &dependency)
161 clear := vulkan.ClearValue{}
162 attachment := vulkan.RenderingAttachmentInfo{SType: vulkan.StructureTypeRenderingAttachmentInfo, ImageView: f.View, ImageLayout: vulkan.ImageLayoutColorAttachmentOptimal, LoadOp: vulkan.AttachmentLoadOpClear, StoreOp: vulkan.AttachmentStoreOpStore, ClearValue: clear}
163 area := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(width), Height: uint32(height)}}
164 rendering := vulkan.RenderingInfo{SType: vulkan.StructureTypeRenderingInfo, RenderArea: area, LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &attachment}
165 d.CmdBeginRendering(f.Commands, &rendering)
166 d.CmdBindPipeline(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Handle)
167 if rect != nil {
168 d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, uint32(unsafe.Sizeof(*rect)), unsafe.Pointer(rect))
169 d.CmdDraw(f.Commands, 6, 1, 0, 0)
170 } else {
171 // The fragment shader statically references both descriptor sets even
172 // for non-image draws. Bind a valid initialized image set before any
173 // draw; each image batch then replaces set 1 in painter's order.
174 sets := [2]vulkan.DescriptorSet{f.device.atlas.Set, f.device.images.fallback.Texture.Set}
175 d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 0, 2, &sets[0], 0, nil)
176 screen := [2]float32{float32(width), float32(height)}
177 d.CmdPushConstants(f.Commands, pipe.Layout, vulkan.ShaderStageVertexBit|vulkan.ShaderStageFragmentBit, 0, 8, unsafe.Pointer(&screen))
178 if len(instances) > 0 {
179 offset := vulkan.DeviceSize(0)
180 d.CmdBindVertexBuffers(f.Commands, 0, 1, &f.instances.Buffer, &offset)
181 for _, batch := range batches {
182 if batch.Count == 0 || uint64(batch.First)+uint64(batch.Count) > uint64(len(instances)) {
183 return fmt.Errorf("invalid list batch range")
184 }
185 if batch.Source != nil {
186 if batch.Texture == nil {
187 return fmt.Errorf("image batch has no GPU texture")
188 }
189 d.CmdBindDescriptorSets(f.Commands, vulkan.PipelineBindPointGraphics, pipe.Layout, 1, 1, &batch.Texture.Set, 0, nil)
190 }
191 d.CmdDraw(f.Commands, 6, batch.Count, 0, batch.First)
192 }
193 }
194 }
195 d.CmdEndRendering(f.Commands)
196 if capture && f.Readback != nil {
197 barrier.SrcStageMask = vulkan.PipelineStage2ColorAttachmentOutputBit
198 barrier.SrcAccessMask = vulkan.Access2ColorAttachmentWriteBit
199 barrier.DstStageMask = vulkan.PipelineStage2TransferBit
200 barrier.DstAccessMask = vulkan.Access2TransferReadBit
201 barrier.OldLayout = vulkan.ImageLayoutColorAttachmentOptimal
202 barrier.NewLayout = vulkan.ImageLayoutTransferSrcOptimal
203 d.CmdPipelineBarrier2(f.Commands, &dependency)
204 f.Readback.record(f.Commands, img.Image)
205 barrier.SrcStageMask = vulkan.PipelineStage2TransferBit
206 barrier.SrcAccessMask = vulkan.Access2TransferReadBit
207 barrier.OldLayout = vulkan.ImageLayoutTransferSrcOptimal
208 } else {
209 barrier.SrcStageMask = vulkan.PipelineStage2ColorAttachmentOutputBit
210 barrier.SrcAccessMask = vulkan.Access2ColorAttachmentWriteBit
211 barrier.OldLayout = vulkan.ImageLayoutColorAttachmentOptimal
212 }
213 barrier.DstStageMask = vulkan.PipelineStage2AllCommandsBit
214 barrier.DstAccessMask = vulkan.Access2MemoryReadBit
215 barrier.NewLayout = vulkan.ImageLayoutGeneral
216 d.CmdPipelineBarrier2(f.Commands, &dependency)
217 return vulkan.Check(d.EndCommandBuffer(f.Commands))
218}
219
220// Submit has no CPU wait: the next render waits on a temporarily imported
221// release SYNC_FD, and the compositor waits on the exported acquire SYNC_FD.
222func (f *Frame) Submit(wait *BinaryFence) error {
223 cmd := vulkan.CommandBufferSubmitInfo{SType: vulkan.StructureTypeCommandBufferSubmitInfo, CommandBuffer: f.Commands}
224 signal := vulkan.SemaphoreSubmitInfo{SType: vulkan.StructureTypeSemaphoreSubmitInfo, Semaphore: f.Signal.Semaphore, StageMask: vulkan.PipelineStage2AllCommandsBit}
225 info := vulkan.SubmitInfo2{SType: vulkan.StructureTypeSubmitInfo2, CommandBufferInfoCount: 1, CommandBufferInfos: &cmd, SignalSemaphoreInfoCount: 1, SignalSemaphoreInfos: &signal}
226 if wait != nil {
227 waiting := vulkan.SemaphoreSubmitInfo{SType: vulkan.StructureTypeSemaphoreSubmitInfo, Semaphore: wait.Semaphore, StageMask: vulkan.PipelineStage2AllCommandsBit}
228 info.WaitSemaphoreInfoCount = 1
229 info.WaitSemaphoreInfos = &waiting
230 }
231 if err := vulkan.Check(f.device.Dispatch.QueueSubmit2(f.device.Queue, 1, &info, f.Done)); err != nil {
232 // Only this recording's unsubmitted image state is released; a
233 // previous submission's serial was completed by record.
234 if f.device.images != nil && f.imageSerial != 0 {
235 f.device.images.abort(f)
236 }
237 return err
238 }
239 f.submitted = true
240 if f.device.images != nil && f.imageSerial != 0 {
241 f.device.images.inflight[f] = struct{}{}
242 }
243 if f.atlasRecorded {
244 f.device.atlas.initialized = true
245 }
246 if f.fallbackRecorded {
247 f.device.images.fallbackReady = true
248 }
249 return nil
250}
251
252// settleImages runs once the frame is Ready. A submitted frame's serial is
253// completed and its upload list forgotten, so a later failed submission can
254// never abort textures that submitted work used. A recorded but unsubmitted
255// frame is aborted: nothing on the GPU references its new textures.
256func (f *Frame) settleImages() {
257 if f.device.images == nil || f.imageSerial == 0 {
258 return
259 }
260 if f.submitted {
261 delete(f.device.images.inflight, f)
262 f.device.images.complete(f.imageSerial)
263 f.imageSerial = 0
264 f.imageCopies = f.imageCopies[:0]
265 return
266 }
267 f.device.images.abort(f)
268}
269
270func (f *Frame) Wait() error {
271 if !f.submitted {
272 return nil
273 }
274 return vulkan.Check(f.device.Dispatch.WaitForFences(f.device.Logical, 1, &f.Done, 1, math.MaxUint64))
275}
276func (f *Frame) Close() {
277 if f == nil || f.device == nil {
278 return
279 }
280 _ = f.Wait()
281 d := f.device.Dispatch
282 if f.Signal != nil {
283 f.Signal.Close()
284 f.Signal = nil
285 }
286 if f.Done != 0 {
287 d.DestroyFence(f.device.Logical, f.Done, nil)
288 f.Done = 0
289 }
290 if f.View != 0 {
291 d.DestroyImageView(f.device.Logical, f.View, nil)
292 f.View = 0
293 }
294 if f.Pool != 0 {
295 d.DestroyCommandPool(f.device.Logical, f.Pool, nil)
296 f.Pool = 0
297 }
298 if f.instances != nil {
299 f.instances.Close()
300 f.instances = nil
301 }
302 if f.glyphStaging != nil {
303 f.glyphStaging.Close()
304 f.glyphStaging = nil
305 }
306 if f.imageStaging != nil {
307 f.imageStaging.Close()
308 f.imageStaging = nil
309 }
310 // After Wait: complete submitted work, abort a recorded-but-unsubmitted list.
311 f.settleImages()
312 if f.Readback != nil {
313 f.Readback.Close()
314 f.Readback = nil
315 }
316}