blit-server 0.28.0

blit terminal multiplexer server
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
//! Runtime GPU library loading via dlopen.
//!
//! All GPU driver libraries are loaded on first use.  If a library is
//! missing the corresponding encoder backend is simply unavailable —
//! the binary remains fully functional with software-only encoding.
//!
//! Release binaries are dynamically linked against musl libc so that
//! dlopen works, while all other dependencies are statically linked.
//! This avoids a build-time dependency on libva, libcuda,
//! libnvidia-encode, etc. — hardware acceleration is available when
//! the drivers are installed at runtime.

#![allow(non_camel_case_types, non_snake_case, dead_code)]

use std::ffi::{c_int, c_uint, c_void};
use std::sync::OnceLock;

// ---------------------------------------------------------------------------
// dlopen helpers
// ---------------------------------------------------------------------------

pub(crate) struct DynLib {
    handle: *mut c_void,
}

unsafe impl Send for DynLib {}
unsafe impl Sync for DynLib {}

impl DynLib {
    #[cfg(unix)]
    pub(crate) fn open(names: &[&str]) -> Result<Self, String> {
        Self::open_flags(names, libc::RTLD_NOW | libc::RTLD_LOCAL)
    }

    #[cfg(unix)]
    fn open_flags(names: &[&str], flags: libc::c_int) -> Result<Self, String> {
        let mut last_err = String::new();
        for name in names {
            let Some(cname) = std::ffi::CString::new(*name).ok() else {
                continue;
            };
            let handle = unsafe { libc::dlopen(cname.as_ptr(), flags) };
            if !handle.is_null() {
                return Ok(Self { handle });
            }
            let err = unsafe { libc::dlerror() };
            if !err.is_null() {
                last_err = unsafe { std::ffi::CStr::from_ptr(err) }
                    .to_string_lossy()
                    .into_owned();
            }
        }
        Err(last_err)
    }

    #[cfg(not(unix))]
    pub(crate) fn open(_names: &[&str]) -> Result<Self, String> {
        Err("dlopen not available on this platform".into())
    }

    #[cfg(unix)]
    pub(crate) unsafe fn sym<T>(&self, name: &str) -> Result<T, String> {
        let cname =
            std::ffi::CString::new(name).map_err(|_| format!("invalid symbol name: {name}"))?;
        let ptr = unsafe { libc::dlsym(self.handle, cname.as_ptr()) };
        if ptr.is_null() {
            let err = unsafe { libc::dlerror() };
            let detail = if !err.is_null() {
                unsafe { std::ffi::CStr::from_ptr(err) }
                    .to_string_lossy()
                    .into_owned()
            } else {
                "symbol not found".into()
            };
            return Err(format!("{name}: {detail}"));
        }
        Ok(unsafe { std::mem::transmute_copy(&ptr) })
    }

    #[cfg(not(unix))]
    unsafe fn sym<T>(&self, _name: &str) -> Result<T, String> {
        Err("dlsym not available on this platform".into())
    }
}

impl Drop for DynLib {
    fn drop(&mut self) {
        #[cfg(unix)]
        unsafe {
            libc::dlclose(self.handle);
        }
    }
}

// ---------------------------------------------------------------------------
// CUDA driver API
// ---------------------------------------------------------------------------

pub type CUresult = c_int;
pub type CUdevice = c_int;
pub type CUcontext = *mut c_void;
pub type CUdeviceptr = u64;

/// Opaque handle for imported external memory (CUDA 10.0+).
pub type CUexternalMemory = *mut c_void;

