main f2d166b

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

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 26

Tags 10

↑↓ moveenter openesc close