v0.7.2 71ebe61

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 = 4096
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	entries          map[image.Image]*imageEntry
30	free             []int
31	retired          []*imageEntry
32	bytes            int
33	frame, completed uint64
34}
35
36func newImageCachePolicy() *imageCachePolicy {
37	p := &imageCachePolicy{entries: make(map[image.Image]*imageEntry)}
38	// Slot 0 is permanently reserved for the valid set 1 fallback.
39	for i := imageDescriptorSlots - 1; i >= 1; i-- {
40		p.free = append(p.free, i)
41	}
42	return p
43}
44
45func (p *imageCachePolicy) begin(serial, completed uint64) []*imageEntry {
46	p.frame = serial
47	if completed > p.completed {
48		p.completed = completed
49	}
50	return p.collect()
51}
52
53func (p *imageCachePolicy) collect() []*imageEntry {
54	var destroyed []*imageEntry
55	keep := p.retired[:0]
56	for _, e := range p.retired {
57		if e.lastSerial > p.completed {
58			keep = append(keep, e)
59			continue
60		}
61		p.free = append(p.free, e.slot)
62		p.bytes -= e.bytes
63		destroyed = append(destroyed, e)
64	}
65	p.retired = keep
66	return destroyed
67}
68
69func (p *imageCachePolicy) reserve(key image.Image) (*imageEntry, []*imageEntry, error) {
70	if key == nil || !reflect.TypeOf(key).Comparable() {
71		return nil, nil, fmt.Errorf("image cache: image identity must be a comparable non-nil image.Image value")
72	}
73	bounds := key.Bounds()
74	w, h := bounds.Dx(), bounds.Dy()
75	if w <= 0 || h <= 0 || w > maxImageDimension || h > maxImageDimension {
76		return nil, nil, fmt.Errorf("image cache: invalid dimensions %dx%d (maximum %d)", w, h, maxImageDimension)
77	}
78	if e := p.entries[key]; e != nil {
79		e.used, e.lastSerial = p.frame, p.frame
80		return e, nil, nil
81	}
82	size := w * h * 4
83	if size > maxImageBytes {
84		return nil, nil, fmt.Errorf("image cache: %d bytes exceeds %d-byte budget", size, maxImageBytes)
85	}
86	var destroyed []*imageEntry
87	for len(p.free) == 0 || p.bytes+size > maxImageBytes {
88		var victim *imageEntry
89		for _, e := range p.entries {
90			if e.used == p.frame || e.lastSerial > p.completed || (victim != nil && e.used >= victim.used) {
91				continue
92			}
93			victim = e
94		}
95		if victim == nil {
96			return nil, destroyed, fmt.Errorf("image cache: 64 textures/64 MiB budget exhausted by images used this frame or awaiting fences")
97		}
98		delete(p.entries, victim.key)
99		victim.retiring = true
100		p.retired = append(p.retired, victim)
101		destroyed = append(destroyed, p.collect()...)
102	}
103	slot := p.free[len(p.free)-1]
104	p.free = p.free[:len(p.free)-1]
105	e := &imageEntry{key: key, slot: slot, bytes: size, used: p.frame, lastSerial: p.frame}
106	p.entries[key] = e
107	p.bytes += size
108	return e, destroyed, nil
109}
110
111// drop forgets an entry that no submitted work has used and frees its slot.
112func (p *imageCachePolicy) drop(e *imageEntry) {
113	if p.entries[e.key] != e {
114		return
115	}
116	delete(p.entries, e.key)
117	p.free = append(p.free, e.slot)
118	p.bytes -= e.bytes
119}

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