ark-api 0.17.0-pre.15

Ark API
Documentation
//! # 🖼️ Render API
//!
//! Immediate mode rendering API for drawing 2D and 3D meshes on screen.
//!
//! Unlike the World API, which has persistent entities, in the Render API you list everything that should
//! be drawn every frame, hence immediate mode.
//!
//! The Render API supports the creation of textures through a [`TextureBuilder`].
//! Textures are created from image data and can be "wrapped around" surfaces to add
//! another level of artistic control to your game.
//!
//! # Examples
//!
//! Given a valid texture named `texture` created with the [`TextureBuilder`], you
//! can draw a textured 2D square in screen space like so:
//!
//! ``` no_run
//! use ark_api::render::{Rectangle};
//! use ark_api::{Vec2, ColorRgba8};
//!
//! // We draw two triangles, reusing some of the vertices:
//! let indices = [
//!     [0, 1, 2], // first triangle
//!     [1, 2, 3], // second triangle
//! ];
//!
//! // We can specify a region to which to clip our triangles.
//! // In this case, we don't want to clip.
//! let clip_rect = Rectangle {
//!     min_x: 0.0,
//!     min_y: 0.0,
//!     max_x: 1_000_000.0,
//!     max_y: 1_000_000.0,
//! };
//!
//! // Render API expects positions in physical pixel coordinates,
//! // so we multiply our local coordinates with dpi_factor:
//! let dpi_factor = applet().window_state().dpi_factor;
//!
//! let positions = [
//!     dpi_factor * Vec2::new(50.0, 50.0),   // 0
//!     dpi_factor * Vec2::new(50.0, 300.0),  // 1
//!     dpi_factor * Vec2::new(300.0, 50.0),  // 2
//!     dpi_factor * Vec2::new(300.0, 300.0), // 3
//! ];
//! let colors = [
//!     ColorRgba8([255, 0, 0, 255]),     // 0
//!     ColorRgba8([0, 255, 0, 255]),     // 1
//!     ColorRgba8([0, 0, 255, 255]),     // 2
//!     ColorRgba8([255, 255, 255, 255]), // 3
//! ];
//!
//! let uvs = [
//!     Vec2::new(0.0, 0.0), // 0
//!     Vec2::new(0.0, 1.0), // 1
//!     Vec2::new(1.0, 0.0), // 2
//!     Vec2::new(1.0, 1.0), // 3
//! ];
//!
//! render().draw_textured_triangles(&clip_rect, &texture, &indices, &positions, &colors, &uvs)?;
//! # Ok::<(), ark_api::Error>(())
//! ```

mod ffi {
    pub use crate::ffi::render_v0::*;
    pub use crate::ffi::render_v1::*;
}

use crate::{Error, Mesh, MeshData};
use bitflags::bitflags;
pub use ffi::{
    BoneTransform, GltfFlags, Rectangle, RenderMaterial, RenderMeshInstance2 as RenderMeshInstance,
    RenderMeshSection, RenderMeshStyleFlags, RenderMeshVisibilityFlags, TextureFormat,
    TextureHandle,
};
use macaw::{ColorRgba8, IsoTransform, Mat4, Vec2, Vec3, Vec4};
use static_assertions::assert_eq_size;
use std::{fmt::Debug, mem::size_of, num::NonZeroU64, sync::Arc};

mod render_util;

#[doc(hidden)]
pub use crate::ffi::render_v1::API as FFI_API;

bitflags! {
    /// Flags to specify attributes about mesh data when creating meshes.
    #[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
    #[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
    #[repr(C)]
    pub struct RenderMeshCreateFlags : u32 {
        /// Specifies that the colors provided in the mesh creation data are premultiplied alpha.
        const PREMULTIPLIED_ALPHA = 0b0000_0001;
    }
}

/// Adjusts the style of drawn meshes.
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct RenderMeshStyle {
    /// Diffuse color tint (RGBA multiplier). Premultiplied alpha.
    diffuse_tint: Vec4,
    /// Style flags
    flags: RenderMeshStyleFlags,
    /// Visibility Flags
    visibility_flags: RenderMeshVisibilityFlags,
    pad: [u8; 11], // ensure 16-byte alignment
}

assert_eq_size!(RenderMeshStyle, ffi::RenderMeshStyle);

impl RenderMeshStyle {
    /// Creates a mesh style with a diffuse tint
    pub fn new(diffuse_tint: Vec4) -> Self {
        Self {
            diffuse_tint,
            flags: RenderMeshStyleFlags::default(),
            visibility_flags: RenderMeshVisibilityFlags::all(),
            pad: Default::default(),
        }
    }
}

impl Default for RenderMeshStyle {
    fn default() -> Self {
        Self {
            diffuse_tint: Vec4::ONE,
            flags: RenderMeshStyleFlags::default(),
            visibility_flags: RenderMeshVisibilityFlags::all(),
            pad: Default::default(),
        }
    }
}

