#![warn(missing_docs)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unstable_features)]
#![deny(unused_import_braces)]
#![deny(
clippy::complexity,
clippy::correctness,
clippy::perf,
clippy::style,
clippy::pedantic
)]
#![allow(
clippy::too_many_arguments, // API design
clippy::missing_safety_doc, // Until we add them...
clippy::similar_names, // This requires effort to ensure
// Due to vzeroupper use, compiler does not inline intrinsics
// but rather creates a function for each one that wraps the operation followed
// by vzeroupper().
// This is detrimental to performance
clippy::inline_always,
)]
mod color_space;
mod convert_image;
mod cpu_info;
mod dispatcher;
mod pixel_format;
mod static_assert;
use cpu_info::{CpuManufacturer, InstructionSet};
use paste::paste;
use std::error;
use std::fmt;
pub use color_space::ColorSpace;
pub use pixel_format::{PixelFormat, STRIDE_AUTO};
#[derive(Debug)]
#[repr(C)]
pub enum ErrorKind {
NotInitialized,
InvalidValue,
InvalidOperation,
NotEnoughData,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ErrorKind::NotInitialized => {
write!(f, "Library was not initialized by calling initialize()")
}
ErrorKind::InvalidValue => write!(
f,
"One or more parameters have not legal values for the command"
),
ErrorKind::InvalidOperation => write!(
f,
"The combination of parameters is not legal for the command"
),
ErrorKind::NotEnoughData => write!(f, "Not enough data provided"),
}
}
}
impl error::Error for ErrorKind {
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
#[repr(C)]
pub struct ImageFormat {
pub pixel_format: PixelFormat,
pub color_space: ColorSpace,
pub num_planes: u32,
}
type ConvertDispatcher =
fn(u32, u32, u32, &[usize], &[&[u8]], u32, &[usize], &mut [&mut [u8]]) -> bool;
macro_rules! rgb_to_yuv {
($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident, $dst_cs:ident) => {
paste! {
$conv[dispatcher::get_index(
dispatcher::get_image_index(
PixelFormat::$src_pf as u32,
ColorSpace::Rgb as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
),
dispatcher::get_image_index(
PixelFormat::$dst_pf as u32,
ColorSpace::$dst_cs as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
),
)] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower _ $dst_cs:lower>])
}
};
}
macro_rules! yuv_to_rgb {
($conv:expr, $set:ident, $src_pf:ident, $src_cs:ident, $dst_pf:ident) => {
paste! {
$conv[dispatcher::get_index(
dispatcher::get_image_index(
PixelFormat::$src_pf as u32,
ColorSpace::$src_cs as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
),
dispatcher::get_image_index(
PixelFormat::$dst_pf as u32,
ColorSpace::Rgb as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
),
)] = Some(convert_image::$set::[<$src_pf:lower _ $src_cs:lower _ $dst_pf:lower>])
}
};
}
macro_rules! rgb_to_rgb {
($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident) => {
paste! {
$conv[dispatcher::get_index(
dispatcher::get_image_index(
PixelFormat::$src_pf as u32,
ColorSpace::Rgb as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
),
dispatcher::get_image_index(
PixelFormat::$dst_pf as u32,
ColorSpace::Rgb as u32,
dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
),
)] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower>])
}
};
}
macro_rules! set_dispatch_table {
($conv:expr, $set:ident) => {
rgb_to_rgb!($conv, $set, Bgr, Rgb);
rgb_to_rgb!($conv, $set, Bgra, Rgb);
rgb_to_rgb!($conv, $set, Rgb, Bgra);
rgb_to_yuv!($conv, $set, Argb, I420, Bt601);
rgb_to_yuv!($conv, $set, Argb, I420, Bt601FR);
rgb_to_yuv!($conv, $set, Argb, I420, Bt709);
rgb_to_yuv!($conv, $set, Argb, I420, Bt709FR);
rgb_to_yuv!($conv, $set, Argb, I444, Bt601);
rgb_to_yuv!($conv, $set, Argb, I444, Bt601FR);
rgb_to_yuv!($conv, $set, Argb, I444, Bt709);
rgb_to_yuv!($conv, $set, Argb, I444, Bt709FR);
rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601);
rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601FR);
rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709);
rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709FR);
rgb_to_yuv!($conv, $set, Bgr, I420, Bt601);
rgb_to_yuv!($conv, $set, Bgr, I420, Bt601FR);
rgb_to_yuv!($conv, $set, Bgr, I420, Bt709);
rgb_to_yuv!($conv, $set, Bgr, I420, Bt709FR);
rgb_to_yuv!($conv, $set, Bgr, I444, Bt601);
rgb_to_yuv!($conv, $set, Bgr, I444, Bt601FR);
rgb_to_yuv!($conv, $set, Bgr, I444, Bt709);
rgb_to_yuv!($conv, $set, Bgr, I444, Bt709FR);
rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601);
rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601FR);
rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709);
rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709FR);
rgb_to_yuv!($conv, $set, Bgra, I420, Bt601);
rgb_to_yuv!($conv, $set, Bgra, I420, Bt601FR);
rgb_to_yuv!($conv, $set, Bgra, I420, Bt709);
rgb_to_yuv!($conv, $set, Bgra, I420, Bt709FR);
rgb_to_yuv!($conv, $set, Bgra, I444, Bt601);
rgb_to_yuv!($conv, $set, Bgra, I444, Bt601FR);
rgb_to_yuv!($conv, $set, Bgra, I444, Bt709);
rgb_to_yuv!($conv, $set, Bgra, I444, Bt709FR);
rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601);
rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601FR);
rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709);
rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709FR);
yuv_to_rgb!($conv, $set, I420, Bt601, Bgra);
yuv_to_rgb!($conv, $set, I420, Bt601FR, Bgra);
yuv_to_rgb!($conv, $set, I420, Bt709, Bgra);
yuv_to_rgb!($conv, $set, I420, Bt709FR, Bgra);
yuv_to_rgb!($conv, $set, I444, Bt601, Bgra);
yuv_to_rgb!($conv, $set, I444, Bt601FR, Bgra);
yuv_to_rgb!($conv, $set, I444, Bt709, Bgra);
yuv_to_rgb!($conv, $set, I444, Bt709FR, Bgra);
yuv_to_rgb!($conv, $set, Nv12, Bt601, Bgra);
yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Bgra);
yuv_to_rgb!($conv, $set, Nv12, Bt709, Bgra);
yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Bgra);
};
}
struct GlobalState {
init: bool,
manufacturer: CpuManufacturer,
set: InstructionSet,
converters: [Option<ConvertDispatcher>; dispatcher::TABLE_SIZE],
}
static mut GLOBAL_STATE: GlobalState = GlobalState {
init: false,
manufacturer: CpuManufacturer::Unknown,
set: InstructionSet::X86,
converters: [None; dispatcher::TABLE_SIZE],
};
fn initialize_global_state(manufacturer: CpuManufacturer, set: InstructionSet) {
unsafe {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
match set {
InstructionSet::X86 => {
set_dispatch_table!(GLOBAL_STATE.converters, x86);
}
InstructionSet::Sse2 => {
set_dispatch_table!(GLOBAL_STATE.converters, sse2);
}
InstructionSet::Avx2 => {
set_dispatch_table!(GLOBAL_STATE.converters, avx2);
}
}
#[cfg(all(not(target_arch = "x86"), not(target_arch = "x86_64")))]
{
set_dispatch_table!(GLOBAL_STATE.converters, x86);
}
GLOBAL_STATE.manufacturer = manufacturer;
GLOBAL_STATE.set = set;
GLOBAL_STATE.init = true;
}
}
#[cfg(not(tarpaulin_include))]
pub fn initialize() {
unsafe {
if GLOBAL_STATE.init {
return;
}
}
let (manufacturer, set) = cpu_info::get();
initialize_global_state(manufacturer, set);
}
#[cfg(feature = "test_instruction_sets")]
pub fn initialize_with_instruction_set(instruction_set: &str) {
let (manufacturer, set) = cpu_info::get();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let set = match instruction_set {
"x86" => InstructionSet::X86,
"sse2" => match set {
InstructionSet::Avx2 => InstructionSet::Sse2,
_ => set,
},
_ => set,
};
initialize_global_state(manufacturer, set);
}
pub fn describe_acceleration() -> Result<String, ErrorKind> {
unsafe {
if GLOBAL_STATE.init {
Ok(format!(
"{{cpu-manufacturer:{:?},instruction-set:{:?}}}",
GLOBAL_STATE.manufacturer, GLOBAL_STATE.set
))
} else {
Err(ErrorKind::NotInitialized)
}
}
}
pub fn get_buffers_size(
width: u32,
height: u32,
format: &ImageFormat,
strides: Option<&[usize]>,
buffers_size: &mut [usize],
) -> Result<(), ErrorKind> {
let pixel_format = format.pixel_format as u32;
let last_plane = format.num_planes.wrapping_sub(1);
if !pixel_format::is_compatible(pixel_format, width, height, last_plane) {
return Err(ErrorKind::InvalidValue);
}
if pixel_format::get_buffers_size(
pixel_format,
width,
height,
last_plane,
strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
buffers_size,
) {
Ok(())
} else {
Err(ErrorKind::NotEnoughData)
}
}
pub fn convert_image(
width: u32,
height: u32,
src_format: &ImageFormat,
src_strides: Option<&[usize]>,
src_buffers: &[&[u8]],
dst_format: &ImageFormat,
dst_strides: Option<&[usize]>,
dst_buffers: &mut [&mut [u8]],
) -> Result<(), ErrorKind> {
unsafe {
if !GLOBAL_STATE.init {
return Err(ErrorKind::NotInitialized);
}
}
let src_pixel_format = src_format.pixel_format as u32;
let dst_pixel_format = dst_format.pixel_format as u32;
let src_color_space = src_format.color_space as u32;
let dst_color_space = dst_format.color_space as u32;
let src_pf_mode = dispatcher::get_pixel_format_mode(src_pixel_format);
let src_cs_mode = dispatcher::get_color_space_mode(src_color_space);
let dst_pf_mode = dispatcher::get_pixel_format_mode(dst_pixel_format);
let dst_cs_mode = dispatcher::get_color_space_mode(dst_color_space);
let src_pf_cs_mismatch = src_pf_mode ^ src_cs_mode;
let dst_pf_cs_mismatch = dst_pf_mode ^ dst_cs_mode;
if src_pf_cs_mismatch | dst_pf_cs_mismatch {
return Err(ErrorKind::InvalidValue);
}
let last_src_plane = src_format.num_planes.wrapping_sub(1);
if !pixel_format::is_compatible(src_pixel_format, width, height, last_src_plane) {
return Err(ErrorKind::InvalidValue);
}
let last_dst_plane = dst_format.num_planes.wrapping_sub(1);
if !pixel_format::is_compatible(dst_pixel_format, width, height, last_dst_plane) {
return Err(ErrorKind::InvalidValue);
}
let src_index = dispatcher::get_image_index(src_pixel_format, src_color_space, src_pf_mode);
let dst_index = dispatcher::get_image_index(dst_pixel_format, dst_color_space, dst_pf_mode);
let index = dispatcher::get_index(src_index, dst_index);
let converters = { unsafe { &GLOBAL_STATE.converters } };
if index >= converters.len() {
return Err(ErrorKind::InvalidOperation);
}
let converter = converters[index];
match converter {
None => Err(ErrorKind::InvalidOperation),
Some(image_converter) => {
if image_converter(
width,
height,
last_src_plane,
src_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
src_buffers,
last_dst_plane,
dst_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
dst_buffers,
) {
Ok(())
} else {
Err(ErrorKind::NotEnoughData)
}
}
}
}
#[doc(hidden)]
#[cfg(not(feature = "test_instruction_sets"))]
pub mod c_api {
#![allow(clippy::wildcard_imports)]
use super::*; use pixel_format::{are_planes_compatible, MAX_NUMBER_OF_PLANES};
use std::cmp;
use std::ffi::CString;
use std::mem::{transmute, MaybeUninit};
use std::os::raw::c_char;
use std::ptr;
use std::slice;
const UNBOUNDED_C_ARRAY: usize = std::isize::MAX as usize;
#[repr(C)]
pub enum Result {
Ok,
Err,
}
unsafe fn set_error(error: *mut ErrorKind, value: ErrorKind) -> self::Result {
if !error.is_null() {
*error = value;
}
self::Result::Err
}
#[no_mangle]
pub extern "C" fn dcp_initialize() {
initialize();
}
#[no_mangle]
pub extern "C" fn dcp_describe_acceleration() -> *mut c_char {
if let Ok(acc) = describe_acceleration() {
if let Ok(s) = CString::new(acc) {
s.into_raw()
} else {
let p: *const c_char = ptr::null();
p as *mut c_char
}
} else {
let p: *const c_char = ptr::null();
p as *mut c_char
}
}
#[no_mangle]
pub unsafe extern "C" fn dcp_unref_string(string: *mut c_char) {
if !string.is_null() {
let _unused = CString::from_raw(string);
}
}
#[no_mangle]
pub unsafe extern "C" fn dcp_get_buffers_size(
width: u32,
height: u32,
format: *const ImageFormat,
strides: *const usize,
buffers_size: *mut usize,
error: *mut ErrorKind,
) -> self::Result {
if format.is_null() || buffers_size.is_null() {
return set_error(error, ErrorKind::InvalidValue);
}
let format = &*format;
let pixel_format = format.pixel_format as u32;
if !dispatcher::is_pixel_format_valid(pixel_format) {
return set_error(error, ErrorKind::InvalidValue);
}
let num_planes = format.num_planes as usize;
if !are_planes_compatible(pixel_format, format.num_planes) {
return set_error(error, ErrorKind::InvalidValue);
}
let strides = if strides.is_null() {
None
} else {
Some(slice::from_raw_parts(strides, num_planes))
};
let buffers_size = slice::from_raw_parts_mut(buffers_size, num_planes);
match get_buffers_size(width, height, format, strides, buffers_size) {
Ok(_) => self::Result::Ok,
Err(error_kind) => set_error(error, error_kind),
}
}
#[no_mangle]
pub unsafe extern "C" fn dcp_convert_image(
width: u32,
height: u32,
src_format: *const ImageFormat,
src_strides: *const usize,
src_buffers: *const *const u8,
dst_format: *const ImageFormat,
dst_strides: *const usize,
dst_buffers: *const *mut u8,
error: *mut ErrorKind,
) -> self::Result {
if src_format.is_null()
|| dst_format.is_null()
|| src_buffers.is_null()
|| dst_buffers.is_null()
{
return set_error(error, ErrorKind::InvalidValue);
}
let src_format: &ImageFormat = &*src_format;
let dst_format: &ImageFormat = &*dst_format;
let src_pixel_format = src_format.pixel_format as u32;
let dst_pixel_format = dst_format.pixel_format as u32;
if !dispatcher::is_pixel_format_valid(src_pixel_format)
|| !dispatcher::is_pixel_format_valid(dst_pixel_format)
|| !dispatcher::is_color_space_valid(src_format.color_space as u32)
|| !dispatcher::is_color_space_valid(dst_format.color_space as u32)
{
return set_error(error, ErrorKind::InvalidValue);
}
if !are_planes_compatible(src_pixel_format, src_format.num_planes)
|| !are_planes_compatible(dst_pixel_format, dst_format.num_planes)
{
return set_error(error, ErrorKind::InvalidValue);
}
let src_buffers = {
let src_num_planes = src_format.num_planes as usize;
let num_planes = cmp::min(src_num_planes, MAX_NUMBER_OF_PLANES);
let mut src_buf: [MaybeUninit<&[u8]>; MAX_NUMBER_OF_PLANES] =
[MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];
for (plane_index, item) in src_buf.iter_mut().enumerate().take(num_planes) {
let ptr = *src_buffers.add(plane_index);
if ptr.is_null() {
return set_error(error, ErrorKind::InvalidValue);
}
*item = MaybeUninit::new(slice::from_raw_parts(ptr, UNBOUNDED_C_ARRAY));
}
transmute::<_, [&[u8]; MAX_NUMBER_OF_PLANES]>(src_buf)
};
let mut dst_buffers = {
let dst_num_planes = dst_format.num_planes as usize;
let num_planes = cmp::min(dst_num_planes, MAX_NUMBER_OF_PLANES);
let mut dst_buf: [MaybeUninit<&[u8]>; MAX_NUMBER_OF_PLANES] =
[MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];
for (plane_index, item) in dst_buf.iter_mut().enumerate().take(num_planes) {
let ptr = *dst_buffers.add(plane_index);
if ptr.is_null() {
return set_error(error, ErrorKind::InvalidValue);
}
*item = MaybeUninit::new(slice::from_raw_parts_mut(ptr, UNBOUNDED_C_ARRAY));
}
transmute::<_, [&mut [u8]; MAX_NUMBER_OF_PLANES]>(dst_buf)
};
let src_strides = if src_strides.is_null() {
None
} else {
Some(slice::from_raw_parts(src_strides, UNBOUNDED_C_ARRAY))
};
let dst_strides = if dst_strides.is_null() {
None
} else {
Some(slice::from_raw_parts(dst_strides, UNBOUNDED_C_ARRAY))
};
match convert_image(
width,
height,
src_format,
src_strides,
&src_buffers[..],
dst_format,
dst_strides,
&mut dst_buffers[..],
) {
Ok(_) => self::Result::Ok,
Err(error_kind) => set_error(error, error_kind),
}
}
}