pub struct CudaFns {
    pub cuInit: unsafe extern "C" fn(flags: c_uint) -> CUresult,
    pub cuDeviceGet: unsafe extern "C" fn(device: *mut CUdevice, ordinal: c_int) -> CUresult,
    pub cuCtxCreate_v2:
        unsafe extern "C" fn(pctx: *mut CUcontext, flags: c_uint, dev: CUdevice) -> CUresult,
    pub cuCtxDestroy_v2: unsafe extern "C" fn(ctx: CUcontext) -> CUresult,
    pub cuCtxPushCurrent_v2: unsafe extern "C" fn(ctx: CUcontext) -> CUresult,
    pub cuCtxPopCurrent_v2: unsafe extern "C" fn(pctx: *mut CUcontext) -> CUresult,
    pub cuMemAlloc_v2: unsafe extern "C" fn(dptr: *mut CUdeviceptr, bytesize: usize) -> CUresult,
    pub cuMemFree_v2: unsafe extern "C" fn(dptr: CUdeviceptr) -> CUresult,
    pub cuMemcpyHtoD_v2:
        unsafe extern "C" fn(dst: CUdeviceptr, src: *const c_void, bytesize: usize) -> CUresult,
    pub cuMemAllocHost_v2: unsafe extern "C" fn(pp: *mut *mut c_void, bytesize: usize) -> CUresult,
    pub cuMemFreeHost: unsafe extern "C" fn(p: *mut c_void) -> CUresult,
    pub cuMemAllocPitch_v2: unsafe extern "C" fn(
        dptr: *mut CUdeviceptr,
        pPitch: *mut usize,
        WidthInBytes: usize,
        Height: usize,
        ElementSizeBytes: c_uint,
    ) -> CUresult,
    pub cuStreamSynchronize: unsafe extern "C" fn(hStream: *mut c_void) -> CUresult,
    // External memory import (CUDA 10.0+) — used for zero-copy DMA-BUF import.
    pub cuImportExternalMemory: Option<
        unsafe extern "C" fn(
            extMem_out: *mut CUexternalMemory,
            memHandleDesc: *const c_void,
        ) -> CUresult,
    >,
    pub cuExternalMemoryGetMappedBuffer: Option<
        unsafe extern "C" fn(
            devPtr: *mut CUdeviceptr,
            extMem: CUexternalMemory,
            bufferDesc: *const c_void,
        ) -> CUresult,
    >,
    pub cuDestroyExternalMemory: Option<unsafe extern "C" fn(extMem: CUexternalMemory) -> CUresult>,
    _lib: DynLib,
}

impl CudaFns {
    pub fn load() -> Result<Self, String> {
        let lib = DynLib::open(&["libcuda.so.1", "libcuda.so"])?;
        unsafe {
            Ok(Self {
                cuInit: lib.sym("cuInit")?,
                cuDeviceGet: lib.sym("cuDeviceGet")?,
                cuCtxCreate_v2: lib.sym("cuCtxCreate_v2")?,
                cuCtxDestroy_v2: lib.sym("cuCtxDestroy_v2")?,
                cuCtxPushCurrent_v2: lib.sym("cuCtxPushCurrent_v2")?,
                cuCtxPopCurrent_v2: lib.sym("cuCtxPopCurrent_v2")?,
                cuMemAlloc_v2: lib.sym("cuMemAlloc_v2")?,
                cuMemFree_v2: lib.sym("cuMemFree_v2")?,
                cuMemcpyHtoD_v2: lib.sym("cuMemcpyHtoD_v2")?,
                cuMemAllocHost_v2: lib.sym("cuMemAllocHost_v2")?,
                cuMemFreeHost: lib.sym("cuMemFreeHost")?,
                cuMemAllocPitch_v2: lib.sym("cuMemAllocPitch_v2")?,
                cuStreamSynchronize: lib.sym("cuStreamSynchronize")?,
                // Optional: only available with CUDA 10.0+ drivers.
                cuImportExternalMemory: lib.sym("cuImportExternalMemory").ok(),
                cuExternalMemoryGetMappedBuffer: lib.sym("cuExternalMemoryGetMappedBuffer").ok(),
                cuDestroyExternalMemory: lib.sym("cuDestroyExternalMemory").ok(),
                _lib: lib,
            })
        }
    }
}

// ---------------------------------------------------------------------------
// NVENC API
// ---------------------------------------------------------------------------

