use std::{mem::size_of, ops::Range};
use ecolor::Rgba;
use smallvec::{smallvec, SmallVec};
use crate::{
allocator::create_and_fill_uniform_buffer_batch,
debug_label::DebugLabel,
renderer::MeshRenderer,
resource_managers::GpuTexture2D,
wgpu_resources::{BindGroupDesc, BindGroupEntry, BufferDesc, GpuBindGroup, GpuBuffer},
RenderContext, Rgba32Unmul,
};
pub mod mesh_vertices {
use crate::wgpu_resources::VertexBufferLayout;
pub fn vertex_buffer_layouts() -> smallvec::SmallVec<[VertexBufferLayout; 4]> {
VertexBufferLayout::from_formats(
[
wgpu::VertexFormat::Float32x3, wgpu::VertexFormat::Unorm8x4, wgpu::VertexFormat::Float32x3, wgpu::VertexFormat::Float32x2, ]
.into_iter(),
)
}
pub fn next_free_shader_location() -> u32 {
vertex_buffer_layouts()
.iter()
.flat_map(|layout| layout.attributes.iter())
.max_by(|a1, a2| a1.shader_location.cmp(&a2.shader_location))
.unwrap()
.shader_location
+ 1
}
}
#[derive(Clone)]
pub struct CpuMesh {
pub label: DebugLabel,
pub triangle_indices: Vec<glam::UVec3>,
pub vertex_positions: Vec<glam::Vec3>,
pub vertex_colors: Vec<Rgba32Unmul>,
pub vertex_normals: Vec<glam::Vec3>,
pub vertex_texcoords: Vec<glam::Vec2>,
pub materials: SmallVec<[Material; 1]>,
}
impl CpuMesh {
pub fn sanity_check(&self) -> Result<(), MeshError> {
re_tracing::profile_function!();
let Self {
label: _,
triangle_indices,
vertex_positions,
vertex_colors,
vertex_normals,
vertex_texcoords,
materials: _,
} = self;
let num_pos = vertex_positions.len();
let num_color = vertex_colors.len();
let num_normals = vertex_normals.len();
let num_texcoords = vertex_texcoords.len();
if num_pos != num_color {
return Err(MeshError::WrongNumberOfColors { num_pos, num_color });
}
if num_pos != num_normals {
return Err(MeshError::WrongNumberOfNormals {
num_pos,
num_normals,
});
}
if num_pos != num_texcoords {
return Err(MeshError::WrongNumberOfTexcoord {
num_pos,
num_texcoords,
});
}
if self.vertex_positions.is_empty() {
return Err(MeshError::ZeroVertices);
}
if self.triangle_indices.is_empty() {
return Err(MeshError::ZeroIndices);
}
for indices in triangle_indices {
let max_index = indices.max_element();
if num_pos <= max_index as usize {
return Err(MeshError::IndexOutOfBounds {
num_pos,
index: max_index,
});
}
}
Ok(())
}
}
#[derive(thiserror::Error, Debug)]
pub enum MeshError {
#[error("Number of vertex positions {num_pos} differed from the number of vertex colors {num_color}")]
WrongNumberOfColors { num_pos: usize, num_color: usize },
#[error("Number of vertex positions {num_pos} differed from the number of vertex normals {num_normals}")]
WrongNumberOfNormals { num_pos: usize, num_normals: usize },
#[error("Number of vertex positions {num_pos} differed from the number of vertex tex-coords {num_texcoords}")]
WrongNumberOfTexcoord {
num_pos: usize,
num_texcoords: usize,
},
#[error("Mesh has no vertices.")]
ZeroVertices,
#[error("Mesh has no triangle indices.")]
ZeroIndices,
#[error("Index {index} was out of bounds for {num_pos} vertex positions")]
IndexOutOfBounds { num_pos: usize, index: u32 },
#[error(transparent)]
CpuWriteGpuReadError(#[from] crate::allocator::CpuWriteGpuReadError),
}
#[derive(Clone)]
pub struct Material {
pub label: DebugLabel,
pub index_range: Range<u32>,
pub albedo: GpuTexture2D,
pub albedo_factor: Rgba,
}
#[derive(Clone)]
pub struct GpuMesh {
pub index_buffer: GpuBuffer,
pub vertex_buffer_combined: GpuBuffer,
pub vertex_buffer_positions_range: Range<u64>,
pub vertex_buffer_colors_range: Range<u64>,
pub vertex_buffer_normals_range: Range<u64>,
pub vertex_buffer_texcoord_range: Range<u64>,
pub index_buffer_range: Range<u64>,
pub materials: SmallVec<[GpuMaterial; 1]>,
}
#[derive(Clone)]
pub struct GpuMaterial {
pub index_range: Range<u32>,
pub bind_group: GpuBindGroup,
}
pub(crate) mod gpu_data {
use crate::wgpu_buffer_types;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MaterialUniformBuffer {
pub albedo_factor: wgpu_buffer_types::Vec4,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
}
}
impl GpuMesh {
pub fn new(ctx: &RenderContext, data: &CpuMesh) -> Result<Self, MeshError> {
re_tracing::profile_function!();
data.sanity_check()?;
re_log::trace!(
"uploading new mesh named {:?} with {} vertices and {} triangles",
data.label.get(),
data.vertex_positions.len(),
data.triangle_indices.len(),
);
let vb_positions_size = (data.vertex_positions.len() * size_of::<glam::Vec3>()) as u64;
let vb_color_size = (data.vertex_colors.len() * size_of::<Rgba32Unmul>()) as u64;
let vb_normals_size = (data.vertex_normals.len() * size_of::<glam::Vec3>()) as u64;
let vb_texcoords_size = (data.vertex_texcoords.len() * size_of::<glam::Vec2>()) as u64;
let vb_combined_size =
vb_positions_size + vb_color_size + vb_normals_size + vb_texcoords_size;
let pools = &ctx.gpu_resources;
let device = &ctx.device;
let vertex_buffer_combined = {
let vertex_buffer_combined = pools.buffers.alloc(
device,
&BufferDesc {
label: format!("{} - vertices", data.label).into(),
size: vb_combined_size,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
},
);
let mut staging_buffer = ctx.cpu_write_gpu_read_belt.lock().allocate::<u8>(
&ctx.device,
&ctx.gpu_resources.buffers,
vb_combined_size as _,
)?;
staging_buffer.extend_from_slice(bytemuck::cast_slice(&data.vertex_positions))?;
staging_buffer.extend_from_slice(bytemuck::cast_slice(&data.vertex_colors))?;
staging_buffer.extend_from_slice(bytemuck::cast_slice(&data.vertex_normals))?;
staging_buffer.extend_from_slice(bytemuck::cast_slice(&data.vertex_texcoords))?;
staging_buffer.copy_to_buffer(
ctx.active_frame.before_view_builder_encoder.lock().get(),
&vertex_buffer_combined,
0,
)?;
vertex_buffer_combined
};
let index_buffer_size = (size_of::<glam::UVec3>() * data.triangle_indices.len()) as u64;
let index_buffer = {
let index_buffer = pools.buffers.alloc(
device,
&BufferDesc {
label: format!("{} - indices", data.label).into(),
size: index_buffer_size,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
},
);
let mut staging_buffer = ctx.cpu_write_gpu_read_belt.lock().allocate::<glam::UVec3>(
&ctx.device,
&ctx.gpu_resources.buffers,
data.triangle_indices.len(),
)?;
staging_buffer.extend_from_slice(bytemuck::cast_slice(&data.triangle_indices))?;
staging_buffer.copy_to_buffer(
ctx.active_frame.before_view_builder_encoder.lock().get(),
&index_buffer,
0,
)?;
index_buffer
};
let materials = {
let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
format!("{} - material uniforms", data.label).into(),
data.materials
.iter()
.map(|material| gpu_data::MaterialUniformBuffer {
albedo_factor: material.albedo_factor.into(),
end_padding: Default::default(),
}),
);
let mut materials = SmallVec::with_capacity(data.materials.len());
let mesh_bind_group_layout = ctx.renderer::<MeshRenderer>().bind_group_layout;
for (material, uniform_buffer_binding) in data
.materials
.iter()
.zip(uniform_buffer_bindings.into_iter())
{
let bind_group = pools.bind_groups.alloc(
device,
pools,
&BindGroupDesc {
label: material.label.clone(),
entries: smallvec![
BindGroupEntry::DefaultTextureView(material.albedo.handle()),
uniform_buffer_binding
],
layout: mesh_bind_group_layout,
},
);
materials.push(GpuMaterial {
index_range: material.index_range.clone(),
bind_group,
});
}
materials
};
let vb_colors_start = vb_positions_size;
let vb_normals_start = vb_colors_start + vb_color_size;
let vb_texcoord_start = vb_normals_start + vb_normals_size;
Ok(Self {
index_buffer,
vertex_buffer_combined,
vertex_buffer_positions_range: 0..vb_positions_size,
vertex_buffer_colors_range: vb_colors_start..vb_normals_start,
vertex_buffer_normals_range: vb_normals_start..vb_texcoord_start,
vertex_buffer_texcoord_range: vb_texcoord_start..vb_combined_size,
index_buffer_range: 0..index_buffer_size,
materials,
})
}
}