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 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}