spitfire_glow/
renderer.rs

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