Skip to main content

pebble/wgpu/
mesh.rs

1use crate::{assets::upload::Asset, wgpu::backend::WGPUBackend};
2
3/// Standard per-vertex data: position, UV, normal, and tangent (`w` is the
4/// bitangent handedness sign, ±1 — cross `normal` with `tangent.xyz` and
5/// scale by `tangent.w` to get the bitangent).
6///
7/// Occupies vertex buffer locations 0–3 — [`InstanceVertex::layout`]
8/// deliberately starts at location 4 to leave room for this, so pairing
9/// them in the same pipeline doesn't collide. Adding a 5th attribute here
10/// would need a matching shift there.
11#[repr(C)]
12#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
13pub struct Vertex {
14    pub position: glam::Vec3,
15    pub tex_coords: glam::Vec2,
16    pub normal: glam::Vec3,
17    pub tangent: glam::Vec4,
18}
19
20impl Vertex {
21    pub fn new(
22        position: glam::Vec3,
23        tex_coords: glam::Vec2,
24        normal: glam::Vec3,
25        tangent: glam::Vec4,
26    ) -> Self {
27        Self {
28            position,
29            tex_coords,
30            normal,
31            tangent,
32        }
33    }
34
35    pub fn layout() -> wgpu::VertexBufferLayout<'static> {
36        const ATTRS: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![
37            0 => Float32x3,  // position
38            1 => Float32x2,  // tex_coords
39            2 => Float32x3,  // normal
40            3 => Float32x4,  // tangent
41        ];
42        wgpu::VertexBufferLayout {
43            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
44            step_mode: wgpu::VertexStepMode::Vertex,
45            attributes: ATTRS,
46        }
47    }
48}
49
50/// Per-instance vertex data. Carries a model matrix as four `Vec4` columns,
51/// passed as vertex attributes with `step_mode: Instance`.
52#[repr(C)]
53#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
54pub struct InstanceVertex {
55    pub model: glam::Mat4,
56}
57
58impl InstanceVertex {
59    pub fn new(model: glam::Mat4) -> Self {
60        Self { model }
61    }
62
63    pub fn layout() -> wgpu::VertexBufferLayout<'static> {
64        const ATTRS: &[wgpu::VertexAttribute] = &wgpu::vertex_attr_array![
65            4 => Float32x4,  // model col 0
66            5 => Float32x4,  // model col 1
67            6 => Float32x4,  // model col 2
68            7 => Float32x4,  // model col 3
69        ];
70        wgpu::VertexBufferLayout {
71            array_stride: std::mem::size_of::<InstanceVertex>() as wgpu::BufferAddress,
72            step_mode: wgpu::VertexStepMode::Instance,
73            attributes: ATTRS,
74        }
75    }
76}
77
78/// Source data for [`GPUMesh`]: a plain vertex/index list, uploaded as-is.
79pub struct MeshDescriptor {
80    pub vertices: Vec<Vertex>,
81    pub indices: Vec<u32>,
82}
83
84/// A mesh uploaded to the GPU. `index_buffer`/`index_count` must stay in
85/// sync if you ever mutate one after construction — there's no invariant
86/// check, so a mismatched pair silently draws garbage or the wrong index
87/// range.
88pub struct GPUMesh {
89    pub vertex_buffer: wgpu::Buffer,
90    pub index_buffer: wgpu::Buffer,
91    pub index_count: u32,
92}
93
94impl Asset<WGPUBackend> for GPUMesh {
95    type Source = MeshDescriptor;
96    type Deps<'a> = ();
97
98    fn upload<'a>(source: &MeshDescriptor, backend: &WGPUBackend, _deps: &()) -> Option<Self> {
99        use wgpu::util::DeviceExt;
100        let vertex_buffer = backend
101            .device
102            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
103                label: Some("Mesh Vertex Buffer"),
104                contents: bytemuck::cast_slice(source.vertices.as_slice()),
105                usage: wgpu::BufferUsages::VERTEX,
106            });
107        let index_buffer = backend
108            .device
109            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
110                label: Some("Mesh Index Buffer"),
111                contents: bytemuck::cast_slice(&source.indices),
112                usage: wgpu::BufferUsages::INDEX,
113            });
114        Some(Self {
115            vertex_buffer,
116            index_buffer,
117            index_count: source.indices.len() as u32,
118        })
119    }
120}
121
122crate::wgpu::plugin_macros::asset_plugin! {
123    /// Registers the [`GPUMesh`] asset pipeline (`Assets<MeshDescriptor>` →
124    /// `ProcessedAssets<GPUMesh>`). Included by
125    /// [`WGPUPlugin`](super::backend::WGPUPlugin); add directly only if you're
126    /// assembling the `wgpu` module's plugins by hand.
127    MeshPlugin, GPUMesh
128}