impl From<RenderMeshStyle> for ffi::RenderMeshStyle {
    fn from(style: RenderMeshStyle) -> Self {
        // Visibility flags are only part of `ffi::RenderMeshStyle2`
        assert!(style.visibility_flags == RenderMeshVisibilityFlags::all(), "Visibility flags can only be used in combination with `draw_meshes_with_materials_and_styles`");

        Self {
            diffuse_tint: style.diffuse_tint.into(),
            flags: style.flags,
            pad: Default::default(),
        }
    }
}

impl From<RenderMeshStyle> for ffi::RenderMeshStyle2 {
    fn from(style: RenderMeshStyle) -> Self {
        Self {
            diffuse_tint: style.diffuse_tint.into(),
            flags: style.flags,
            visibility_flags: style.visibility_flags,
            pad: Default::default(),
        }
    }
}

/// Create `RenderMeshStyle` through builder pattern
#[derive(Default, Debug, Clone, Copy)]
pub struct RenderMeshStyleBuilder {
    style: RenderMeshStyle,
}

impl RenderMeshStyleBuilder {
    /// Creates a new builder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set diffuse color tinting (RGBA multiplier), standard alpha behavior. Default is `Vec4::ONE` (no tint).
    pub fn with_diffuse_tint(&mut self, mut tint: Vec4) -> &mut Self {
        tint.x *= tint.w;
        tint.y *= tint.w;
        tint.z *= tint.w;
        self.style.diffuse_tint = tint;
        self
    }

    /// Set diffuse color tinting (RGBA multiplier), premultiplied alpha. Default is `Vec4::ONE` (no tint).
    pub fn with_diffuse_tint_premultiplied(&mut self, tint: Vec4) -> &mut Self {
        self.style.diffuse_tint = tint;
        self
    }

    /// Sets lighting toggle
    pub fn with_lighting(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::LIGHTING, e);
        self
    }

    /// Sets flat shading toggle. Flat shading gives smooth objects a faceted look, by not
    /// interpolating normals across surfaces before lighting.
    pub fn with_flat_shading(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::FLAT_SHADING, e);
        self
    }

    /// Will make the mesh face the camera at all times, can be handy for things like particles or
    /// ui.
    pub fn with_billboard_rendering(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::BILLBOARD, e);
        self
    }

    /// Will make the mesh two-sided (backface culling is not applied). Only recommended for solid
    /// meshes (and even then, when you don't need this, don't use it).
    pub fn with_two_sided(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::TWO_SIDED, e);
        self
    }

    /// Enables depth testing for the mesh. If depth testing is disabled, the mesh will be drawn
    /// last, and will be drawn on top of everything else. On by default.
    pub fn with_depth_test(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::DEPTH_TEST, e);
        self
    }

    /// Enables depth writing for the mesh. NOTE: only used for SDF meshes for backwards compatibility
    /// reasons.
    pub fn with_depth_write(&mut self, e: bool) -> &mut Self {
        self.style.flags.set(RenderMeshStyleFlags::DEPTH_WRITE, e);
        self
    }

    /// Set the visibility flags of a mesh which allows you to disable, reflections, globalillumination, shadows and primary rendering.
    pub fn with_visibility_flags(&mut self, flags: RenderMeshVisibilityFlags) -> &mut Self {
        self.style.visibility_flags = flags;
        self
    }

    /// Builds mesh style
    pub fn build(&self) -> RenderMeshStyle {
        self.style
    }
}

/// Create `RenderMaterial` through builder pattern
#[derive(Default, Debug, Clone)]
pub struct RenderMaterialBuilder {
    desc: RenderMaterial,
}

impl RenderMaterialBuilder {
    /// Creates a new builder
    pub fn new() -> Self {
        Self::default()
    }

    /// Set diffuse albedo color. Default is `Vec3::ONE`
    pub fn with_diffuse_albedo(&mut self, albedo: Vec3) -> &mut Self {
        self.desc.diffuse_albedo = albedo.into();
        self
    }

    /// Set the transparency. Default is `1.0`
    pub fn with_alpha(&mut self, alpha: f32) -> &mut Self {
        self.desc.alpha = alpha;
        self
    }

    /// Set emissive color (can exceed `Vec3::ONE`). Default is `Vec3::ZERO`.
    pub fn with_emissive_color(&mut self, color: Vec3) -> &mut Self {
        self.desc.emissive_color = color.into();
        self
    }

    /// Set the perceptual roughness
    pub fn with_roughness(&mut self, perceptual_roughness: f32) -> &mut Self {
        self.desc.perceptual_roughness = perceptual_roughness;
        self
    }

