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