spitfire_glow/
renderer.rs

1use bytemuck::{checked::cast_slice, Pod};
2use glow::{
3    Buffer, Context, HasContext, Program, Texture, VertexArray, ARRAY_BUFFER, BLEND, DST_COLOR,
4    ELEMENT_ARRAY_BUFFER, FLOAT, INT, LINEAR, NEAREST, ONE, ONE_MINUS_SRC_ALPHA, RGB, RGBA,
5    RGBA16F, RGBA32F, SCISSOR_TEST, SRC_ALPHA, STREAM_DRAW, TEXTURE0, TEXTURE_2D_ARRAY,
6    TEXTURE_MAG_FILTER, TEXTURE_MIN_FILTER, TRIANGLES, UNSIGNED_INT, ZERO,
7};
8use spitfire_core::{Triangle, VertexStream, VertexStreamRenderer};
9use std::{borrow::Cow, collections::HashMap, marker::PhantomData, ops::Range};
10
11#[derive(Clone, Copy)]
12pub enum GlowVertexAttrib {
13    Float { channels: u8, normalized: bool },
14    Integer { channels: u8 },
15}
16
17impl GlowVertexAttrib {
18    pub fn channels(&self) -> u8 {
19        match self {
20            Self::Float { channels, .. } => *channels,
21            Self::Integer { channels } => *channels,
22        }
23    }
24}
25
26pub trait GlowVertexAttribs: Pod {
27    const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)];
28}
29
30#[derive(Debug, Copy, Clone, PartialEq)]
31pub enum GlowUniformValue {
32    F1(f32),
33    F2([f32; 2]),
34    F3([f32; 3]),
35    F4([f32; 4]),
36    M2([f32; 4]),
37    M3([f32; 9]),
38    M4([f32; 16]),
39    I1(i32),
40    I2([i32; 2]),
41    I3([i32; 3]),
42    I4([i32; 4]),
43}
44
45#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
46pub enum GlowBlending {
47    #[default]
48    None,
49    Alpha,
50    Multiply,
51    Additive,
52}
53
54impl GlowBlending {
55    pub fn into_gl(self) -> Option<(u32, u32)> {
56        match self {
57            Self::None => None,
58            Self::Alpha => Some((SRC_ALPHA, ONE_MINUS_SRC_ALPHA)),
59            Self::Multiply => Some((DST_COLOR, ZERO)),
60            Self::Additive => Some((ONE, ONE)),
61        }
62    }
63}
64
65#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
66pub enum GlowTextureFiltering {
67    #[default]
68    Nearest,
69    Linear,
70}
71
72impl GlowTextureFiltering {
73    pub fn into_gl(self) -> (i32, i32) {
74        match self {
75            Self::Nearest => (NEAREST as _, NEAREST as _),
76            Self::Linear => (LINEAR as _, LINEAR as _),
77        }
78    }
79}
80
81#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
82pub enum GlowTextureFormat {
83    #[default]
84    Rgba,
85    Rgb,
86    Monochromatic,
87    Data16,
88    Data32,
89}
90
91impl GlowTextureFormat {
92    pub fn into_gl(self) -> u32 {
93        match self {
94            Self::Rgba => RGBA,
95            Self::Rgb => RGB,
96            #[cfg(not(target_arch = "wasm32"))]
97            Self::Monochromatic => glow::RED,
98            #[cfg(target_arch = "wasm32")]
99            Self::Monochromatic => glow::LUMINANCE,
100            Self::Data16 => RGBA16F,
101            Self::Data32 => RGBA32F,
102        }
103    }
104}
105
106#[derive(Debug, Default, Clone, PartialEq)]
107pub struct GlowBatch {
108    pub shader_program: Option<Program>,
109    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
110    /// [(texture object, texture target, min filter, mag filter)?]
111    pub textures: Vec<(Texture, u32, i32, i32)>,
112    /// (source, destination)?
113    pub blending: Option<(u32, u32)>,
114    /// [x, y, width, height]?
115    pub scissor: Option<[i32; 4]>,
116}
117
118impl GlowBatch {
119    pub fn draw<V: GlowVertexAttribs>(&self, context: &Context, range: Range<usize>, prev: &Self) {
120        unsafe {
121            if let Some(program) = self.shader_program {
122                let changed = prev
123                    .shader_program
124                    .map(|program_prev| program != program_prev)
125                    .unwrap_or(true);
126                context.use_program(Some(program));
127                for (name, value) in &self.uniforms {
128                    if changed
129                        || prev
130                            .uniforms
131                            .get(name)
132                            .map(|v| value != v)
133                            .unwrap_or_default()
134                    {
135                        let location = context.get_uniform_location(program, name.as_ref());
136                        if let Some(location) = location {
137                            match value {
138                                GlowUniformValue::F1(value) => {
139                                    context.uniform_1_f32(Some(&location), *value);
140                                }
141                                GlowUniformValue::F2(value) => {
142                                    context.uniform_2_f32_slice(Some(&location), value);
143                                }
144                                GlowUniformValue::F3(value) => {
145                                    context.uniform_3_f32_slice(Some(&location), value);
146                                }
147                                GlowUniformValue::F4(value) => {
148                                    context.uniform_4_f32_slice(Some(&location), value);
149                                }
150                                GlowUniformValue::M2(value) => {
151                                    context.uniform_matrix_2_f32_slice(
152                                        Some(&location),
153                                        false,
154                                        value,
155                                    );
156                                }
157                                GlowUniformValue::M3(value) => {
158                                    context.uniform_matrix_3_f32_slice(
159                                        Some(&location),
160                                        false,
161                                        value,
162                                    );
163                                }
164                                GlowUniformValue::M4(value) => {
165                                    context.uniform_matrix_4_f32_slice(
166                                        Some(&location),
167                                        false,
168                                        value,
169                                    );
170                                }
171                                GlowUniformValue::I1(value) => {
172                                    context.uniform_1_i32(Some(&location), *value);
173                                }
174                                GlowUniformValue::I2(value) => {
175                                    context.uniform_2_i32_slice(Some(&location), value);
176                                }
177                                GlowUniformValue::I3(value) => {
178                                    context.uniform_3_i32_slice(Some(&location), value);
179                                }
180                                GlowUniformValue::I4(value) => {
181                                    context.uniform_4_i32_slice(Some(&location), value);
182                                }
183                            }
184                        }
185                    }
186                }
187            }
188            for (index, data) in self.textures.iter().enumerate() {
189                context.active_texture(TEXTURE0 + index as u32);
190                let data_prev = prev.textures.get(index);
191                if data_prev.map(|prev| prev != data).unwrap_or(true) {
192                    let (texture, target, min_filter, mag_filter) = data;
193                    context.bind_texture(*target, Some(*texture));
194                    context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MIN_FILTER, *min_filter);
195                    context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MAG_FILTER, *mag_filter);
196                }
197            }
198            if self.blending != prev.blending {
199                if let Some((source, destination)) = self.blending {
200                    context.enable(BLEND);
201                    context.blend_func(source, destination);
202                } else {
203                    context.disable(BLEND);
204                }
205            }
206            if self.scissor != prev.scissor {
207                if let Some([x, y, w, h]) = self.scissor {
208                    context.enable(SCISSOR_TEST);
209                    context.scissor(x, y, w, h);
210                } else {
211                    context.disable(SCISSOR_TEST);
212                }
213            }
214            context.draw_elements(
215                TRIANGLES,
216                range.len() as i32 * 3,
217                UNSIGNED_INT,
218                (range.start * std::mem::size_of::<u32>() * 3) as i32,
219            );
220        }
221    }
222}
223
224#[derive(Copy, Clone)]
225struct GlowMesh {
226    vertex_array: VertexArray,
227    vertex_buffer: Buffer,
228    index_buffer: Buffer,
229}
230
231impl GlowMesh {
232    fn new(context: &Context) -> Result<Self, String> {
233        unsafe {
234            Ok(GlowMesh {
235                vertex_array: context.create_vertex_array()?,
236                vertex_buffer: context.create_buffer()?,
237                index_buffer: context.create_buffer()?,
238            })
239        }
240    }
241
242    fn dispose(self, context: &Context) {
243        unsafe {
244            context.delete_vertex_array(self.vertex_array);
245            context.delete_buffer(self.vertex_buffer);
246            context.delete_buffer(self.index_buffer);
247        }
248    }
249
250    fn upload<V: GlowVertexAttribs>(
251        &self,
252        context: &Context,
253        vertices: &[V],
254        triangles: &[Triangle],
255    ) {
256        unsafe {
257            context.bind_vertex_array(Some(self.vertex_array));
258            context.bind_buffer(ARRAY_BUFFER, Some(self.vertex_buffer));
259            context.buffer_data_u8_slice(ARRAY_BUFFER, cast_slice(vertices), STREAM_DRAW);
260            context.bind_buffer(ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
261            context.buffer_data_u8_slice(ELEMENT_ARRAY_BUFFER, cast_slice(triangles), STREAM_DRAW);
262            let mut offset = 0;
263            let stride = V::ATTRIBS
264                .iter()
265                .map(|(_, info)| info.channels() * 4)
266                .sum::<u8>();
267            for (location, (_, info)) in V::ATTRIBS.iter().enumerate() {
268                match info {
269                    GlowVertexAttrib::Float {
270                        channels,
271                        normalized,
272                    } => {
273                        context.vertex_attrib_pointer_f32(
274                            location as _,
275                            *channels as _,
276                            FLOAT,
277                            *normalized,
278                            stride as _,
279                            offset as _,
280                        );
281                    }
282                    GlowVertexAttrib::Integer { channels } => {
283                        context.vertex_attrib_pointer_i32(
284                            location as _,
285                            *channels as _,
286                            INT,
287                            stride as _,
288                            offset as _,
289                        );
290                    }
291                }
292                context.enable_vertex_attrib_array(location as _);
293                offset += info.channels() * 4;
294            }
295        }
296    }
297}
298
299#[derive(Default)]
300pub struct GlowState {
301    mesh: Option<GlowMesh>,
302}
303
304impl Drop for GlowState {
305    fn drop(&mut self) {
306        if self.mesh.is_some() {
307            panic!("Mesh was not disposed!");
308        }
309    }
310}
311
312impl GlowState {
313    pub fn dispose(&mut self, context: &Context) {
314        if let Some(mesh) = self.mesh.take() {
315            mesh.dispose(context)
316        }
317    }
318
319    fn mesh(&mut self, context: &Context) -> Result<GlowMesh, String> {
320        if let Some(mesh) = self.mesh.as_ref().copied() {
321            Ok(mesh)
322        } else {
323            self.mesh = Some(GlowMesh::new(context)?);
324            Ok(self.mesh.unwrap())
325        }
326    }
327}
328
329pub struct GlowRenderer<'a, B: Into<GlowBatch>> {
330    context: &'a Context,
331    state: &'a mut GlowState,
332    _phantom: PhantomData<fn() -> B>,
333}
334
335impl<'a, B> GlowRenderer<'a, B>
336where
337    B: Into<GlowBatch>,
338{
339    pub fn new(context: &'a Context, state: &'a mut GlowState) -> Self {
340        Self {
341            context,
342            state,
343            _phantom: Default::default(),
344        }
345    }
346}
347
348impl<V, B> VertexStreamRenderer<V, B> for GlowRenderer<'_, B>
349where
350    V: GlowVertexAttribs,
351    B: Into<GlowBatch> + Default + Clone,
352{
353    type Error = String;
354
355    fn render(&mut self, stream: &mut VertexStream<V, B>) -> Result<(), Self::Error> {
356        let mesh = self.state.mesh(self.context)?;
357        mesh.upload(self.context, stream.vertices(), stream.triangles());
358        let mut prev = GlowBatch::default();
359        for (batch, range) in stream.batches().iter().cloned() {
360            let batch = batch.into();
361            batch.draw::<V>(self.context, range, &prev);
362            prev = batch;
363        }
364        Ok(())
365    }
366}