    /// Set the metallicness
    pub fn with_metallic(&mut self, metallic: f32) -> &mut Self {
        self.desc.metallic = metallic;
        self
    }

    /// Builds material
    pub fn build(&self) -> RenderMaterial {
        self.desc
    }
}

/// Converts an `IsoTransform` to a `BoneTransform`. Lossless.
pub fn bone_transform_from_iso(iso: &IsoTransform) -> BoneTransform {
    BoneTransform {
        pos: iso.translation().into(),
        _padding: 0.0,
        rot: iso.rotation().into(),
    }
}

/// Colored 3D line
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Line(ffi::Line);

impl Line {
    /// Creates line with single color
    pub fn new(pos0: Vec3, pos1: Vec3, color: ColorRgba8) -> Self {
        Self(ffi::Line {
            pos0: pos0.into(),
            pos1: pos1.into(),
            color0: color.0,
            color1: color.0,
        })
    }
}

/// Gives access to rendering specific functionality like drawing 2D triangles.
///
/// Use the `require_render_api` macro's `render()` function to get an instance of [`Render`].
/// See the module level documentation for an example.
///
/// A `Render` object can be cheaply cloned.
#[derive(Clone)]
pub struct Render {
    _private: (),
}

// Canvas rendering
impl Render {
    #[doc(hidden)]
    pub fn __create() -> Self {
        Self { _private: () }
    }

    /// Draw colored 2D triangles in screen space (physical pixel coordinates).
    ///
    /// * `colors` - Assumes premultiplied alpha.
    #[inline]
    pub fn draw_triangles(
        &self,
        clip_rect: &Rectangle,
        indices: impl AsRef<[[u32; 3]]>,
        positions: impl AsRef<[Vec2]>,
        colors: impl AsRef<[ColorRgba8]>,
    ) {
        let indices = render_util::u32_slice_from_triangle_index(indices.as_ref());
        let positions = render_util::f32_slice_from_vec2(positions.as_ref());
        let colors = render_util::u8_slice_from_srgba(colors.as_ref());

        ffi::draw_triangles_2d(clip_rect, indices, positions, colors);
    }

    /// Draw colored 2D textured triangles in screen space (physical pixel coordinates).
    ///
    /// * `colors` - Assumes premultiplied alpha.
    #[inline]
    pub fn draw_textured_triangles(
        &self,
        clip_rect: &Rectangle,
        texture: TextureHandle,
        indices: impl AsRef<[[u32; 3]]>,
        positions: impl AsRef<[Vec2]>,
        colors: impl AsRef<[ColorRgba8]>,
        uvs: impl AsRef<[Vec2]>,
    ) {
        let indices = render_util::u32_slice_from_triangle_index(indices.as_ref());
        let positions = render_util::f32_slice_from_vec2(positions.as_ref());
        let colors = render_util::u8_slice_from_srgba(colors.as_ref());
        let uvs = render_util::f32_slice_from_vec2(uvs.as_ref());

        ffi::draw_textured_triangles_2d(clip_rect, texture, indices, positions, colors, uvs);
    }

    /// Draws 3D debug lines.
    ///
    /// These are simple but fast primitive lines that are meant to quickly get something up
    /// with little overhead, when you just need to visualize something for debugging.
    /// They will never deliver a polished look, so do not use for user-facing things.
    #[inline]
    pub fn draw_debug_lines(&self, lines: &[Line]) {
        // SAFETY: Line is transparent newtype, a cast here is both safe and justified
        let lines = unsafe { &*(lines as *const [Line] as *const [ffi::Line]) };
        ffi::draw_debug_lines(lines);
    }

    /// Creates a mesh.
    ///
    /// NOTE: Normals can be automatically generated by the engine. Simply set `normals`
    /// to `None` in `mesh.data`.
    pub fn create_mesh(&self, mesh: &Mesh, flags: RenderMeshCreateFlags) -> RenderMesh {
        self.create_mesh_with_materials_and_sections(mesh, flags, &[], &[])
    }

    /// Creates a mesh with a material.
    ///
    /// NOTE: Normals can be automatically generated by the engine. Simply set `normals`
    /// to `None` in `mesh.data`.
    pub fn create_mesh_with_material(
        &self,
        mesh: &Mesh,
        flags: RenderMeshCreateFlags,
        material: RenderMaterial,
    ) -> RenderMesh {
        self.create_mesh_with_materials_and_sections(mesh, flags, &[material], &[])
    }

