Skip to main content

polyscope_render/
shader.rs

1//! Shader management.
2
3use crate::error::{RenderError, RenderResult};
4
5/// A compiled shader program.
6pub struct ShaderProgram {
7    /// The render pipeline.
8    pub pipeline: wgpu::RenderPipeline,
9    /// Bind group layouts.
10    pub bind_group_layouts: Vec<wgpu::BindGroupLayout>,
11}
12
13/// Builder for creating shader programs.
14pub struct ShaderBuilder {
15    vertex_source: Option<String>,
16    fragment_source: Option<String>,
17    vertex_entry: String,
18    fragment_entry: String,
19    label: Option<String>,
20}
21
22impl ShaderBuilder {
23    /// Creates a new shader builder.
24    #[must_use]
25    pub fn new() -> Self {
26        Self {
27            vertex_source: None,
28            fragment_source: None,
29            vertex_entry: "vs_main".to_string(),
30            fragment_entry: "fs_main".to_string(),
31            label: None,
32        }
33    }
34
35    /// Sets the vertex shader source (WGSL).
36    #[must_use]
37    pub fn with_vertex(mut self, source: impl Into<String>) -> Self {
38        self.vertex_source = Some(source.into());
39        self
40    }
41
42    /// Sets the fragment shader source (WGSL).
43    #[must_use]
44    pub fn with_fragment(mut self, source: impl Into<String>) -> Self {
45        self.fragment_source = Some(source.into());
46        self
47    }
48
49    /// Sets the vertex shader entry point.
50    #[must_use]
51    pub fn with_vertex_entry(mut self, entry: impl Into<String>) -> Self {
52        self.vertex_entry = entry.into();
53        self
54    }
55
56    /// Sets the fragment shader entry point.
57    #[must_use]
58    pub fn with_fragment_entry(mut self, entry: impl Into<String>) -> Self {
59        self.fragment_entry = entry.into();
60        self
61    }
62
63    /// Sets the shader label for debugging.
64    #[must_use]
65    pub fn with_label(mut self, label: impl Into<String>) -> Self {
66        self.label = Some(label.into());
67        self
68    }
69
70    /// Builds the shader module (does not create pipeline).
71    pub fn build_module(self, device: &wgpu::Device) -> RenderResult<wgpu::ShaderModule> {
72        let source = self.combined_source()?;
73
74        let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
75            label: self.label.as_deref(),
76            source: wgpu::ShaderSource::Wgsl(source.into()),
77        });
78
79        Ok(module)
80    }
81
82    fn combined_source(&self) -> RenderResult<String> {
83        let vertex = self
84            .vertex_source
85            .as_ref()
86            .ok_or_else(|| RenderError::ShaderCompilationFailed("missing vertex shader".into()))?;
87
88        let fragment = self.fragment_source.as_ref().ok_or_else(|| {
89            RenderError::ShaderCompilationFailed("missing fragment shader".into())
90        })?;
91
92        // If sources are the same file, just return one
93        if vertex == fragment {
94            return Ok(vertex.clone());
95        }
96
97        // Otherwise combine them
98        Ok(format!("{vertex}\n\n{fragment}"))
99    }
100}
101
102impl Default for ShaderBuilder {
103    fn default() -> Self {
104        Self::new()
105    }
106}