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