    /// Creates a mesh with multiple materials split by sections.
    ///
    /// The mesh can be split into multiple sections with different materials.
    /// If no sections are provided the entire mesh will be a single section.
    /// NOTE: Normals can be automatically generated by the engine. Simply set `normals`
    /// to `None` in `mesh.data`.
    pub fn create_mesh_with_materials_and_sections(
        &self,
        mesh: &Mesh,
        flags: RenderMeshCreateFlags,
        materials: &[RenderMaterial],
        sections: &[RenderMeshSection],
    ) -> RenderMesh {
        let mesh_data = mesh.data.as_ref().expect("Can't create mesh without data");

        let mut streams: Vec<ffi::MeshStreamLayout> = vec![];

        // indices
        if !mesh_data.indices.is_empty() {
            streams.push(ffi::MeshStreamLayout {
                semantic: ffi::MeshStreamSemantic::Indices,
                component_format: ffi::MeshComponentFormat::UInt32,
                component_count: 1,
                buffer_ptr: mesh_data.indices.as_ptr() as u32,
                buffer_size: (mesh_data.indices.len() * size_of::<u32>()) as u32,
            });
        }

        // positions
        streams.push(ffi::MeshStreamLayout {
            semantic: ffi::MeshStreamSemantic::Positions,
            component_format: ffi::MeshComponentFormat::Float32,
            component_count: 3,
            buffer_ptr: mesh_data.positions.as_ptr() as u32,
            buffer_size: (mesh_data.positions.len() * size_of::<[f32; 3]>()) as u32,
        });

        // normals
        if let Some(normals) = &mesh_data.normals {
            streams.push(ffi::MeshStreamLayout {
                semantic: ffi::MeshStreamSemantic::Normals,
                component_format: ffi::MeshComponentFormat::Float32,
                component_count: 3,
                buffer_ptr: normals.as_ptr() as u32,
                buffer_size: (normals.len() * size_of::<[f32; 3]>()) as u32,
            });
        }

        // colors
        let is_premul = flags.contains(RenderMeshCreateFlags::PREMULTIPLIED_ALPHA);
        // This is scary! We need to allocate the data outside of any if statement, because we pass
        // in `arr.as_ptr() as u32` into `create_mesh`. That means the data still needs to be alive
        // at the point of the call, so we allocate this temporary variable to live long enough.
        let premul_data = if is_premul {
            Default::default()
        } else {
            mesh_data.colors.as_ref().map(|colors| {
                colors
                    .iter()
                    .map(|color| {
                        let mut linear: Vec4 = (*color).into();
                        linear.x *= linear.w;
                        linear.y *= linear.w;
                        linear.z *= linear.w;
                        linear.into()
                    })
                    .collect::<Vec<_>>()
            })
        };
        if let Some(colors) = if is_premul {
            &mesh_data.colors
        } else {
            &premul_data
        } {
            streams.push(ffi::MeshStreamLayout {
                semantic: ffi::MeshStreamSemantic::Colors,
                component_format: ffi::MeshComponentFormat::UInt8,
                component_count: 4,
                buffer_ptr: colors.as_ptr() as u32,
                buffer_size: (colors.len() * size_of::<[u8; 4]>()) as u32,
            });
        }

        RenderMesh::new(ffi::create_named_mesh_with_materials_and_sections(
            ffi::MeshPrimitiveTopology::TriangleList,
            &streams[..],
            materials,
            sections,
            &mesh_data.name,
        ))
    }

    /// Loads a mesh from directly provided GLTF data.
    ///
    /// Supports .gltf and .glb, and a single additional buffer file.
    /// `buffer_name` is not used directly, but might be used for validation.
    pub fn create_mesh_from_gltf(
        &self,
        debug_name: &str,
        gltf_data: &[u8],
        buffer_data: &[u8],
        flags: GltfFlags,
    ) -> Result<RenderMesh, Error> {
        Ok(RenderMesh::new(ffi::create_mesh_from_gltf_with_flags_name(
            debug_name,
            gltf_data,
            buffer_data,
            flags.bits(),
        )?))
    }

    /// Creates a mesh from a GLTF resource handle and additional buffer resource handle.
    pub fn create_mesh_from_gltf_resource(
        &self,
        debug_name: &str,
        gltf_resource: ffi::ResourceHandleRepr,
        buffer_resource: Option<ffi::ResourceHandleRepr>,
        flags: GltfFlags,
    ) -> Result<RenderMesh, Error> {
        Ok(RenderMesh::new(ffi::create_mesh_from_gltf_resource(
            debug_name,
            gltf_resource,
            buffer_resource.unwrap_or(ffi::INVALID_RESOURCE_HANDLE),
            flags.bits(),
        )?))
    }

