wrend/uniforms/
uniform.rs

1use crate::Callback;
2use crate::Id;
3use crate::UniformContext;
4use crate::UniformCreateUpdateCallback;
5use crate::UniformJs;
6use crate::UniformJsInner;
7use crate::UniformShouldUpdateCallback;
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::hash::Hash;
11use wasm_bindgen::JsValue;
12use web_sys::{WebGl2RenderingContext, WebGlProgram, WebGlUniformLocation};
13
14/// Contains the build information for a WebGL uniform.
15///
16/// A [`Uniform`] can be associated with any number of programs,
17/// and can be updated with [crate::RendererData::update_uniform] or
18/// [crate::RendererData::update_uniforms].
19#[derive(Clone)]
20pub struct Uniform<ProgramId: Id, UniformId: Id> {
21    program_ids: Vec<ProgramId>,
22    uniform_id: UniformId,
23    uniform_locations: HashMap<ProgramId, WebGlUniformLocation>,
24    uniform_create_callback: UniformCreateUpdateCallback,
25    update_callback: Option<UniformCreateUpdateCallback>,
26    should_update_callback: Option<UniformShouldUpdateCallback>,
27    use_init_callback_for_update: bool,
28}
29
30impl<ProgramId: Id, UniformId: Id> Uniform<ProgramId, UniformId> {
31    // @todo move into builder pattern
32    pub(crate) fn new(
33        program_ids: Vec<ProgramId>,
34        uniform_id: UniformId,
35        // a single "conceptual" uniform can be shared across multiple programs and updated in tandem
36        uniform_locations: HashMap<ProgramId, WebGlUniformLocation>,
37        initialize_callback: UniformCreateUpdateCallback,
38        update_callback: Option<UniformCreateUpdateCallback>,
39        should_update_callback: Option<UniformShouldUpdateCallback>,
40        use_init_callback_for_update: bool,
41    ) -> Self {
42        Self {
43            program_ids,
44            uniform_id,
45            uniform_locations,
46            uniform_create_callback: initialize_callback,
47            update_callback,
48            should_update_callback,
49            use_init_callback_for_update,
50        }
51    }
52
53    /// Gets all program ids associated with this uniform
54    pub fn program_ids(&self) -> &Vec<ProgramId> {
55        &self.program_ids
56    }
57
58    /// Gets this uniform's uniform id
59    pub fn uniform_id(&self) -> &UniformId {
60        &self.uniform_id
61    }
62
63    /// Gets this uniform's location for all associated program ids
64    pub fn uniform_locations(&self) -> &HashMap<ProgramId, WebGlUniformLocation> {
65        &self.uniform_locations
66    }
67
68    /// Gets the callback that is used to initialize this uniform
69    pub fn initialize_callback(&self) -> UniformCreateUpdateCallback {
70        self.uniform_create_callback.clone()
71    }
72
73    /// Gets the callback that is used to determine whether this uniform should update when
74    /// [crate::RendererData::update_uniform] or [crate::RendererData::update_uniforms] is called.
75    pub fn should_update_callback(&self) -> Option<UniformShouldUpdateCallback> {
76        self.should_update_callback.as_ref().map(Clone::clone)
77    }
78
79    /// Gets the callback that is used to updated this uniform whenever
80    /// [crate::RendererData::update_uniform] or [crate::RendererData::update_uniforms] is called.
81    pub fn update_callback(&self) -> Option<UniformCreateUpdateCallback> {
82        self.update_callback.as_ref().map(Clone::clone)
83    }
84
85    /// If set to `true`, [Uniform] will use the [Uniform::initialize_callback] callback
86    /// to update when [crate::RendererData::update_uniform] or [crate::RendererData::update_uniforms] is called
87    /// rather than the [Uniform::update_callback]
88    pub fn use_init_callback_for_update(&self) -> bool {
89        self.use_init_callback_for_update
90    }
91
92    /// Updates the value of this uniform in WebGl for every Program where this uniform is used,
93    /// using the update callback that was passed in at creation time.
94    ///
95    /// @todo: calling this function for anything more than the current program is useless without a UBO
96    pub fn update(
97        &self,
98        gl: &WebGl2RenderingContext,
99        now: f64,
100        programs: &HashMap<ProgramId, WebGlProgram>,
101    ) {
102        let uniform_locations = self.uniform_locations();
103
104        for (program_id, uniform_location) in uniform_locations.iter() {
105            let program = programs
106                .get(program_id)
107                .expect("Program id should correspond to a saved WebGlProgram");
108
109            gl.use_program(Some(program));
110
111            let ctx = UniformContext::new(gl.clone(), now, uniform_location.clone());
112            let should_update_callback = self.should_update_callback();
113
114            let should_call = if let Some(should_update_callback) = should_update_callback {
115                match &*should_update_callback {
116                    Callback::Rust(rust_callback) => (rust_callback)(&ctx),
117                    Callback::Js(js_callback) => {
118                        JsValue::as_bool(&js_callback.call0(&JsValue::NULL).expect(
119                            "Should be able to call `should_update_callback` JavaScript callback",
120                        ))
121                        .unwrap_or(false)
122                    }
123                }
124            } else {
125                // by default, assume that all uniforms should be updated, since uniforms should
126                // only be updated if no custom optimization callback is provided
127                true
128            };
129
130            if should_call {
131                if self.use_init_callback_for_update {
132                    self.uniform_create_callback.call_with_into_js_arg(&ctx);
133                } else if let Some(update_callback) = &self.update_callback {
134                    update_callback.call_with_into_js_arg(&ctx)
135                }
136            }
137
138            gl.use_program(None);
139        }
140    }
141}
142
143impl<ProgramId: Id, UniformId: Id> Debug for Uniform<ProgramId, UniformId> {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.debug_struct("Uniform")
146            .field("id", &self.uniform_id)
147            .field("uniform_locations", &self.uniform_locations)
148            .finish()
149    }
150}
151impl<ProgramId: Id, UniformId: Id> Hash for Uniform<ProgramId, UniformId> {
152    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
153        self.uniform_id.hash(state);
154    }
155}
156
157impl<ProgramId: Id, UniformId: Id> PartialEq for Uniform<ProgramId, UniformId> {
158    fn eq(&self, other: &Self) -> bool {
159        self.uniform_id == other.uniform_id && self.uniform_locations == other.uniform_locations
160    }
161}
162
163impl<ProgramId: Id, UniformId: Id> Eq for Uniform<ProgramId, UniformId> {}
164
165impl From<UniformJsInner> for JsValue {
166    fn from(uniform: UniformJsInner) -> Self {
167        let js_uniform: UniformJs = uniform.into();
168        js_uniform.into()
169    }
170}
171
172impl From<UniformJs> for UniformJsInner {
173    fn from(js_uniform: UniformJs) -> Self {
174        js_uniform.into_inner()
175    }
176}