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