v0.1.0 47aaa0f

render.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close