/// NVENC uses a function-pointer table returned by NvEncodeAPICreateInstance.
/// We store the entry point loaded from libnvidia-encode.so and the
/// function table is obtained at encoder creation time.
pub struct NvEncFns {
    pub NvEncodeAPICreateInstance: unsafe extern "C" fn(functionList: *mut c_void) -> c_uint,
    _lib: DynLib,
}

impl NvEncFns {
    pub fn load() -> Result<Self, String> {
        let lib = DynLib::open(&["libnvidia-encode.so.1", "libnvidia-encode.so"])?;
        unsafe {
            Ok(Self {
                NvEncodeAPICreateInstance: lib.sym("NvEncodeAPICreateInstance")?,
                _lib: lib,
            })
        }
    }
}

// ---------------------------------------------------------------------------
// VA-API
// ---------------------------------------------------------------------------

pub type VADisplay = *mut c_void;
pub type VAConfigID = c_uint;
pub type VAContextID = c_uint;
pub type VASurfaceID = c_uint;
pub type VABufferID = c_uint;
pub type VAStatus = c_int;
pub type VAImageID = c_uint;
pub type VAEntrypoint = c_int;
pub type VAProfile = c_int;

pub const VA_STATUS_SUCCESS: VAStatus = 0;

pub struct VaFns {
    pub vaInitialize:
        unsafe extern "C" fn(dpy: VADisplay, major: *mut c_int, minor: *mut c_int) -> VAStatus,
    pub vaTerminate: unsafe extern "C" fn(dpy: VADisplay) -> VAStatus,
    pub vaQueryConfigEntrypoints: unsafe extern "C" fn(
        dpy: VADisplay,
        profile: VAProfile,
        entrypoints: *mut VAEntrypoint,
        num: *mut c_int,
    ) -> VAStatus,
    pub vaCreateConfig: unsafe extern "C" fn(
        dpy: VADisplay,
        profile: VAProfile,
        entrypoint: VAEntrypoint,
        attrib_list: *mut c_void,
        num_attribs: c_int,
        config_id: *mut VAConfigID,
    ) -> VAStatus,
    pub vaDestroyConfig: unsafe extern "C" fn(dpy: VADisplay, config: VAConfigID) -> VAStatus,
    pub vaCreateContext: unsafe extern "C" fn(
        dpy: VADisplay,
        config: VAConfigID,
        width: c_int,
        height: c_int,
        flag: c_int,
        render_targets: *mut VASurfaceID,
        num_render_targets: c_int,
        context: *mut VAContextID,
    ) -> VAStatus,
    pub vaDestroyContext: unsafe extern "C" fn(dpy: VADisplay, context: VAContextID) -> VAStatus,
    pub vaCreateSurfaces: unsafe extern "C" fn(
        dpy: VADisplay,
        format: c_uint,
        width: c_uint,
        height: c_uint,
        surfaces: *mut VASurfaceID,
        num_surfaces: c_uint,
        attrib_list: *mut c_void,
        num_attribs: c_uint,
    ) -> VAStatus,
    pub vaDestroySurfaces: unsafe extern "C" fn(
        dpy: VADisplay,
        surfaces: *mut VASurfaceID,
        num_surfaces: c_int,
    ) -> VAStatus,
    pub vaCreateBuffer: unsafe extern "C" fn(
        dpy: VADisplay,
        context: VAContextID,
        type_: c_int,
        size: c_uint,
        num_elements: c_uint,
        data: *mut c_void,
        buf_id: *mut VABufferID,
    ) -> VAStatus,
    pub vaDestroyBuffer: unsafe extern "C" fn(dpy: VADisplay, buf: VABufferID) -> VAStatus,
    pub vaMapBuffer:
        unsafe extern "C" fn(dpy: VADisplay, buf: VABufferID, pbuf: *mut *mut c_void) -> VAStatus,
    pub vaUnmapBuffer: unsafe extern "C" fn(dpy: VADisplay, buf: VABufferID) -> VAStatus,
    pub vaDeriveImage: unsafe extern "C" fn(
        dpy: VADisplay,
        surface: VASurfaceID,
        image: *mut c_void, // VAImage*
    ) -> VAStatus,
    pub vaDestroyImage: unsafe extern "C" fn(dpy: VADisplay, image: VAImageID) -> VAStatus,
    pub vaBeginPicture: unsafe extern "C" fn(
        dpy: VADisplay,
        context: VAContextID,
        render_target: VASurfaceID,
    ) -> VAStatus,
    pub vaRenderPicture: unsafe extern "C" fn(
        dpy: VADisplay,
        context: VAContextID,
        buffers: *mut VABufferID,
        num_buffers: c_int,
    ) -> VAStatus,
    pub vaEndPicture: unsafe extern "C" fn(dpy: VADisplay, context: VAContextID) -> VAStatus,
    pub vaSyncSurface: unsafe extern "C" fn(dpy: VADisplay, surface: VASurfaceID) -> VAStatus,
    pub vaExportSurfaceHandle: unsafe extern "C" fn(
        dpy: VADisplay,
        surface: VASurfaceID,
        mem_type: c_uint,
        flags: c_uint,
        descriptor: *mut c_void,
    ) -> VAStatus,
    _lib: DynLib,
}

