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    Context, Framebuffer as GlowFrameBuffer, HasContext, Program as GlowProgram,
8    Shader as GlowShader, Texture as GlowTexture, BLEND, CLAMP_TO_EDGE, COLOR_ATTACHMENT0,
9    COLOR_BUFFER_BIT, FRAGMENT_SHADER, FRAMEBUFFER, NEAREST, SCISSOR_TEST, TEXTURE_2D_ARRAY,
10    TEXTURE_MAG_FILTER, TEXTURE_MIN_FILTER, TEXTURE_WRAP_R, TEXTURE_WRAP_S, TEXTURE_WRAP_T,
11    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 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::from(self.transform).inverted()
444    }
445
446    pub fn world_matrix(&self) -> Mat4<f32> {
447        self.world_projection_matrix() * self.world_view_matrix()
448    }
449
450    pub fn world_polygon(&self) -> [Vec2<f32>; 4] {
451        let matrix = self.world_matrix().inverted();
452        [
453            matrix.mul_point(Vec2::new(-1.0, -1.0)),
454            matrix.mul_point(Vec2::new(1.0, -1.0)),
455            matrix.mul_point(Vec2::new(1.0, 1.0)),
456            matrix.mul_point(Vec2::new(-1.0, 1.0)),
457        ]
458    }
459
460    pub fn world_rectangle(&self) -> Rect<f32, f32> {
461        let [tl, tr, br, bl] = self.world_polygon();
462        let xf = tl.x.min(tr.x).min(br.x).min(bl.x);
463        let xt = tl.x.max(tr.x).max(br.x).max(bl.x);
464        let yf = tl.y.min(tr.y).min(br.y).min(bl.y);
465        let yt = tl.y.max(tr.y).max(br.y).max(bl.y);
466        Rect {
467            x: xf,
468            y: yf,
469            w: xt - xf,
470            h: yt - yf,
471        }
472    }
473}
474
475#[derive(Debug, Default, Clone, PartialEq)]
476pub struct GraphicsBatch {
477    pub shader: Option<Shader>,
478    pub uniforms: HashMap<Cow<'static, str>, GlowUniformValue>,
479    pub textures: Vec<(Texture, GlowTextureFiltering)>,
480    /// (source, destination)?
481    pub blending: GlowBlending,
482    pub scissor: Option<Rect<i32, i32>>,
483}
484
485#[allow(clippy::from_over_into)]
486impl Into<GlowBatch> for GraphicsBatch {
487    fn into(self) -> GlowBatch {
488        GlowBatch {
489            shader_program: self.shader.as_ref().map(|shader| shader.handle()),
490            uniforms: if let Some(shader) = self.shader.as_ref() {
491                let uniforms = &*shader.inner.shared_uniforms.borrow();
492                if uniforms.is_empty() {
493                    self.uniforms
494                } else {
495                    uniforms
496                        .iter()
497                        .map(|(k, v)| (k.clone(), *v))
498                        .chain(self.uniforms)
499                        .collect()
500                }
501            } else {
502                self.uniforms
503            },
504            textures: self
505                .textures
506                .into_iter()
507                .map(|(texture, filtering)| {
508                    let (min, mag) = filtering.into_gl();
509                    (texture.handle(), TEXTURE_2D_ARRAY, min, mag)
510                })
511                .collect(),
512            blending: self.blending.into_gl(),
513            scissor: self.scissor.map(|v| [v.x, v.y, v.w, v.h]),
514        }
515    }
516}
517
518#[derive(Debug, Clone, PartialEq)]
519pub struct SurfaceAttachment {
520    pub texture: Texture,
521    pub layer: usize,
522}
523
524impl From<Texture> for SurfaceAttachment {
525    fn from(texture: Texture) -> Self {
526        Self { texture, layer: 0 }
527    }
528}
529
530#[derive(Debug)]
531struct SurfaceInner {
532    context: MaybeContext,
533    framebuffer: GlowFrameBuffer,
534    attachments: Vec<SurfaceAttachment>,
535    color: Cell<[f32; 4]>,
536}
537
538impl Drop for SurfaceInner {
539    fn drop(&mut self) {
540        unsafe {
541            if let Some(context) = self.context.get() {
542                context.delete_framebuffer(self.framebuffer);
543            }
544        }
545    }
546}
547
548#[derive(Debug, Clone)]
549pub struct Surface {
550    inner: Rc<SurfaceInner>,
551}
552
553impl Surface {
554    pub fn handle(&self) -> GlowFrameBuffer {
555        self.inner.framebuffer
556    }
557
558    pub fn width(&self) -> u32 {
559        self.inner.attachments[0].texture.width()
560    }
561
562    pub fn height(&self) -> u32 {
563        self.inner.attachments[0].texture.height()
564    }
565
566    pub fn attachments(&self) -> &[SurfaceAttachment] {
567        &self.inner.attachments
568    }
569
570    pub fn color(&self) -> [f32; 4] {
571        self.inner.color.get()
572    }
573
574    pub fn set_color(&mut self, value: [f32; 4]) {
575        self.inner.color.set(value);
576    }
577}
578
579impl PartialEq for Surface {
580    fn eq(&self, other: &Self) -> bool {
581        Rc::ptr_eq(&self.inner, &other.inner)
582    }
583}
584
585#[derive(Debug)]
586struct TextureInner {
587    context: MaybeContext,
588    texture: GlowTexture,
589    format: Cell<GlowTextureFormat>,
590    size: Cell<(u32, u32, u32)>,
591}
592
593impl Drop for TextureInner {
594    fn drop(&mut self) {
595        unsafe {
596            if let Some(context) = self.context.get() {
597                context.delete_texture(self.texture);
598            }
599        }
600    }
601}
602
603#[derive(Debug, Clone)]
604pub struct Texture {
605    inner: Rc<TextureInner>,
606}
607
608impl Texture {
609    pub fn handle(&self) -> GlowTexture {
610        self.inner.texture
611    }
612
613    pub fn width(&self) -> u32 {
614        self.inner.size.get().0
615    }
616
617    pub fn height(&self) -> u32 {
618        self.inner.size.get().1
619    }
620
621    pub fn depth(&self) -> u32 {
622        self.inner.size.get().2
623    }
624
625    pub fn format(&self) -> GlowTextureFormat {
626        self.inner.format.get()
627    }
628
629    pub fn upload(
630        &mut self,
631        width: u32,
632        height: u32,
633        depth: u32,
634        format: GlowTextureFormat,
635        data: Option<&[u8]>,
636    ) {
637        unsafe {
638            if let Some(context) = self.inner.context.get() {
639                context.bind_texture(TEXTURE_2D_ARRAY, Some(self.inner.texture));
640                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_S, CLAMP_TO_EDGE as _);
641                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_T, CLAMP_TO_EDGE as _);
642                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_WRAP_R, CLAMP_TO_EDGE as _);
643                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MIN_FILTER, NEAREST as _);
644                context.tex_parameter_i32(TEXTURE_2D_ARRAY, TEXTURE_MAG_FILTER, NEAREST as _);
645                context.tex_image_3d(
646                    TEXTURE_2D_ARRAY,
647                    0,
648                    format.into_gl() as _,
649                    width as _,
650                    height as _,
651                    depth as _,
652                    0,
653                    format.into_gl(),
654                    UNSIGNED_BYTE,
655                    data,
656                );
657                self.inner.size.set((width, height, depth));
658                self.inner.format.set(format);
659            }
660        }
661    }
662}
663
664impl PartialEq for Texture {
665    fn eq(&self, other: &Self) -> bool {
666        Rc::ptr_eq(&self.inner, &other.inner)
667    }
668}
669
670#[derive(Debug)]
671struct ShaderInner {
672    context: MaybeContext,
673    program: GlowProgram,
674    vertex_shader: GlowShader,
675    fragment_shader: GlowShader,
676    shared_uniforms: RefCell<HashMap<Cow<'static, str>, GlowUniformValue>>,
677}
678
679impl Drop for ShaderInner {
680    fn drop(&mut self) {
681        unsafe {
682            if let Some(context) = self.context.get() {
683                context.delete_program(self.program);
684                context.delete_shader(self.vertex_shader);
685                context.delete_shader(self.fragment_shader);
686            }
687        }
688    }
689}
690
691#[derive(Debug, Clone)]
692pub struct Shader {
693    inner: Rc<ShaderInner>,
694}
695
696impl Shader {
697    pub const PASS_VERTEX_2D: &'static str = r#"#version 300 es
698    layout(location = 0) in vec2 a_position;
699    layout(location = 2) in vec4 a_color;
700    out vec4 v_color;
701
702    void main() {
703        gl_Position = vec4(a_position, 0.0, 1.0);
704        v_color = a_color;
705    }
706    "#;
707
708    pub const PASS_VERTEX_3D: &'static str = r#"#version 300 es
709    layout(location = 0) in vec3 a_position;
710    layout(location = 3) in vec4 a_color;
711    out vec4 v_color;
712
713    void main() {
714        gl_Position = vec4(a_position, 1.0);
715        v_color = a_color;
716    }
717    "#;
718
719    pub const PASS_FRAGMENT: &'static str = r#"#version 300 es
720    precision highp float;
721    precision highp int;
722    in vec4 v_color;
723    out vec4 o_color;
724
725    void main() {
726        o_color = v_color;
727    }
728    "#;
729
730    pub const COLORED_VERTEX_2D: &'static str = r#"#version 300 es
731    layout(location = 0) in vec2 a_position;
732    layout(location = 2) in vec4 a_color;
733    out vec4 v_color;
734    uniform mat4 u_projection_view;
735
736    void main() {
737        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
738        v_color = a_color;
739    }
740    "#;
741
742    pub const COLORED_VERTEX_3D: &'static str = r#"#version 300 es
743    layout(location = 0) in vec3 a_position;
744    layout(location = 3) in vec4 a_color;
745    out vec4 v_color;
746    uniform mat4 u_projection_view;
747
748    void main() {
749        gl_Position = u_projection_view * vec4(a_position, 1.0);
750        v_color = a_color;
751    }
752    "#;
753
754    pub const TEXTURED_VERTEX_2D: &'static str = r#"#version 300 es
755    layout(location = 0) in vec2 a_position;
756    layout(location = 1) in vec3 a_uv;
757    layout(location = 2) in vec4 a_color;
758    out vec4 v_color;
759    out vec3 v_uv;
760    uniform mat4 u_projection_view;
761
762    void main() {
763        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
764        v_color = a_color;
765        v_uv = a_uv;
766    }
767    "#;
768
769    pub const TEXTURED_VERTEX_3D: &'static str = r#"#version 300 es
770    layout(location = 0) in vec3 a_position;
771    layout(location = 2) in vec3 a_uv;
772    layout(location = 3) in vec4 a_color;
773    out vec4 v_color;
774    out vec3 v_uv;
775    uniform mat4 u_projection_view;
776
777    void main() {
778        gl_Position = u_projection_view * vec4(a_position, 1.0);
779        v_color = a_color;
780        v_uv = a_uv;
781    }
782    "#;
783
784    pub const TEXTURED_FRAGMENT: &'static str = r#"#version 300 es
785    precision highp float;
786    precision highp int;
787    precision highp sampler2DArray;
788    in vec4 v_color;
789    in vec3 v_uv;
790    out vec4 o_color;
791    uniform sampler2DArray u_image;
792
793    void main() {
794        o_color = texture(u_image, v_uv) * v_color;
795    }
796    "#;
797
798    pub const TEXT_VERTEX: &'static str = r#"#version 300 es
799    layout(location = 0) in vec2 a_position;
800    layout(location = 1) in vec3 a_uv;
801    layout(location = 2) in vec4 a_color;
802    out vec4 v_color;
803    out vec3 v_uv;
804    uniform mat4 u_projection_view;
805
806    void main() {
807        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);
808        v_color = a_color;
809        v_uv = a_uv;
810    }
811    "#;
812
813    pub const TEXT_FRAGMENT: &'static str = r#"#version 300 es
814    precision highp float;
815    precision highp int;
816    precision highp sampler2DArray;
817    in vec4 v_color;
818    in vec3 v_uv;
819    out vec4 o_color;
820    uniform sampler2DArray u_image;
821
822    void main() {
823        float alpha = texture(u_image, v_uv).x;
824        o_color = vec4(v_color.xyz, v_color.w * alpha);
825    }
826    "#;
827
828    pub fn handle(&self) -> GlowProgram {
829        self.inner.program
830    }
831
832    pub fn set_shared_uniform(
833        &mut self,
834        id: impl Into<Cow<'static, str>>,
835        value: GlowUniformValue,
836    ) {
837        self.inner
838            .shared_uniforms
839            .borrow_mut()
840            .insert(id.into(), value);
841    }
842
843    pub fn unset_shared_uniform(&mut self, id: &str) {
844        self.inner.shared_uniforms.borrow_mut().remove(id);
845    }
846
847    pub fn get_shared_uniform(&self, id: &str) -> Option<GlowUniformValue> {
848        self.inner.shared_uniforms.borrow().get(id).cloned()
849    }
850
851    pub fn clear_shared_uniforms(&mut self) {
852        self.inner.shared_uniforms.borrow_mut().clear();
853    }
854}
855
856impl PartialEq for Shader {
857    fn eq(&self, other: &Self) -> bool {
858        Rc::ptr_eq(&self.inner, &other.inner)
859    }
860}