Skip to main content

radiant_rs/core/
program.rs

1use crate::prelude::*;
2use crate::core::{Context, AsUniform, UniformList, Color};
3use crate::core::math::*;
4use crate::backends::backend;
5
6const SPRITE_INC: &'static str = include_str!("../shader/sprite.inc.wgsl");
7const TEXTURE_INC: &'static str = include_str!("../shader/texture.inc.wgsl");
8
9/// A shader program and its uniforms.
10///
11/// Cloning a program creates a new program, referencing the internal shaders
12/// of the source program but using its own copy of the uniforms.
13#[derive(Clone)]
14pub struct Program {
15    pub uniforms: UniformList,
16    pub(crate) sprite_program: Option<Arc<backend::Program>>,
17    pub(crate) texture_program: Arc<backend::Program>,
18}
19
20impl Debug for Program {
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        f.debug_struct("Program")
23            .field("uniforms", &self.uniforms)
24            .finish()
25    }
26}
27
28impl Program {
29    /// Creates a program from a fragment shader file.
30    pub fn from_file(context: &Context, file: &str) -> crate::core::Result<Self> {
31        use std::io::Read;
32        let mut source = String::new();
33        let mut f = File::open(file)?;
34        f.read_to_string(&mut source)?;
35        Self::from_string(context, &source)
36    }
37    /// Creates a program from a WGSL fragment shader string.
38    ///
39    /// If the shader does not already declare its own bindings (`@group(0) @binding`),
40    /// the engine automatically prepends the appropriate preamble, which provides
41    /// `sheet()`, `sheetSize()`, `sheetComponent()`, and the input struct.
42    ///
43    /// For texture shaders (postprocessors, fills): write `@fragment fn main(input: TextureFragmentInput)`.
44    /// For sprite shaders (layers): write `@fragment fn main(input: SpriteFragmentInput)`.
45    pub fn from_string(context: &Context, source: &str) -> crate::core::Result<Self> {
46        Self::new(context, source)
47    }
48    /// Sets a uniform value by name.
49    pub fn set_uniform<T>(self: &mut Self, name: &str, value: &T) where T: AsUniform {
50        self.uniforms.insert(name, value.as_uniform());
51    }
52    /// Removes a uniform value by name.
53    pub fn remove_uniform<T>(self: &mut Self, name: &str) -> bool {
54        self.uniforms.remove(name)
55    }
56    /// Creates a new program from user-provided WGSL source.
57    pub(crate) fn new(context: &Context, source: &str) -> crate::core::Result<Program> {
58        let mut uniforms = UniformList::new();
59        uniforms.insert("u_view", Mat4::viewport(1.0, 1.0).as_uniform());
60        uniforms.insert("u_model", Mat4::<f32>::identity().as_uniform());
61        uniforms.insert("_rd_color", Color::WHITE.as_uniform());
62        let context = context.lock();
63        let backend_context = context.backend_context.as_ref().unwrap();
64
65        // Prepend preamble unless the shader is self-contained (already declares bindings).
66        let combined = if source.contains("@group(0) @binding") {
67            source.to_string()
68        } else if source.contains("SpriteFragmentInput") {
69            format!("{}\n{}", SPRITE_INC, source)
70        } else {
71            format!("{}\n{}", TEXTURE_INC, source)
72        };
73
74        let prog = Arc::new(backend::Program::new(backend_context, &combined)?);
75        let (sprite_program, texture_program) = if matches!(prog.kind, backend::ProgramKind::Sprite) {
76            let default_tex = Arc::new(backend::Program::new_default_texture(backend_context)?);
77            (Some(prog), default_tex)
78        } else {
79            (None, prog)
80        };
81
82        Ok(Program { uniforms, sprite_program, texture_program })
83    }
84    /// Creates the built-in default program (no custom effect).
85    pub(crate) fn new_default(context: &Context) -> crate::core::Result<Program> {
86        let mut uniforms = UniformList::new();
87        uniforms.insert("u_view", Mat4::viewport(1.0, 1.0).as_uniform());
88        uniforms.insert("u_model", Mat4::<f32>::identity().as_uniform());
89        uniforms.insert("_rd_color", Color::WHITE.as_uniform());
90        let context = context.lock();
91        let backend_context = context.backend_context.as_ref().unwrap();
92        let texture_program = Arc::new(backend::Program::new_default_texture(backend_context)?);
93        Ok(Program { uniforms, sprite_program: None, texture_program })
94    }
95}