ark-api-ffi 0.17.0-pre.15

Ark low-level Wasm FFI API
Documentation
define_api_id!(0xc711_14ac_a9a4_ae3b, "render-v0");

use crate::FFIResult;
use crate::PodBool;
use bytemuck::CheckedBitPattern;
use bytemuck::NoUninit;
use bytemuck::Pod;
use bytemuck::Zeroable;

// TODO: figure out a safer way to go about texture handles. -- max
/// Handle to a texture in Ark.
///
/// # Safety
///
/// Care should be taken around the validity of a raw [`TextureHandle`]. Some validity issues are:
///
/// * Trying to destroy a texture with [`destroy_texture`] that has already been destroyed.
/// * Dropping the [`TextureHandle`] before calling [`destroy_texture`], causing a leak.
/// * Passing an invalid [`TextureHandle`], one that has already been destroyed, to a function that
///   operates on a texture.
///
/// To safely handle these cases we recommend wrapping your [`TextureHandle`] in a [`std::rc::Rc`]
/// or [`std::sync::Arc`]. See the examples section for a safe implementation.
///
/// # Examples
///
/// Safe implementation example around [`TextureHandle`].
///
/// ```no_run
/// use ark_api_ffi::render_v0::TextureHandle;
///
/// pub struct Texture {
///     handle: std::rc::Rc<TextureHandle>,
/// }
///
/// impl Texture {
///     // Pass the handle retrieved from `create_texture` here.
///     pub fn new(handle: TextureHandle) -> Self {
///         Self {
///             handle: std::rc::Rc::new(handle),
///         }
///     }
///
///     pub fn handle(&self) -> TextureHandle {
///         *self.handle
///     }
/// }
///
/// impl Drop for Texture {
///     // Never call `destroy_texture` yourself, let `Drop` call it.
///     fn drop(&mut self) {
///         if std::rc::Rc::strong_count(&self.handle) == 1 {
/// # #[cfg(target_arch = "wasm32")]
///              destroy_texture(*self.handle);
///         }
///      }
/// }
///
/// impl Clone for Texture {
///     fn clone(&self) -> Self {
///         Self {
///            handle: std::rc::Rc::clone(&self.handle),
///         }
///     }
/// }
/// ```
pub type TextureHandle = u64;

/// Used to restrict the drawing of 2d triangles to a specific region.
#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C)]
pub struct Rectangle {
    pub min_x: f32,
    pub min_y: f32,
    pub max_x: f32,
    pub max_y: f32,
}

impl Rectangle {
    #[inline(always)]
    pub fn width(&self) -> f32 {
        self.max_x - self.min_x
    }

    #[inline(always)]
    pub fn height(&self) -> f32 {
        self.max_y - self.min_y
    }

    pub fn from_resolution(resolution: [u32; 2]) -> Self {
        Self {
            min_x: 0.0,
            min_y: 0.0,
            max_x: resolution[0] as f32,
            max_y: resolution[1] as f32,
        }
    }
}

/// Used for 3D culling and tessellation applications.
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct BoundingBox {
    pub min: [f32; 3],
    pub max: [f32; 3],
}

/// Describes how a texture stores its data.
#[derive(Copy, Clone, Debug, PartialEq, Eq, NoUninit, CheckedBitPattern)]
#[repr(u32)]
#[non_exhaustive]
#[allow(non_camel_case_types)]
pub enum TextureFormat {
    /// 32-bit-per-pixel, fixed-point pixel format assuming premultiplied alpha. SRGB encoded.
    R8G8B8A8_SRGB = 1,
    /// 32-bit-per-pixel, fixed-point pixel format assuming premultiplied alpha. Linear.
    R8G8B8A8_UNORM = 2,
    // A single red channel in gamma space
    //R8,
}

impl TextureFormat {
    /// The amount of storage required for a single texture pixel.
    #[inline]
    pub fn bytes_per_pixel(&self) -> u64 {
        match self {
            TextureFormat::R8G8B8A8_SRGB | TextureFormat::R8G8B8A8_UNORM => 4,
            //TextureFormat::R8 => 1,
        }
    }
}