    /// Draws a single mesh.
    ///
    /// It is OK to drop the [`RenderMesh`] in the same frame after calling
    /// this, it will still be drawn.
    ///
    /// When drawing multiple meshes, prefer to use [`Render::draw_meshes`] instead for improved performance.
    ///
    /// If you are drawing the same mesh over multiple frames, pick a unique-enough `instance_id` and
    /// pass the same one every frame. Pass 0 if you can't come up with one, but you'll end up with
    /// rendering artifacts on quick movements.
    ///
    /// # Errors
    ///
    /// Under normal safe circumstances, this function cannot error. However, errors through the
    /// underlying API can happen - for example, if the raw handle inside `mesh` has already been
    /// destroyed (via unsafe code).
    pub fn draw_mesh(
        &self,
        mesh: &RenderMesh,
        world_transform: &Mat4,
        style: &RenderMeshStyle,
        instance_id: Option<InstanceId>,
    ) {
        self.draw_meshes(&[RenderMeshInstance {
            world_transform: world_transform.to_cols_array(),
            mesh: mesh.handle,
            style: (*style).into(),
            instance_id: instance_id.map_or(0, |id| id.0.get()),
            materials_offset: 0,
            materials_len: 0,
            mesh_styles_index: 0,
            _pad: Default::default(),
        }]);
    }

    /// Draws multiple meshes.
    ///
    /// Use [`RenderMeshInstanceBuilder`] to create a [`RenderMeshInstance`].
    ///
    /// It is OK to drop the [`RenderMesh`] in the same frame after calling
    /// this, it will still be drawn.
    ///
    /// # Errors
    ///
    /// Under normal safe circumstances, this function cannot error. However, errors through the
    /// underlying API can happen - for example, if the raw handle inside `mesh` has already been
    /// destroyed (via unsafe code).
    ///
    /// If an error occurs drawing one or more of the meshes in the list, *no* meshes in the list
    /// will be drawn, and an error is returned.
    pub fn draw_meshes(&self, mesh_instances: &[RenderMeshInstance]) {
        ffi::draw_meshes2(mesh_instances);
    }

    /// Draws multiple meshes with a list of material overrides.
    ///
    /// Use [`RenderMeshInstanceBuilder`] to create a [`RenderMeshInstance`].
    ///
    /// It is okay to drop the [`RenderMesh`] in the same frame after calling
    /// this, it will still be drawn.
    ///
    /// The material ID inside the meshes will be used to index into the `material_overrides` list offsetted by [`RenderMeshInstance::materials_offset`].
    /// If no override is provided for an index the meshes original material will be used.
    pub fn draw_meshes_with_materials(
        &self,
        mesh_instances: &[RenderMeshInstance],
        material_overrides: &[RenderMaterial],
    ) {
        ffi::draw_meshes_with_materials(mesh_instances, material_overrides);
    }

    /// Draws multiple meshes with a list of material and style overrides.
    ///
    /// Use [`RenderMeshInstanceBuilder`] to create a [`RenderMeshInstance`].
    ///
    /// It is okay to drop the [`RenderMesh`] in the same frame after calling
    /// this, it will still be drawn.
    ///
    /// The material ID inside the meshes will be used to index into the `material_overrides` list offsetted by [`RenderMeshInstance::materials_offset`].
    /// If no override is provided for an index the meshes original material will be used.
    pub fn draw_meshes_with_materials_and_styles(
        &self,
        mesh_instances: &[RenderMeshInstance],
        material_overrides: &[RenderMaterial],
        mesh_style_overrides: &[RenderMeshStyle],
    ) {
        let mesh_style_overrides: Vec<ffi::RenderMeshStyle2> =
            mesh_style_overrides.iter().map(|s| (*s).into()).collect();
        ffi::draw_meshes_with_materials_and_styles(
            mesh_instances,
            material_overrides,
            &mesh_style_overrides,
        );
    }

    /// Allocate a new texture.
    ///
    /// # Example
    ///
    /// ``` no_run
    /// # let render = Render::__create();
    /// let data = [255, 0, 0, 255,
    ///             0, 255, 0, 255];
    /// let texture = render
    ///     .create_texture()
    ///     .name("my_texture")
    ///     .dimension(2, 1)
    ///     .format(TextureFormat::R8G8B8A8_SRGB) // can be omitted - this is the default
    ///     .data(pixels)
    ///     .build()?;
    /// ```
    pub fn create_texture(&self) -> TextureBuilder<'_> {
        TextureBuilder::new(self)
    }

    /// Creates a new SDF function from [Saft](https://docs.rs/saft) opcodes and constants.
    pub fn create_sdf_model(
        &self,
        opcodes: &[u32],
        constants: &[f32],
        bounding_box: &macaw::BoundingBox,
    ) -> Result<SdfModel, Error> {
        let bbox = ffi::BoundingBox {
            min: bounding_box.min.into(),
            max: bounding_box.max.into(),
        };
        let handle = ffi::create_sdf_model(opcodes, constants, &bbox)?;
        Ok(SdfModel(handle))
    }
}

/// Builder for creating textures.
///
/// This is used to control all the various configuration options and such
/// to create a texture. More documentation is provided on each method itself.
#[derive(Copy, Clone, Default)]
pub struct TextureBuilder<'a> {
    name: Option<&'a str>,
    description: Option<ffi::TextureDescription>,
    data: Option<&'a [u8]>,
}

