v0.7.3 bcc9a45

image_cache_policy.go RawHistoryFolder

1package vkdevice
2
3import (
4	"fmt"
5	"image"
6	"reflect"
7)
8
9const maxImageTextures = 64
10const imageDescriptorSlots = maxImageTextures + 1 // one fallback plus the 64-texture budget
11const maxImageBytes = 64 << 20
12const maxImageDimension = 8192
13
14type imageEntry struct {
15	key              image.Image
16	slot             int
17	bytes            int
18	used, lastSerial uint64
19	retiring         bool
20}
21
22// imageCachePolicy tracks frame use separately from completed fence serials.
23// The 64 image descriptor sets plus one fallback set are allocated once,
24// without FREE_DESCRIPTOR_SET. A
25// retired texture keeps its slot until its last sampling fence completes;
26// only then may UpdateDescriptorSets overwrite the recycled set. The pool is
27// destroyed once at shutdown. This avoids requiring FreeDescriptorSets.
28type imageCachePolicy struct {
29	maxDimension     int
30	entries          map[image.Image]*imageEntry
31	free             []int
32	retired          []*imageEntry
33	bytes            int
34	frame, completed uint64
35}
36
37func newImageCachePolicy() *imageCachePolicy {
38	p := &imageCachePolicy{entries: make(map[image.Image]*imageEntry), maxDimension: maxImageDimension}
39	// Slot 0 is permanently reserved for the valid set 1 fallback.
40	for i := imageDescriptorSlots - 1; i >= 1; i-- {
41		p.free = append(p.free, i)
42	}
43	return p
44}
45
46func (p *imageCachePolicy) begin(serial, completed uint64) []*imageEntry {
47	p.frame = serial
48	if completed > p.completed {
49		p.completed = completed
50	}
51	return p.collect()
52}
53
54func (p *imageCachePolicy) collect() []*imageEntry {
55	var destroyed []*imageEntry
56	keep := p.retired[:0]
57	for _, e := range p.retired {
58		if e.lastSerial > p.completed {
59			keep = append(keep, e)
60			continue
61		}
62		p.free = append(p.free, e.slot)
63		p.bytes -= e.bytes
64		destroyed = append(destroyed, e)
65	}
66	p.retired = keep
67	return destroyed
68}
69
70func (p *imageCachePolicy) reserve(key image.Image) (*imageEntry, []*imageEntry, error) {
71	if key == nil || !reflect.TypeOf(key).Comparable() {
72		return nil, nil, fmt.Errorf("image cache: image identity must be a comparable non-nil image.Image value")
73	}
74	bounds := key.Bounds()
75	w, h := bounds.Dx(), bounds.Dy()
76	if w <= 0 || h <= 0 || w > p.maxDimension || h > p.maxDimension {
77		return nil, nil, fmt.Errorf("image cache: invalid dimensions %dx%d (maximum %d)", w, h, p.maxDimension)
78	}
79	if e := p.entries[key]; e != nil {
80		e.used, e.lastSerial = p.frame, p.frame
81		return e, nil, nil
82	}
83	size := w * h * 4
84	if size > maxImageBytes {
85		return nil, nil, fmt.Errorf("image cache: %d bytes exceeds %d-byte budget", size, maxImageBytes)
86	}
87	var destroyed []*imageEntry
88	for len(p.free) == 0 || p.bytes+size > maxImageBytes {
89		var victim *imageEntry
90		for _, e := range p.entries {
91			if e.used == p.frame || e.lastSerial > p.completed || (victim != nil && e.used >= victim.used) {
92				continue
93			}
94			victim = e
95		}
96		if victim == nil {
97			return nil, destroyed, fmt.Errorf("image cache: 64 textures/64 MiB budget exhausted by images used this frame or awaiting fences")
98		}
99		delete(p.entries, victim.key)
100		victim.retiring = true
101		p.retired = append(p.retired, victim)
102		destroyed = append(destroyed, p.collect()...)
103	}
104	slot := p.free[len(p.free)-1]
105	p.free = p.free[:len(p.free)-1]
106	e := &imageEntry{key: key, slot: slot, bytes: size, used: p.frame, lastSerial: p.frame}
107	p.entries[key] = e
108	p.bytes += size
109	return e, destroyed, nil
110}
111
112// drop forgets an entry that no submitted work has used and frees its slot.
113func (p *imageCachePolicy) drop(e *imageEntry) {
114	if p.entries[e.key] != e {
115		return
116	}
117	delete(p.entries, e.key)
118	p.free = append(p.free, e.slot)
119	p.bytes -= e.bytes
120}

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