/// Defines the type of texture.
#[derive(Copy, Clone, Debug, PartialEq, Eq, NoUninit, CheckedBitPattern)]
#[repr(u32)]
#[non_exhaustive]
pub enum TextureType {
    /// 2 dimensional texture.
    D2 = 1,
}

/// Describes a texture and is used for texture creation.
///
/// * `depth`, `mips`, `array_len` - Currently not supported by Ark but is made public for future-proofing the API.
#[derive(Copy, Clone, Debug, PartialEq, Eq, NoUninit, CheckedBitPattern)]
#[repr(C)]
pub struct TextureDescription {
    pub texture_type: TextureType,
    pub format: TextureFormat,
    pub width: u64,
    pub height: u64,
    pub depth: u64,
    pub mipmaps: u32,
    pub array_len: u32,
}

/// Describes the transform of a bone. More compact and more defined than a Mat4.
///
/// Currently uses a pos + quat representation, although this may changed in the future.
/// An extra float is provided for padding.
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct BoneTransform {
    pub pos: [f32; 3],
    pub _padding: f32,
    pub rot: [f32; 4],
}

impl PartialEq for BoneTransform {
    fn eq(&self, other: &Self) -> bool {
        self.pos == other.pos && self.rot == other.rot
    }
}

impl BoneTransform {
    pub fn zero() -> Self {
        Self {
            pos: [0.0; 3],
            _padding: 0.0,
            rot: [0.0; 4],
        }
    }
}

/// Describes an instance of an Sdf function to be rendered.
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct SdfInstanceData {
    /// Instance space to world transform for the draw call. Column major.
    pub world_from_instance: [f32; 16], // TODO: [f32; 16]

    /// Index into the bounding box array.
    pub bounding_box_index: u32,

    /// Dynamic data for the procedural instance
    ///
    /// Set both to zero in order to render it without modification.
    pub dynamic_data_offset: u32,
    pub dynamic_data_length: u32,

    /// Detail level. 0.0 means automatic detail.
    ///
    /// TODO: Define what this means.
    pub detail_bias: f32,

    /// Range: `[0, 1]`. Set to 1 for fully opaque rendering.
    /// Set to `< 1` for transparent rendering.
    /// Transparent objects are rendered back-to-front, after opaque objects,
    /// but before objects with `depth_test=false`.
    pub opacity: f32,

    /// Range: `[0, 1]`. Interpolated between unlit and lit colors.
    ///
    /// Set to `0` to disable lighting. Set to `1` to enable lighting.
    pub lighting: f32,

    /// If `true`, depth testing is enabled, which means things closer to the camera
    /// will rendered on top of things further away. This is normally what you want.
    ///
    /// If `false`, this instance will be rendered on top of previous instances, even if this instance is further away.
    /// In other words, settings `depth_test=false` will
    /// make your instance visible through all other instances, even if they are not transparent.
    ///
    /// Instances with `depth_test=false` are always rendered last.
    pub depth_test: PodBool,

    /// Control whether or not to write to the depth buffer.
    ///
    /// NOTE: render order is respected, EXCEPT for instances which are transparent or has `depth_test=false`.
    pub depth_write: PodBool,
    pub _pad: [u8; 2],
}

#[derive(Debug, Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct SkinnedSdfInstanceData {
    pub detail_bias: f32,
    pub opacity: f32,
    pub lighting: f32,
}

pub type SdfHandle = u64;

#[ark_api_macros::ark_bindgen(imports = "ark-render-v0")]
mod render {
    use super::*;

