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, FRAGMENT_SHADER,
8    FRAMEBUFFER, Framebuffer as GlowFrameBuffer, HasContext, NEAREST, Program as GlowProgram,
9    SCISSOR_TEST, Shader as GlowShader, TEXTURE_2D_ARRAY, TEXTURE_MAG_FILTER, TEXTURE_MIN_FILTER,
10    TEXTURE_WRAP_R, TEXTURE_WRAP_S, TEXTURE_WRAP_T, Texture as GlowTexture, UNSIGNED_BYTE,
11    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 struct Graphics<V: GlowVertexAttribs> {
109    pub main_camera: Camera,
110    pub color: [f32; 4],
111    pub stream: VertexStream<V, GraphicsBatch>,
112    state: GlowState,
113    context: StrongContext,
114    surface_stack: Vec<(Surface, Vec2<f32>, [f32; 4])>,
115}
116
117impl<V: GlowVertexAttribs> Drop for Graphics<V> {
118    fn drop(&mut self) {
119        if let Some(context) = self.context.get() {
120            self.state.dispose(&context);
121        }
122    }
123}
124
125impl<V: GlowVertexAttribs> Graphics<V> {
126    pub fn new(context: Context) -> Self {
127        Self {
128            main_camera: Default::default(),
129            color: [1.0, 1.0, 1.0, 1.0],
130            stream: Default::default(),
131            state: Default::default(),
132            context: StrongContext::new(context),
133            surface_stack: Default::default(),
134        }
135    }
136
137    pub fn context(&self) -> Option<Ref<Context>> {
138        self.context.get()
139    }
140
141    pub fn surface(&self, attachments: Vec<SurfaceAttachment>) -> Result<Surface, String> {
142        if attachments.is_empty() {
143            return Err("Surface must have at least one texture!".to_owned());
144        }
145        for (index, attachment) in attachments.iter().enumerate() {
146            if attachment.texture.depth() < attachment.layer as _ {
147                return Err(format!(
148                    "Surface texture #{} has layer: {} out of texture depth range: {}",
149                    index,
150                    attachment.layer,
151                    attachment.texture.depth()
152                ));
153            }
154        }
155        if let [first, rest @ ..] = attachments.as_slice() {
156            let width = first.texture.width();
157            let height = first.texture.height();
158            if rest
159                .iter()
160                .any(|item| item.texture.width() != width || item.texture.height() != height)
161            {
162                return Err(format!(
163                    "Some surface texture has different size than expected: {} x {}",
164                    width, height
165                ));
166            }
167        }
168        unsafe {
169            if let Some(context) = self.context.get() {
170                let framebuffer = context.create_framebuffer()?;
171                context.bind_framebuffer(FRAMEBUFFER, Some(framebuffer));
172                for (index, attachment) in attachments.iter().enumerate() {
173                    context.framebuffer_texture_layer(
174                        FRAMEBUFFER,
175                        COLOR_ATTACHMENT0 + index as u32,
176                        Some(attachment.texture.handle()),
177                        0,
178                        attachment.layer as _,
179                    );
180                }
181                context.bind_framebuffer(FRAMEBUFFER, None);
182                Ok(Surface {
183                    inner: Rc::new(SurfaceInner {
184                        context: self.context.0.clone(),
185                        framebuffer,
186                        attachments,
187                        color: Default::default(),
188                    }),
189                })
190            } else {
191                Err("Invalid context".to_owned())
192            }
193        }
194    }
195
196    pub fn pixel_texture(&self, color: [u8; 3]) -> Result<Texture, String> {
197        self.texture(1, 1, 1, GlowTextureFormat::Rgb, Some(&color))
198    }
199
200    pub fn texture(
201        &self,
202        width: u32,
203        height: u32,
204        depth: u32,
205        format: GlowTextureFormat,
206        data: Option<&[u8]>,
207    ) -> Result<Texture, String> {
208        unsafe {
209            if let Some(context) = self.context.get() {
210                let texture = context.create_texture()?;
211                let mut result = Texture {
212                    inner: Rc::new(TextureInner {
213                        context: self.context.0.clone(),
214                        texture,
215                        size: Cell::new((0, 0, 0)),
216                        format: Cell::new(format),
217                    }),
218                };
219                result.upload(width, height, depth, format, data);
220                Ok(result)
221            } else {
222                Err("Invalid context".to_owned())
223            }
224        }
225    }
226
227    pub fn shader(&self, vertex: &str, fragment: &str) -> Result<Shader, String> {
228        unsafe {
229            if let Some(context) = self.context.get() {
230                let vertex_shader = context.create_shader(VERTEX_SHADER)?;
231                let fragment_shader = context.create_shader(FRAGMENT_SHADER)?;
232                let program = context.create_program()?;
233                context.shader_source(vertex_shader, vertex);
234                context.compile_shader(vertex_shader);
235                if !context.get_shader_compile_status(vertex_shader) {
236                    return Err(format!(
237                        "Vertex Shader: {}",
238                        context.get_shader_info_log(vertex_shader)
239                    ));
240                }
241                context.shader_source(fragment_shader, fragment);
242                context.compile_shader(fragment_shader);
243                if !context.get_shader_compile_status(fragment_shader) {
244                    return Err(format!(
245                        "Fragment Shader: {}",
246                        context.get_shader_info_log(fragment_shader)
247                    ));
248                }
249                context.attach_shader(program, vertex_shader);
250                context.attach_shader(program, fragment_shader);
251                context.link_program(program);
252                if !context.get_program_link_status(program) {
253                    return Err(format!(
254                        "Shader Program: {}",
255                        context.get_program_info_log(program)
256                    ));
257                }
258                Ok(Shader {
259                    inner: Rc::new(ShaderInner {
260                        context: self.context.0.clone(),
261                        program,
262                        vertex_shader,
263                        fragment_shader,
264                        shared_uniforms: Default::default(),
265                    }),
266                })
267            } else {
268                Err("Invalid context".to_owned())
269            }
270        }
271    }
272
273    pub fn prepare_frame(&self, clear: bool) -> Result<(), String> {
274        unsafe {
275            if let Some(context) = self.context.get() {
276                context.viewport(
277                    0,
278                    0,
279                    self.main_camera.screen_size.x as _,
280                    self.main_camera.screen_size.y as _,
281                );
282                context.bind_texture(TEXTURE_2D_ARRAY, None);
283                context.bind_vertex_array(None);
284                context.use_program(None);
285                context.disable(BLEND);
286                context.disable(SCISSOR_TEST);
287                if clear {
288                    let [r, g, b, a] = self.color;
289                    context.clear_color(r, g, b, a);
290                    context.clear(COLOR_BUFFER_BIT);
291                }
292                Ok(())
293            } else {
294                Err("Invalid context".to_owned())
295            }
296        }
297    }
298
299    pub fn draw(&mut self) -> Result<(), String> {
300        if let Some(context) = self.context.get() {
301            let mut renderer = GlowRenderer::<GraphicsBatch>::new(&context, &mut self.state);
302            self.stream.batch_end();
303            renderer.render(&mut self.stream)?;
304            self.stream.clear();
305            Ok(())
306        } else {
307            Err("Invalid context".to_owned())
308        }
309    }
310
311    pub fn push_surface(&mut self, surface: Surface) -> Result<(), String> {
312        unsafe {
313            let old_size = self.main_camera.screen_size;
314            let old_color = self.color;
315            self.main_camera.screen_size.x = surface.width() as _;
316            self.main_camera.screen_size.y = surface.height() as _;
317            self.color = surface.color();
318            if let Some(context) = self.context.get() {
319                context.bind_framebuffer(FRAMEBUFFER, Some(surface.handle()));
320                self.surface_stack.push((surface, old_size, old_color));
321                Ok(())
322            } else {
323                Err("Invalid context".to_owned())
324            }
325        }
326    }
327
328    pub fn pop_surface(&mut self) -> Result<Option<Surface>, String> {
329        unsafe {
330            if let Some(context) = self.context.get() {
331                if let Some((surface, size, color)) = self.surface_stack.pop() {
332                    self.main_camera.screen_size = size;
333                    self.color = color;
334                    if let Some((surface, _, _)) = self.surface_stack.last() {
335                        context.bind_framebuffer(FRAMEBUFFER, Some(surface.handle()));
336                    } else {
337                        context.bind_framebuffer(FRAMEBUFFER, None);
338                    }
339                    Ok(Some(surface))
340                } else {
341                    Ok(None)
342                }
343            } else {
344                Err("Invalid context".to_owned())
345            }
346        }
347    }
348}
349
350#[derive(Debug, Default, Clone, Copy)]
351pub enum CameraScaling {
352    #[default]
353    None,
354    Constant(f32),
355    Stretch(Vec2<f32>),
356    FitHorizontal(f32),
357    FitVertical(f32),
358    FitToView {
359        size: Vec2<f32>,
360        inside: bool,
361    },
362}
363
364impl CameraScaling {
365    pub fn world_size(self, viewport_size: Vec2<f32>) -> Vec2<f32> {
366        match self {
367            Self::None => viewport_size,
368            Self::Constant(value) => viewport_size * value,
369            Self::Stretch(size) => size,
370            Self::FitHorizontal(value) => Vec2 {
371                x: value,
372                y: value * viewport_size.y / viewport_size.x,
373            },
374            Self::FitVertical(value) => Vec2 {
375                x: value * viewport_size.x / viewport_size.y,
376                y: value,
377            },
378            Self::FitToView { size, inside } => {
379                let source_aspect = size.x / size.y;
380                let target_aspect = viewport_size.x / viewport_size.y;
381                if (target_aspect >= source_aspect) != inside {
382                    Vec2 {
383                        x: viewport_size.x * size.x / viewport_size.y,
384                        y: size.y,
385                    }
386                } else {
387                    Vec2 {
388                        x: size.x,
389                        y: viewport_size.y * size.y / viewport_size.x,
390                    }
391                }
392            }
393        }
394    }
395}
396
397#[derive(Debug, Default, Clone, Copy)]
398pub struct Camera {
399    pub screen_alignment: Vec2<f32>,
400    pub screen_size: Vec2<f32>,
401    pub scaling: CameraScaling,
402    pub transform: Transform<f32, f32, f32>,
403}
404
405impl Camera {
406    pub fn screen_projection_matrix(&self) -> Mat4<f32> {
407        Mat4::orthographic_without_depth_planes(FrustumPlanes {
408            left: 0.0,
409            right: self.screen_size.x,
410            top: 0.0,
411            bottom: self.screen_size.y,
412            near: -1.0,
413            far: 1.0,
414        })
415    }
416
417    pub fn screen_matrix(&self) -> Mat4<f32> {
418        self.screen_projection_matrix()
419    }
420
421    pub fn world_size(&self) -> Vec2<f32> {
422        self.scaling.world_size(self.screen_size)
423    }
424
425    pub fn world_offset(&self) -> Vec2<f32> {
426        self.world_size() * -self.screen_alignment
427    }
428
429    pub fn world_projection_matrix(&self) -> Mat4<f32> {
430        let size = self.world_size();
431        let offset = size * -self.screen_alignment;
432        Mat4::orthographic_without_depth_planes(FrustumPlanes {
433            left: offset.x,
434            right: size.x + offset.x,
435            top: offset.y,
436            bottom: size.y + offset.y,
437            near: -1.0,
438            far: 1.0,
439        })
440    }
441
442    pub fn world_view_matrix(&self) -> Mat4<f32> {
443        (Mat4::<f32>::scaling_3d(self.transform.scale)
444            * Mat4::<f32>::from(self.transform.orientation)
445            * Mat4::<f32>::translation_3d(self.transform.position))
446        .inverted()
447    }
448
449    pub fn world_matrix(&self) -> Mat4<f32> {
450        self.world_projection_matrix() * self.world_view_matrix()
451    }
452
453    pub fn world_polygon(&self) -> [Vec2<f32>; 4] {
454        let matrix = self.world_matrix().inverted();
455        [
456            matrix.mul_point(Vec2::new(-1.0, -1.0)),
457            matrix.mul_point(Vec2::new(1.0, -1.0)),
458            matrix.mul_point(Vec2::new(1.0, 1.0)),
459            matrix.mul_point(Vec2::new(-1.0, 1.0)),
460        ]
461    }
462
463    pub fn world_rectangle(&self) -> Rect<f32, f32> {
464        let [tl, tr, br, bl] = self.world_polygon();
465        let xf = tl.x.min(tr.x).min(br.x).min(bl.x);
466        let xt = tl.x.max(tr.x).max(br.x).max(bl.x);
467        let yf = tl.y.min(tr.y).min(br.y).min(bl.y);
468        let yt = tl.y.max(tr.y).max(br.y).max(bl.y);
469        Rect {
470            x: xf,
471            y: yf,
472            w: xt - xf,
473            h: yt - yf,
474        }
475    }
476}
477
478#[derive(Debug, Default, Clone, PartialEq)]
479pub struct GraphicsBatch {
480    pub shader: Option<Shader>,
481    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
482    pub textures: Vec<(Texture, GlowTextureFiltering)>,
483    /// (source, destination)?
484    pub blending: GlowBlending,
485    pub scissor: Option<Rect<i32, i32>>,
486}
487
488#[allow(clippy::from_over_into)]
489impl Into<GlowBatch> for GraphicsBatch {
490    fn into(self) -> GlowBatch {
491        GlowBatch {
492            shader_program: self.shader.as_ref().map(|shader| shader.handle()),
493            uniforms: if let Some(shader) = self.shader.as_ref() {
494                let uniforms = &*shader.inner.shared_uniforms.borrow();
495                if uniforms.is_empty() {
496                    self.uniforms
497                } else {
498                    uniforms
499                        .iter()
500                        .map(|(k, v)| (k.clone(), *v))
501                        .chain(self.uniforms)
502                        .collect()
503                }
504            } else {
505                self.uniforms
506            },
507            textures: self
508                .textures
509                .into_iter()
510                .map(|(texture, filtering)| {
511                    let (min, mag) = filtering.into_gl();
512                    (texture.handle(), TEXTURE_2D_ARRAY, min, mag)
513                })
514                .collect(),
515            blending: self.blending.into_gl(),
516            scissor: self.scissor.map(|v| [v.x, v.y, v.w, v.h]),
517        }
518    }
519}
520
521#[derive(Debug, Clone, PartialEq)]
522pub struct SurfaceAttachment {
523    pub texture: Texture,
524    pub layer: usize,
525}
526
527impl From<Texture> for SurfaceAttachment {
528    fn from(texture: Texture) -> Self {
529        Self { texture, layer: 0 }
530    }
531}
532
533#[derive(Debug)]
534struct SurfaceInner {
535    context: MaybeContext,
536    framebuffer: GlowFrameBuffer,
537    attachments: Vec<SurfaceAttachment>,
538    color: Cell<[f32; 4]>,
539}
540
541impl Drop for SurfaceInner {
542    fn drop(&mut self) {
543        unsafe {
544            if let Some(context) = self.context.get() {
545                context.delete_framebuffer(self.framebuffer);
546            }
547        }
548    }
549}
550
551#[derive(Debug, Clone)]
552pub struct Surface {
553    inner: Rc<SurfaceInner>,
554}
555
556impl Surface {
557    pub fn handle(&self) -> GlowFrameBuffer {
558        self.inner.framebuffer
559    }
560
561    pub fn width(&self) -> u32 {
562        self.inner.attachments[0].texture.width()
563    }
564
565    pub fn height(&self) -> u32 {
566        self.inner.attachments[0].texture.height()
567    }
568
569    pub fn attachments(&self) -> &[SurfaceAttachment] {
570        &self.inner.attachments
571    }
572
573    pub fn color(&self) -> [f32; 4] {
574        self.inner.color.get()
575    }
576
577    pub fn set_color(&mut self, value: [f32; 4]) {
578        self.inner.color.set(value);
579    }
580}
581
582impl PartialEq for Surface {
583    fn eq(&self, other: &Self) -> bool {
584        Rc::ptr_eq(&self.inner, &other.inner)
585    }
586}
587
588#[derive(Debug)]
589struct TextureInner {
590    context: MaybeContext,
591    texture: GlowTexture,
592    format: Cell<GlowTextureFormat>,
593    size: Cell<(u32, u32, u32)>,
594}
595
596impl Drop for TextureInner {
597    fn drop(&mut self) {
598        unsafe {
599            if let Some(context) = self.context.get() {
600                context.delete_texture(self.texture);
601            }
602        }
603    }
604}
605
606#[derive(Debug, Clone)]
607pub struct Texture {
608    inner: Rc<TextureInner>,
609}
610
611impl Texture {
612    pub fn handle(&self) -> GlowTexture {
613        self.inner.texture
614    }
615
616    pub fn width(&self) -> u32 {
617        self.inner.size.get().0
618    }
619
620    pub fn height(&self) -> u32 {
621        self.inner.size.get().1
622    }
623
624    pub fn depth(&self) -> u32 {
625        self.inner.size.get().2
626    }
627
628    pub fn format(&self) -> GlowTextureFormat {
629        self.inner.format.get()
630    }
631
632    pub fn upload(
633        &mut self,
634        width: u32,
635        height: u32,
636        depth: u32,
637        format: GlowTextureFormat,
638        data: Option<&[u8]>,
639    ) {
640        unsafe {
641            if let Some(context) = self.inner.context.get() {
642                context.bind_texture(TEXTURE_2D_ARRAY, Some(self.inner.texture));
643                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_S, CLAMP_TO_EDGE as _);
644                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_T, CLAMP_TO_EDGE as _);
645                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_R, CLAMP_TO_EDGE as _);
646                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MIN_FILTER, NEAREST as _);
647                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MAG_FILTER, NEAREST as _);
648                context.tex_image_3d(
649                    TEXTURE_2D_ARRAY,
650                    0,
651                    format.into_gl() as _,
652                    width as _,
653                    height as _,
654                    depth as _,
655                    0,
656                    format.into_gl(),
657                    UNSIGNED_BYTE,
658                    data,
659                );
660                self.inner.size.set((width, height, depth));
661                self.inner.format.set(format);
662            }
663        }
664    }
665}
666
667impl PartialEq for Texture {
668    fn eq(&self, other: &Self) -> bool {
669        Rc::ptr_eq(&self.inner, &other.inner)
670    }
671}
672
673#[derive(Debug)]
674struct ShaderInner {
675    context: MaybeContext,
676    program: GlowProgram,
677    vertex_shader: GlowShader,
678    fragment_shader: GlowShader,
679    shared_uniforms: RefCell<HashMap<Cow<'static, str>, GlowUniformValue>>,
680}
681
682impl Drop for ShaderInner {
683    fn drop(&mut self) {
684        unsafe {
685            if let Some(context) = self.context.get() {
686                context.delete_program(self.program);
687                context.delete_shader(self.vertex_shader);
688                context.delete_shader(self.fragment_shader);
689            }
690        }
691    }
692}
693
694#[derive(Debug, Clone)]
695pub struct Shader {
696    inner: Rc<ShaderInner>,
697}
698
699impl Shader {
700    pub const PASS_VERTEX_2D: &'static str = r#"#version 300 es
701    layout(location = 0) in vec2 a_position;
702    layout(location = 2) in vec4 a_color;
703    out vec4 v_color;
704
705    void main() {
706        gl_Position = vec4(a_position, 0.0, 1.0);
707        v_color = a_color;
708    }
709    "#;
710
711    pub const PASS_VERTEX_3D: &'static str = r#"#version 300 es
712    layout(location = 0) in vec3 a_position;
713    layout(location = 3) in vec4 a_color;
714    out vec4 v_color;
715
716    void main() {
717        gl_Position = vec4(a_position, 1.0);
718        v_color = a_color;
719    }
720    "#;
721
722    pub const PASS_FRAGMENT: &'static str = r#"#version 300 es
723    precision highp float;
724    precision highp int;
725    in vec4 v_color;
726    out vec4 o_color;
727
728    void main() {
729        o_color = v_color;
730    }
731    "#;
732
733    pub const COLORED_VERTEX_2D: &'static str = r#"#version 300 es
734    layout(location = 0) in vec2 a_position;
735    layout(location = 2) in vec4 a_color;
736    out vec4 v_color;
737    uniform mat4 u_projection_view;
738
739    void main() {
740        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
741        v_color = a_color;
742    }
743    "#;
744
745    pub const COLORED_VERTEX_3D: &'static str = r#"#version 300 es
746    layout(location = 0) in vec3 a_position;
747    layout(location = 3) in vec4 a_color;
748    out vec4 v_color;
749    uniform mat4 u_projection_view;
750
751    void main() {
752        gl_Position = u_projection_view * vec4(a_position, 1.0);
753        v_color = a_color;
754    }
755    "#;
756
757    pub const TEXTURED_VERTEX_2D: &'static str = r#"#version 300 es
758    layout(location = 0) in vec2 a_position;
759    layout(location = 1) in vec3 a_uv;
760    layout(location = 2) in vec4 a_color;
761    out vec4 v_color;
762    out vec3 v_uv;
763    uniform mat4 u_projection_view;
764
765    void main() {
766        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
767        v_color = a_color;
768        v_uv = a_uv;
769    }
770    "#;
771
772    pub const TEXTURED_VERTEX_3D: &'static str = r#"#version 300 es
773    layout(location = 0) in vec3 a_position;
774    layout(location = 2) in vec3 a_uv;
775    layout(location = 3) in vec4 a_color;
776    out vec4 v_color;
777    out vec3 v_uv;
778    uniform mat4 u_projection_view;
779
780    void main() {
781        gl_Position = u_projection_view * vec4(a_position, 1.0);
782        v_color = a_color;
783        v_uv = a_uv;
784    }
785    "#;
786
787    pub const TEXTURED_FRAGMENT: &'static str = r#"#version 300 es
788    precision highp float;
789    precision highp int;
790    precision highp sampler2DArray;
791    in vec4 v_color;
792    in vec3 v_uv;
793    out vec4 o_color;
794    uniform sampler2DArray u_image;
795
796    void main() {
797        o_color = texture(u_image, v_uv) * v_color;
798    }
799    "#;
800
801    pub const TEXT_VERTEX: &'static str = r#"#version 300 es
802    layout(location = 0) in vec2 a_position;
803    layout(location = 1) in vec3 a_uv;
804    layout(location = 2) in vec4 a_color;
805    out vec4 v_color;
806    out vec3 v_uv;
807    uniform mat4 u_projection_view;
808
809    void main() {
810        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
811        v_color = a_color;
812        v_uv = a_uv;
813    }
814    "#;
815
816    pub const TEXT_FRAGMENT: &'static str = r#"#version 300 es
817    precision highp float;
818    precision highp int;
819    precision highp sampler2DArray;
820    in vec4 v_color;
821    in vec3 v_uv;
822    out vec4 o_color;
823    uniform sampler2DArray u_image;
824
825    void main() {
826        float alpha = texture(u_image, v_uv).x;
827        o_color = vec4(v_color.xyz, v_color.w * alpha);
828    }
829    "#;
830
831    pub fn handle(&self) -> GlowProgram {
832        self.inner.program
833    }
834
835    pub fn set_shared_uniform(
836        &mut self,
837        id: impl Into<Cow<'static, str>>,
838        value: GlowUniformValue,
839    ) {
840        self.inner
841            .shared_uniforms
842            .borrow_mut()
843            .insert(id.into(), value);
844    }
845
846    pub fn unset_shared_uniform(&mut self, id: &str) {
847        self.inner.shared_uniforms.borrow_mut().remove(id);
848    }
849
850    pub fn get_shared_uniform(&self, id: &str) -> Option<GlowUniformValue> {
851        self.inner.shared_uniforms.borrow().get(id).cloned()
852    }
853
854    pub fn clear_shared_uniforms(&mut self) {
855        self.inner.shared_uniforms.borrow_mut().clear();
856    }
857}
858
859impl PartialEq for Shader {
860    fn eq(&self, other: &Self) -> bool {
861        Rc::ptr_eq(&self.inner, &other.inner)
862    }
863}