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! {
#[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 {
const PREMULTIPLIED_ALPHA = 0b0000_0001;
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct RenderMeshStyle {
diffuse_tint: Vec4,
flags: RenderMeshStyleFlags,
visibility_flags: RenderMeshVisibilityFlags,
pad: [u8; 11], }
assert_eq_size!(RenderMeshStyle, ffi::RenderMeshStyle);
impl RenderMeshStyle {
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 {
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(),
}
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct RenderMeshStyleBuilder {
style: RenderMeshStyle,
}
impl RenderMeshStyleBuilder {
pub fn new() -> Self {
Self::default()
}
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
}
pub fn with_diffuse_tint_premultiplied(&mut self, tint: Vec4) -> &mut Self {
self.style.diffuse_tint = tint;
self
}
pub fn with_lighting(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::LIGHTING, e);
self
}
pub fn with_flat_shading(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::FLAT_SHADING, e);
self
}
pub fn with_billboard_rendering(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::BILLBOARD, e);
self
}
pub fn with_two_sided(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::TWO_SIDED, e);
self
}
pub fn with_depth_test(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::DEPTH_TEST, e);
self
}
pub fn with_depth_write(&mut self, e: bool) -> &mut Self {
self.style.flags.set(RenderMeshStyleFlags::DEPTH_WRITE, e);
self
}
pub fn with_visibility_flags(&mut self, flags: RenderMeshVisibilityFlags) -> &mut Self {
self.style.visibility_flags = flags;
self
}
pub fn build(&self) -> RenderMeshStyle {
self.style
}
}
#[derive(Default, Debug, Clone)]
pub struct RenderMaterialBuilder {
desc: RenderMaterial,
}
impl RenderMaterialBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_diffuse_albedo(&mut self, albedo: Vec3) -> &mut Self {
self.desc.diffuse_albedo = albedo.into();
self
}
pub fn with_alpha(&mut self, alpha: f32) -> &mut Self {
self.desc.alpha = alpha;
self
}
pub fn with_emissive_color(&mut self, color: Vec3) -> &mut Self {
self.desc.emissive_color = color.into();
self
}
pub fn with_roughness(&mut self, perceptual_roughness: f32) -> &mut Self {
self.desc.perceptual_roughness = perceptual_roughness;
self
}
pub fn with_metallic(&mut self, metallic: f32) -> &mut Self {
self.desc.metallic = metallic;
self
}
pub fn build(&self) -> RenderMaterial {
self.desc
}
}
pub fn bone_transform_from_iso(iso: &IsoTransform) -> BoneTransform {
BoneTransform {
pos: iso.translation().into(),
_padding: 0.0,
rot: iso.rotation().into(),
}
}
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Line(ffi::Line);
impl Line {
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,
})
}
}
#[derive(Clone)]
pub struct Render {
_private: (),
}
impl Render {
#[doc(hidden)]
pub fn __create() -> Self {
Self { _private: () }
}
#[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);
}
#[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);
}
#[inline]
pub fn draw_debug_lines(&self, lines: &[Line]) {
let lines = unsafe { &*(lines as *const [Line] as *const [ffi::Line]) };
ffi::draw_debug_lines(lines);
}
pub fn create_mesh(&self, mesh: &Mesh, flags: RenderMeshCreateFlags) -> RenderMesh {
self.create_mesh_with_materials_and_sections(mesh, flags, &[], &[])
}
pub fn create_mesh_with_material(
&self,
mesh: &Mesh,
flags: RenderMeshCreateFlags,
material: RenderMaterial,
) -> RenderMesh {
self.create_mesh_with_materials_and_sections(mesh, flags, &[material], &[])
}
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![];
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,
});
}
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,
});
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,
});
}
let is_premul = flags.contains(RenderMeshCreateFlags::PREMULTIPLIED_ALPHA);
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,
))
}
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(),
)?))
}
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(),
)?))
}
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(),
}]);
}
pub fn draw_meshes(&self, mesh_instances: &[RenderMeshInstance]) {
ffi::draw_meshes2(mesh_instances);
}
pub fn draw_meshes_with_materials(
&self,
mesh_instances: &[RenderMeshInstance],
material_overrides: &[RenderMaterial],
) {
ffi::draw_meshes_with_materials(mesh_instances, material_overrides);
}
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,
);
}
pub fn create_texture(&self) -> TextureBuilder<'_> {
TextureBuilder::new(self)
}
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))
}
}
#[derive(Copy, Clone, Default)]
pub struct TextureBuilder<'a> {
name: Option<&'a str>,
description: Option<ffi::TextureDescription>,
data: Option<&'a [u8]>,
}
impl<'a> TextureBuilder<'a> {
#[inline]
fn new(_: &Render) -> Self {
Default::default()
}
#[inline]
pub fn data(&mut self, buffer: &'a [u8]) -> &mut Self {
self.data = Some(buffer);
self
}
#[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
}
#[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
}
#[inline]
pub fn name(&mut self, name: &'a str) -> &mut Self {
self.name = Some(name);
self
}
#[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),
})
}
}
pub struct Texture {
handle: Arc<TextureHandle>,
name: Arc<String>,
description: Arc<ffi::TextureDescription>,
}
impl Texture {
#[inline]
pub fn name(&self) -> &str {
(*self.name).as_str()
}
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);
}
#[inline]
pub fn format(&self) -> TextureFormat {
self.description.format
}
#[inline]
pub fn dimensions(&self) -> (usize, usize, usize) {
let desc = *self.description;
(
desc.width as usize,
desc.height as usize,
desc.depth as usize,
)
}
#[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()
}
}
#[derive(PartialEq, Debug, Eq, Hash, Copy, Clone)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InstanceId(pub NonZeroU64);
impl InstanceId {
pub fn new(id: u64) -> Option<Self> {
NonZeroU64::new(id).map(Self)
}
}
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 }
}
pub fn raw_handle(&self) -> ffi::RenderMeshHandle {
self.handle
}
fn get_mesh_data_stream<T: Sized + Clone>(
&self,
ty: ffi::MeshStreamSemantic,
mesh_info: &ffi::MeshDataInfo,
) -> Vec<T> {
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
}
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,
}
}
}
#[derive(Debug, Clone)]
pub struct RenderMeshInstanceBuilder {
inst: RenderMeshInstance,
}
impl RenderMeshInstanceBuilder {
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(),
},
}
}
pub fn with_world_transform(&mut self, world_transform: &Mat4) -> &mut Self {
self.inst.world_transform = world_transform.to_cols_array();
self
}
pub fn with_style(&mut self, style: &RenderMeshStyle) -> &mut Self {
self.inst.style = (*style).into();
self
}
pub fn with_instance_id(&mut self, instance_id: InstanceId) -> &mut Self {
self.inst.instance_id = instance_id.0.get();
self
}
pub fn with_materials_offset(&mut self, offset: u32) -> &mut Self {
self.inst.materials_offset = offset;
self
}
pub fn with_materials_len(&mut self, len: u32) -> &mut Self {
self.inst.materials_len = len;
self
}
pub fn with_mesh_styles_index(&mut self, idx: u32) -> &mut Self {
self.inst.mesh_styles_index = idx;
self
}
pub fn build(&self) -> RenderMeshInstance {
self.inst
}
}
pub struct SdfInstanceData {
pub world_from_instance: [f32; 16],
pub dynamic_data_length: u32,
pub style: RenderMeshStyle,
pub detail_bias: f32,
pub instance_id: Option<InstanceId>,
}
pub struct SdfModel(ffi::SdfHandle);
impl SdfModel {
pub fn draw(
&self,
instance_data: &[SdfInstanceData],
dynamic_constants: &[f32],
bounding_boxes: &[macaw::BoundingBox],
) {
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);
}
}