feat/data-dnd e05cb15

compose_batch.go RawHistoryFolder

1package vulkan
2
3import (
4	"fmt"
5	"runtime"
6	"unsafe"
7
8	vk "github.com/bnema/purego-vulkan/vulkan"
9)
10
11// Bound descriptor-table work and memory even on devices with huge limits.
12const maxComposeResources = 64
13
14// instanceDraw matches Draw in compose.vert (std430, 128-byte stride).
15type instanceDraw struct {
16	pc       drawParameters
17	resource [4]uint32
18}
19
20// resourceKey retains the Go identity as well as the Vulkan handles: a
21// destroyed object's recycled handle must not look like a cached descriptor.
22type resourceKey struct {
23	im  *imported
24	shm *shmCopy
25}
26
27// composeBatch belongs to a frame slot. Neither its mapped memory nor its
28// descriptors are changed until the slot's previous submission completed.
29type composeBatch struct {
30	gpu        gpuBuffer
31	pool       vk.DescriptorPool
32	set        vk.DescriptorSet
33	count      uint32
34	last       uint64 // frame that last prepared this batch
35	instances  []instanceDraw
36	keys       [maxComposeResources]resourceKey
37	cached     [maxComposeResources]resourceKey
38	used       int
39	valid      bool
40	images     [maxComposeResources]vk.DescriptorImageInfo
41	chroma     [maxComposeResources]vk.DescriptorImageInfo
42	buffers    [maxComposeResources]vk.DescriptorBufferInfo
43	drawBuffer vk.DescriptorBufferInfo
44	writes     [4]vk.WriteDescriptorSet
45}
46
47// add preserves paint order. Solids need no resource and can share either
48// neighbouring resource's batch, including on devices without indexing.
49func (b *composeBatch) add(dr draw, resources, maxDraws int) bool {
50	if len(b.instances) == maxDraws {
51		return false
52	}
53	key := resourceKey{im: dr.im, shm: dr.shm}
54	index := 0
55	if key != (resourceKey{}) {
56		for index < b.used && b.keys[index] != key {
57			index++
58		}
59		if index == b.used {
60			if b.used == resources {
61				return false
62			}
63			b.keys[index] = key
64			b.used++
65		}
66	}
67	b.instances = append(b.instances, instanceDraw{pc: dr.pc, resource: [4]uint32{uint32(index)}})
68	return true
69}
70
71func (r *Renderer) prepareCompose(slot *frameSlot, ds []draw) ([]*composeBatch, error) {
72	resources := int(r.compose.resources)
73	maxDraws := r.maxRange / int(unsafe.Sizeof(instanceDraw{}))
74	if maxDraws < 1 {
75		return nil, fmt.Errorf("storage buffer range too small for composition")
76	}
77	used := 0
78	for len(ds) > 0 {
79		if used == len(slot.batches) {
80			slot.batches = append(slot.batches, &composeBatch{})
81		}
82		b := slot.batches[used]
83		clear(b.keys[:])
84		b.used = 0
85		b.instances = b.instances[:0]
86		for len(ds) > 0 && b.add(ds[0], resources, maxDraws) {
87			ds = ds[1:]
88		}
89		if err := r.uploadCompose(b); err != nil {
90			return nil, err
91		}
92		b.last = r.frame
93		used++
94	}
95	// The slot is idle. Drop trailing batches after the same frame TTL
96	// as client resources, rather than retaining a peak scene forever.
97	for len(slot.batches) > used {
98		last := len(slot.batches) - 1
99		b := slot.batches[last]
100		if r.frame-b.last <= importTTL {
101			break
102		}
103		r.freeComposeBatch(b)
104		slot.batches[last] = nil
105		slot.batches = slot.batches[:last]
106	}
107	return slot.batches[:used], nil
108}
109
110func (r *Renderer) uploadCompose(b *composeBatch) error {
111	count := len(b.instances)
112	size := count * int(unsafe.Sizeof(instanceDraw{}))
113	if b.gpu.size < size {
114		// This slot is idle, so its previous buffer can be replaced now.
115		capacity := min(r.maxRange, max(size, b.gpu.size*2, 64*int(unsafe.Sizeof(instanceDraw{}))))
116		b.valid = false
117		r.freeGPUBuffer(&b.gpu)
118		if err := r.newGPUBuffer(&b.gpu, capacity); err != nil {
119			return err
120		}
121	}
122	copy(unsafe.Slice((*instanceDraw)(b.gpu.mapped), count), b.instances)
123	b.count = uint32(count)
124	if b.set == 0 {
125		if err := r.createComposeSet(b); err != nil {
126			return err
127		}
128	}
129	if b.valid && b.keys == b.cached {
130		return nil
131	}
132	n := int(r.compose.resources)
133	for i, key := range b.keys[:n] {
134		view, chroma, buffer := r.compose.dummyView, r.compose.dummyView, r.compose.dummyBuffer.buffer
135		if key.im != nil {
136			view = key.im.view
137			chroma = view
138			if key.im.chromaView != 0 {
139				chroma = key.im.chromaView
140			}
141		}
142		if key.shm != nil {
143			buffer = key.shm.gpu.buffer
144		}
145		b.images[i] = vk.DescriptorImageInfo{Sampler: r.compose.sampler, ImageView: view, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal}
146		b.chroma[i] = vk.DescriptorImageInfo{Sampler: r.compose.sampler, ImageView: chroma, ImageLayout: vk.ImageLayoutShaderReadOnlyOptimal}
147		b.buffers[i] = vk.DescriptorBufferInfo{Buffer: buffer, Range: wholeSize}
148	}
149	b.drawBuffer = vk.DescriptorBufferInfo{Buffer: b.gpu.buffer, Range: vk.DeviceSize(b.gpu.size)}
150	b.writes = [4]vk.WriteDescriptorSet{
151		{SType: vk.StructureTypeWriteDescriptorSet, DstSet: b.set, DstBinding: 0, DescriptorCount: uint32(n), DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &b.images[0]},
152		{SType: vk.StructureTypeWriteDescriptorSet, DstSet: b.set, DstBinding: 1, DescriptorCount: uint32(n), DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &b.buffers[0]},
153		{SType: vk.StructureTypeWriteDescriptorSet, DstSet: b.set, DstBinding: 2, DescriptorCount: uint32(n), DescriptorType: vk.DescriptorTypeCombinedImageSampler, ImageInfo: &b.chroma[0]},
154		{SType: vk.StructureTypeWriteDescriptorSet, DstSet: b.set, DstBinding: 3, DescriptorCount: 1, DescriptorType: vk.DescriptorTypeStorageBuffer, BufferInfo: &b.drawBuffer},
155	}
156	r.dd.UpdateDescriptorSets(r.device, uint32(len(b.writes)), &b.writes[0], 0, nil)
157	runtime.KeepAlive(b)
158	b.cached, b.valid = b.keys, true
159	return nil
160}
161
162func (r *Renderer) createComposeSet(b *composeBatch) error {
163	n := r.compose.resources
164	sizes := [2]vk.DescriptorPoolSize{
165		{Type: vk.DescriptorTypeCombinedImageSampler, DescriptorCount: 2 * n},
166		{Type: vk.DescriptorTypeStorageBuffer, DescriptorCount: n + 1},
167	}
168	info := vk.DescriptorPoolCreateInfo{SType: vk.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: uint32(len(sizes)), PoolSizes: &sizes[0]}
169	if err := checked("vkCreateDescriptorPool(compose)", r.dd.CreateDescriptorPool(r.device, &info, nil, &b.pool)); err != nil {
170		return err
171	}
172	alloc := vk.DescriptorSetAllocateInfo{SType: vk.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: b.pool, DescriptorSetCount: 1, SetLayouts: &r.compose.setLayout}
173	if err := checked("vkAllocateDescriptorSets(compose)", r.dd.AllocateDescriptorSets(r.device, &alloc, &b.set)); err != nil {
174		r.dd.DestroyDescriptorPool(r.device, b.pool, nil)
175		b.pool = 0
176		return err
177	}
178	return nil
179}
180
181// Released resources may leave unbound descriptors naming dead handles.
182// Invalidate their cached identities before destruction: the next bind
183// rewrites the entire table after its slot fence. No GPU-visible mutation
184// happens here, even if another batch in the slot is still in flight.
185func (r *Renderer) invalidateCompose(key resourceKey) {
186	for i := range r.slots {
187		for _, b := range r.slots[i].batches {
188			for j := range b.cached {
189				if b.cached[j] == key {
190					b.cached[j], b.keys[j] = resourceKey{}, resourceKey{}
191					b.valid = false
192				}
193			}
194		}
195	}
196}
197
198// trimCompose runs only after poll, and never frees an in-flight slot.
199func (r *Renderer) trimCompose(cutoff uint64) {
200	for i := range r.slots {
201		s := &r.slots[i]
202		if s.busy {
203			continue
204		}
205		for len(s.batches) > 0 {
206			last := len(s.batches) - 1
207			b := s.batches[last]
208			if b.last > cutoff || b.last >= r.frame {
209				break
210			}
211			r.freeComposeBatch(b)
212			s.batches[last] = nil
213			s.batches = s.batches[:last]
214		}
215	}
216}
217
218func (r *Renderer) freeComposeBatch(b *composeBatch) {
219	if b.pool != 0 {
220		r.dd.DestroyDescriptorPool(r.device, b.pool, nil)
221	}
222	r.freeGPUBuffer(&b.gpu)
223	*b = composeBatch{}
224}

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 25

Tags 10

↑↓ moveenter openesc close