spitfire_glow/
graphics.rs

1use crate::renderer::{
2    GlowBatch, GlowBlending, GlowRenderer, GlowState, GlowTextureFiltering, GlowTextureFormat,
3    GlowUniformValue, GlowVertexAttrib, GlowVertexAttribs,
4};
5use bytemuck::{Pod, Zeroable};
6use glow::{
7    BLEND, CLAMP_TO_EDGE, COLOR_ATTACHMENT0, COLOR_BUFFER_BIT, Context, FILL, FRAGMENT_SHADER,
8    FRAMEBUFFER, FRONT_AND_BACK, Framebuffer as GlowFrameBuffer, HasContext, NEAREST,
9    PixelUnpackData, Program as GlowProgram, SCISSOR_TEST, Shader as GlowShader, TEXTURE_2D_ARRAY,
10    TEXTURE_MAG_FILTER, TEXTURE_MIN_FILTER, TEXTURE_WRAP_R, TEXTURE_WRAP_S, TEXTURE_WRAP_T,
11    Texture as GlowTexture, UNSIGNED_BYTE, VERTEX_SHADER,
12};
13use spitfire_core::{VertexStream, VertexStreamRenderer};
14use std::{
15    borrow::Cow,
16    cell::{Cell, Ref, RefCell},
17    collections::HashMap,
18    rc::Rc,
19};
20use vek::{FrustumPlanes, Mat4, Rect, Transform, Vec2};
21
22#[derive(Debug, Copy, Clone, Pod, Zeroable)]
23#[repr(C)]
24pub struct Vertex3d {
25    pub position: [f32; 3],
26    pub normal: [f32; 3],
27    pub uv: [f32; 3],
28    pub color: [f32; 4],
29}
30
31impl GlowVertexAttribs for Vertex3d {
32    const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)] = &[
33        (
34            "a_position",
35            GlowVertexAttrib::Float {
36                channels: 3,
37                normalized: false,
38            },
39        ),
40        (
41            "a_normal",
42            GlowVertexAttrib::Float {
43                channels: 3,
44                normalized: false,
45            },
46        ),
47        (
48            "a_uv",
49            GlowVertexAttrib::Float {
50                channels: 3,
51                normalized: false,
52            },
53        ),
54        (
55            "a_color",
56            GlowVertexAttrib::Float {
57                channels: 4,
58                normalized: false,
59            },
60        ),
61    ];
62}
63
64impl Default for Vertex3d {
65    fn default() -> Self {
66        Self {
67            position: Default::default(),
68            normal: [0.0, 0.0, 1.0],
69            uv: Default::default(),
70            color: [1.0, 1.0, 1.0, 1.0],
71        }
72    }
73}
74
75#[derive(Debug, Clone)]
76pub struct MaybeContext(Rc<RefCell<(Context, bool)>>);
77
78impl MaybeContext {
79    pub fn get(&'_ self) -> Option<Ref<'_, Context>> {
80        let access = self.0.borrow();
81        if access.1 {
82            Some(Ref::map(access, |access| &access.0))
83        } else {
84            None
85        }
86    }
87}
88
89#[derive(Debug)]
90struct StrongContext(MaybeContext);
91
92impl Drop for StrongContext {
93    fn drop(&mut self) {
94        (self.0).0.borrow_mut().1 = false;
95    }
96}
97
98impl StrongContext {
99    fn get(&'_ self) -> Option<Ref<'_, Context>> {
100        self.0.get()
101    }
102
103    fn new(context: Context) -> Self {
104        Self(MaybeContext(Rc::new(RefCell::new((context, true)))))
105    }
106}
107
108pub trait GraphicsTarget<V: GlowVertexAttribs> {
109    fn state(&self) -> &GraphicsState<V>;
110    fn state_mut(&mut self) -> &mut GraphicsState<V>;
111}
112
113pub struct GraphicsState<V: GlowVertexAttribs> {
114    pub main_camera: Camera,
115    pub color: [f32; 4],
116    pub stream: VertexStream<V, GraphicsBatch>,
117}
118
119impl<V: GlowVertexAttribs> Default for GraphicsState<V> {
120    fn default() -> Self {
121        Self {
122            main_camera: Default::default(),
123            color: [1.0, 1.0, 1.0, 1.0],
124            stream: Default::default(),
125        }
126    }
127}
128
129impl<V: GlowVertexAttribs> Clone for GraphicsState<V> {
130    fn clone(&self) -> Self {
131        Self {
132            main_camera: self.main_camera,
133            color: self.color,
134            stream: self.stream.clone(),
135        }
136    }
137}
138
139impl<V: GlowVertexAttribs> GraphicsState<V> {
140    pub fn fork(&self) -> Self {
141        Self {
142            main_camera: self.main_camera,
143            color: self.color,
144            stream: self.stream.fork(),
145        }
146    }
147}
148
149impl<V: GlowVertexAttribs> GraphicsTarget<V> for GraphicsState<V> {
150    fn state(&self) -> &Self {
151        self
152    }
153
154    fn state_mut(&mut self) -> &mut Self {
155        self
156    }
157}
158
159pub struct Graphics<V: GlowVertexAttribs> {
160    pub state: GraphicsState<V>,
161    glow_state: GlowState,
162    context: StrongContext,
163    surface_stack: Vec<(Surface, Vec2<f32>, [f32; 4])>,
164}
165
166impl<V: GlowVertexAttribs> Drop for Graphics<V> {
167    fn drop(&mut self) {
168        if let Some(context) = self.context.get() {
169            self.glow_state.dispose(&context);
170        }
171    }
172}
173
174impl<V: GlowVertexAttribs> Graphics<V> {
175    pub fn new(context: Context) -> Self {
176        Self {
177            state: Default::default(),
178            glow_state: Default::default(),
179            context: StrongContext::new(context),
180            surface_stack: Default::default(),
181        }
182    }
183
184    pub fn context(&'_ self) -> Option<Ref<'_, Context>> {
185        self.context.get()
186    }
187
188    pub fn surface(&self, attachments: Vec<SurfaceAttachment>) -> Result<Surface, String> {
189        if attachments.is_empty() {
190            return Err("Surface must have at least one texture!".to_owned());
191        }
192        for (index, attachment) in attachments.iter().enumerate() {
193            if attachment.texture.depth() < attachment.layer as _ {
194                return Err(format!(
195                    "Surface texture #{} has layer: {} out of texture depth range: {}",
196                    index,
197                    attachment.layer,
198                    attachment.texture.depth()
199                ));
200            }
201        }
202        if let [first, rest @ ..] = attachments.as_slice() {
203            let width = first.texture.width();
204            let height = first.texture.height();
205            if rest
206                .iter()
207                .any(|item| item.texture.width() != width || item.texture.height() != height)
208            {
209                return Err(format!(
210                    "Some surface texture has different size than expected: {width} x {height}"
211                ));
212            }
213        }
214        unsafe {
215            if let Some(context) = self.context.get() {
216                let framebuffer = context.create_framebuffer()?;
217                context.bind_framebuffer(FRAMEBUFFER, Some(framebuffer));
218                for (index, attachment) in attachments.iter().enumerate() {
219                    context.framebuffer_texture_layer(
220                        FRAMEBUFFER,
221                        COLOR_ATTACHMENT0 + index as u32,
222                        Some(attachment.texture.handle()),
223                        0,
224                        attachment.layer as _,
225                    );
226                }
227                context.bind_framebuffer(FRAMEBUFFER, None);
228                Ok(Surface {
229                    inner: Rc::new(SurfaceInner {
230                        context: self.context.0.clone(),
231                        framebuffer,
232                        attachments,
233                        color: Default::default(),
234                    }),
235                })
236            } else {
237                Err("Invalid context".to_owned())
238            }
239        }
240    }
241
242    pub fn pixel_texture(&self, color: [u8; 3]) -> Result<Texture, String> {
243        self.texture(1, 1, 1, GlowTextureFormat::Rgb, Some(&color))
244    }
245
246    pub fn texture(
247        &self,
248        width: u32,
249        height: u32,
250        depth: u32,
251        format: GlowTextureFormat,
252        data: Option<&[u8]>,
253    ) -> Result<Texture, String> {
254        unsafe {
255            if let Some(context) = self.context.get() {
256                let texture = context.create_texture()?;
257                let mut result = Texture {
258                    inner: Rc::new(TextureInner {
259                        context: self.context.0.clone(),
260                        texture,
261                        size: Cell::new((0, 0, 0)),
262                        format: Cell::new(format),
263                    }),
264                };
265                result.upload(width, height, depth, format, data);
266                Ok(result)
267            } else {
268                Err("Invalid context".to_owned())
269            }
270        }
271    }
272
273    pub fn shader(&self, vertex: &str, fragment: &str) -> Result<Shader, String> {
274        unsafe {
275            if let Some(context) = self.context.get() {
276                let vertex_shader = context.create_shader(VERTEX_SHADER)?;
277                let fragment_shader = context.create_shader(FRAGMENT_SHADER)?;
278                let program = context.create_program()?;
279                context.shader_source(vertex_shader, vertex);
280                context.compile_shader(vertex_shader);
281                if !context.get_shader_compile_status(vertex_shader) {
282                    return Err(format!(
283                        "Vertex Shader: {}",
284                        context.get_shader_info_log(vertex_shader)
285                    ));
286                }
287                context.shader_source(fragment_shader, fragment);
288                context.compile_shader(fragment_shader);
289                if !context.get_shader_compile_status(fragment_shader) {
290                    return Err(format!(
291                        "Fragment Shader: {}",
292                        context.get_shader_info_log(fragment_shader)
293                    ));
294                }
295                context.attach_shader(program, vertex_shader);
296                context.attach_shader(program, fragment_shader);
297                context.link_program(program);
298                if !context.get_program_link_status(program) {
299                    return Err(format!(
300                        "Shader Program: {}",
301                        context.get_program_info_log(program)
302                    ));
303                }
304                Ok(Shader {
305                    inner: Rc::new(ShaderInner {
306                        context: self.context.0.clone(),
307                        program,
308                        vertex_shader,
309                        fragment_shader,
310                        shared_uniforms: Default::default(),
311                    }),
312                })
313            } else {
314                Err("Invalid context".to_owned())
315            }
316        }
317    }
318
319    pub fn prepare_frame(&self, clear: bool) -> Result<(), String> {
320        unsafe {
321            if let Some(context) = self.context.get() {
322                context.viewport(
323                    0,
324                    0,
325                    self.state.main_camera.screen_size.x as _,
326                    self.state.main_camera.screen_size.y as _,
327                );
328                context.bind_texture(TEXTURE_2D_ARRAY, None);
329                context.bind_vertex_array(None);
330                context.use_program(None);
331                context.disable(BLEND);
332                context.disable(SCISSOR_TEST);
333                if clear {
334                    let [r, g, b, a] = self.state.color;
335                    context.clear_color(r, g, b, a);
336                    context.clear(COLOR_BUFFER_BIT);
337                }
338                context.polygon_mode(FRONT_AND_BACK, FILL);
339                Ok(())
340            } else {
341                Err("Invalid context".to_owned())
342            }
343        }
344    }
345
346    pub fn draw(&mut self) -> Result<(), String> {
347        if let Some(context) = self.context.get() {
348            let mut renderer = GlowRenderer::<GraphicsBatch>::new(&context, &mut self.glow_state);
349            self.state.stream.batch_end();
350            renderer.render(&mut self.state.stream)?;
351            self.state.stream.clear();
352            Ok(())
353        } else {
354            Err("Invalid context".to_owned())
355        }
356    }
357
358    pub fn push_surface(&mut self, surface: Surface) -> Result<(), String> {
359        unsafe {
360            let old_size = self.state.main_camera.screen_size;
361            let old_color = self.state.color;
362            self.state.main_camera.screen_size.x = surface.width() as _;
363            self.state.main_camera.screen_size.y = surface.height() as _;
364            self.state.color = surface.color();
365            if let Some(context) = self.context.get() {
366                context.bind_framebuffer(FRAMEBUFFER, Some(surface.handle()));
367                self.surface_stack.push((surface, old_size, old_color));
368                Ok(())
369            } else {
370                Err("Invalid context".to_owned())
371            }
372        }
373    }
374
375    pub fn pop_surface(&mut self) -> Result<Option<Surface>, String> {
376        unsafe {
377            if let Some(context) = self.context.get() {
378                if let Some((surface, size, color)) = self.surface_stack.pop() {
379                    self.state.main_camera.screen_size = size;
380                    self.state.color = color;
381                    if let Some((surface, _, _)) = self.surface_stack.last() {
382                        context.bind_framebuffer(FRAMEBUFFER, Some(surface.handle()));
383                    } else {
384                        context.bind_framebuffer(FRAMEBUFFER, None);
385                    }
386                    Ok(Some(surface))
387                } else {
388                    Ok(None)
389                }
390            } else {
391                Err("Invalid context".to_owned())
392            }
393        }
394    }
395}
396
397impl<V: GlowVertexAttribs> GraphicsTarget<V> for Graphics<V> {
398    fn state(&self) -> &GraphicsState<V> {
399        &self.state
400    }
401
402    fn state_mut(&mut self) -> &mut GraphicsState<V> {
403        &mut self.state
404    }
405}
406
407#[derive(Debug, Default, Clone, Copy)]
408pub enum CameraScaling {
409    #[default]
410    None,
411    Constant(f32),
412    Stretch(Vec2<f32>),
413    FitHorizontal(f32),
414    FitVertical(f32),
415    FitToView {
416        size: Vec2<f32>,
417        inside: bool,
418    },
419}
420
421impl CameraScaling {
422    pub fn world_size(self, viewport_size: Vec2<f32>) -> Vec2<f32> {
423        match self {
424            Self::None => viewport_size,
425            Self::Constant(value) => viewport_size * value,
426            Self::Stretch(size) => size,
427            Self::FitHorizontal(value) => Vec2 {
428                x: value,
429                y: value * viewport_size.y / viewport_size.x,
430            },
431            Self::FitVertical(value) => Vec2 {
432                x: value * viewport_size.x / viewport_size.y,
433                y: value,
434            },
435            Self::FitToView { size, inside } => {
436                let source_aspect = size.x / size.y;
437                let target_aspect = viewport_size.x / viewport_size.y;
438                if (target_aspect >= source_aspect) != inside {
439                    Vec2 {
440                        x: viewport_size.x * size.y / viewport_size.y,
441                        y: size.y,
442                    }
443                } else {
444                    Vec2 {
445                        x: size.x,
446                        y: viewport_size.y * size.x / viewport_size.x,
447                    }
448                }
449            }
450        }
451    }
452}
453
454#[derive(Debug, Default, Clone, Copy)]
455pub struct Camera {
456    pub screen_alignment: Vec2<f32>,
457    pub screen_size: Vec2<f32>,
458    pub scaling: CameraScaling,
459    pub transform: Transform<f32, f32, f32>,
460}
461
462impl Camera {
463    pub fn screen_projection_matrix(&self) -> Mat4<f32> {
464        Mat4::orthographic_without_depth_planes(FrustumPlanes {
465            left: 0.0,
466            right: self.screen_size.x,
467            top: 0.0,
468            bottom: self.screen_size.y,
469            near: -1.0,
470            far: 1.0,
471        })
472    }
473
474    pub fn screen_matrix(&self) -> Mat4<f32> {
475        self.screen_projection_matrix()
476    }
477
478    pub fn world_size(&self) -> Vec2<f32> {
479        self.scaling.world_size(self.screen_size)
480    }
481
482    pub fn world_offset(&self) -> Vec2<f32> {
483        self.world_size() * -self.screen_alignment
484    }
485
486    pub fn world_projection_matrix(&self) -> Mat4<f32> {
487        let size = self.world_size();
488        let offset = size * -self.screen_alignment;
489        Mat4::orthographic_without_depth_planes(FrustumPlanes {
490            left: offset.x,
491            right: size.x + offset.x,
492            top: offset.y,
493            bottom: size.y + offset.y,
494            near: -1.0,
495            far: 1.0,
496        })
497    }
498
499    pub fn world_view_matrix(&self) -> Mat4<f32> {
500        (Mat4::<f32>::scaling_3d(self.transform.scale)
501            * Mat4::<f32>::from(self.transform.orientation)
502            * Mat4::<f32>::translation_3d(self.transform.position))
503        .inverted()
504    }
505
506    pub fn world_matrix(&self) -> Mat4<f32> {
507        self.world_projection_matrix() * self.world_view_matrix()
508    }
509
510    pub fn world_polygon(&self) -> [Vec2<f32>; 4] {
511        let matrix = self.world_matrix().inverted();
512        [
513            matrix.mul_point(Vec2::new(-1.0, -1.0)),
514            matrix.mul_point(Vec2::new(1.0, -1.0)),
515            matrix.mul_point(Vec2::new(1.0, 1.0)),
516            matrix.mul_point(Vec2::new(-1.0, 1.0)),
517        ]
518    }
519
520    pub fn world_rectangle(&self) -> Rect<f32, f32> {
521        let [tl, tr, br, bl] = self.world_polygon();
522        let xf = tl.x.min(tr.x).min(br.x).min(bl.x);
523        let xt = tl.x.max(tr.x).max(br.x).max(bl.x);
524        let yf = tl.y.min(tr.y).min(br.y).min(bl.y);
525        let yt = tl.y.max(tr.y).max(br.y).max(bl.y);
526        Rect {
527            x: xf,
528            y: yf,
529            w: xt - xf,
530            h: yt - yf,
531        }
532    }
533
534    pub fn view_to_screen_point(&self, position: Vec2<f32>) -> Vec2<f32> {
535        self.screen_matrix().inverted().mul_point(position)
536    }
537
538    pub fn screen_to_view_point(&self, position: Vec2<f32>) -> Vec2<f32> {
539        self.screen_matrix().mul_point(position)
540    }
541
542    pub fn view_to_world_point(&self, position: Vec2<f32>) -> Vec2<f32> {
543        self.world_matrix().inverted().mul_point(position)
544    }
545
546    pub fn world_to_view_point(&self, position: Vec2<f32>) -> Vec2<f32> {
547        self.world_matrix().mul_point(position)
548    }
549
550    pub fn screen_to_world_point(&self, position: Vec2<f32>) -> Vec2<f32> {
551        let position = self.screen_to_view_point(position);
552        self.view_to_world_point(position)
553    }
554
555    pub fn world_to_screen_point(&self, position: Vec2<f32>) -> Vec2<f32> {
556        let position = self.world_to_view_point(position);
557        self.view_to_screen_point(position)
558    }
559
560    pub fn view_to_screen_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
561        self.screen_matrix().inverted().mul_direction(direction)
562    }
563
564    pub fn screen_to_view_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
565        self.screen_matrix().mul_direction(direction)
566    }
567
568    pub fn view_to_world_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
569        self.world_matrix().inverted().mul_direction(direction)
570    }
571
572    pub fn world_to_view_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
573        self.world_matrix().mul_direction(direction)
574    }
575
576    pub fn screen_to_world_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
577        let direction = self.screen_to_view_direction(direction);
578        self.view_to_world_direction(direction)
579    }
580
581    pub fn world_to_screen_direction(&self, direction: Vec2<f32>) -> Vec2<f32> {
582        let direction = self.world_to_view_direction(direction);
583        self.view_to_screen_direction(direction)
584    }
585}
586
587#[derive(Debug, Default, Clone, PartialEq)]
588pub struct GraphicsBatch {
589    pub shader: Option<Shader>,
590    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
591    pub textures: Vec<(Texture, GlowTextureFiltering)>,
592    /// (source, destination)?
593    pub blending: GlowBlending,
594    pub scissor: Option<Rect<i32, i32>>,
595    pub wireframe: bool,
596}
597
598#[allow(clippy::from_over_into)]
599impl Into<GlowBatch> for GraphicsBatch {
600    fn into(self) -> GlowBatch {
601        GlowBatch {
602            shader_program: self.shader.as_ref().map(|shader| shader.handle()),
603            uniforms: if let Some(shader) = self.shader.as_ref() {
604                let uniforms = &*shader.inner.shared_uniforms.borrow();
605                if uniforms.is_empty() {
606                    self.uniforms
607                } else {
608                    uniforms
609                        .iter()
610                        .map(|(k, v)| (k.clone(), *v))
611                        .chain(self.uniforms)
612                        .collect()
613                }
614            } else {
615                self.uniforms
616            },
617            textures: self
618                .textures
619                .into_iter()
620                .map(|(texture, filtering)| {
621                    let (min, mag) = filtering.into_gl();
622                    (texture.handle(), TEXTURE_2D_ARRAY, min, mag)
623                })
624                .collect(),
625            blending: self.blending.into_gl(),
626            scissor: self.scissor.map(|v| [v.x, v.y, v.w, v.h]),
627            wireframe: self.wireframe,
628        }
629    }
630}
631
632#[derive(Debug, Clone, PartialEq)]
633pub struct SurfaceAttachment {
634    pub texture: Texture,
635    pub layer: usize,
636}
637
638impl From<Texture> for SurfaceAttachment {
639    fn from(texture: Texture) -> Self {
640        Self { texture, layer: 0 }
641    }
642}
643
644#[derive(Debug)]
645struct SurfaceInner {
646    context: MaybeContext,
647    framebuffer: GlowFrameBuffer,
648    attachments: Vec<SurfaceAttachment>,
649    color: Cell<[f32; 4]>,
650}
651
652impl Drop for SurfaceInner {
653    fn drop(&mut self) {
654        unsafe {
655            if let Some(context) = self.context.get() {
656                context.delete_framebuffer(self.framebuffer);
657            }
658        }
659    }
660}
661
662#[derive(Debug, Clone)]
663pub struct Surface {
664    inner: Rc<SurfaceInner>,
665}
666
667impl Surface {
668    pub fn handle(&self) -> GlowFrameBuffer {
669        self.inner.framebuffer
670    }
671
672    pub fn width(&self) -> u32 {
673        self.inner.attachments[0].texture.width()
674    }
675
676    pub fn height(&self) -> u32 {
677        self.inner.attachments[0].texture.height()
678    }
679
680    pub fn attachments(&self) -> &[SurfaceAttachment] {
681        &self.inner.attachments
682    }
683
684    pub fn color(&self) -> [f32; 4] {
685        self.inner.color.get()
686    }
687
688    pub fn set_color(&mut self, value: [f32; 4]) {
689        self.inner.color.set(value);
690    }
691}
692
693impl PartialEq for Surface {
694    fn eq(&self, other: &Self) -> bool {
695        Rc::ptr_eq(&self.inner, &other.inner)
696    }
697}
698
699#[derive(Debug)]
700struct TextureInner {
701    context: MaybeContext,
702    texture: GlowTexture,
703    format: Cell<GlowTextureFormat>,
704    size: Cell<(u32, u32, u32)>,
705}
706
707impl Drop for TextureInner {
708    fn drop(&mut self) {
709        unsafe {
710            if let Some(context) = self.context.get() {
711                context.delete_texture(self.texture);
712            }
713        }
714    }
715}
716
717#[derive(Debug, Clone)]
718pub struct Texture {
719    inner: Rc<TextureInner>,
720}
721
722impl Texture {
723    pub fn handle(&self) -> GlowTexture {
724        self.inner.texture
725    }
726
727    pub fn width(&self) -> u32 {
728        self.inner.size.get().0
729    }
730
731    pub fn height(&self) -> u32 {
732        self.inner.size.get().1
733    }
734
735    pub fn depth(&self) -> u32 {
736        self.inner.size.get().2
737    }
738
739    pub fn format(&self) -> GlowTextureFormat {
740        self.inner.format.get()
741    }
742
743    pub fn upload(
744        &mut self,
745        width: u32,
746        height: u32,
747        depth: u32,
748        format: GlowTextureFormat,
749        data: Option<&[u8]>,
750    ) {
751        unsafe {
752            if let Some(context) = self.inner.context.get() {
753                context.bind_texture(TEXTURE_2D_ARRAY, Some(self.inner.texture));
754                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_S, CLAMP_TO_EDGE as _);
755                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_T, CLAMP_TO_EDGE as _);
756                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_R, CLAMP_TO_EDGE as _);
757                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MIN_FILTER, NEAREST as _);
758                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MAG_FILTER, NEAREST as _);
759                // TODO: make fix in web_sys module of `glow` to fix depth and border args ordering.
760                #[cfg(target_arch = "wasm32")]
761                context.tex_image_3d(
762                    TEXTURE_2D_ARRAY,
763                    0,
764                    format.into_gl() as _,
765                    width as _,
766                    height as _,
767                    0,
768                    depth as _,
769                    format.into_gl(),
770                    UNSIGNED_BYTE,
771                    PixelUnpackData::Slice(data),
772                );
773                #[cfg(not(target_arch = "wasm32"))]
774                context.tex_image_3d(
775                    TEXTURE_2D_ARRAY,
776                    0,
777                    format.into_gl() as _,
778                    width as _,
779                    height as _,
780                    depth as _,
781                    0,
782                    format.into_gl(),
783                    UNSIGNED_BYTE,
784                    PixelUnpackData::Slice(data),
785                );
786                self.inner.size.set((width, height, depth));
787                self.inner.format.set(format);
788            }
789        }
790    }
791}
792
793impl PartialEq for Texture {
794    fn eq(&self, other: &Self) -> bool {
795        Rc::ptr_eq(&self.inner, &other.inner)
796    }
797}
798
799#[derive(Debug)]
800struct ShaderInner {
801    context: MaybeContext,
802    program: GlowProgram,
803    vertex_shader: GlowShader,
804    fragment_shader: GlowShader,
805    shared_uniforms: RefCell<HashMap<Cow<'static, str>, GlowUniformValue>>,
806}
807
808impl Drop for ShaderInner {
809    fn drop(&mut self) {
810        unsafe {
811            if let Some(context) = self.context.get() {
812                context.delete_program(self.program);
813                context.delete_shader(self.vertex_shader);
814                context.delete_shader(self.fragment_shader);
815            }
816        }
817    }
818}
819
820#[derive(Debug, Clone)]
821pub struct Shader {
822    inner: Rc<ShaderInner>,
823}
824
825impl Shader {
826    pub const PASS_VERTEX_2D: &'static str = r#"#version 300 es
827    layout(location = 0) in vec2 a_position;
828    layout(location = 2) in vec4 a_color;
829    out vec4 v_color;
830
831    void main() {
832        gl_Position = vec4(a_position, 0.0, 1.0);
833        v_color = a_color;
834    }
835    "#;
836
837    pub const PASS_VERTEX_3D: &'static str = r#"#version 300 es
838    layout(location = 0) in vec3 a_position;
839    layout(location = 3) in vec4 a_color;
840    out vec4 v_color;
841
842    void main() {
843        gl_Position = vec4(a_position, 1.0);
844        v_color = a_color;
845    }
846    "#;
847
848    pub const PASS_FRAGMENT: &'static str = r#"#version 300 es
849    precision highp float;
850    precision highp int;
851    in vec4 v_color;
852    out vec4 o_color;
853
854    void main() {
855        o_color = v_color;
856    }
857    "#;
858
859    pub const COLORED_VERTEX_2D: &'static str = r#"#version 300 es
860    layout(location = 0) in vec2 a_position;
861    layout(location = 2) in vec4 a_color;
862    out vec4 v_color;
863    uniform mat4 u_projection_view;
864
865    void main() {
866        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
867        v_color = a_color;
868    }
869    "#;
870
871    pub const COLORED_VERTEX_3D: &'static str = r#"#version 300 es
872    layout(location = 0) in vec3 a_position;
873    layout(location = 3) in vec4 a_color;
874    out vec4 v_color;
875    uniform mat4 u_projection_view;
876
877    void main() {
878        gl_Position = u_projection_view * vec4(a_position, 1.0);
879        v_color = a_color;
880    }
881    "#;
882
883    pub const TEXTURED_VERTEX_2D: &'static str = r#"#version 300 es
884    layout(location = 0) in vec2 a_position;
885    layout(location = 1) in vec3 a_uv;
886    layout(location = 2) in vec4 a_color;
887    out vec4 v_color;
888    out vec3 v_uv;
889    uniform mat4 u_projection_view;
890
891    void main() {
892        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
893        v_color = a_color;
894        v_uv = a_uv;
895    }
896    "#;
897
898    pub const TEXTURED_VERTEX_3D: &'static str = r#"#version 300 es
899    layout(location = 0) in vec3 a_position;
900    layout(location = 2) in vec3 a_uv;
901    layout(location = 3) in vec4 a_color;
902    out vec4 v_color;
903    out vec3 v_uv;
904    uniform mat4 u_projection_view;
905
906    void main() {
907        gl_Position = u_projection_view * vec4(a_position, 1.0);
908        v_color = a_color;
909        v_uv = a_uv;
910    }
911    "#;
912
913    pub const TEXTURED_FRAGMENT: &'static str = r#"#version 300 es
914    precision highp float;
915    precision highp int;
916    precision highp sampler2DArray;
917    in vec4 v_color;
918    in vec3 v_uv;
919    out vec4 o_color;
920    uniform sampler2DArray u_image;
921
922    void main() {
923        o_color = texture(u_image, v_uv) * v_color;
924    }
925    "#;
926
927    pub const TEXT_VERTEX: &'static str = r#"#version 300 es
928    layout(location = 0) in vec2 a_position;
929    layout(location = 1) in vec3 a_uv;
930    layout(location = 2) in vec4 a_color;
931    out vec4 v_color;
932    out vec3 v_uv;
933    uniform mat4 u_projection_view;
934
935    void main() {
936        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
937        v_color = a_color;
938        v_uv = a_uv;
939    }
940    "#;
941
942    pub const TEXT_FRAGMENT: &'static str = r#"#version 300 es
943    precision highp float;
944    precision highp int;
945    precision highp sampler2DArray;
946    in vec4 v_color;
947    in vec3 v_uv;
948    out vec4 o_color;
949    uniform sampler2DArray u_image;
950
951    void main() {
952        float alpha = texture(u_image, v_uv).x;
953        o_color = vec4(v_color.xyz, v_color.w * alpha);
954    }
955    "#;
956
957    pub fn handle(&self) -> GlowProgram {
958        self.inner.program
959    }
960
961    pub fn set_shared_uniform(
962        &mut self,
963        id: impl Into<Cow<'static, str>>,
964        value: GlowUniformValue,
965    ) {
966        self.inner
967            .shared_uniforms
968            .borrow_mut()
969            .insert(id.into(), value);
970    }
971
972    pub fn unset_shared_uniform(&mut self, id: &str) {
973        self.inner.shared_uniforms.borrow_mut().remove(id);
974    }
975
976    pub fn get_shared_uniform(&self, id: &str) -> Option<GlowUniformValue> {
977        self.inner.shared_uniforms.borrow().get(id).cloned()
978    }
979
980    pub fn clear_shared_uniforms(&mut self) {
981        self.inner.shared_uniforms.borrow_mut().clear();
982    }
983}
984
985impl PartialEq for Shader {
986    fn eq(&self, other: &Self) -> bool {
987        Rc::ptr_eq(&self.inner, &other.inner)
988    }
989}