impl VaFns {
    pub fn load() -> Result<Self, String> {
        let lib = DynLib::open(&["libva.so.2", "libva.so"])?;
        unsafe {
            Ok(Self {
                vaInitialize: lib.sym("vaInitialize")?,
                vaTerminate: lib.sym("vaTerminate")?,
                vaQueryConfigEntrypoints: lib.sym("vaQueryConfigEntrypoints")?,
                vaCreateConfig: lib.sym("vaCreateConfig")?,
                vaDestroyConfig: lib.sym("vaDestroyConfig")?,
                vaCreateContext: lib.sym("vaCreateContext")?,
                vaDestroyContext: lib.sym("vaDestroyContext")?,
                vaCreateSurfaces: lib.sym("vaCreateSurfaces")?,
                vaDestroySurfaces: lib.sym("vaDestroySurfaces")?,
                vaCreateBuffer: lib.sym("vaCreateBuffer")?,
                vaDestroyBuffer: lib.sym("vaDestroyBuffer")?,
                vaMapBuffer: lib.sym("vaMapBuffer")?,
                vaUnmapBuffer: lib.sym("vaUnmapBuffer")?,
                vaDeriveImage: lib.sym("vaDeriveImage")?,
                vaDestroyImage: lib.sym("vaDestroyImage")?,
                vaBeginPicture: lib.sym("vaBeginPicture")?,
                vaRenderPicture: lib.sym("vaRenderPicture")?,
                vaEndPicture: lib.sym("vaEndPicture")?,
                vaSyncSurface: lib.sym("vaSyncSurface")?,
                vaExportSurfaceHandle: lib.sym("vaExportSurfaceHandle")?,
                _lib: lib,
            })
        }
    }
}

/// VA-API DRM display creation (from libva-drm.so).
pub struct VaDrmFns {
    pub vaGetDisplayDRM: unsafe extern "C" fn(fd: c_int) -> VADisplay,
    _lib: DynLib,
}

impl VaDrmFns {
    pub fn load() -> Result<Self, String> {
        let lib = DynLib::open(&["libva-drm.so.2", "libva-drm.so"])?;
        unsafe {
            Ok(Self {
                vaGetDisplayDRM: lib.sym("vaGetDisplayDRM")?,
                _lib: lib,
            })
        }
    }
}

// ---------------------------------------------------------------------------
// GBM (Generic Buffer Manager) — allocates DRM-native DMA-BUFs that both
// VA-API and Vulkan can import.
// ---------------------------------------------------------------------------

pub type GbmDevice = *mut c_void;
pub type GbmBo = *mut c_void;

