use std::ffi::{c_char, c_int, c_uint, c_void};
use std::sync::OnceLock;
use libloading::Library;
use crate::GbmError;
pub(crate) type DevicePtr = *mut c_void;
pub(crate) type BoPtr = *mut c_void;
pub(crate) type SurfacePtr = *mut c_void;
#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) union BoHandle {
pub(crate) ptr: *mut c_void,
pub(crate) s32: i32,
pub(crate) u32_: u32,
pub(crate) s64: i64,
pub(crate) u64_: u64,
}
type CreateBoV1 = unsafe extern "C" fn(DevicePtr, u32, u32, u32, *const u64, c_uint) -> BoPtr;
type CreateBoV2 = unsafe extern "C" fn(DevicePtr, u32, u32, u32, *const u64, c_uint, u32) -> BoPtr;
type CreateSurfaceV1 =
unsafe extern "C" fn(DevicePtr, u32, u32, u32, *const u64, c_uint) -> SurfacePtr;
type CreateSurfaceV2 =
unsafe extern "C" fn(DevicePtr, u32, u32, u32, *const u64, c_uint, u32) -> SurfacePtr;
pub(crate) struct Gbm {
pub(crate) create_device: unsafe extern "C" fn(c_int) -> DevicePtr,
pub(crate) device_destroy: unsafe extern "C" fn(DevicePtr),
pub(crate) device_get_backend_name: unsafe extern "C" fn(DevicePtr) -> *const c_char,
pub(crate) bo_create: unsafe extern "C" fn(DevicePtr, u32, u32, u32, u32) -> BoPtr,
pub(crate) bo_create_with_modifiers2: Option<CreateBoV2>,
pub(crate) bo_create_with_modifiers: Option<CreateBoV1>,
pub(crate) bo_destroy: unsafe extern "C" fn(BoPtr),
pub(crate) bo_get_width: unsafe extern "C" fn(BoPtr) -> u32,
pub(crate) bo_get_height: unsafe extern "C" fn(BoPtr) -> u32,
pub(crate) bo_get_stride: unsafe extern "C" fn(BoPtr) -> u32,
pub(crate) bo_get_format: unsafe extern "C" fn(BoPtr) -> u32,
pub(crate) bo_get_modifier: Option<unsafe extern "C" fn(BoPtr) -> u64>,
pub(crate) bo_get_plane_count: Option<unsafe extern "C" fn(BoPtr) -> c_int>,
pub(crate) bo_get_handle: unsafe extern "C" fn(BoPtr) -> BoHandle,
pub(crate) bo_get_fd: unsafe extern "C" fn(BoPtr) -> c_int,
pub(crate) surface_create: unsafe extern "C" fn(DevicePtr, u32, u32, u32, u32) -> SurfacePtr,
pub(crate) surface_create_with_modifiers2: Option<CreateSurfaceV2>,
pub(crate) surface_create_with_modifiers: Option<CreateSurfaceV1>,
pub(crate) surface_destroy: unsafe extern "C" fn(SurfacePtr),
pub(crate) surface_lock_front_buffer: unsafe extern "C" fn(SurfacePtr) -> BoPtr,
pub(crate) surface_release_buffer: unsafe extern "C" fn(SurfacePtr, BoPtr),
pub(crate) surface_has_free_buffers: unsafe extern "C" fn(SurfacePtr) -> c_int,
_library: Library,
}
const NAMES: [&str; 2] = ["libgbm.so.1", "libgbm.so"];
pub const HIDE_ENV: &str = "DRMKIT_GBM_HIDE_SYMBOLS";
#[cfg(test)]
pub(crate) const OPTIONAL: [&str; 6] = [
"gbm_bo_create_with_modifiers2",
"gbm_bo_create_with_modifiers",
"gbm_surface_create_with_modifiers2",
"gbm_surface_create_with_modifiers",
"gbm_bo_get_modifier",
"gbm_bo_get_plane_count",
];
static GBM: OnceLock<Result<Gbm, String>> = OnceLock::new();
pub(crate) fn gbm() -> Result<&'static Gbm, GbmError> {
GBM.get_or_init(open)
.as_ref()
.map_err(|reason| GbmError::NotGbmCapable(reason.clone()))
}
fn open() -> Result<Gbm, String> {
let mut failures = Vec::new();
for name in NAMES {
match unsafe { Library::new(name) } {
Ok(library) => return resolve(library),
Err(error) => failures.push(format!("{name}: {error}")),
}
}
Err(failures.join("; "))
}
macro_rules! required {
($library:expr, $name:literal) => {
*unsafe { $library.get(concat!($name, "\0").as_bytes()) }
.map_err(|error| format!("{}: {error}", $name))?
};
}
macro_rules! optional {
($library:expr, $hidden:expr, $name:literal) => {
if $hidden.iter().any(|hidden| hidden == $name) {
None
} else {
unsafe { $library.get(concat!($name, "\0").as_bytes()) }
.ok()
.map(|symbol| *symbol)
}
};
}
fn resolve(library: Library) -> Result<Gbm, String> {
let hidden: Vec<String> = std::env::var(HIDE_ENV)
.unwrap_or_default()
.split(',')
.map(|name| name.trim().to_owned())
.filter(|name| !name.is_empty())
.collect();
Ok(Gbm {
create_device: required!(library, "gbm_create_device"),
device_destroy: required!(library, "gbm_device_destroy"),
device_get_backend_name: required!(library, "gbm_device_get_backend_name"),
bo_create: required!(library, "gbm_bo_create"),
bo_create_with_modifiers2: optional!(library, hidden, "gbm_bo_create_with_modifiers2"),
bo_create_with_modifiers: optional!(library, hidden, "gbm_bo_create_with_modifiers"),
bo_destroy: required!(library, "gbm_bo_destroy"),
bo_get_width: required!(library, "gbm_bo_get_width"),
bo_get_height: required!(library, "gbm_bo_get_height"),
bo_get_stride: required!(library, "gbm_bo_get_stride"),
bo_get_format: required!(library, "gbm_bo_get_format"),
bo_get_modifier: optional!(library, hidden, "gbm_bo_get_modifier"),
bo_get_plane_count: optional!(library, hidden, "gbm_bo_get_plane_count"),
bo_get_handle: required!(library, "gbm_bo_get_handle"),
bo_get_fd: required!(library, "gbm_bo_get_fd"),
surface_create: required!(library, "gbm_surface_create"),
surface_create_with_modifiers2: optional!(
library,
hidden,
"gbm_surface_create_with_modifiers2"
),
surface_create_with_modifiers: optional!(
library,
hidden,
"gbm_surface_create_with_modifiers"
),
surface_destroy: required!(library, "gbm_surface_destroy"),
surface_lock_front_buffer: required!(library, "gbm_surface_lock_front_buffer"),
surface_release_buffer: required!(library, "gbm_surface_release_buffer"),
surface_has_free_buffers: required!(library, "gbm_surface_has_free_buffers"),
_library: library,
})
}
#[cfg(test)]
impl Gbm {
pub(crate) fn has(&self, name: &str) -> Option<bool> {
Some(match name {
"gbm_bo_create_with_modifiers2" => self.bo_create_with_modifiers2.is_some(),
"gbm_bo_create_with_modifiers" => self.bo_create_with_modifiers.is_some(),
"gbm_surface_create_with_modifiers2" => self.surface_create_with_modifiers2.is_some(),
"gbm_surface_create_with_modifiers" => self.surface_create_with_modifiers.is_some(),
"gbm_bo_get_modifier" => self.bo_get_modifier.is_some(),
"gbm_bo_get_plane_count" => self.bo_get_plane_count.is_some(),
_ => return None,
})
}
}
pub(crate) fn last_error(call: &str) -> String {
format!("{call}: {}", std::io::Error::last_os_error())
}