use core::ffi::c_char;
use core::fmt;
use core::{ptr, slice};
pub use fxplug_sys as sys;
pub use sys::{
FxPlugRustImageTile as RawImageTile, FxPlugRustMetalRenderPlan as RawMetalRenderPlan,
FxPlugRustParameterValue as RawParameterValue, FxPlugRustRect as RawRect,
FxPlugRustRenderContext as RawRenderContext, FxPlugRustTime as RawTime,
FxPlugRustVertex2D as RawVertex2D, FXPLUG_RUST_PIXEL_FORMAT_BGRA_U8,
FXPLUG_RUST_PIXEL_FORMAT_RGBA_F32,
};
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
message: String,
}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl From<&str> for Error {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for Error {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Rect {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl From<RawRect> for Rect {
fn from(value: RawRect) -> Self {
Self {
x: value.x,
y: value.y,
width: value.width,
height: value.height,
}
}
}
impl From<Rect> for RawRect {
fn from(value: Rect) -> Self {
Self {
x: value.x,
y: value.y,
width: value.width,
height: value.height,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Time {
pub value: i64,
pub timescale: i32,
pub flags: u32,
pub epoch: i64,
}
impl From<RawTime> for Time {
fn from(value: RawTime) -> Self {
Self {
value: value.value,
timescale: value.timescale,
flags: value.flags,
epoch: value.epoch,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Quality {
Draft,
#[default]
Normal,
High,
Best,
Unknown(u32),
}
impl From<u32> for Quality {
fn from(value: u32) -> Self {
match value {
sys::FXPLUG_RUST_QUALITY_DRAFT => Self::Draft,
sys::FXPLUG_RUST_QUALITY_NORMAL => Self::Normal,
sys::FXPLUG_RUST_QUALITY_HIGH => Self::High,
sys::FXPLUG_RUST_QUALITY_BEST => Self::Best,
other => Self::Unknown(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Properties {
pub may_remap_time: bool,
pub varies_when_params_are_static: bool,
pub pixel_transform_support: u32,
}
impl Default for Properties {
fn default() -> Self {
Self {
may_remap_time: false,
varies_when_params_are_static: false,
pixel_transform_support: sys::FXPLUG_RUST_PIXEL_TRANSFORM_SCALE_TRANSLATE,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FloatSliderSpec {
pub parameter_id: u32,
pub name: &'static str,
pub default_value: f64,
pub parameter_min: f64,
pub parameter_max: f64,
pub slider_min: f64,
pub slider_max: f64,
pub delta: f64,
pub flags: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IntSliderSpec {
pub parameter_id: u32,
pub name: &'static str,
pub default_value: i32,
pub parameter_min: i32,
pub parameter_max: i32,
pub slider_min: i32,
pub slider_max: i32,
pub delta: i32,
pub flags: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ToggleButtonSpec {
pub parameter_id: u32,
pub name: &'static str,
pub default_value: bool,
pub flags: u32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParameterSpec {
FloatSlider(FloatSliderSpec),
IntSlider(IntSliderSpec),
ToggleButton(ToggleButtonSpec),
}
impl ParameterSpec {
pub fn parameter_id(&self) -> u32 {
match self {
Self::FloatSlider(spec) => spec.parameter_id,
Self::IntSlider(spec) => spec.parameter_id,
Self::ToggleButton(spec) => spec.parameter_id,
}
}
pub fn kind(&self) -> ParameterKind {
match self {
Self::FloatSlider(_) => ParameterKind::FloatSlider,
Self::IntSlider(_) => ParameterKind::IntSlider,
Self::ToggleButton(_) => ParameterKind::ToggleButton,
}
}
fn write_raw(self, out: &mut sys::FxPlugRustParameterSpec) {
match self {
Self::FloatSlider(spec) => {
out.parameter_id = spec.parameter_id;
out.kind = sys::FXPLUG_RUST_PARAMETER_KIND_FLOAT_SLIDER;
out.flags = spec.flags;
out.default_value = spec.default_value;
out.parameter_min = spec.parameter_min;
out.parameter_max = spec.parameter_max;
out.slider_min = spec.slider_min;
out.slider_max = spec.slider_max;
out.delta = spec.delta;
write_c_string(&mut out.name, spec.name);
}
Self::IntSlider(spec) => {
out.parameter_id = spec.parameter_id;
out.kind = sys::FXPLUG_RUST_PARAMETER_KIND_INT_SLIDER;
out.flags = spec.flags;
out.default_value = f64::from(spec.default_value);
out.parameter_min = f64::from(spec.parameter_min);
out.parameter_max = f64::from(spec.parameter_max);
out.slider_min = f64::from(spec.slider_min);
out.slider_max = f64::from(spec.slider_max);
out.delta = f64::from(spec.delta);
write_c_string(&mut out.name, spec.name);
}
Self::ToggleButton(spec) => {
out.parameter_id = spec.parameter_id;
out.kind = sys::FXPLUG_RUST_PARAMETER_KIND_TOGGLE_BUTTON;
out.flags = spec.flags;
out.default_value = if spec.default_value { 1.0 } else { 0.0 };
write_c_string(&mut out.name, spec.name);
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ParameterKind {
FloatSlider,
IntSlider,
ToggleButton,
Other(u32),
}
impl From<u32> for ParameterKind {
fn from(value: u32) -> Self {
match value {
sys::FXPLUG_RUST_PARAMETER_KIND_FLOAT_SLIDER => Self::FloatSlider,
sys::FXPLUG_RUST_PARAMETER_KIND_INT_SLIDER => Self::IntSlider,
sys::FXPLUG_RUST_PARAMETER_KIND_TOGGLE_BUTTON => Self::ToggleButton,
other => Self::Other(other),
}
}
}
impl From<ParameterKind> for u32 {
fn from(value: ParameterKind) -> Self {
match value {
ParameterKind::FloatSlider => sys::FXPLUG_RUST_PARAMETER_KIND_FLOAT_SLIDER,
ParameterKind::IntSlider => sys::FXPLUG_RUST_PARAMETER_KIND_INT_SLIDER,
ParameterKind::ToggleButton => sys::FXPLUG_RUST_PARAMETER_KIND_TOGGLE_BUTTON,
ParameterKind::Other(other) => other,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParameterValue {
Float {
parameter_id: u32,
value: f64,
},
Int {
parameter_id: u32,
value: i32,
},
Bool {
parameter_id: u32,
value: bool,
},
Unknown {
parameter_id: u32,
kind: u32,
value: f64,
},
}
impl ParameterValue {
pub fn float(parameter_id: u32, value: f64) -> Self {
Self::Float {
parameter_id,
value,
}
}
pub fn int(parameter_id: u32, value: i32) -> Self {
Self::Int {
parameter_id,
value,
}
}
pub fn bool(parameter_id: u32, value: bool) -> Self {
Self::Bool {
parameter_id,
value,
}
}
pub fn parameter_id(&self) -> u32 {
match *self {
Self::Float { parameter_id, .. }
| Self::Int { parameter_id, .. }
| Self::Bool { parameter_id, .. }
| Self::Unknown { parameter_id, .. } => parameter_id,
}
}
pub fn kind(&self) -> ParameterKind {
match *self {
Self::Float { .. } => ParameterKind::FloatSlider,
Self::Int { .. } => ParameterKind::IntSlider,
Self::Bool { .. } => ParameterKind::ToggleButton,
Self::Unknown { kind, .. } => ParameterKind::Other(kind),
}
}
pub fn as_f64(&self) -> Option<f64> {
match *self {
Self::Float { value, .. } | Self::Unknown { value, .. } => Some(value),
Self::Int { value, .. } => Some(f64::from(value)),
Self::Bool { value, .. } => Some(if value { 1.0 } else { 0.0 }),
}
}
pub fn as_i32(&self) -> Option<i32> {
match *self {
Self::Int { value, .. } => Some(value),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match *self {
Self::Bool { value, .. } => Some(value),
_ => None,
}
}
}
impl From<RawParameterValue> for ParameterValue {
fn from(value: RawParameterValue) -> Self {
match ParameterKind::from(value.kind) {
ParameterKind::FloatSlider => Self::Float {
parameter_id: value.parameter_id,
value: value.value,
},
ParameterKind::IntSlider => Self::Int {
parameter_id: value.parameter_id,
value: value.value as i32,
},
ParameterKind::ToggleButton => Self::Bool {
parameter_id: value.parameter_id,
value: value.value != 0.0,
},
ParameterKind::Other(kind) => Self::Unknown {
parameter_id: value.parameter_id,
kind,
value: value.value,
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PixelFormat {
RgbaF32,
BgraU8,
Other(u32),
}
impl From<u32> for PixelFormat {
fn from(value: u32) -> Self {
match value {
sys::FXPLUG_RUST_PIXEL_FORMAT_RGBA_F32 => Self::RgbaF32,
sys::FXPLUG_RUST_PIXEL_FORMAT_BGRA_U8 => Self::BgraU8,
other => Self::Other(other),
}
}
}
impl PixelFormat {
pub fn raw(self) -> Option<u32> {
match self {
Self::RgbaF32 => Some(sys::FXPLUG_RUST_PIXEL_FORMAT_RGBA_F32),
Self::BgraU8 => Some(sys::FXPLUG_RUST_PIXEL_FORMAT_BGRA_U8),
Self::Other(_) => None,
}
}
pub fn bytes_per_pixel(self) -> Option<usize> {
match self {
Self::RgbaF32 => Some(16),
Self::BgraU8 => Some(4),
Self::Other(_) => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ImageTile {
raw: RawImageTile,
}
impl ImageTile {
pub fn from_raw(raw: RawImageTile) -> Self {
Self { raw }
}
pub fn width(&self) -> usize {
self.raw.width as usize
}
pub fn height(&self) -> usize {
self.raw.height as usize
}
pub fn row_bytes(&self) -> usize {
self.raw.row_bytes
}
pub fn pixel_format(&self) -> PixelFormat {
self.raw.pixel_format.into()
}
pub fn bounds(&self) -> Rect {
self.raw.bounds.into()
}
pub fn metal_texture_ptr(&self) -> *mut core::ffi::c_void {
if self.raw.metal_texture_ptr != 0 {
self.raw.metal_texture_ptr as *mut core::ffi::c_void
} else {
self.raw.metal_texture
}
}
pub fn metal_pixel_format(&self) -> u64 {
self.raw.metal_pixel_format
}
pub fn metal_device_registry_id(&self) -> u64 {
self.raw.metal_device_registry_id
}
pub fn io_surface_ptr(&self) -> *mut core::ffi::c_void {
self.raw.io_surface
}
pub fn bytes(&self) -> Option<&[u8]> {
if self.raw.data.is_null() || self.raw.row_bytes == 0 {
return None;
}
let len = self.raw.row_bytes.checked_mul(self.height())?;
Some(unsafe { slice::from_raw_parts(self.raw.data.cast::<u8>(), len) })
}
pub fn bytes_mut(&mut self) -> Option<&mut [u8]> {
if self.raw.data_mut.is_null() || self.raw.row_bytes == 0 {
return None;
}
let len = self.raw.row_bytes.checked_mul(self.height())?;
Some(unsafe { slice::from_raw_parts_mut(self.raw.data_mut.cast::<u8>(), len) })
}
pub fn rgba_f32_rows(&self) -> Result<RgbaF32Rows<'_>> {
if self.pixel_format() != PixelFormat::RgbaF32 {
return Err(Error::new("image tile is not RGBA f32"));
}
let bytes = self
.bytes()
.ok_or_else(|| Error::new("missing source bytes"))?;
Ok(RgbaF32Rows {
bytes,
width: self.width(),
height: self.height(),
row_bytes: self.row_bytes(),
})
}
pub fn rgba_f32_rows_mut(&mut self) -> Result<RgbaF32RowsMut<'_>> {
if self.pixel_format() != PixelFormat::RgbaF32 {
return Err(Error::new("image tile is not RGBA f32"));
}
let width = self.width();
let height = self.height();
let row_bytes = self.row_bytes();
let bytes = self
.bytes_mut()
.ok_or_else(|| Error::new("missing destination bytes"))?;
Ok(RgbaF32RowsMut {
bytes,
width,
height,
row_bytes,
})
}
}
pub struct RgbaF32Rows<'a> {
bytes: &'a [u8],
width: usize,
height: usize,
row_bytes: usize,
}
impl RgbaF32Rows<'_> {
pub fn pixel(&self, x: usize, y: usize) -> Option<[f32; 4]> {
if x >= self.width || y >= self.height {
return None;
}
let offset = y
.checked_mul(self.row_bytes)?
.checked_add(x.checked_mul(16)?)?;
let pixel_bytes = self.bytes.get(offset..offset + 16)?;
Some([
f32::from_ne_bytes(pixel_bytes[0..4].try_into().ok()?),
f32::from_ne_bytes(pixel_bytes[4..8].try_into().ok()?),
f32::from_ne_bytes(pixel_bytes[8..12].try_into().ok()?),
f32::from_ne_bytes(pixel_bytes[12..16].try_into().ok()?),
])
}
}
pub struct RgbaF32RowsMut<'a> {
bytes: &'a mut [u8],
width: usize,
height: usize,
row_bytes: usize,
}
impl RgbaF32RowsMut<'_> {
pub fn set_pixel(&mut self, x: usize, y: usize, rgba: [f32; 4]) -> Result<()> {
if x >= self.width || y >= self.height {
return Err(Error::new("pixel coordinate out of bounds"));
}
let offset = y
.checked_mul(self.row_bytes)
.and_then(|row| row.checked_add(x.checked_mul(16)?))
.ok_or_else(|| Error::new("pixel offset overflow"))?;
let pixel_bytes = self
.bytes
.get_mut(offset..offset + 16)
.ok_or_else(|| Error::new("pixel row is shorter than expected"))?;
pixel_bytes[0..4].copy_from_slice(&rgba[0].to_ne_bytes());
pixel_bytes[4..8].copy_from_slice(&rgba[1].to_ne_bytes());
pixel_bytes[8..12].copy_from_slice(&rgba[2].to_ne_bytes());
pixel_bytes[12..16].copy_from_slice(&rgba[3].to_ne_bytes());
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenderBackend {
Cpu,
Metal,
Wgpu,
Other(u32),
}
impl From<u32> for RenderBackend {
fn from(value: u32) -> Self {
match value {
sys::FXPLUG_RUST_RENDER_BACKEND_CPU => Self::Cpu,
sys::FXPLUG_RUST_RENDER_BACKEND_METAL => Self::Metal,
sys::FXPLUG_RUST_RENDER_BACKEND_WGPU => Self::Wgpu,
other => Self::Other(other),
}
}
}
#[derive(Debug)]
pub struct RenderContext {
pub time: Time,
pub quality: Quality,
pub backend: RenderBackend,
pub tile_rect: Rect,
pub source: ImageTile,
pub destination: ImageTile,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PixelTransformSupport {
Scale,
ScaleTranslate,
Full,
Other(u32),
}
impl From<PixelTransformSupport> for u32 {
fn from(value: PixelTransformSupport) -> Self {
match value {
PixelTransformSupport::Scale => sys::FXPLUG_RUST_PIXEL_TRANSFORM_SCALE,
PixelTransformSupport::ScaleTranslate => {
sys::FXPLUG_RUST_PIXEL_TRANSFORM_SCALE_TRANSLATE
}
PixelTransformSupport::Full => sys::FXPLUG_RUST_PIXEL_TRANSFORM_FULL,
PixelTransformSupport::Other(other) => other,
}
}
}
impl RenderContext {
fn from_raw(raw: RawRenderContext) -> Self {
Self {
time: raw.time.into(),
quality: raw.quality.into(),
backend: raw.backend.into(),
tile_rect: raw.tile_rect.into(),
source: ImageTile::from_raw(raw.source),
destination: ImageTile::from_raw(raw.destination),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MetalRenderPlan {
pub vertices: [RawVertex2D; 4],
pub vertex_count: u32,
pub brightness: f32,
}
impl Default for MetalRenderPlan {
fn default() -> Self {
Self {
vertices: fullscreen_vertices(1.0, 1.0),
vertex_count: 4,
brightness: 1.0,
}
}
}
impl From<MetalRenderPlan> for RawMetalRenderPlan {
fn from(value: MetalRenderPlan) -> Self {
Self {
vertex_count: value.vertex_count,
vertices: value.vertices,
brightness: value.brightness,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GpuRenderResult {
Ok,
NotImplemented,
Err(Error),
}
impl From<Result<()>> for GpuRenderResult {
fn from(value: Result<()>) -> Self {
match value {
Ok(()) => Self::Ok,
Err(error) => Self::Err(error),
}
}
}
pub trait TileableEffect: Sized {
const IDENTIFIER: &'static str;
const DISPLAY_NAME: &'static str;
const GROUP_NAME: &'static str;
const INFO: &'static str;
const UUID: &'static str;
const SOURCE_COUNT: u32 = 1;
fn properties() -> Properties {
Properties::default()
}
fn parameters() -> &'static [ParameterSpec];
fn plugin_state(params: &[ParameterValue], time: Time, quality: Quality) -> Result<Vec<u8>>;
fn from_plugin_state(data: &[u8]) -> Result<Self>;
fn destination_rect(&self, source_rects: &[Rect], destination_bounds: Rect) -> Result<Rect> {
Ok(source_rects.first().copied().unwrap_or(destination_bounds))
}
fn source_tile_rect(&self, _source_index: u32, destination_tile_rect: Rect) -> Result<Rect> {
Ok(destination_tile_rect)
}
fn render(&self, context: &mut RenderContext) -> Result<()>;
fn render_gpu(&self, _context: &mut RenderContext) -> GpuRenderResult {
GpuRenderResult::NotImplemented
}
fn metal_render_plan(&self, _context: &RenderContext) -> Result<Option<MetalRenderPlan>> {
Ok(None)
}
}
pub fn fullscreen_vertices(width: f32, height: f32) -> [RawVertex2D; 4] {
let half_width = width / 2.0;
let half_height = height / 2.0;
[
RawVertex2D {
position: [-half_width, -half_height],
texture_coordinate: [0.0, 1.0],
},
RawVertex2D {
position: [half_width, -half_height],
texture_coordinate: [1.0, 1.0],
},
RawVertex2D {
position: [-half_width, half_height],
texture_coordinate: [0.0, 0.0],
},
RawVertex2D {
position: [half_width, half_height],
texture_coordinate: [1.0, 0.0],
},
]
}
pub mod ffi {
use super::*;
pub fn plugin_descriptor<T: TileableEffect>(out: *mut sys::FxPlugRustPluginDescriptor) -> i32 {
let Some(out) = mut_ptr(out) else {
return sys::FXPLUG_RUST_ERROR;
};
if T::SOURCE_COUNT != 1 {
return sys::FXPLUG_RUST_ERROR;
}
*out = sys::FxPlugRustPluginDescriptor::default();
write_c_string(&mut out.identifier, T::IDENTIFIER);
write_c_string(&mut out.display_name, T::DISPLAY_NAME);
write_c_string(&mut out.group_name, T::GROUP_NAME);
write_c_string(&mut out.info, T::INFO);
write_c_string(&mut out.uuid, T::UUID);
out.source_count = T::SOURCE_COUNT;
sys::FXPLUG_RUST_OK
}
pub fn plugin_properties<T: TileableEffect>(out: *mut sys::FxPlugRustProperties) -> i32 {
let Some(out) = mut_ptr(out) else {
return sys::FXPLUG_RUST_ERROR;
};
let props = T::properties();
*out = sys::FxPlugRustProperties {
may_remap_time: u8::from(props.may_remap_time),
varies_when_params_are_static: u8::from(props.varies_when_params_are_static),
pixel_transform_support: props.pixel_transform_support,
};
sys::FXPLUG_RUST_OK
}
pub fn parameter_count<T: TileableEffect>() -> u32 {
T::parameters().len() as u32
}
pub fn parameter_spec<T: TileableEffect>(
parameter_index: u32,
out: *mut sys::FxPlugRustParameterSpec,
) -> i32 {
let Some(out) = mut_ptr(out) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(spec) = T::parameters().get(parameter_index as usize).copied() else {
return sys::FXPLUG_RUST_ERROR;
};
*out = sys::FxPlugRustParameterSpec::default();
spec.write_raw(out);
sys::FXPLUG_RUST_OK
}
pub fn plugin_state<T: TileableEffect>(
values: *const RawParameterValue,
value_count: usize,
time: RawTime,
quality: u32,
out_data: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
let Some(out_data) = mut_ptr(out_data) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(out_len) = mut_ptr(out_len) else {
return sys::FXPLUG_RUST_ERROR;
};
*out_data = ptr::null_mut();
*out_len = 0;
let raw_values = match raw_slice(values, value_count) {
Some(values) => values,
None => return sys::FXPLUG_RUST_ERROR,
};
let params: Vec<_> = raw_values
.iter()
.copied()
.map(ParameterValue::from)
.collect();
match T::plugin_state(¶ms, time.into(), quality.into()) {
Ok(bytes) => {
let mut bytes = bytes.into_boxed_slice();
*out_len = bytes.len();
*out_data = bytes.as_mut_ptr();
let _ = Box::into_raw(bytes);
sys::FXPLUG_RUST_OK
}
Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn free_bytes(data: *mut u8, len: usize) {
if data.is_null() {
return;
}
unsafe {
let slice = ptr::slice_from_raw_parts_mut(data, len);
drop(Box::from_raw(slice));
}
}
pub fn destination_rect<T: TileableEffect>(
state_data: *const u8,
state_len: usize,
source_rects: *const RawRect,
source_count: usize,
destination_bounds: RawRect,
out_rect: *mut RawRect,
) -> i32 {
let Some(out_rect) = mut_ptr(out_rect) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(state) = state_slice(state_data, state_len) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(source_rects) = raw_slice(source_rects, source_count) else {
return sys::FXPLUG_RUST_ERROR;
};
let source_rects: Vec<_> = source_rects.iter().copied().map(Rect::from).collect();
match T::from_plugin_state(state)
.and_then(|effect| effect.destination_rect(&source_rects, destination_bounds.into()))
{
Ok(rect) => {
*out_rect = rect.into();
sys::FXPLUG_RUST_OK
}
Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
pub fn source_tile_rect<T: TileableEffect>(
state_data: *const u8,
state_len: usize,
source_index: u32,
destination_tile_rect: RawRect,
out_rect: *mut RawRect,
) -> i32 {
let Some(out_rect) = mut_ptr(out_rect) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(state) = state_slice(state_data, state_len) else {
return sys::FXPLUG_RUST_ERROR;
};
match T::from_plugin_state(state)
.and_then(|effect| effect.source_tile_rect(source_index, destination_tile_rect.into()))
{
Ok(rect) => {
*out_rect = rect.into();
sys::FXPLUG_RUST_OK
}
Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
pub fn render_cpu<T: TileableEffect>(
state_data: *const u8,
state_len: usize,
context: *const RawRenderContext,
) -> i32 {
let Some(state) = state_slice(state_data, state_len) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(context) = const_ptr(context) else {
return sys::FXPLUG_RUST_ERROR;
};
let mut context = RenderContext::from_raw(*context);
match T::from_plugin_state(state).and_then(|effect| effect.render(&mut context)) {
Ok(()) => sys::FXPLUG_RUST_OK,
Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
pub fn render_gpu<T: TileableEffect>(
state_data: *const u8,
state_len: usize,
context: *const RawRenderContext,
) -> i32 {
let Some(state) = state_slice(state_data, state_len) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(context) = const_ptr(context) else {
return sys::FXPLUG_RUST_ERROR;
};
let mut context = RenderContext::from_raw(*context);
match T::from_plugin_state(state).map(|effect| effect.render_gpu(&mut context)) {
Ok(GpuRenderResult::Ok) => sys::FXPLUG_RUST_OK,
Ok(GpuRenderResult::NotImplemented) => sys::FXPLUG_RUST_NOT_IMPLEMENTED,
Ok(GpuRenderResult::Err(_)) | Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
pub fn metal_render_plan<T: TileableEffect>(
state_data: *const u8,
state_len: usize,
context: *const RawRenderContext,
out_plan: *mut RawMetalRenderPlan,
) -> i32 {
let Some(out_plan) = mut_ptr(out_plan) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(state) = state_slice(state_data, state_len) else {
return sys::FXPLUG_RUST_ERROR;
};
let Some(context) = const_ptr(context) else {
return sys::FXPLUG_RUST_ERROR;
};
let context = RenderContext::from_raw(*context);
match T::from_plugin_state(state).and_then(|effect| effect.metal_render_plan(&context)) {
Ok(Some(plan)) => {
*out_plan = plan.into();
sys::FXPLUG_RUST_OK
}
Ok(None) => sys::FXPLUG_RUST_NOT_IMPLEMENTED,
Err(_) => sys::FXPLUG_RUST_ERROR,
}
}
}
fn const_ptr<T>(ptr: *const T) -> Option<&'static T> {
if ptr.is_null() {
None
} else {
Some(unsafe { &*ptr })
}
}
fn mut_ptr<T>(ptr: *mut T) -> Option<&'static mut T> {
if ptr.is_null() {
None
} else {
Some(unsafe { &mut *ptr })
}
}
fn raw_slice<T>(ptr: *const T, len: usize) -> Option<&'static [T]> {
if len == 0 {
return Some(&[]);
}
if ptr.is_null() {
return None;
}
Some(unsafe { slice::from_raw_parts(ptr, len) })
}
fn state_slice(ptr: *const u8, len: usize) -> Option<&'static [u8]> {
raw_slice(ptr, len)
}
fn write_c_string<const N: usize>(out: &mut [c_char; N], value: &str) {
out.fill(0);
if N == 0 {
return;
}
let bytes = value.as_bytes();
let len = bytes.len().min(N - 1);
for (dst, src) in out.iter_mut().zip(bytes.iter()).take(len) {
*dst = *src as c_char;
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __fxplug_dispatch {
($index:expr, $function:ident $(, $arg:expr)*; $($plugin:ty),+ $(,)?) => {{
let handlers = [$( $crate::ffi::$function::<$plugin> ),+];
match handlers.get($index as usize) {
Some(handler) => handler($($arg),*),
None => $crate::sys::FXPLUG_RUST_ERROR,
}
}};
}
#[macro_export]
macro_rules! register_plugin {
($plugin:ty $(,)?) => {
#[no_mangle]
pub extern "C" fn fxplug_rust_plugin_count() -> u32 {
1
}
#[no_mangle]
pub extern "C" fn fxplug_rust_plugin_descriptor(
plugin_index: u32,
out_descriptor: *mut $crate::sys::FxPlugRustPluginDescriptor,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::plugin_descriptor::<$plugin>(out_descriptor)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_plugin_properties(
plugin_index: u32,
out_properties: *mut $crate::sys::FxPlugRustProperties,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::plugin_properties::<$plugin>(out_properties)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_parameter_count(plugin_index: u32) -> u32 {
if plugin_index == 0 {
$crate::ffi::parameter_count::<$plugin>()
} else {
0
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_parameter_spec(
plugin_index: u32,
parameter_index: u32,
out_spec: *mut $crate::sys::FxPlugRustParameterSpec,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::parameter_spec::<$plugin>(parameter_index, out_spec)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_plugin_state(
plugin_index: u32,
values: *const $crate::sys::FxPlugRustParameterValue,
value_count: usize,
time: $crate::sys::FxPlugRustTime,
quality: u32,
out_data: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::plugin_state::<$plugin>(
values,
value_count,
time,
quality,
out_data,
out_len,
)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_free_bytes(data: *mut u8, len: usize) {
$crate::ffi::free_bytes(data, len);
}
#[no_mangle]
pub extern "C" fn fxplug_rust_destination_rect(
plugin_index: u32,
state_data: *const u8,
state_len: usize,
source_rects: *const $crate::sys::FxPlugRustRect,
source_count: usize,
destination_bounds: $crate::sys::FxPlugRustRect,
out_rect: *mut $crate::sys::FxPlugRustRect,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::destination_rect::<$plugin>(
state_data,
state_len,
source_rects,
source_count,
destination_bounds,
out_rect,
)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_source_tile_rect(
plugin_index: u32,
state_data: *const u8,
state_len: usize,
source_index: u32,
destination_tile_rect: $crate::sys::FxPlugRustRect,
out_rect: *mut $crate::sys::FxPlugRustRect,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::source_tile_rect::<$plugin>(
state_data,
state_len,
source_index,
destination_tile_rect,
out_rect,
)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_render_cpu(
plugin_index: u32,
state_data: *const u8,
state_len: usize,
context: *const $crate::sys::FxPlugRustRenderContext,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::render_cpu::<$plugin>(state_data, state_len, context)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_render_gpu(
plugin_index: u32,
state_data: *const u8,
state_len: usize,
context: *const $crate::sys::FxPlugRustRenderContext,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::render_gpu::<$plugin>(state_data, state_len, context)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
#[no_mangle]
pub extern "C" fn fxplug_rust_metal_render_plan(
plugin_index: u32,
state_data: *const u8,
state_len: usize,
context: *const $crate::sys::FxPlugRustRenderContext,
out_plan: *mut $crate::sys::FxPlugRustMetalRenderPlan,
) -> i32 {
if plugin_index == 0 {
$crate::ffi::metal_render_plan::<$plugin>(state_data, state_len, context, out_plan)
} else {
$crate::sys::FXPLUG_RUST_ERROR
}
}
};
}
#[macro_export]
macro_rules! register_plugins {
($plugin:ty $(,)?) => {
$crate::register_plugin!($plugin);
};
($first:ty, $($rest:ty),+ $(,)?) => {
compile_error!(
"fxplug 0.1 supports one Rust effect per PlugInKit service; use register_plugin!(MyEffect)"
);
};
}
#[cfg(test)]
mod tests {
use super::*;
use core::ffi::c_void;
#[test]
fn writes_bounded_c_strings() {
let mut buf = [0 as c_char; 4];
write_c_string(&mut buf, "abcdef");
assert_eq!(buf[0], b'a' as c_char);
assert_eq!(buf[1], b'b' as c_char);
assert_eq!(buf[2], b'c' as c_char);
assert_eq!(buf[3], 0);
}
#[test]
fn fullscreen_vertices_cover_clip_space() {
let vertices = fullscreen_vertices(2.0, 2.0);
assert_eq!(vertices[0].position, [-1.0, -1.0]);
assert_eq!(vertices[3].texture_coordinate, [1.0, 0.0]);
}
#[test]
fn rgba_rows_respect_row_stride() {
let mut pixels = [0u8; 40];
pixels[0..4].copy_from_slice(&1.0f32.to_ne_bytes());
pixels[16..20].copy_from_slice(&0.5f32.to_ne_bytes());
let tile = RawImageTile {
data: pixels.as_ptr().cast::<c_void>(),
width: 2,
height: 1,
row_bytes: 40,
pixel_format: sys::FXPLUG_RUST_PIXEL_FORMAT_RGBA_F32,
..RawImageTile::default()
};
let tile = ImageTile::from_raw(tile);
let rows = tile.rgba_f32_rows().unwrap();
assert_eq!(rows.pixel(0, 0).unwrap()[0], 1.0);
assert_eq!(rows.pixel(1, 0).unwrap()[0], 0.5);
}
}