    extern "C" {
        /// Create a texture, returning a handle to it.
        ///
        /// For valid texture creation, the description's `width`, `height`, `depth`, and [`TextureFormat`]
        /// need to match up against the length of the `data` so that `width` * `height` * `format.bytes_per_pixel()` == `data.len()`.
        ///
        /// # Errors
        ///
        /// Returns an [`crate::ErrorCode::InvalidArguments`] if `data`'s length doesn't match
        /// up against the description's dimensions and format or if description dimensions has
        /// values equal to 0.
        ///
        /// # Examples
        ///
        /// Basic usage:
        /// ```no_run
        /// use ark_api_ffi::render_v0::{TextureDescription, TextureFormat, TextureType};
        ///
        /// // Create a simple 1x1 opaque red texture. More sophisticated data can be
        /// // acquired through the use of, for example, the `image` crate.
        /// let data = [255, 0, 0, 255];
        /// let description = TextureDescription {
        ///     width: 1,
        ///     height: 1,
        ///     depth: 1,
        ///     format: TextureFormat::R8G8B8A8_SRGB,
        ///     mipmaps: 1,
        ///     array_len: 1,
        ///     texture_type: TextureType::D2,
        /// };
        ///
        /// # #[cfg(target_arch = "wasm32")]
        /// let texture = create_texture("my amazing texture", &description, &data)?;
        /// # Ok::<(), ark_api_ffi::ErrorCode>(())
        /// ```
        pub fn create_texture(
            name: &str,
            description: &TextureDescription,
            data: &[u8],
        ) -> FFIResult<TextureHandle>;

        /// Updates a subrectangle of a texture with new data.
        ///
        /// Format is implied to be the same as the texture already is in.
        pub fn update_texture(
            handle: TextureHandle,
            pos_x: u32,
            pos_y: u32,
            width: u32,
            height: u32,
            data: &[u8],
        );

        /// Destroy a [`TextureHandle`]'s associated texture data.
        ///
        /// # Errors
        ///
        /// Returns an [`crate::ErrorCode::InvalidArguments`] if there is no texture associated
        /// to the provided handle.
        #[deprecated_infallible]
        pub fn destroy_texture(handle: TextureHandle);

        /// Draw colored 2D triangles in screen space (physical pixel coordinates).
        ///
        /// * `colors` - Assumes premultiplied alpha.
        ///
        /// # Errors
        ///
        /// Returns an [`crate::ErrorCode::InvalidArguments`] for the following cases:
        /// * `positions` length is not an even multiple of 2.
        /// * `indices` length is not an even multiple of 3.
        /// * `colors` length is not an even multiple of 4.
        #[deprecated_infallible]
        pub fn draw_triangles_2d(
            clip_rect: &Rectangle,
            indices: &[u32],   // vertex index triplets
            positions: &[f32], // x,y interleaved
            colors: &[u8],     // r,g,b,a interleaved
        );

        /// Draw colored 2D textured triangles in screen space (physical pixel coordinates).
        ///
        /// * `colors` - Assumes premultiplied alpha.
        ///
        /// # Errors
        ///
        /// Returns an [`crate::ErrorCode::InvalidArguments`] for the following reasons:
        /// * `positions`/`uvs` length is not an even multiple of 2.
        /// * `indices` length is not an even multiple of 3.
        /// * `colors` length is not an even multiple of 4.
        /// * `handle` is invalid
        #[deprecated_infallible]
        pub fn draw_textured_triangles_2d(
            clip_rect: &Rectangle,
            handle: TextureHandle,
            indices: &[u32],   // vertex index triplets
            positions: &[f32], // x,y interleaved
            colors: &[u8],     // r,g,b,a interleaved
            uvs: &[f32],       // u,v interleaved
        );

        /// Creates an SDF model from a Saft program.
        ///
        /// These can be rendered directly or used to define bones for skinned SDF models.
        /// A correct bounding box is required for now (later we will derive it from the program).
        pub fn create_sdf_model(
            opcodes: &[u32],
            constants: &[f32],
            bounding_box: &BoundingBox,
        ) -> FFIResult<u64>;

        /// Destroys an SDF model.
        #[deprecated_infallible]
        pub fn destroy_sdf_model(sdf: u64);

        /// Draws 1 or more instances of an SDF model.
        ///
        /// Bounding boxes are only looked at if you pass in modified `constants`.
        /// To keep the old constants, just pass in an empty slice.
        #[deprecated_infallible]
        pub fn draw_sdf_model(
            sdf: SdfHandle,
            instances: &[SdfInstanceData],
            constants: &[f32],
            bounding_boxes: &[BoundingBox],
        );
    }
}

pub use render::*;