impl<'a> TextureBuilder<'a> {
    /// Use [`Render::create_texture`] instead
    #[inline]
    fn new(_: &Render) -> Self {
        Default::default()
    }

    /// Set the texture data of the texture.
    ///
    /// See [`Render::create_texture`] for an example.
    #[inline]
    pub fn data(&mut self, buffer: &'a [u8]) -> &mut Self {
        self.data = Some(buffer);
        self
    }

    /// Set the width and height of the texture's description.
    ///
    /// For valid texture dimensions, `width` * `height` * [`TextureFormat::bytes_per_pixel`]
    /// should be equal to the texture's buffer length.
    ///
    /// See [`Render::create_texture`] for an example.
    #[inline]
    pub fn dimensions(&mut self, width: usize, height: usize) -> &mut Self {
        let mut desc = self.description.unwrap_or(ffi::TextureDescription {
            width: 0,
            height: 0,
            depth: 1,
            format: TextureFormat::R8G8B8A8_SRGB,
            mipmaps: 1,
            array_len: 1,
            texture_type: ffi::TextureType::D2,
        });
        desc.width = width as u64;
        desc.height = height as u64;
        self.description = Some(desc);
        self
    }

    /// Set the format of the texture's description.
    ///
    /// For a valid texture format, [`TextureFormat::bytes_per_pixel`] * the texture
    /// dimensions' `width` * `height` should be equal to the texture's buffer length.
    ///
    /// See [`Render::create_texture`] for an example.
    #[inline]
    pub fn format(&mut self, format: TextureFormat) -> &mut Self {
        let mut desc = self.description.unwrap_or(ffi::TextureDescription {
            width: 0,
            height: 0,
            depth: 1,
            format: TextureFormat::R8G8B8A8_SRGB,
            mipmaps: 1,
            array_len: 1,
            texture_type: ffi::TextureType::D2,
        });
        desc.format = format;
        self.description = Some(desc);
        self
    }

    /// Set the name of the texture.
    ///
    /// Although optional, setting the name of a texture is highly recommended
    /// as it improves the debugging experience significantly.
    ///
    /// The default is "unnamed".
    #[inline]
    pub fn name(&mut self, name: &'a str) -> &mut Self {
        self.name = Some(name);
        self
    }

    /// Build the texture.
    ///
    /// See [`Render::create_texture`] for an example.
    ///
    /// # Errors
    ///
    /// Returns an [`Error::InvalidArguments`] if the texture buffer's length doesn't match
    /// up against the texture's dimensions and [`TextureFormat`] or if the texture dimensions
    /// have values lower or equal to 0.
    #[inline]
    pub fn build(&self) -> Result<Texture, Error> {
        let name = self.name.unwrap_or("unnamed");
        let desc = self.description.unwrap_or(ffi::TextureDescription {
            width: 0,
            height: 0,
            depth: 1,
            format: TextureFormat::R8G8B8A8_SRGB,
            mipmaps: 1,
            array_len: 1,
            texture_type: ffi::TextureType::D2,
        });
        let data = self.data.unwrap_or(&[]);

        let handle = ffi::create_texture(name, &desc, data).map_err(Error::from)?;

        Ok(Texture {
            handle: Arc::new(handle),
            name: Arc::new(name.to_string()),
            description: Arc::new(desc),
        })
    }
}

/// Immutable texture object stored in GPU memory.
///
/// Internally this uses reference counting, so cloning a is very cheap and reuses the texture contents
pub struct Texture {
    handle: Arc<TextureHandle>,
    name: Arc<String>,
    description: Arc<ffi::TextureDescription>,
}

impl Texture {
    /// Retrieves the texture's name.
    #[inline]
    pub fn name(&self) -> &str {
        (*self.name).as_str()
    }

    /// Updates a subrectangle of a texture with new data.
    ///
    /// Added mainly for egui support, but might have other interesting uses.
    pub fn update_rectangle(&self, pos_x: u32, pos_y: u32, width: u32, height: u32, data: &[u8]) {
        ffi::update_texture(*self.handle, pos_x, pos_y, width, height, data);
    }

    /// Retrieves the texture's format.
    #[inline]
    pub fn format(&self) -> TextureFormat {
        self.description.format
    }

    /// Retrieves the texture's dimensions.
    #[inline]
    pub fn dimensions(&self) -> (usize, usize, usize) {
        let desc = *self.description;
        (
            desc.width as usize,
            desc.height as usize,
            desc.depth as usize,
        )
    }

