1package vulkan
2
3import (
4 "time"
5
6 vk "github.com/bnema/purego-vulkan/vulkan"
7 "golang.org/x/sys/unix"
8)
9
10// idleTTL is how long a client buffer cache survives undrawn in wall-clock
11// time. An idle output renders no frame, so importTTL alone never frees
12// the pools, GPU copies and imports of windows that left the screen.
13const idleTTL = 20 * time.Second
14
15// trimMark is the last frame recorded when Trim ran at a time.
16type trimMark struct {
17 frame uint64
18 at time.Time
19}
20
21// Trim frees the pool mappings, GPU copies and dmabuf imports not drawn
22// for idleTTL, and the objects of frames the GPU finished, without a new
23// frame. What the last frame drew stays: it is on screen and a redraw
24// would copy it again. The output calls it periodically on its goroutine.
25func (r *Renderer) Trim(now time.Time) error {
26 // An idle output keeps its oldest mark: the frame is the same.
27 if n := len(r.marks); n == 0 || r.marks[n-1].frame != r.frame {
28 r.marks = append(r.marks, trimMark{frame: r.frame, at: now})
29 }
30 // cutoff is the newest mark at least idleTTL old: everything last
31 // drawn at or before it has been undrawn for idleTTL.
32 i := -1
33 for j, m := range r.marks {
34 if now.Sub(m.at) >= idleTTL {
35 i = j
36 }
37 }
38 if i >= 0 {
39 cutoff := r.marks[i].frame
40 r.marks = append(r.marks[:0], r.marks[i:]...)
41 stale := func(last uint64) bool { return last <= cutoff && last < r.frame }
42 // The GPU reads the copies, never a mapping, and no frame is being
43 // recorded: unmapping now is safe.
44 for id, m := range r.pools {
45 if stale(m.last) {
46 _ = unix.Munmap(m.data)
47 delete(r.pools, id)
48 }
49 }
50 for key, s := range r.shm {
51 if stale(s.last) {
52 r.retireShm(s)
53 delete(r.shm, key)
54 }
55 }
56 for id, im := range r.imports {
57 if stale(im.last) {
58 r.retire(im.last, func() { r.release(im) })
59 delete(r.imports, id)
60 }
61 }
62 }
63 if err := r.poll(); err != nil {
64 return err
65 }
66 if i >= 0 {
67 r.trimCompose(r.marks[0].frame)
68 }
69 return nil
70}
71
72// poll records the frames the GPU finished, without waiting, and frees
73// what they held.
74func (r *Renderer) poll() error {
75 var pending uint64 // oldest frame still running, 0: none
76 for i := range r.slots {
77 s := &r.slots[i]
78 if !s.busy {
79 continue
80 }
81 res := r.dd.WaitForFences(r.device, 1, &s.fence, 1, 0)
82 if res == vk.Timeout {
83 if pending == 0 || s.frame < pending {
84 pending = s.frame
85 }
86 continue
87 }
88 if err := checked("vkWaitForFences", res); err != nil {
89 return err
90 }
91 if err := checked("vkResetFences", r.dd.ResetFences(r.device, 1, &s.fence)); err != nil {
92 return err
93 }
94 s.busy = false
95 }
96 if pending == 0 {
97 // Nothing runs: frames recorded but never submitted have no GPU
98 // work either (like idle).
99 r.completed = r.frame
100 } else {
101 r.completed = max(r.completed, pending-1)
102 }
103 r.runDeferred()
104 return nil
105}