use std::ffi::{c_void, CString};
use std::path::Path;
use std::ptr::{self, NonNull};
use edgefirst_tflite_sys::xnnpack_ffi::XnnPackFunctions;
use edgefirst_tflite_sys::TfLiteDelegate;
use crate::error::{Error, Result};
#[cfg(feature = "dmabuf")]
use edgefirst_tflite_sys::hal_ffi::HalDmaBufFunctions;
#[cfg(feature = "dmabuf")]
use edgefirst_tflite_sys::vx_ffi::VxDmaBufFunctions;
#[cfg(feature = "camera_adaptor")]
use edgefirst_tflite_sys::hal_ffi::HalCameraAdaptorFunctions;
#[cfg(feature = "camera_adaptor")]
use edgefirst_tflite_sys::vx_ffi::VxCameraAdaptorFunctions;
#[derive(Debug, Default, Clone)]
pub struct DelegateOptions {
options: Vec<(String, String)>,
}
impl DelegateOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.push((key.into(), value.into()));
self
}
}
#[allow(clippy::struct_field_names)]
pub struct Delegate {
delegate: NonNull<TfLiteDelegate>,
free: unsafe extern "C" fn(*mut TfLiteDelegate),
_lib: libloading::Library,
#[cfg(feature = "dmabuf")]
hal_dmabuf_fns: Option<HalDmaBufFunctions>,
#[cfg(feature = "dmabuf")]
hal_delegate_handle: Option<*mut c_void>,
#[cfg(feature = "dmabuf")]
dmabuf_fns: Option<VxDmaBufFunctions>,
#[cfg(feature = "camera_adaptor")]
hal_camera_fns: Option<HalCameraAdaptorFunctions>,
#[cfg(feature = "camera_adaptor")]
camera_adaptor_fns: Option<VxCameraAdaptorFunctions>,
}
#[cfg(feature = "dmabuf")]
unsafe impl Send for Delegate {}
#[cfg(feature = "dmabuf")]
unsafe impl Sync for Delegate {}
impl Delegate {
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
Self::load_with_options(path, &DelegateOptions::default())
}
pub fn load_with_options(path: impl AsRef<Path>, options: &DelegateOptions) -> Result<Self> {
let lib =
unsafe { libloading::Library::new(path.as_ref().as_os_str()) }.map_err(Error::from)?;
let create_fn = unsafe {
lib.get::<unsafe extern "C" fn(
*const *const std::os::raw::c_char,
*const *const std::os::raw::c_char,
usize,
Option<unsafe extern "C" fn(*const std::os::raw::c_char)>,
) -> *mut TfLiteDelegate>(b"tflite_plugin_create_delegate")
}
.map_err(Error::from)?;
let destroy_fn = unsafe {
lib.get::<unsafe extern "C" fn(*mut TfLiteDelegate)>(b"tflite_plugin_destroy_delegate")
}
.map_err(Error::from)?;
let (keys_c, values_c): (Vec<CString>, Vec<CString>) = options
.options
.iter()
.map(|(k, v)| {
Ok((
CString::new(k.as_str()).map_err(|_| {
Error::invalid_argument(format!(
"option key \"{k}\" contains interior NUL byte"
))
})?,
CString::new(v.as_str()).map_err(|_| {
Error::invalid_argument(format!(
"option value \"{v}\" contains interior NUL byte"
))
})?,
))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.unzip();
let keys_ptrs: Vec<*const std::os::raw::c_char> =
keys_c.iter().map(|c| c.as_ptr()).collect();
let values_ptrs: Vec<*const std::os::raw::c_char> =
values_c.iter().map(|c| c.as_ptr()).collect();
let raw = unsafe {
create_fn(
if keys_ptrs.is_empty() {
ptr::null()
} else {
keys_ptrs.as_ptr()
},
if values_ptrs.is_empty() {
ptr::null()
} else {
values_ptrs.as_ptr()
},
options.options.len(),
None,
)
};
let delegate = NonNull::new(raw)
.ok_or_else(|| Error::null_pointer("tflite_plugin_create_delegate returned null"))?;
let free = *destroy_fn;
#[cfg(feature = "dmabuf")]
let hal_dmabuf_fns = unsafe { HalDmaBufFunctions::try_load(&lib) };
#[cfg(feature = "dmabuf")]
let hal_delegate_handle: Option<*mut c_void> = hal_dmabuf_fns.as_ref().and_then(|fns| {
let ptr = unsafe { (fns.get_instance)() };
if ptr.is_null() {
None
} else {
Some(ptr)
}
});
#[cfg(feature = "dmabuf")]
let dmabuf_fns = unsafe { VxDmaBufFunctions::try_load(&lib) };
#[cfg(feature = "camera_adaptor")]
let hal_camera_fns = unsafe { HalCameraAdaptorFunctions::try_load(&lib) };
#[cfg(feature = "camera_adaptor")]
let camera_adaptor_fns = unsafe { VxCameraAdaptorFunctions::try_load(&lib) };
Ok(Self {
delegate,
free,
_lib: lib,
#[cfg(feature = "dmabuf")]
hal_dmabuf_fns,
#[cfg(feature = "dmabuf")]
hal_delegate_handle,
#[cfg(feature = "dmabuf")]
dmabuf_fns,
#[cfg(feature = "camera_adaptor")]
hal_camera_fns,
#[cfg(feature = "camera_adaptor")]
camera_adaptor_fns,
})
}
pub fn xnnpack(lib: &crate::Library, num_threads: i32) -> Result<Self> {
let fns =
unsafe { XnnPackFunctions::try_load(lib.as_sys().library()) }.ok_or_else(|| {
Error::invalid_argument(
"XNNPACK delegate symbols not found — \
the TFLite library may not have been compiled with XNNPACK support",
)
})?;
let mut opts = unsafe { (fns.options_default)() };
opts.num_threads = num_threads;
let raw = unsafe { (fns.create)(&opts) };
let delegate = NonNull::new(raw)
.ok_or_else(|| Error::null_pointer("TfLiteXNNPackDelegateCreate returned null"))?;
let free = fns.delete;
let tflite_lib = lib.reopen()?;
Ok(Self {
delegate,
free,
_lib: tflite_lib,
#[cfg(feature = "dmabuf")]
hal_dmabuf_fns: None,
#[cfg(feature = "dmabuf")]
hal_delegate_handle: None,
#[cfg(feature = "dmabuf")]
dmabuf_fns: None,
#[cfg(feature = "camera_adaptor")]
hal_camera_fns: None,
#[cfg(feature = "camera_adaptor")]
camera_adaptor_fns: None,
})
}
#[must_use]
pub fn as_ptr(&self) -> *mut TfLiteDelegate {
self.delegate.as_ptr()
}
#[cfg(feature = "dmabuf")]
#[must_use]
pub fn dmabuf(&self) -> Option<crate::dmabuf::DmaBuf<'_>> {
if self.hal_dmabuf_fns.is_some() || self.dmabuf_fns.is_some() {
Some(crate::dmabuf::DmaBuf::new(
self.delegate,
self.hal_delegate_handle,
self.hal_dmabuf_fns.as_ref(),
self.dmabuf_fns.as_ref(),
))
} else {
None
}
}
#[cfg(feature = "dmabuf")]
#[must_use]
pub fn has_dmabuf(&self) -> bool {
self.hal_dmabuf_fns.is_some() || self.dmabuf_fns.is_some()
}
#[cfg(feature = "camera_adaptor")]
#[must_use]
pub fn camera_adaptor(&self) -> Option<crate::camera_adaptor::CameraAdaptor<'_>> {
if self.hal_camera_fns.is_some() || self.camera_adaptor_fns.is_some() {
#[cfg(feature = "dmabuf")]
let hal_handle = self.hal_delegate_handle;
#[cfg(not(feature = "dmabuf"))]
let hal_handle: Option<*mut std::ffi::c_void> = None;
Some(crate::camera_adaptor::CameraAdaptor::new(
self.delegate,
hal_handle,
self.hal_camera_fns.as_ref(),
self.camera_adaptor_fns.as_ref(),
))
} else {
None
}
}
#[cfg(feature = "camera_adaptor")]
#[must_use]
pub fn has_camera_adaptor(&self) -> bool {
self.hal_camera_fns.is_some() || self.camera_adaptor_fns.is_some()
}
}
#[allow(clippy::missing_fields_in_debug)]
impl std::fmt::Debug for Delegate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut d = f.debug_struct("Delegate");
d.field("ptr", &self.delegate);
#[cfg(feature = "dmabuf")]
d.field("has_hal_dmabuf", &self.hal_dmabuf_fns.is_some());
#[cfg(feature = "dmabuf")]
d.field(
"has_hal_delegate_handle",
&self.hal_delegate_handle.is_some(),
);
#[cfg(feature = "dmabuf")]
d.field("has_vx_dmabuf", &self.dmabuf_fns.is_some());
#[cfg(feature = "camera_adaptor")]
d.field("has_hal_camera_adaptor", &self.hal_camera_fns.is_some());
#[cfg(feature = "camera_adaptor")]
d.field("has_vx_camera_adaptor", &self.camera_adaptor_fns.is_some());
d.finish_non_exhaustive()
}
}
impl Drop for Delegate {
fn drop(&mut self) {
unsafe { (self.free)(self.delegate.as_ptr()) };
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_empty_options() {
let opts = DelegateOptions::new();
let debug = format!("{opts:?}");
assert_eq!(debug, "DelegateOptions { options: [] }");
}
#[test]
fn builder_chaining() {
let opts = DelegateOptions::new().option("a", "1").option("b", "2");
assert_eq!(opts.options.len(), 2);
}
#[test]
fn default_matches_new() {
let from_new = format!("{:?}", DelegateOptions::new());
let from_default = format!("{:?}", DelegateOptions::default());
assert_eq!(from_new, from_default);
}
#[test]
fn clone_produces_equal_values() {
let opts = DelegateOptions::new().option("key", "value");
let cloned = opts.clone();
assert_eq!(format!("{opts:?}"), format!("{cloned:?}"));
}
#[test]
fn debug_formatting_not_empty() {
let opts = DelegateOptions::new().option("cache", "/tmp");
let debug = format!("{opts:?}");
assert!(!debug.is_empty());
assert!(debug.contains("DelegateOptions"));
assert!(debug.contains("cache"));
assert!(debug.contains("/tmp"));
}
}