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 pub textures: Vec<(Texture, u32, i32, i32)>,
113 pub blending: Option<(u32, u32)>,
115 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}