    // these are not exposed yet as we only support 2D textures without mipmaps and not arrays right now
    /*
        /// Retrieves the texture's type.
        #[inline]
        pub fn texture_type(&self) -> TextureType {
            self.description.texture_type
        }

        /// Retrieves the amount of mipmaps the texture have.
        #[inline]
        pub fn mipmaps(&self) -> usize {
            self.description.mipmaps as usize
        }

        /// Retrieves the amount of texture array slices in the texture.
        ///
        /// This will be 0 if the texture is not a texture array
        #[inline]
        pub fn array_len(&self) -> usize {
            self.description.mipmaps as usize
        }
    */

    /// Retrieves the texture's handle.
    #[inline]
    pub fn handle(&self) -> TextureHandle {
        *self.handle
    }
}

impl Drop for Texture {
    fn drop(&mut self) {
        if Arc::strong_count(&self.handle) == 1 {
            ffi::destroy_texture(*self.handle);
        }
    }
}

impl Clone for Texture {
    fn clone(&self) -> Self {
        Self {
            handle: Arc::clone(&self.handle),
            name: Arc::clone(&self.name),
            description: Arc::clone(&self.description),
        }
    }
}

impl Debug for Texture {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Texture")
            .field("name", &self.name())
            .field("description", &*self.description)
            .finish()
    }
}

/// Stable instance ID for identifying an instance over frame.
///
/// This may be typically stored in an option like [`Option<InstanceId>`] as not all instances have an ID,
/// when used that way the option won't take any extra space, the type will remain 64-bits
#[derive(PartialEq, Debug, Eq, Hash, Copy, Clone)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InstanceId(pub NonZeroU64);

impl InstanceId {
    /// Create a non-zero instance id. If zero is provided this function will return `None`.
    pub fn new(id: u64) -> Option<Self> {
        NonZeroU64::new(id).map(Self)
    }
}

/// A handle to a mesh.
pub struct RenderMesh {
    handle: ffi::RenderMeshHandle,
}

impl Drop for RenderMesh {
    fn drop(&mut self) {
        ffi::destroy_mesh(self.handle);
    }
}

impl RenderMesh {
    fn new(handle: ffi::RenderMeshHandle) -> Self {
        Self { handle }
    }

    /// Returns the raw handle. Useful when constructing raw [`RenderMeshInstance`] structs without the builder.
    pub fn raw_handle(&self) -> ffi::RenderMeshHandle {
        self.handle
    }

    /// Generic way to get mesh data streams from FFI
    fn get_mesh_data_stream<T: Sized + Clone>(
        &self,
        ty: ffi::MeshStreamSemantic,
        mesh_info: &ffi::MeshDataInfo,
    ) -> Vec<T> {
        // Early out for optional attributes.
        if (ty == ffi::MeshStreamSemantic::Colors
            && !mesh_info.flags.contains(ffi::MeshDataInfoFlags::COLORS))
            || (ty == ffi::MeshStreamSemantic::TexCoords
                && !mesh_info.flags.contains(ffi::MeshDataInfoFlags::TEX_COORDS))
        {
            return vec![];
        }

        let num_elements = if ty == ffi::MeshStreamSemantic::Indices {
            mesh_info.num_indices
        } else {
            mesh_info.num_vertices
        } as usize;

        let bytes = ffi::get_mesh_data_stream(self.handle, ty);

        assert!((bytes.len() % size_of::<T>()) == 0);

        let result =
            unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast::<T>(), num_elements) }
                .to_vec();

        assert_eq!(result.len(), num_elements);

        result
    }

    /// Retrieve a `MeshData` from the host.
    /// This function should be called sparingly since the data is not cached.
    pub fn retrieve_mesh_data(&self) -> MeshData {
        let info = ffi::get_mesh_data_info(self.handle);

        let indices = self.get_mesh_data_stream(ffi::MeshStreamSemantic::Indices, &info);
        let positions = self.get_mesh_data_stream(ffi::MeshStreamSemantic::Positions, &info);
        let normals = self.get_mesh_data_stream(ffi::MeshStreamSemantic::Normals, &info);
        let colors = if info.flags.contains(ffi::MeshDataInfoFlags::COLORS) {
            Some(self.get_mesh_data_stream(ffi::MeshStreamSemantic::Colors, &info))
        } else {
            None
        };

        MeshData {
            name: ffi::get_mesh_data_name(self.handle),
            positions,
            normals: Some(normals),
            indices,
            colors,
        }
    }
}

/// Creates `RenderMeshInstance` through the builder pattern
#[derive(Debug, Clone)]
pub struct RenderMeshInstanceBuilder {
    inst: RenderMeshInstance,
}

impl RenderMeshInstanceBuilder {
    /// Creates a new builder from a [`RenderMesh`].
    pub fn new(mesh: &RenderMesh) -> Self {
        Self {
            inst: RenderMeshInstance {
                world_transform: Mat4::IDENTITY.to_cols_array(),
                mesh: mesh.handle,
                style: RenderMeshStyle::default().into(),
                instance_id: 0,
                materials_offset: 0,
                materials_len: 0,
                mesh_styles_index: 0,
                _pad: Default::default(),
            },
        }
    }

