//! # 🖼️ 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);
}
}