|
libfunnel
|
GPUs require access to shared buffers to be synchronize, to ensure that a frame is fully written to memory before it is read. Without synchronization, an application might read incomplete frames or even garbage data.
While it is possible to do this using CPU synchronization (this is how Spout2 works on Windows), this is inefficient because it prevents GPU parallelism and forces CPU-GPU synchronization. Linux has two synchronization mechanisms:
PipeWire supports both mechanisms, allowing applications to negotiate the preferred synchronization style. OpenGL supports both implicit and explicit synchronization, while Vulkan requires explicit synchronization.
What happens if you write an app using Vulkan, and you want to send video to an app using OpenGL that only supports explicit sync? Thanfully, this is possible. libfunnel can use some dark magic to convert between explicit and explicit synchronization and handle it for you.
For this reason, libfunnel has two settings for synchronization: The frontend synchronization mode (the sync style you use with the API) and the backend synchronization mode (the sync style that other apps see when they connect to your app through PipeWire). These two modes are configured using the funnel_stream_set_sync() function. The first argument specifies the frontend sync mode, and the second argument specifies the backend sync mode. Each mode can have one of these values:
If you use Vulkan, the default (FUNNEL_SYNC_EXPLICIT, FUNNEL_SYNC_BOTH) is fine. If you use OpenGL, use FUNNEL_SYNC_BOTH, FUNNEL_SYNC_BOTH for maximum compatibility, and make sure to implement explicit sync. In both cases, if you think users might want to do one-to-many video connections, consider providing an option to force the second (backend) sync mode to FUNNEL_SYNC_IMPLICIT (this will not work on Nvidia drivers).
Use implicit buffer sync only.
This will only advertise implicit sync on the PipeWire stream. The other end must support implicit sync.
Use explicit buffer sync only.
This will only advertise explicit sync on the PipeWire stream. The other end must support explicit sync.
You must use the explicit sync APIs to synchronize buffer access.
Support both implicit and explicit sync.
Advertise both implicit and explicit sync, and negotiate automatically depending on the capabilities of the other end. Explicit sync is preferred if available.
You must use the explicit sync APIs to synchronize buffer access if required (check with funnel_buffer_has_sync()).
Use explicit buffer sync in the API, with automatic conversion to implicit sync if required.
Advertise both implicit and explicit sync, and negotiate automatically depending on the capabilities of the other end. Explicit sync is preferred if available.
You must use the explicit sync APIs to synchronize buffer access (funnel_buffer_has_sync() will always return true).
Like the previous case, but forces implicit sync on the PipeWire node. This is useful if the stream will be connected to more than one consumer, since that only works with implicit sync (currently).
Converting explicit sync to implicit sync is not supported at this time, so these combinations are not legal:
The Vulkan API requires explicit sync, so the frontend synchronization mode must be FUNNEL_SYNC_EXPLICIT if you use Vulkan.
The Nvidia proprietary drivers do not support implicit sync. Passing FUNNEL_SYNC_IMPLICIT as either mode will fail on those drivers, and FUNNEL_SYNC_BOTH will behave as FUNNEL_SYNC_EXPLICIT.
To support explicit (and implicit) sync in OpenGL, add this code before issuing any OpenGL commands that draw or blit to the buffer image/texture:
// Explicit sync support
if (funnel_buffer_has_sync(buf)) {
EGLSync acquire;
funnel_buffer_get_acquire_egl_sync(buf, &acquire);
eglWaitSync(egl_display, acquire, 0);
eglDestroySync(egl_display, acquire);
}
And add this code after you are done issuing draw/blit commands:
if (funnel_buffer_has_sync(buf)) {
// Explicit sync support
EGLSync release = eglCreateSync(
egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
funnel_buffer_set_release_egl_sync(buf, release);
eglDestroySync(egl_display, release);
} else {
// Required for implicit sync
glFlush();
}