    /// Set world transform. Default is [`Mat4::IDENTITY`]
    pub fn with_world_transform(&mut self, world_transform: &Mat4) -> &mut Self {
        self.inst.world_transform = world_transform.to_cols_array();
        self
    }

    /// Set the style. Default is [`RenderMeshStyle::default()`]
    pub fn with_style(&mut self, style: &RenderMeshStyle) -> &mut Self {
        self.inst.style = (*style).into();
        self
    }

    /// Set the instance id. Default is `0` which means no instance id.
    pub fn with_instance_id(&mut self, instance_id: InstanceId) -> &mut Self {
        self.inst.instance_id = instance_id.0.get();
        self
    }

    /// Set the materials offset. Default is `0`.
    /// This is the offset the instance will use to read into the material overrides provided with [`Render::draw_meshes_with_materials`].
    pub fn with_materials_offset(&mut self, offset: u32) -> &mut Self {
        self.inst.materials_offset = offset;
        self
    }

    /// Set the materials length. Default is `0`.
    ///
    /// This is the number of material overrides the instance will use [`Render::draw_meshes_with_materials`].
    /// By default it will take all materials after the offset
    pub fn with_materials_len(&mut self, len: u32) -> &mut Self {
        self.inst.materials_len = len;
        self
    }

    /// Set the materials offset. Default is `0`.
    /// This is the index of the mesh style in the `mesh_style_overrides` list passed into [`Render::draw_meshes_with_materials_and_styles`].
    /// If the index is not valid in the overrides list it will use the `RenderMeshInstance::style` variable instead.
    pub fn with_mesh_styles_index(&mut self, idx: u32) -> &mut Self {
        self.inst.mesh_styles_index = idx;
        self
    }

    /// Builds the mesh instance
    pub fn build(&self) -> RenderMeshInstance {
        self.inst
    }
}

/// Specified an SDF instance to be rendered.
pub struct SdfInstanceData {
    /// World transform. Prefer to use this for moving the instance rather than modifying the program.
    pub world_from_instance: [f32; 16],
    /// Set this if you use any dynamic data to update the program. With the current mesh rendering, bad idea.
    pub dynamic_data_length: u32,
    /// Controls how the instance will be rendered.
    pub style: RenderMeshStyle,
    /// Higher value will render in higher detail.
    pub detail_bias: f32,
    /// If you can, set to something unique that's consistent between each rendered frame.
    pub instance_id: Option<InstanceId>,
}

/// An Sdf (signed distance field) that can be rendered directly.
///
/// It can also be used as the shape of a bone.
/// Unlike the procedural api, ref counting is not built in, wrap it in an Arc if you need it.
pub struct SdfModel(ffi::SdfHandle);

/// Simple Saft Sdf graph rendering. Any parameter can be directly modified by changing the dynamic constants.
/// Changing `dynamic_constants` will result in full re-meshing.
impl SdfModel {
    /// Renders the Sdf function.
    ///
    /// In `instance_data` you can specify a world transform and other parameters.
    /// Changing the shape through `dynamic_constants` may incur a performance hit due to remeshing.
    pub fn draw(
        &self,
        instance_data: &[SdfInstanceData],
        dynamic_constants: &[f32],
        bounding_boxes: &[macaw::BoundingBox],
    ) {
        // TODO: Make bboxes compatible so we can transmute (or maybe just use macaw directly in the FFI?).
        let bounding_boxes = bounding_boxes
            .iter()
            .map(|b| ffi::BoundingBox {
                min: b.min.into(),
                max: b.max.into(),
            })
            .collect::<Vec<_>>();
        let mut instances = Vec::with_capacity(instance_data.len());
        let mut dynamic_data_offset = 0;
        let mut bounding_box_index = 0;
        for instance in instance_data {
            instances.push(ffi::SdfInstanceData2 {
                world_from_instance: instance.world_from_instance,
                dynamic_data_offset,
                dynamic_data_length: instance.dynamic_data_length,
                bounding_box_index,
                style: instance.style.into(),
                instance_id: instance.instance_id.map_or(0, |id| id.0.get()),
                detail_bias: instance.detail_bias,
                reserved: [0; 4],
            });

            if instance.dynamic_data_length > 0 {
                bounding_box_index += 1;
                dynamic_data_offset += instance.dynamic_data_length;
            }
        }
        ffi::draw_sdf_model2(self.0, &instances, dynamic_constants, &bounding_boxes);
    }
}

impl Drop for SdfModel {
    fn drop(&mut self) {
        ffi::destroy_sdf_model(self.0);
    }
}