onedrop_renderer/
per_vertex_pipeline.rs

1//! Per-vertex shader execution pipeline
2//!
3//! Executes per-vertex equations on the GPU using dynamically compiled shaders.
4
5use crate::error::{RenderError, Result};
6use wgpu::util::DeviceExt;
7
8/// Per-vertex variables uniform buffer
9#[repr(C)]
10#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
11pub struct VertexVarsUniform {
12    /// Time in seconds
13    pub time: f32,
14    /// Frame number
15    pub frame: f32,
16    /// Frames per second
17    pub fps: f32,
18    /// Bass level (0.0-1.0)
19    pub bass: f32,
20
21    /// Mid level (0.0-1.0)
22    pub mid: f32,
23    /// Treble level (0.0-1.0)
24    pub treb: f32,
25    /// Bass attenuated
26    pub bass_att: f32,
27    /// Mid attenuated
28    pub mid_att: f32,
29
30    /// Treble attenuated
31    pub treb_att: f32,
32    /// Padding for alignment
33    pub _padding1: f32,
34    pub _padding2: f32,
35    pub _padding3: f32,
36
37    /// Custom variables q1-q64 (as 16 vec4s for GPU alignment)
38    pub q: [[f32; 4]; 16],
39}
40
41impl Default for VertexVarsUniform {
42    fn default() -> Self {
43        Self {
44            time: 0.0,
45            frame: 0.0,
46            fps: 60.0,
47            bass: 0.0,
48            mid: 0.0,
49            treb: 0.0,
50            bass_att: 0.0,
51            mid_att: 0.0,
52            treb_att: 0.0,
53            _padding1: 0.0,
54            _padding2: 0.0,
55            _padding3: 0.0,
56            q: [[0.0; 4]; 16],
57        }
58    }
59}
60
61/// Per-vertex shader execution pipeline
62pub struct PerVertexPipeline {
63    device: wgpu::Device,
64    queue: wgpu::Queue,
65
66    /// Uniform buffer for vertex variables
67    uniform_buffer: wgpu::Buffer,
68
69    /// Bind group layout
70    bind_group_layout: wgpu::BindGroupLayout,
71
72    /// Bind group
73    bind_group: Option<wgpu::BindGroup>,
74
75    /// Render pipeline
76    render_pipeline: Option<wgpu::RenderPipeline>,
77
78    /// Vertex buffer
79    vertex_buffer: wgpu::Buffer,
80
81    /// Index buffer
82    index_buffer: wgpu::Buffer,
83
84    /// Number of indices
85    num_indices: u32,
86}
87
88impl PerVertexPipeline {
89    /// Create a new per-vertex pipeline
90    pub fn new(device: wgpu::Device, queue: wgpu::Queue, vertex_count: u32) -> Result<Self> {
91        // Create uniform buffer
92        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
93            label: Some("Vertex Vars Uniform Buffer"),
94            size: std::mem::size_of::<VertexVarsUniform>() as u64,
95            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
96            mapped_at_creation: false,
97        });
98
99        // Create bind group layout
100        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
101            label: Some("Per-Vertex Bind Group Layout"),
102            entries: &[
103                // Uniform buffer
104                wgpu::BindGroupLayoutEntry {
105                    binding: 0,
106                    visibility: wgpu::ShaderStages::VERTEX,
107                    ty: wgpu::BindingType::Buffer {
108                        ty: wgpu::BufferBindingType::Uniform,
109                        has_dynamic_offset: false,
110                        min_binding_size: None,
111                    },
112                    count: None,
113                },
114            ],
115        });
116
117        // Create vertex buffer (for waveform points)
118        let vertices = Self::create_waveform_vertices(vertex_count);
119        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
120            label: Some("Vertex Buffer"),
121            contents: bytemuck::cast_slice(&vertices),
122            usage: wgpu::BufferUsages::VERTEX,
123        });
124
125        // Create index buffer
126        let indices = Self::create_waveform_indices(vertex_count);
127        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
128            label: Some("Index Buffer"),
129            contents: bytemuck::cast_slice(&indices),
130            usage: wgpu::BufferUsages::INDEX,
131        });
132
133        Ok(Self {
134            device,
135            queue,
136            uniform_buffer,
137            bind_group_layout,
138            bind_group: None,
139            render_pipeline: None,
140            vertex_buffer,
141            index_buffer,
142            num_indices: indices.len() as u32,
143        })
144    }
145
146    /// Create waveform vertices
147    fn create_waveform_vertices(count: u32) -> Vec<[f32; 3]> {
148        let mut vertices = Vec::with_capacity(count as usize);
149
150        for i in 0..count {
151            let t = i as f32 / (count - 1) as f32;
152            let x = t * 2.0 - 1.0; // -1 to 1
153            let y = 0.0;
154            let z = 0.0;
155            vertices.push([x, y, z]);
156        }
157
158        vertices
159    }
160
161    /// Create waveform indices (line strip)
162    fn create_waveform_indices(count: u32) -> Vec<u16> {
163        let mut indices = Vec::with_capacity((count * 2) as usize);
164
165        for i in 0..(count - 1) {
166            indices.push(i as u16);
167            indices.push((i + 1) as u16);
168        }
169
170        indices
171    }
172
173    /// Set the shader for this pipeline
174    pub fn set_shader(&mut self, shader_module: &wgpu::ShaderModule) -> Result<()> {
175        // Create bind group
176        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
177            label: Some("Per-Vertex Bind Group"),
178            layout: &self.bind_group_layout,
179            entries: &[wgpu::BindGroupEntry {
180                binding: 0,
181                resource: self.uniform_buffer.as_entire_binding(),
182            }],
183        });
184
185        // Create pipeline layout
186        let pipeline_layout = self
187            .device
188            .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
189                label: Some("Per-Vertex Pipeline Layout"),
190                bind_group_layouts: &[Some(&self.bind_group_layout)],
191                immediate_size: 0,
192            });
193
194        // Create render pipeline
195        let render_pipeline = self
196            .device
197            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
198                label: Some("Per-Vertex Render Pipeline"),
199                layout: Some(&pipeline_layout),
200                vertex: wgpu::VertexState {
201                    module: shader_module,
202                    entry_point: Some("vs_main"),
203                    buffers: &[wgpu::VertexBufferLayout {
204                        array_stride: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
205                        step_mode: wgpu::VertexStepMode::Vertex,
206                        attributes: &[wgpu::VertexAttribute {
207                            offset: 0,
208                            shader_location: 0,
209                            format: wgpu::VertexFormat::Float32x3,
210                        }],
211                    }],
212                    compilation_options: wgpu::PipelineCompilationOptions::default(),
213                },
214                fragment: Some(wgpu::FragmentState {
215                    module: shader_module,
216                    entry_point: Some("fs_main"),
217                    targets: &[Some(wgpu::ColorTargetState {
218                        format: wgpu::TextureFormat::Rgba8Unorm,
219                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
220                        write_mask: wgpu::ColorWrites::ALL,
221                    })],
222                    compilation_options: wgpu::PipelineCompilationOptions::default(),
223                }),
224                primitive: wgpu::PrimitiveState {
225                    topology: wgpu::PrimitiveTopology::LineList,
226                    strip_index_format: None,
227                    front_face: wgpu::FrontFace::Ccw,
228                    cull_mode: None,
229                    unclipped_depth: false,
230                    polygon_mode: wgpu::PolygonMode::Fill,
231                    conservative: false,
232                },
233                depth_stencil: None,
234                multisample: wgpu::MultisampleState {
235                    count: 1,
236                    mask: !0,
237                    alpha_to_coverage_enabled: false,
238                },
239                multiview_mask: None,
240                cache: None,
241            });
242
243        self.bind_group = Some(bind_group);
244        self.render_pipeline = Some(render_pipeline);
245
246        Ok(())
247    }
248
249    /// Update vertex variables
250    pub fn update_vars(&mut self, vars: VertexVarsUniform) {
251        self.queue
252            .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[vars]));
253    }
254
255    /// Render per-vertex effects
256    pub fn render(&mut self, output_view: &wgpu::TextureView) -> Result<()> {
257        let pipeline = self
258            .render_pipeline
259            .as_ref()
260            .ok_or_else(|| RenderError::RenderFailed("No shader set".to_string()))?;
261
262        let bind_group = self
263            .bind_group
264            .as_ref()
265            .ok_or_else(|| RenderError::RenderFailed("No bind group".to_string()))?;
266
267        let mut encoder = self
268            .device
269            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
270                label: Some("Per-Vertex Render Encoder"),
271            });
272
273        {
274            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
275                label: Some("Per-Vertex Render Pass"),
276                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
277                    view: output_view,
278                    depth_slice: None,
279                    resolve_target: None,
280                    ops: wgpu::Operations {
281                        load: wgpu::LoadOp::Load,
282                        store: wgpu::StoreOp::Store,
283                    },
284                })],
285                depth_stencil_attachment: None,
286                timestamp_writes: None,
287                occlusion_query_set: None,
288                multiview_mask: None,
289            });
290
291            render_pass.set_pipeline(pipeline);
292            render_pass.set_bind_group(0, bind_group, &[]);
293            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
294            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
295            render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
296        }
297
298        self.queue.submit(std::iter::once(encoder.finish()));
299
300        Ok(())
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_vertex_vars_size() {
310        // Verify struct size is correct (304 bytes)
311        assert_eq!(std::mem::size_of::<VertexVarsUniform>(), 304);
312    }
313
314    #[test]
315    fn test_vertex_vars_default() {
316        let vars = VertexVarsUniform::default();
317        assert_eq!(vars.fps, 60.0);
318        assert_eq!(vars.time, 0.0);
319    }
320
321    #[test]
322    fn test_waveform_vertices() {
323        let vertices = PerVertexPipeline::create_waveform_vertices(100);
324        assert_eq!(vertices.len(), 100);
325        assert_eq!(vertices[0][0], -1.0); // First vertex at x=-1
326        assert_eq!(vertices[99][0], 1.0); // Last vertex at x=1
327    }
328
329    #[test]
330    fn test_waveform_indices() {
331        let indices = PerVertexPipeline::create_waveform_indices(100);
332        assert_eq!(indices.len(), 198); // (100-1) * 2
333    }
334}