Skip to main content

pebble/graphics/pipeline/
mesh.rs

1use crate::{
2    assets::{handle::Handle, storage::Assets, upload::{Asset, AssetSource}},
3    graphics::{
4        pipeline::buffers::{Buffer, BufferBuilder},
5        render::Backend,
6        types::{
7            VertexFormat, VertexStepMode,
8            flags::BufferUsages,
9            pipeline_state::{VertexAttribute, VertexBufferLayout},
10        },
11    },
12};
13
14/// The built-in vertex format — position, UV, normal, tangent. [`Mesh`] is
15/// generic over vertex type, so this is a convenient default, not a
16/// requirement; define your own `bytemuck::Pod` struct for anything else.
17#[repr(C)]
18#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
19pub struct Vertex {
20    pub position: glam::Vec3,
21    pub tex_coords: glam::Vec2,
22    pub normal: glam::Vec3,
23    pub tangent: glam::Vec4,
24}
25
26impl Vertex {
27    pub fn new(position: glam::Vec3, tex_coords: glam::Vec2, normal: glam::Vec3, tangent: glam::Vec4) -> Self {
28        Self { position, tex_coords, normal, tangent }
29    }
30
31    /// This type's [`VertexBufferLayout`], for wiring it into a [`Material`](super::material::Material)'s pipeline.
32    pub fn layout() -> VertexBufferLayout {
33        VertexBufferLayout {
34            array_stride: std::mem::size_of::<Vertex>() as u64,
35            step_mode: VertexStepMode::Vertex,
36            attributes: vec![
37                VertexAttribute { format: VertexFormat::Float32x3, offset: 0, shader_location: 0 },
38                VertexAttribute { format: VertexFormat::Float32x2, offset: 12, shader_location: 1 },
39                VertexAttribute { format: VertexFormat::Float32x3, offset: 20, shader_location: 2 },
40                VertexAttribute { format: VertexFormat::Float32x4, offset: 32, shader_location: 3 },
41            ],
42        }
43    }
44}
45
46/// A per-instance vertex format carrying just a model matrix, for instanced
47/// draws — bind alongside a regular vertex buffer at [`VertexStepMode::Instance`].
48#[repr(C)]
49#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
50pub struct InstanceVertex {
51    pub model: glam::Mat4,
52}
53
54impl InstanceVertex {
55    pub fn new(model: glam::Mat4) -> Self {
56        Self { model }
57    }
58
59    pub fn layout() -> VertexBufferLayout {
60        VertexBufferLayout {
61            array_stride: std::mem::size_of::<InstanceVertex>() as u64,
62            step_mode: VertexStepMode::Instance,
63            attributes: vec![
64                VertexAttribute { format: VertexFormat::Float32x4, offset: 0, shader_location: 4 },
65                VertexAttribute { format: VertexFormat::Float32x4, offset: 16, shader_location: 5 },
66                VertexAttribute { format: VertexFormat::Float32x4, offset: 32, shader_location: 6 },
67                VertexAttribute { format: VertexFormat::Float32x4, offset: 48, shader_location: 7 },
68            ],
69        }
70    }
71}
72
73/// A vertex + index buffer asset, generic over vertex type (defaults to the
74/// built-in [`Vertex`]). Construct with `new()`, then [`build_asset`](Self::build_asset).
75pub struct Mesh<V: bytemuck::Pod = Vertex> {
76    vertices: Option<Vec<V>>,
77    indices: Option<Vec<u32>>,
78}
79
80impl<V: bytemuck::Pod> Mesh<V> {
81    pub fn new(vertices: Vec<V>, indices: Vec<u32>) -> Self {
82        Self { vertices: Some(vertices), indices: Some(indices) }
83    }
84
85    fn validate(&self) {
86        if self.vertices.as_ref().is_none_or(|v| v.is_empty()) {
87            tracing::warn!("Mesh::new(): no vertices — did you forget to pass them?");
88        }
89        if self.indices.as_ref().is_none_or(|i| i.is_empty()) {
90            tracing::warn!("Mesh::new(): no indices — did you forget to pass them?");
91        }
92    }
93
94    pub fn build_asset(self, name: &str, assets: &mut Assets<Mesh<V>>) -> Handle<Mesh<V>> {
95        self.validate();
96        assets.insert(name, self)
97    }
98
99    /// CPU-side vertices — e.g. for building a collision mesh from the same
100    /// source data used to upload the GPU buffer. `None` once
101    /// [`release_cpu_data`](Self::release_cpu_data) has been called.
102    pub fn vertices(&self) -> Option<&[V]> {
103        self.vertices.as_deref()
104    }
105
106    pub fn indices(&self) -> Option<&[u32]> {
107        self.indices.as_deref()
108    }
109
110    /// Frees the CPU-side copy once you've read what you needed from
111    /// [`vertices`](Self::vertices)/[`indices`](Self::indices). Unlike other
112    /// asset types, a released mesh can never be re-uploaded — if the GPU
113    /// backend is lost and recreated afterward, this mesh logs an error and
114    /// stays not-ready permanently. Only call this if that's acceptable.
115    pub fn release_cpu_data(&mut self) {
116        self.vertices = None;
117        self.indices = None;
118    }
119}
120
121/// The GPU-resident buffers an uploaded [`Mesh`] produces.
122pub struct GPUMesh {
123    pub vertex_buffer: Buffer,
124    pub index_buffer: Buffer,
125    pub index_count: u32,
126}
127
128impl<V: bytemuck::Pod + 'static> AssetSource for Mesh<V> {
129    type Processed = GPUMesh;
130}
131
132impl<V: bytemuck::Pod + 'static> Asset<Backend> for Mesh<V> {
133    type Deps<'a> = ();
134
135    fn upload<'a>(&self, backend: &Backend, _deps: &()) -> Option<GPUMesh> {
136        let (Some(vertices), Some(indices)) = (&self.vertices, &self.indices) else {
137            tracing::error!(
138                "Mesh::upload: CPU vertex/index data was released via release_cpu_data() and \
139                 the GPU resource needs to be (re)built — this mesh can never become ready"
140            );
141            return None;
142        };
143
144        let vertex_buffer = BufferBuilder::with_data(bytemuck::cast_slice(vertices.as_slice()))
145            .with_label("Mesh Vertex Buffer")
146            .with_usage(BufferUsages::VERTEX)
147            .build(backend);
148        let index_buffer = BufferBuilder::with_data(bytemuck::cast_slice(indices))
149            .with_label("Mesh Index Buffer")
150            .with_usage(BufferUsages::INDEX)
151            .build(backend);
152        Some(GPUMesh {
153            vertex_buffer,
154            index_buffer,
155            index_count: indices.len() as u32,
156        })
157    }
158}