1use std::rc::Rc;
18
19#[cfg(any(feature = "image-loading", doc, doctest))]
20use {
21 crate::image::ImageFileFormat,
22 image::GenericImageView,
23 std::fs::File,
24 std::io::{BufRead, BufReader, Seek},
25 std::path::Path
26};
27
28use crate::color::Color;
29use crate::dimen::{UVec2, Vec2};
30use crate::error::{BacktraceError, Context, ErrorMessage};
31use crate::font::{FormattedGlyph, FormattedTextBlock};
32use crate::font_cache::GlyphCache;
33use crate::glwrapper::*;
34use crate::image::{ImageDataType, ImageHandle, ImageSmoothingMode};
35use crate::{Polygon, RawBitmapData, Rect, Rectangle};
36
37struct AttributeBuffers
38{
39 position: Vec<f32>,
40 color: Vec<f32>,
41 texture_coord: Vec<f32>,
42 texture_mix: Vec<f32>,
43 circle_mix: Vec<f32>,
44
45 glbuf_position: GLBuffer,
46 glbuf_color: GLBuffer,
47 glbuf_texture_coord: GLBuffer,
48 glbuf_texture_mix: GLBuffer,
49 glbuf_circle_mix: GLBuffer
50}
51
52impl AttributeBuffers
53{
54 pub fn new(
55 context: &GLContextManager,
56 program: &GLProgram
57 ) -> Result<Self, BacktraceError<ErrorMessage>>
58 {
59 Ok(AttributeBuffers {
60 position: Vec::new(),
61 color: Vec::new(),
62 texture_coord: Vec::new(),
63 texture_mix: Vec::new(),
64 circle_mix: Vec::new(),
65
66 glbuf_position: context
67 .new_buffer(
68 GLBufferTarget::Array,
69 2,
70 program
71 .get_attribute_handle(Renderer2D::ATTR_NAME_POSITION)
72 .context("Failed to get attribute POSITION")?
73 )
74 .context("Failed to create buffer for attribute POSITION")?,
75
76 glbuf_color: context
77 .new_buffer(
78 GLBufferTarget::Array,
79 4,
80 program
81 .get_attribute_handle(Renderer2D::ATTR_NAME_COLOR)
82 .context("Failed to get attribute COLOR")?
83 )
84 .context("Failed to create buffer for attribute COLOR")?,
85
86 glbuf_texture_coord: context
87 .new_buffer(
88 GLBufferTarget::Array,
89 2,
90 program
91 .get_attribute_handle(Renderer2D::ATTR_NAME_TEXTURE_COORD)
92 .context("Failed to get attribute TEXTURE_COORD")?
93 )
94 .context("Failed to create buffer for attribute TEXTURE_COORD")?,
95
96 glbuf_texture_mix: context
97 .new_buffer(
98 GLBufferTarget::Array,
99 1,
100 program
101 .get_attribute_handle(Renderer2D::ATTR_NAME_TEXTURE_MIX)
102 .context("Failed to get attribute TEXTURE_MIX")?
103 )
104 .context("Failed to create buffer for attribute TEXTURE_MIX")?,
105
106 glbuf_circle_mix: context
107 .new_buffer(
108 GLBufferTarget::Array,
109 1,
110 program
111 .get_attribute_handle(Renderer2D::ATTR_NAME_CIRCLE_MIX)
112 .context("Failed to get attribute CIRCLE_MIX")?
113 )
114 .context("Failed to create buffer for attribute CIRCLE_MIX")?
115 })
116 }
117
118 #[inline]
119 pub fn get_vertex_count(&self) -> usize
120 {
121 self.texture_mix.len()
122 }
123
124 pub fn upload_and_clear(&mut self, context: &GLContextManager)
125 {
126 self.glbuf_position.set_data(context, &self.position);
127 self.glbuf_color.set_data(context, &self.color);
128 self.glbuf_texture_coord
129 .set_data(context, &self.texture_coord);
130 self.glbuf_texture_mix.set_data(context, &self.texture_mix);
131 self.glbuf_circle_mix.set_data(context, &self.circle_mix);
132 self.clear();
133 }
134
135 pub fn clear(&mut self)
136 {
137 self.position.clear();
138 self.color.clear();
139 self.texture_coord.clear();
140 self.texture_mix.clear();
141 self.circle_mix.clear();
142 }
143
144 #[inline]
145 pub fn append(
146 &mut self,
147 position: &Vec2,
148 color: &Color,
149 texture_coord: &Vec2,
150 texture_mix: f32,
151 circle_mix: f32
152 )
153 {
154 AttributeBuffers::push_vec2(&mut self.position, position);
155 AttributeBuffers::push_color(&mut self.color, color);
156 AttributeBuffers::push_vec2(&mut self.texture_coord, texture_coord);
157 self.texture_mix.push(texture_mix);
158 self.circle_mix.push(circle_mix);
159 }
160
161 #[inline]
162 fn push_vec2(dest: &mut Vec<f32>, vertices: &Vec2)
163 {
164 dest.push(vertices.x);
165 dest.push(vertices.y);
166 }
167
168 #[inline]
169 fn push_color(dest: &mut Vec<f32>, color: &Color)
170 {
171 dest.push(color.r());
172 dest.push(color.g());
173 dest.push(color.b());
174 dest.push(color.a());
175 }
176}
177
178struct Uniforms
179{
180 scale_x: GLUniformHandle,
181 scale_y: GLUniformHandle,
182 texture: GLUniformHandle
183}
184
185impl Uniforms
186{
187 fn new(
188 context: &GLContextManager,
189 program: &Rc<GLProgram>
190 ) -> Result<Uniforms, BacktraceError<ErrorMessage>>
191 {
192 Ok(Uniforms {
193 scale_x: program
194 .get_uniform_handle(context, Renderer2D::UNIFORM_NAME_SCALE_X)
195 .context("Failed to find SCALE_X uniform")?,
196 scale_y: program
197 .get_uniform_handle(context, Renderer2D::UNIFORM_NAME_SCALE_Y)
198 .context("Failed to find SCALE_Y uniform")?,
199 texture: program
200 .get_uniform_handle(context, Renderer2D::UNIFORM_NAME_TEXTURE)
201 .context("Failed to find TEXTURE uniform")?
202 })
203 }
204
205 fn set_viewport_size_pixels(
206 &self,
207 context: &GLContextManager,
208 viewport_size_pixels: UVec2
209 )
210 {
211 self.scale_x
212 .set_value_float(context, 2.0 / viewport_size_pixels.x as f32);
213 self.scale_y
214 .set_value_float(context, -2.0 / viewport_size_pixels.y as f32);
215 }
216
217 fn set_texture_unit(&self, context: &GLContextManager, texture_unit: i32)
218 {
219 self.texture.set_value_int(context, texture_unit);
220 }
221}
222
223pub(crate) struct Renderer2DVertex
224{
225 pub position: Vec2,
226 pub texture_coord: Vec2,
227 pub color: Color,
228 pub texture_mix: f32,
229 pub circle_mix: f32
230}
231
232impl Renderer2DVertex
233{
234 #[inline]
235 fn append_to_attribute_buffers(&self, attribute_buffers: &mut AttributeBuffers)
236 {
237 attribute_buffers.append(
238 &self.position,
239 &self.color,
240 &self.texture_coord,
241 self.texture_mix,
242 self.circle_mix
243 );
244 }
245}
246
247pub(crate) struct Renderer2DAction
248{
249 pub texture: Option<GLTexture>,
250 pub vertices_clockwise: [Renderer2DVertex; 3]
251}
252
253impl Renderer2DAction
254{
255 #[inline]
256 fn update_current_texture_if_empty(
257 &self,
258 current_texture: &mut Option<GLTexture>
259 ) -> bool
260 {
261 match &self.texture {
262 None => true,
263
264 Some(own_texture) => match current_texture {
265 None => {
266 *current_texture = Some(own_texture.clone());
267 true
268 }
269 Some(current_texture) => *current_texture == *own_texture
270 }
271 }
272 }
273
274 #[inline]
275 fn append_to_attribute_buffers(&self, attribute_buffers: &mut AttributeBuffers)
276 {
277 for vertex in self.vertices_clockwise.iter() {
278 vertex.append_to_attribute_buffers(attribute_buffers);
279 }
280 }
281}
282
283enum RenderQueueItem
284{
285 FormattedTextBlock
286 {
287 position: Vec2,
288 color: Color,
289 block: FormattedTextBlock
290 },
291
292 FormattedTextGlyph
293 {
294 position: Vec2,
295 color: Color,
296 glyph: FormattedGlyph,
297 crop_window: Rect
298 },
299
300 CircleSectionColored
301 {
302 vertex_positions_clockwise: [Vec2; 3],
303 vertex_colors_clockwise: [Color; 3],
304 vertex_normalized_circle_coords_clockwise: [Vec2; 3]
305 },
306
307 TriangleColored
308 {
309 vertex_positions_clockwise: [Vec2; 3],
310 vertex_colors_clockwise: [Color; 3]
311 },
312
313 TriangleTextured
314 {
315 vertex_positions_clockwise: [Vec2; 3],
316 vertex_colors_clockwise: [Color; 3],
317 vertex_texture_coords_clockwise: [Vec2; 3],
318 texture: GLTexture
319 }
320}
321
322impl RenderQueueItem
323{
324 #[inline]
325 fn generate_actions(
326 &self,
327 glyph_cache: &GlyphCache,
328 runner: &mut impl FnMut(Renderer2DAction)
329 )
330 {
331 match self {
332 RenderQueueItem::FormattedTextBlock {
333 position,
334 color,
335 block
336 } => {
337 for line in block.iter_lines() {
338 for glyph in line.iter_glyphs() {
339 glyph_cache.get_renderer2d_actions(
340 glyph, *position, *color, None, runner
341 );
342 }
343 }
344 }
345
346 RenderQueueItem::FormattedTextGlyph {
347 glyph,
348 position,
349 color,
350 crop_window
351 } => {
352 glyph_cache.get_renderer2d_actions(
353 glyph,
354 *position,
355 *color,
356 Some(crop_window),
357 runner
358 );
359 }
360
361 RenderQueueItem::CircleSectionColored {
362 vertex_positions_clockwise,
363 vertex_colors_clockwise,
364 vertex_normalized_circle_coords_clockwise
365 } => runner(Renderer2DAction {
366 texture: None,
367 vertices_clockwise: [
368 Renderer2DVertex {
369 position: vertex_positions_clockwise[0],
370 texture_coord: vertex_normalized_circle_coords_clockwise[0],
371 color: vertex_colors_clockwise[0],
372 texture_mix: 0.0,
373 circle_mix: 1.0
374 },
375 Renderer2DVertex {
376 position: vertex_positions_clockwise[1],
377 texture_coord: vertex_normalized_circle_coords_clockwise[1],
378 color: vertex_colors_clockwise[1],
379 texture_mix: 0.0,
380 circle_mix: 1.0
381 },
382 Renderer2DVertex {
383 position: vertex_positions_clockwise[2],
384 texture_coord: vertex_normalized_circle_coords_clockwise[2],
385 color: vertex_colors_clockwise[2],
386 texture_mix: 0.0,
387 circle_mix: 1.0
388 }
389 ]
390 }),
391
392 RenderQueueItem::TriangleColored {
393 vertex_positions_clockwise,
394 vertex_colors_clockwise
395 } => runner(Renderer2DAction {
396 texture: None,
397 vertices_clockwise: [
398 Renderer2DVertex {
399 position: vertex_positions_clockwise[0],
400 texture_coord: Vec2::ZERO,
401 color: vertex_colors_clockwise[0],
402 texture_mix: 0.0,
403 circle_mix: 0.0
404 },
405 Renderer2DVertex {
406 position: vertex_positions_clockwise[1],
407 texture_coord: Vec2::ZERO,
408 color: vertex_colors_clockwise[1],
409 texture_mix: 0.0,
410 circle_mix: 0.0
411 },
412 Renderer2DVertex {
413 position: vertex_positions_clockwise[2],
414 texture_coord: Vec2::ZERO,
415 color: vertex_colors_clockwise[2],
416 texture_mix: 0.0,
417 circle_mix: 0.0
418 }
419 ]
420 }),
421
422 RenderQueueItem::TriangleTextured {
423 vertex_positions_clockwise,
424 vertex_colors_clockwise,
425 vertex_texture_coords_clockwise,
426 texture
427 } => runner(Renderer2DAction {
428 texture: Some(texture.clone()),
429 vertices_clockwise: [
430 Renderer2DVertex {
431 position: vertex_positions_clockwise[0],
432 texture_coord: vertex_texture_coords_clockwise[0],
433 color: vertex_colors_clockwise[0],
434 texture_mix: 1.0,
435 circle_mix: 0.0
436 },
437 Renderer2DVertex {
438 position: vertex_positions_clockwise[1],
439 texture_coord: vertex_texture_coords_clockwise[1],
440 color: vertex_colors_clockwise[1],
441 texture_mix: 1.0,
442 circle_mix: 0.0
443 },
444 Renderer2DVertex {
445 position: vertex_positions_clockwise[2],
446 texture_coord: vertex_texture_coords_clockwise[2],
447 color: vertex_colors_clockwise[2],
448 texture_mix: 1.0,
449 circle_mix: 0.0
450 }
451 ]
452 })
453 }
454 }
455}
456
457pub struct Renderer2D
458{
459 context: GLContextManager,
460
461 program: Rc<GLProgram>,
462
463 render_queue: Vec<RenderQueueItem>,
464
465 glyph_cache: GlyphCache,
466 attribute_buffers: AttributeBuffers,
467 current_texture: Option<GLTexture>,
468
469 #[allow(dead_code)]
470 uniforms: Uniforms
471}
472
473impl Renderer2D
474{
475 const ATTR_NAME_POSITION: &'static str = "in_Position";
476 const ATTR_NAME_COLOR: &'static str = "in_Color";
477 const ATTR_NAME_TEXTURE_COORD: &'static str = "in_TextureCoord";
478 const ATTR_NAME_TEXTURE_MIX: &'static str = "in_TextureMix";
479 const ATTR_NAME_CIRCLE_MIX: &'static str = "in_CircleMix";
480
481 const UNIFORM_NAME_SCALE_X: &'static str = "in_ScaleX";
482 const UNIFORM_NAME_SCALE_Y: &'static str = "in_ScaleY";
483 const UNIFORM_NAME_TEXTURE: &'static str = "in_Texture";
484
485 const ALL_ATTRIBUTES: [&'static str; 5] = [
486 Renderer2D::ATTR_NAME_POSITION,
487 Renderer2D::ATTR_NAME_COLOR,
488 Renderer2D::ATTR_NAME_TEXTURE_COORD,
489 Renderer2D::ATTR_NAME_TEXTURE_MIX,
490 Renderer2D::ATTR_NAME_CIRCLE_MIX
491 ];
492
493 pub fn new(
494 context: &GLContextManager,
495 viewport_size_pixels: UVec2
496 ) -> Result<Self, BacktraceError<ErrorMessage>>
497 {
498 log::info!("Creating vertex shader");
499
500 let (vertex_shader_src, fragment_shader_src) = match context.version() {
501 GLVersion::OpenGL2_0 => {
502 log::info!("Using OpenGL 2.0 shaders");
503 (
504 include_str!("shaders/r2d_vertex_v110.glsl"),
505 include_str!("shaders/r2d_fragment_v110.glsl")
506 )
507 }
508 GLVersion::WebGL2_0 => {
509 log::info!("Using WebGL 2.0 shaders");
510 (
511 include_str!("shaders/r2d_vertex_v300es.glsl"),
512 include_str!("shaders/r2d_fragment_v300es.glsl")
513 )
514 }
515 };
516
517 let vertex_shader = context
518 .new_shader(GLShaderType::Vertex, vertex_shader_src)
519 .context("Failed to create Renderer2D vertex shader")?;
520
521 log::info!("Creating fragment shader");
522
523 let fragment_shader = context
524 .new_shader(GLShaderType::Fragment, fragment_shader_src)
525 .context("Failed to create Renderer2D fragment shader")?;
526
527 log::info!("Compiling program");
528
529 let program = context
530 .new_program(
531 &vertex_shader,
532 &fragment_shader,
533 &Renderer2D::ALL_ATTRIBUTES
534 )
535 .context("Failed to create Renderer2D program")?;
536
537 let attribute_buffers = AttributeBuffers::new(context, &program)?;
538 let uniforms = Uniforms::new(context, &program)?;
539
540 context.use_program(&program);
541
542 uniforms.set_texture_unit(context, 0);
543
544 uniforms.set_viewport_size_pixels(context, viewport_size_pixels);
545
546 context.set_viewport_size(viewport_size_pixels);
547
548 Ok(Renderer2D {
549 context: context.clone(),
550 program,
551 render_queue: Vec::new(),
552 glyph_cache: GlyphCache::new(),
553 attribute_buffers,
554 current_texture: None,
555 uniforms
556 })
557 }
558
559 pub fn set_viewport_size_pixels(&self, viewport_size_pixels: UVec2)
560 {
561 self.uniforms
562 .set_viewport_size_pixels(&self.context, viewport_size_pixels);
563
564 self.context.set_viewport_size(viewport_size_pixels);
565 }
566
567 pub fn finish_frame(&mut self)
568 {
569 self.flush_render_queue();
570 self.glyph_cache.on_new_frame_start();
571 }
572
573 fn flush_render_queue(&mut self)
574 {
575 if self.render_queue.is_empty() {
576 return;
577 }
578
579 self.attribute_buffers.clear();
580
581 let mut has_text = false;
582
583 for item in &self.render_queue {
584 match item {
585 RenderQueueItem::FormattedTextBlock {
586 block, position, ..
587 } => {
588 for line in block.iter_lines() {
589 for glyph in line.iter_glyphs() {
590 self.glyph_cache.add_to_cache(
591 &self.context,
592 glyph,
593 *position
594 );
595 }
596 }
597
598 has_text = true;
599 }
600 RenderQueueItem::FormattedTextGlyph {
601 glyph, position, ..
602 } => {
603 self.glyph_cache
604 .add_to_cache(&self.context, glyph, *position);
605 has_text = true;
606 }
607 RenderQueueItem::CircleSectionColored { .. }
608 | RenderQueueItem::TriangleColored { .. }
609 | RenderQueueItem::TriangleTextured { .. } => {}
610 }
611 }
612
613 if has_text {
614 if let Err(err) = self.glyph_cache.prepare_for_draw(&self.context) {
615 log::error!("Error updating font texture, continuing anyway: {:?}", err);
616 }
617 }
618
619 {
620 let current_texture = &mut self.current_texture;
621 let context = &self.context;
622 let program = &self.program;
623 let attribute_buffers = &mut self.attribute_buffers;
624
625 for item in &self.render_queue {
626 item.generate_actions(&self.glyph_cache, &mut |action| {
627 if !action.update_current_texture_if_empty(current_texture) {
628 Renderer2D::draw_buffers(
629 context,
630 program,
631 attribute_buffers,
632 current_texture
633 );
634
635 current_texture.clone_from(&action.texture);
636 }
637
638 action.append_to_attribute_buffers(attribute_buffers);
639 });
640 }
641 }
642
643 self.render_queue.clear();
644
645 Renderer2D::draw_buffers(
646 &self.context,
647 &self.program,
648 &mut self.attribute_buffers,
649 &mut self.current_texture
650 );
651 }
652
653 fn draw_buffers(
654 context: &GLContextManager,
655 program: &Rc<GLProgram>,
656 attribute_buffers: &mut AttributeBuffers,
657 current_texture: &mut Option<GLTexture>
658 )
659 {
660 let vertex_count = attribute_buffers.get_vertex_count();
661
662 if vertex_count == 0 {
663 return;
664 }
665
666 context.use_program(program);
667
668 attribute_buffers.upload_and_clear(context);
669
670 let current_texture = current_texture.take();
671
672 match ¤t_texture {
673 None => context.unbind_texture(),
674 Some(texture) => context.bind_texture(texture)
675 }
676
677 context.draw_triangles(
678 GLBlendEnabled::Enabled(GLBlendMode::OneMinusSrcAlpha),
679 vertex_count
680 );
681 }
682
683 pub(crate) fn create_image_from_raw_pixels<S: Into<UVec2>>(
684 &self,
685 data_type: ImageDataType,
686 smoothing_mode: ImageSmoothingMode,
687 size: S,
688 data: &[u8]
689 ) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
690 {
691 let size = size.into();
692
693 let pixel_bytes = match data_type {
694 ImageDataType::RGB => 3,
695 ImageDataType::RGBA => 4
696 };
697
698 {
699 let expected_bytes = pixel_bytes * size.x as usize * size.y as usize;
700
701 if expected_bytes != data.len() {
702 return Err(ErrorMessage::msg(format!(
703 "Expecting {} bytes ({}x{}x{}), got {}",
704 expected_bytes,
705 size.x,
706 size.y,
707 pixel_bytes,
708 data.len()
709 )));
710 }
711 }
712
713 let gl_format = data_type.into();
714
715 let gl_smoothing = match smoothing_mode {
716 ImageSmoothingMode::NearestNeighbor => GLTextureSmoothing::NearestNeighbour,
717 ImageSmoothingMode::Linear => GLTextureSmoothing::Linear
718 };
719
720 let texture = self
721 .context
722 .new_texture()
723 .context("Failed to create GPU texture")?;
724
725 texture
726 .set_image_data(&self.context, gl_format, gl_smoothing, &size, data)
727 .context("Failed to upload image data")?;
728
729 Ok(ImageHandle { size, texture })
730 }
731
732 #[cfg(any(feature = "image-loading", doc, doctest))]
733 pub fn create_image_from_file_path<P: AsRef<Path>>(
734 &mut self,
735 data_type: Option<ImageFileFormat>,
736 smoothing_mode: ImageSmoothingMode,
737 path: P
738 ) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
739 {
740 let file = File::open(path.as_ref()).context(format!(
741 "Failed to open file '{:?}' for reading",
742 path.as_ref()
743 ))?;
744
745 self.create_image_from_file_bytes(data_type, smoothing_mode, BufReader::new(file))
746 }
747
748 #[cfg(any(feature = "image-loading", doc, doctest))]
749 pub fn create_image_from_file_bytes<R: Seek + BufRead>(
750 &mut self,
751 data_type: Option<ImageFileFormat>,
752 smoothing_mode: ImageSmoothingMode,
753 file_bytes: R
754 ) -> Result<ImageHandle, BacktraceError<ErrorMessage>>
755 {
756 let mut reader = image::io::Reader::new(file_bytes);
757
758 match data_type {
759 None => {
760 reader = reader
761 .with_guessed_format()
762 .context("Could not guess file format")?
763 }
764 Some(format) => reader.set_format(match format {
765 ImageFileFormat::PNG => image::ImageFormat::Png,
766 ImageFileFormat::JPEG => image::ImageFormat::Jpeg,
767 ImageFileFormat::GIF => image::ImageFormat::Gif,
768 ImageFileFormat::BMP => image::ImageFormat::Bmp,
769 ImageFileFormat::ICO => image::ImageFormat::Ico,
770 ImageFileFormat::TIFF => image::ImageFormat::Tiff,
771 ImageFileFormat::WebP => image::ImageFormat::WebP,
772 ImageFileFormat::AVIF => image::ImageFormat::Avif,
773 ImageFileFormat::PNM => image::ImageFormat::Pnm,
774 ImageFileFormat::DDS => image::ImageFormat::Dds,
775 ImageFileFormat::TGA => image::ImageFormat::Tga,
776 ImageFileFormat::Farbfeld => image::ImageFormat::Farbfeld
777 })
778 }
779
780 let image = reader.decode().context("Failed to parse image data")?;
781
782 let dimensions = image.dimensions();
783
784 let bytes_rgba8 = image.into_rgba8().into_raw();
785
786 self.create_image_from_raw_pixels(
787 ImageDataType::RGBA,
788 smoothing_mode,
789 dimensions,
790 bytes_rgba8.as_slice()
791 )
792 }
793
794 #[inline]
795 pub(crate) fn clear_screen(&mut self, color: Color)
796 {
797 if color.a() < 1.0 {
798 self.flush_render_queue();
799 } else {
800 self.render_queue.clear();
801 }
802
803 self.context.clear_screen(color);
804 }
805
806 #[inline]
807 fn add_to_render_queue(&mut self, item: RenderQueueItem)
808 {
809 self.render_queue.push(item);
810
811 if self.render_queue.len() > 100000 {
812 self.flush_render_queue();
813 }
814 }
815
816 #[inline]
817 pub(crate) fn draw_polygon<V: Into<Vec2>>(
818 &mut self,
819 polygon: &Polygon,
820 offset: V,
821 color: Color
822 )
823 {
824 let color = [color; 3];
825 let offset = offset.into();
826
827 for triangle in polygon.triangles.iter() {
828 let triangle = triangle.map(|vertex| vertex + offset);
829
830 self.draw_triangle_three_color(triangle, color);
831 }
832 }
833
834 #[inline]
835 pub(crate) fn draw_triangle_three_color(
836 &mut self,
837 vertex_positions_clockwise: [Vec2; 3],
838 vertex_colors_clockwise: [Color; 3]
839 )
840 {
841 self.add_to_render_queue(RenderQueueItem::TriangleColored {
842 vertex_positions_clockwise,
843 vertex_colors_clockwise
844 })
845 }
846
847 #[inline]
848 pub(crate) fn draw_triangle_image_tinted(
849 &mut self,
850 vertex_positions_clockwise: [Vec2; 3],
851 vertex_colors_clockwise: [Color; 3],
852 vertex_texture_coords_clockwise: [Vec2; 3],
853 image: &ImageHandle
854 )
855 {
856 self.add_to_render_queue(RenderQueueItem::TriangleTextured {
857 vertex_positions_clockwise,
858 vertex_colors_clockwise,
859 vertex_texture_coords_clockwise,
860 texture: image.texture.clone()
861 })
862 }
863
864 #[inline]
865 pub(crate) fn draw_text<V: Into<Vec2>>(
866 &mut self,
867 position: V,
868 color: Color,
869 text: &FormattedTextBlock
870 )
871 {
872 self.add_to_render_queue(RenderQueueItem::FormattedTextBlock {
873 position: position.into(),
874 color,
875 block: text.clone()
876 })
877 }
878
879 #[inline]
880 pub(crate) fn draw_text_cropped<V: Into<Vec2>>(
881 &mut self,
882 position: V,
883 crop_window: Rect,
884 color: Color,
885 text: &FormattedTextBlock
886 )
887 {
888 let position = position.into();
889
890 for line in text.iter_lines() {
891 for glyph in line.iter_glyphs() {
892 if let Some(glyph_outline) = glyph.pixel_bounding_box() {
893 let glyph_outline = glyph_outline.with_offset(position);
894 if glyph_outline.intersect(&crop_window).is_some() {
895 self.add_to_render_queue(RenderQueueItem::FormattedTextGlyph {
896 position,
897 color,
898 glyph: glyph.clone(),
899 crop_window: crop_window.clone()
900 })
901 }
902 }
903 }
904 }
905 }
906
907 #[inline]
908 pub(crate) fn draw_circle_section(
909 &mut self,
910 vertex_positions_clockwise: [Vec2; 3],
911 vertex_colors_clockwise: [Color; 3],
912 vertex_normalized_circle_coords_clockwise: [Vec2; 3]
913 )
914 {
915 self.add_to_render_queue(RenderQueueItem::CircleSectionColored {
916 vertex_positions_clockwise,
917 vertex_colors_clockwise,
918 vertex_normalized_circle_coords_clockwise
919 })
920 }
921
922 #[inline]
923 pub(crate) fn set_clip(&mut self, rect: Option<Rectangle<i32>>)
924 {
925 self.flush_render_queue();
928 match rect {
929 None => self.context.set_enable_scissor(false),
930 Some(rect) => {
931 self.context.set_enable_scissor(true);
932 self.context.set_clip(
933 rect.top_left().x,
934 rect.top_left().y,
935 rect.width(),
936 rect.height()
937 )
938 }
939 }
940 }
941
942 pub(crate) fn capture(&mut self, format: ImageDataType) -> RawBitmapData
943 {
944 self.flush_render_queue();
945 self.context.capture(format)
946 }
947}