detached 6d3622a

perf(capi): call per-frame video functions through Syscall6 to avoid allocations

commit
6d3622ad879c7e00dd3c64fe84d6cfd8b171b8ad
author
brice · · 2026-10-14 19:28 UTC
parent
9177a2f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +93 -1
M internal/capi/register_all.go +1 -1
@@ -10,5 +10,5 @@ func registerAll(h loader.Handles) error {
1010 registerLoopLock(h.PipeWire)
1111 registerStream_playback(h.PipeWire)
1212 registerVideo(h.PipeWire)
13- return nil
13+ return registerVideoFast(h.PipeWire)
1414 }
@@ -10,5 +10,5 @@ func registerAll(h loader.Handles) error {
10 registerLoopLock(h.PipeWire)10 registerLoopLock(h.PipeWire)
11 registerStream_playback(h.PipeWire)11 registerStream_playback(h.PipeWire)
12 registerVideo(h.PipeWire)12 registerVideo(h.PipeWire)
13- return nil13+ return registerVideoFast(h.PipeWire)
14 }14 }
A internal/capi/video_fast.go +92 -0
@@ -0,0 +1,92 @@
1+package capi
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego"
7+)
8+
9+// The generated bindings call C through purego.RegisterLibFunc, whose
10+// reflect-based wrappers allocate on every call. The calls made once per
11+// video frame (and once per loop iteration) must not allocate, so they go
12+// through purego.Syscall6 instead: the methods below shadow the generated
13+// adapter methods of the same name on defaultCAPI. Everything else keeps using
14+// the generated bindings.
15+//
16+// All hot-path functions take only pointers/integers and return an integer
17+// (or nothing), which is what Syscall6 supports.
18+var (
19+ symLoopGetFd uintptr
20+ symLoopEnter uintptr
21+ symLoopLeave uintptr
22+ symLoopIterate uintptr
23+ symStreamDequeueBuffer uintptr
24+ symStreamQueueBuffer uintptr
25+ symStreamReturnBuffer uintptr
26+ symStreamTrigger uintptr
27+)
28+
29+func registerVideoFast(handle uintptr) error {
30+ for _, s := range []struct {
31+ dst *uintptr
32+ name string
33+ }{
34+ {&symLoopGetFd, "pw_loop_get_fd"},
35+ {&symLoopEnter, "pw_loop_enter"},
36+ {&symLoopLeave, "pw_loop_leave"},
37+ {&symLoopIterate, "pw_loop_iterate"},
38+ {&symStreamDequeueBuffer, "pw_stream_dequeue_buffer"},
39+ {&symStreamQueueBuffer, "pw_stream_queue_buffer"},
40+ {&symStreamReturnBuffer, "pw_stream_return_buffer"},
41+ {&symStreamTrigger, "pw_stream_trigger_process"},
42+ } {
43+ addr, err := purego.Dlsym(handle, s.name)
44+ if err != nil {
45+ return err
46+ }
47+ *s.dst = addr
48+ }
49+ return nil
50+}
51+
52+// uptr converts a C pointer returned as an integer without a
53+// uintptr-to-unsafe.Pointer conversion (the memory is owned by C).
54+func uptr(v uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&v)) }
55+
56+func (*defaultCAPI) PWLoopGetFd(loop unsafe.Pointer) int32 {
57+ r, _, _ := purego.Syscall6(symLoopGetFd, uintptr(loop), 0, 0, 0, 0, 0)
58+ return int32(r)
59+}
60+
61+func (*defaultCAPI) PWLoopEnter(loop unsafe.Pointer) {
62+ purego.Syscall6(symLoopEnter, uintptr(loop), 0, 0, 0, 0, 0)
63+}
64+
65+func (*defaultCAPI) PWLoopLeave(loop unsafe.Pointer) {
66+ purego.Syscall6(symLoopLeave, uintptr(loop), 0, 0, 0, 0, 0)
67+}
68+
69+func (*defaultCAPI) PWLoopIterate(loop unsafe.Pointer, timeout int32) int32 {
70+ r, _, _ := purego.Syscall6(symLoopIterate, uintptr(loop), uintptr(uint32(timeout)), 0, 0, 0, 0)
71+ return int32(r)
72+}
73+
74+func (*defaultCAPI) PWStreamDequeueBuffer(stream unsafe.Pointer) unsafe.Pointer {
75+ r, _, _ := purego.Syscall6(symStreamDequeueBuffer, uintptr(stream), 0, 0, 0, 0, 0)
76+ return uptr(r)
77+}
78+
79+func (*defaultCAPI) PWStreamQueueBuffer(stream unsafe.Pointer, buffer unsafe.Pointer) int32 {
80+ r, _, _ := purego.Syscall6(symStreamQueueBuffer, uintptr(stream), uintptr(buffer), 0, 0, 0, 0)
81+ return int32(r)
82+}
83+
84+func (*defaultCAPI) PWStreamReturnBuffer(stream unsafe.Pointer, buffer unsafe.Pointer) int32 {
85+ r, _, _ := purego.Syscall6(symStreamReturnBuffer, uintptr(stream), uintptr(buffer), 0, 0, 0, 0)
86+ return int32(r)
87+}
88+
89+func (*defaultCAPI) PWStreamTriggerProcess(stream unsafe.Pointer) int32 {
90+ r, _, _ := purego.Syscall6(symStreamTrigger, uintptr(stream), 0, 0, 0, 0, 0)
91+ return int32(r)
92+}
@@ -0,0 +1,92 @@
1+package capi
2+
3+import (
4+ "unsafe"
5+
6+ "github.com/bnema/purego"
7+)
8+
9+// The generated bindings call C through purego.RegisterLibFunc, whose
10+// reflect-based wrappers allocate on every call. The calls made once per
11+// video frame (and once per loop iteration) must not allocate, so they go
12+// through purego.Syscall6 instead: the methods below shadow the generated
13+// adapter methods of the same name on defaultCAPI. Everything else keeps using
14+// the generated bindings.
15+//
16+// All hot-path functions take only pointers/integers and return an integer
17+// (or nothing), which is what Syscall6 supports.
18+var (
19+ symLoopGetFd uintptr
20+ symLoopEnter uintptr
21+ symLoopLeave uintptr
22+ symLoopIterate uintptr
23+ symStreamDequeueBuffer uintptr
24+ symStreamQueueBuffer uintptr
25+ symStreamReturnBuffer uintptr
26+ symStreamTrigger uintptr
27+)
28+
29+func registerVideoFast(handle uintptr) error {
30+ for _, s := range []struct {
31+ dst *uintptr
32+ name string
33+ }{
34+ {&symLoopGetFd, "pw_loop_get_fd"},
35+ {&symLoopEnter, "pw_loop_enter"},
36+ {&symLoopLeave, "pw_loop_leave"},
37+ {&symLoopIterate, "pw_loop_iterate"},
38+ {&symStreamDequeueBuffer, "pw_stream_dequeue_buffer"},
39+ {&symStreamQueueBuffer, "pw_stream_queue_buffer"},
40+ {&symStreamReturnBuffer, "pw_stream_return_buffer"},
41+ {&symStreamTrigger, "pw_stream_trigger_process"},
42+ } {
43+ addr, err := purego.Dlsym(handle, s.name)
44+ if err != nil {
45+ return err
46+ }
47+ *s.dst = addr
48+ }
49+ return nil
50+}
51+
52+// uptr converts a C pointer returned as an integer without a
53+// uintptr-to-unsafe.Pointer conversion (the memory is owned by C).
54+func uptr(v uintptr) unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&v)) }
55+
56+func (*defaultCAPI) PWLoopGetFd(loop unsafe.Pointer) int32 {
57+ r, _, _ := purego.Syscall6(symLoopGetFd, uintptr(loop), 0, 0, 0, 0, 0)
58+ return int32(r)
59+}
60+
61+func (*defaultCAPI) PWLoopEnter(loop unsafe.Pointer) {
62+ purego.Syscall6(symLoopEnter, uintptr(loop), 0, 0, 0, 0, 0)
63+}
64+
65+func (*defaultCAPI) PWLoopLeave(loop unsafe.Pointer) {
66+ purego.Syscall6(symLoopLeave, uintptr(loop), 0, 0, 0, 0, 0)
67+}
68+
69+func (*defaultCAPI) PWLoopIterate(loop unsafe.Pointer, timeout int32) int32 {
70+ r, _, _ := purego.Syscall6(symLoopIterate, uintptr(loop), uintptr(uint32(timeout)), 0, 0, 0, 0)
71+ return int32(r)
72+}
73+
74+func (*defaultCAPI) PWStreamDequeueBuffer(stream unsafe.Pointer) unsafe.Pointer {
75+ r, _, _ := purego.Syscall6(symStreamDequeueBuffer, uintptr(stream), 0, 0, 0, 0, 0)
76+ return uptr(r)
77+}
78+
79+func (*defaultCAPI) PWStreamQueueBuffer(stream unsafe.Pointer, buffer unsafe.Pointer) int32 {
80+ r, _, _ := purego.Syscall6(symStreamQueueBuffer, uintptr(stream), uintptr(buffer), 0, 0, 0, 0)
81+ return int32(r)
82+}
83+
84+func (*defaultCAPI) PWStreamReturnBuffer(stream unsafe.Pointer, buffer unsafe.Pointer) int32 {
85+ r, _, _ := purego.Syscall6(symStreamReturnBuffer, uintptr(stream), uintptr(buffer), 0, 0, 0, 0)
86+ return int32(r)
87+}
88+
89+func (*defaultCAPI) PWStreamTriggerProcess(stream unsafe.Pointer) int32 {
90+ r, _, _ := purego.Syscall6(symStreamTrigger, uintptr(stream), 0, 0, 0, 0, 0)
91+ return int32(r)
92+}

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 8

↑↓ moveenter openesc close