pub struct GbmFns {
    pub gbm_create_device: unsafe extern "C" fn(fd: c_int) -> GbmDevice,
    pub gbm_device_destroy: unsafe extern "C" fn(gbm: GbmDevice),
    pub gbm_bo_create: unsafe extern "C" fn(
        gbm: GbmDevice,
        width: u32,
        height: u32,
        format: u32,
        flags: u32,
    ) -> GbmBo,
    pub gbm_bo_destroy: unsafe extern "C" fn(bo: GbmBo),
    pub gbm_bo_get_fd: unsafe extern "C" fn(bo: GbmBo) -> c_int,
    pub gbm_bo_get_stride: unsafe extern "C" fn(bo: GbmBo) -> u32,
    pub gbm_bo_get_modifier: unsafe extern "C" fn(bo: GbmBo) -> u64,
    pub gbm_bo_get_handle: unsafe extern "C" fn(bo: GbmBo) -> GbmBoHandle,
    _lib: DynLib,
}

/// `union gbm_bo_handle` — only the u32 GEM handle field is used.
#[repr(C)]
#[derive(Copy, Clone)]
pub union GbmBoHandle {
    pub u32_: u32,
    pub u64_: u64,
}

unsafe impl Send for GbmFns {}
unsafe impl Sync for GbmFns {}

// GBM_FORMAT_ARGB8888 = __gbm_fourcc_code('A','R','2','4')
pub const GBM_FORMAT_ARGB8888: u32 = u32::from_le_bytes(*b"AR24");
pub const GBM_BO_USE_RENDERING: u32 = 1 << 2;
pub const GBM_BO_USE_LINEAR: u32 = 1 << 4;

impl GbmFns {
    pub fn load() -> Result<Self, String> {
        let lib = DynLib::open(&["libgbm.so.1", "libgbm.so"])?;
        unsafe {
            Ok(Self {
                gbm_create_device: lib.sym("gbm_create_device")?,
                gbm_device_destroy: lib.sym("gbm_device_destroy")?,
                gbm_bo_create: lib.sym("gbm_bo_create")?,
                gbm_bo_destroy: lib.sym("gbm_bo_destroy")?,
                gbm_bo_get_fd: lib.sym("gbm_bo_get_fd")?,
                gbm_bo_get_stride: lib.sym("gbm_bo_get_stride")?,
                gbm_bo_get_modifier: lib.sym("gbm_bo_get_modifier")?,
                gbm_bo_get_handle: lib.sym("gbm_bo_get_handle")?,
                _lib: lib,
            })
        }
    }
}

// ---------------------------------------------------------------------------
// Singleton accessors
// ---------------------------------------------------------------------------

static CUDA: OnceLock<Result<CudaFns, String>> = OnceLock::new();
static NVENC: OnceLock<Result<NvEncFns, String>> = OnceLock::new();
static VA: OnceLock<Result<VaFns, String>> = OnceLock::new();
static VA_DRM: OnceLock<Result<VaDrmFns, String>> = OnceLock::new();
static GBM: OnceLock<Result<GbmFns, String>> = OnceLock::new();

pub fn cuda() -> Result<&'static CudaFns, &'static str> {
    CUDA.get_or_init(CudaFns::load)
        .as_ref()
        .map_err(|e| e.as_str())
}

pub fn nvenc() -> Result<&'static NvEncFns, &'static str> {
    NVENC
        .get_or_init(NvEncFns::load)
        .as_ref()
        .map_err(|e| e.as_str())
}

pub fn va() -> Result<&'static VaFns, &'static str> {
    VA.get_or_init(VaFns::load).as_ref().map_err(|e| e.as_str())
}

pub fn va_drm() -> Result<&'static VaDrmFns, &'static str> {
    VA_DRM
        .get_or_init(VaDrmFns::load)
        .as_ref()
        .map_err(|e| e.as_str())
}

pub fn gbm() -> Result<&'static GbmFns, &'static str> {
    GBM.get_or_init(GbmFns::load)
        .as_ref()
        .map_err(|e| e.as_str())
}