1#![allow(clippy::collapsible_else_if)]
2#![allow(unsafe_code)]
3
4use std::{collections::HashMap, sync::Arc};
5
6use egui::{
7 emath::Rect,
8 epaint::{Mesh, PaintCallbackInfo, Primitive, Vertex},
9};
10use glow::HasContext as _;
11use memoffset::offset_of;
12
13use crate::check_for_gl_error;
14use crate::misc_util::{compile_shader, link_program};
15use crate::shader_version::ShaderVersion;
16use crate::vao;
17
18pub use glow::Context;
20
21const VERT_SRC: &str = include_str!("shader/vertex.glsl");
22const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
23
24trait TextureFilterExt {
25 fn glow_code(&self, mipmap: Option<egui::TextureFilter>) -> u32;
26}
27
28impl TextureFilterExt for egui::TextureFilter {
29 fn glow_code(&self, mipmap: Option<egui::TextureFilter>) -> u32 {
30 match (self, mipmap) {
31 (Self::Linear, None) => glow::LINEAR,
32 (Self::Nearest, None) => glow::NEAREST,
33 (Self::Linear, Some(Self::Linear)) => glow::LINEAR_MIPMAP_LINEAR,
34 (Self::Nearest, Some(Self::Linear)) => glow::NEAREST_MIPMAP_LINEAR,
35 (Self::Linear, Some(Self::Nearest)) => glow::LINEAR_MIPMAP_NEAREST,
36 (Self::Nearest, Some(Self::Nearest)) => glow::NEAREST_MIPMAP_NEAREST,
37 }
38 }
39}
40
41trait TextureWrapModeExt {
42 fn glow_code(&self) -> u32;
43}
44
45impl TextureWrapModeExt for egui::TextureWrapMode {
46 fn glow_code(&self) -> u32 {
47 match self {
48 Self::ClampToEdge => glow::CLAMP_TO_EDGE,
49 Self::Repeat => glow::REPEAT,
50 Self::MirroredRepeat => glow::MIRRORED_REPEAT,
51 }
52 }
53}
54
55#[derive(Debug)]
56pub struct PainterError(String);
57
58impl std::error::Error for PainterError {}
59
60impl std::fmt::Display for PainterError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "OpenGL: {}", self.0)
63 }
64}
65
66impl From<String> for PainterError {
67 #[inline]
68 fn from(value: String) -> Self {
69 Self(value)
70 }
71}
72
73pub struct Painter {
83 gl: Arc<glow::Context>,
84
85 max_texture_side: usize,
86
87 program: glow::Program,
88 u_screen_size: glow::UniformLocation,
89 u_sampler: glow::UniformLocation,
90 is_webgl_1: bool,
91 vao: crate::vao::VertexArrayObject,
92 srgb_textures: bool,
93 supports_srgb_framebuffer: bool,
94 vbo: glow::Buffer,
95 element_array_buffer: glow::Buffer,
96
97 textures: HashMap<egui::TextureId, glow::Texture>,
98
99 next_native_tex_id: u64,
100
101 textures_to_destroy: Vec<glow::Texture>,
103
104 destroyed: bool,
106}
107
108pub struct CallbackFn {
118 f: Box<dyn Fn(PaintCallbackInfo, &Painter) + Sync + Send>,
119}
120
121impl CallbackFn {
122 pub fn new<F: Fn(PaintCallbackInfo, &Painter) + Sync + Send + 'static>(callback: F) -> Self {
123 let f = Box::new(callback);
124 Self { f }
125 }
126}
127
128impl Painter {
129 pub fn new(
142 gl: Arc<glow::Context>,
143 shader_prefix: &str,
144 shader_version: Option<ShaderVersion>,
145 dithering: bool,
146 ) -> Result<Self, PainterError> {
147 profiling::function_scope!();
148 crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
149
150 unsafe {
152 let version = gl.get_parameter_string(glow::VERSION);
153 let renderer = gl.get_parameter_string(glow::RENDERER);
154 let vendor = gl.get_parameter_string(glow::VENDOR);
155 log::debug!(
156 "\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
157 );
158 }
159
160 #[cfg(not(target_arch = "wasm32"))]
161 if gl.version().major < 2 {
162 return Err(PainterError("egui_glow requires opengl 2.0+. ".to_owned()));
165 }
166
167 let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
168 let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
169 let is_webgl_1 = shader_version == ShaderVersion::Es100;
170 let shader_version_declaration = shader_version.version_declaration();
171 log::debug!("Shader header: {:?}.", shader_version_declaration);
172
173 let supported_extensions = gl.supported_extensions();
174 log::trace!("OpenGL extensions: {supported_extensions:?}");
175 let srgb_textures = shader_version == ShaderVersion::Es300 || supported_extensions.iter().any(|extension| {
177 extension.contains("sRGB")
179 });
180 log::debug!("SRGB texture Support: {:?}", srgb_textures);
181
182 let supports_srgb_framebuffer = !cfg!(target_arch = "wasm32")
183 && supported_extensions.iter().any(|extension| {
184 extension.ends_with("ARB_framebuffer_sRGB")
186 });
187 log::debug!("SRGB framebuffer Support: {:?}", supports_srgb_framebuffer);
188
189 unsafe {
190 let vert = compile_shader(
191 &gl,
192 glow::VERTEX_SHADER,
193 &format!(
194 "{}\n#define NEW_SHADER_INTERFACE {}\n{}\n{}",
195 shader_version_declaration,
196 shader_version.is_new_shader_interface() as i32,
197 shader_prefix,
198 VERT_SRC
199 ),
200 )?;
201 let frag = compile_shader(
202 &gl,
203 glow::FRAGMENT_SHADER,
204 &format!(
205 "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n#define SRGB_TEXTURES {}\n{}\n{}",
206 shader_version_declaration,
207 shader_version.is_new_shader_interface() as i32,
208 dithering as i32,
209 srgb_textures as i32,
210 shader_prefix,
211 FRAG_SRC
212 ),
213 )?;
214 let program = link_program(&gl, [vert, frag].iter())?;
215 gl.detach_shader(program, vert);
216 gl.detach_shader(program, frag);
217 gl.delete_shader(vert);
218 gl.delete_shader(frag);
219 let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
220 let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
221
222 let vbo = gl.create_buffer()?;
223
224 let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
225 let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
226 let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
227
228 let stride = std::mem::size_of::<Vertex>() as i32;
229 let buffer_infos = vec![
230 vao::BufferInfo {
231 location: a_pos_loc,
232 vector_size: 2,
233 data_type: glow::FLOAT,
234 normalized: false,
235 stride,
236 offset: offset_of!(Vertex, pos) as i32,
237 },
238 vao::BufferInfo {
239 location: a_tc_loc,
240 vector_size: 2,
241 data_type: glow::FLOAT,
242 normalized: false,
243 stride,
244 offset: offset_of!(Vertex, uv) as i32,
245 },
246 vao::BufferInfo {
247 location: a_srgba_loc,
248 vector_size: 4,
249 data_type: glow::UNSIGNED_BYTE,
250 normalized: false,
251 stride,
252 offset: offset_of!(Vertex, color) as i32,
253 },
254 ];
255 let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos);
256
257 let element_array_buffer = gl.create_buffer()?;
258
259 crate::check_for_gl_error_even_in_release!(&gl, "after Painter::new");
260
261 Ok(Self {
262 gl,
263 max_texture_side,
264 program,
265 u_screen_size,
266 u_sampler,
267 is_webgl_1,
268 vao,
269 srgb_textures,
270 supports_srgb_framebuffer,
271 vbo,
272 element_array_buffer,
273 textures: Default::default(),
274 next_native_tex_id: 1 << 32,
275 textures_to_destroy: Vec::new(),
276 destroyed: false,
277 })
278 }
279 }
280
281 pub fn gl(&self) -> &Arc<glow::Context> {
283 &self.gl
284 }
285
286 pub fn max_texture_side(&self) -> usize {
287 self.max_texture_side
288 }
289
290 #[allow(clippy::unused_self)]
300 pub fn intermediate_fbo(&self) -> Option<glow::Framebuffer> {
301 None
304 }
305
306 unsafe fn prepare_painting(
307 &mut self,
308 [width_in_pixels, height_in_pixels]: [u32; 2],
309 pixels_per_point: f32,
310 ) {
311 unsafe {
312 self.gl.enable(glow::SCISSOR_TEST);
313 self.gl.disable(glow::CULL_FACE);
315 self.gl.disable(glow::DEPTH_TEST);
316
317 self.gl.color_mask(true, true, true, true);
318
319 self.gl.enable(glow::BLEND);
320 self.gl
321 .blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD);
322 self.gl.blend_func_separate(
323 glow::ONE,
325 glow::ONE_MINUS_SRC_ALPHA,
326 glow::ONE_MINUS_DST_ALPHA,
329 glow::ONE,
330 );
331
332 if self.supports_srgb_framebuffer {
333 self.gl.disable(glow::FRAMEBUFFER_SRGB);
334 check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
335 }
336
337 let width_in_points = width_in_pixels as f32 / pixels_per_point;
338 let height_in_points = height_in_pixels as f32 / pixels_per_point;
339
340 self.gl
341 .viewport(0, 0, width_in_pixels as i32, height_in_pixels as i32);
342 self.gl.use_program(Some(self.program));
343
344 self.gl
345 .uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
346 self.gl.uniform_1_i32(Some(&self.u_sampler), 0);
347 self.gl.active_texture(glow::TEXTURE0);
348
349 self.vao.bind(&self.gl);
350 self.gl
351 .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
352 }
353
354 check_for_gl_error!(&self.gl, "prepare_painting");
355 }
356
357 pub fn clear(&self, screen_size_in_pixels: [u32; 2], clear_color: [f32; 4]) {
358 clear(&self.gl, screen_size_in_pixels, clear_color);
359 }
360
361 pub fn paint_and_update_textures(
363 &mut self,
364 screen_size_px: [u32; 2],
365 pixels_per_point: f32,
366 clipped_primitives: &[egui::ClippedPrimitive],
367 textures_delta: &egui::TexturesDelta,
368 ) {
369 profiling::function_scope!();
370
371 for (id, image_delta) in &textures_delta.set {
372 self.set_texture(*id, image_delta);
373 }
374
375 self.paint_primitives(screen_size_px, pixels_per_point, clipped_primitives);
376
377 for &id in &textures_delta.free {
378 self.free_texture(id);
379 }
380 }
381
382 pub fn paint_primitives(
403 &mut self,
404 screen_size_px: [u32; 2],
405 pixels_per_point: f32,
406 clipped_primitives: &[egui::ClippedPrimitive],
407 ) {
408 profiling::function_scope!();
409 self.assert_not_destroyed();
410
411 unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
412
413 for egui::ClippedPrimitive {
414 clip_rect,
415 primitive,
416 } in clipped_primitives
417 {
418 set_clip_rect(&self.gl, screen_size_px, pixels_per_point, *clip_rect);
419
420 match primitive {
421 Primitive::Mesh(mesh) => {
422 self.paint_mesh(mesh);
423 }
424 Primitive::Callback(callback) => {
425 if callback.rect.is_positive() {
426 profiling::scope!("callback");
427
428 let info = egui::PaintCallbackInfo {
429 viewport: callback.rect,
430 clip_rect: *clip_rect,
431 pixels_per_point,
432 screen_size_px,
433 };
434
435 let viewport_px = info.viewport_in_pixels();
436 unsafe {
437 self.gl.viewport(
438 viewport_px.left_px,
439 viewport_px.from_bottom_px,
440 viewport_px.width_px,
441 viewport_px.height_px,
442 );
443 }
444
445 if let Some(callback) = callback.callback.downcast_ref::<CallbackFn>() {
446 (callback.f)(info, self);
447 } else {
448 log::warn!("Warning: Unsupported render callback. Expected egui_glow::CallbackFn");
449 }
450
451 check_for_gl_error!(&self.gl, "callback");
452
453 unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
455 }
456 }
457 }
458 }
459
460 unsafe {
461 self.vao.unbind(&self.gl);
462 self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
463
464 self.gl.disable(glow::SCISSOR_TEST);
465
466 check_for_gl_error!(&self.gl, "painting");
467 }
468 }
469
470 #[inline(never)] fn paint_mesh(&mut self, mesh: &Mesh) {
472 debug_assert!(mesh.is_valid());
473 if let Some(texture) = self.texture(mesh.texture_id) {
474 unsafe {
475 self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
476 self.gl.buffer_data_u8_slice(
477 glow::ARRAY_BUFFER,
478 bytemuck::cast_slice(&mesh.vertices),
479 glow::STREAM_DRAW,
480 );
481
482 self.gl
483 .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
484 self.gl.buffer_data_u8_slice(
485 glow::ELEMENT_ARRAY_BUFFER,
486 bytemuck::cast_slice(&mesh.indices),
487 glow::STREAM_DRAW,
488 );
489
490 self.gl.bind_texture(glow::TEXTURE_2D, Some(texture));
491 }
492
493 unsafe {
494 self.gl.draw_elements(
495 glow::TRIANGLES,
496 mesh.indices.len() as i32,
497 glow::UNSIGNED_INT,
498 0,
499 );
500 }
501
502 check_for_gl_error!(&self.gl, "paint_mesh");
503 } else {
504 log::warn!("Failed to find texture {:?}", mesh.texture_id);
505 }
506 }
507
508 pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
511 profiling::function_scope!();
512
513 self.assert_not_destroyed();
514
515 let glow_texture = *self
516 .textures
517 .entry(tex_id)
518 .or_insert_with(|| unsafe { self.gl.create_texture().unwrap() });
519 unsafe {
520 self.gl.bind_texture(glow::TEXTURE_2D, Some(glow_texture));
521 }
522
523 match &delta.image {
524 egui::ImageData::Color(image) => {
525 assert_eq!(
526 image.width() * image.height(),
527 image.pixels.len(),
528 "Mismatch between texture size and texel count"
529 );
530
531 let data: &[u8] = bytemuck::cast_slice(image.pixels.as_ref());
532
533 self.upload_texture_srgb(delta.pos, image.size, delta.options, data);
534 }
535 egui::ImageData::Font(image) => {
536 assert_eq!(
537 image.width() * image.height(),
538 image.pixels.len(),
539 "Mismatch between texture size and texel count"
540 );
541
542 let data: Vec<u8> = {
543 profiling::scope!("font -> sRGBA");
544 image
545 .srgba_pixels(None)
546 .flat_map(|a| a.to_array())
547 .collect()
548 };
549
550 self.upload_texture_srgb(delta.pos, image.size, delta.options, &data);
551 }
552 };
553 }
554
555 fn upload_texture_srgb(
556 &mut self,
557 pos: Option<[usize; 2]>,
558 [w, h]: [usize; 2],
559 options: egui::TextureOptions,
560 data: &[u8],
561 ) {
562 profiling::function_scope!();
563 assert_eq!(data.len(), w * h * 4);
564 assert!(
565 w <= self.max_texture_side && h <= self.max_texture_side,
566 "Got a texture image of size {}x{}, but the maximum supported texture side is only {}",
567 w,
568 h,
569 self.max_texture_side
570 );
571
572 unsafe {
573 self.gl.tex_parameter_i32(
574 glow::TEXTURE_2D,
575 glow::TEXTURE_MAG_FILTER,
576 options.magnification.glow_code(None) as i32,
577 );
578 self.gl.tex_parameter_i32(
579 glow::TEXTURE_2D,
580 glow::TEXTURE_MIN_FILTER,
581 options.minification.glow_code(options.mipmap_mode) as i32,
582 );
583
584 self.gl.tex_parameter_i32(
585 glow::TEXTURE_2D,
586 glow::TEXTURE_WRAP_S,
587 options.wrap_mode.glow_code() as i32,
588 );
589 self.gl.tex_parameter_i32(
590 glow::TEXTURE_2D,
591 glow::TEXTURE_WRAP_T,
592 options.wrap_mode.glow_code() as i32,
593 );
594 check_for_gl_error!(&self.gl, "tex_parameter");
595
596 let (internal_format, src_format) = if self.is_webgl_1 {
597 let format = if self.srgb_textures {
598 glow::SRGB_ALPHA
599 } else {
600 glow::RGBA
601 };
602 (format, format)
603 } else if self.srgb_textures {
604 (glow::SRGB8_ALPHA8, glow::RGBA)
605 } else {
606 (glow::RGBA8, glow::RGBA)
607 };
608
609 self.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
610
611 let level = 0;
612 if let Some([x, y]) = pos {
613 profiling::scope!("gl.tex_sub_image_2d");
614 self.gl.tex_sub_image_2d(
615 glow::TEXTURE_2D,
616 level,
617 x as _,
618 y as _,
619 w as _,
620 h as _,
621 src_format,
622 glow::UNSIGNED_BYTE,
623 glow::PixelUnpackData::Slice(Some(data)),
624 );
625 check_for_gl_error!(&self.gl, "tex_sub_image_2d");
626 } else {
627 let border = 0;
628 profiling::scope!("gl.tex_image_2d");
629 self.gl.tex_image_2d(
630 glow::TEXTURE_2D,
631 level,
632 internal_format as _,
633 w as _,
634 h as _,
635 border,
636 src_format,
637 glow::UNSIGNED_BYTE,
638 glow::PixelUnpackData::Slice(Some(data)),
639 );
640 check_for_gl_error!(&self.gl, "tex_image_2d");
641 }
642
643 if options.mipmap_mode.is_some() {
644 self.gl.generate_mipmap(glow::TEXTURE_2D);
645 check_for_gl_error!(&self.gl, "generate_mipmap");
646 }
647 }
648 }
649
650 pub fn free_texture(&mut self, tex_id: egui::TextureId) {
651 if let Some(old_tex) = self.textures.remove(&tex_id) {
652 unsafe { self.gl.delete_texture(old_tex) };
653 }
654 }
655
656 pub fn texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
658 self.textures.get(&texture_id).copied()
659 }
660
661 #[allow(clippy::needless_pass_by_value)] pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId {
663 self.assert_not_destroyed();
664 let id = egui::TextureId::User(self.next_native_tex_id);
665 self.next_native_tex_id += 1;
666 self.textures.insert(id, native);
667 id
668 }
669
670 #[allow(clippy::needless_pass_by_value)] pub fn replace_native_texture(&mut self, id: egui::TextureId, replacing: glow::Texture) {
672 if let Some(old_tex) = self.textures.insert(id, replacing) {
673 self.textures_to_destroy.push(old_tex);
674 }
675 }
676
677 pub fn read_screen_rgba(&self, [w, h]: [u32; 2]) -> egui::ColorImage {
678 profiling::function_scope!();
679
680 let mut pixels = vec![0_u8; (w * h * 4) as usize];
681 unsafe {
682 self.gl.read_pixels(
683 0,
684 0,
685 w as _,
686 h as _,
687 glow::RGBA,
688 glow::UNSIGNED_BYTE,
689 glow::PixelPackData::Slice(Some(&mut pixels)),
690 );
691 }
692 let mut flipped = Vec::with_capacity((w * h * 4) as usize);
693 for row in pixels.chunks_exact((w * 4) as usize).rev() {
694 flipped.extend_from_slice(bytemuck::cast_slice(row));
695 }
696 egui::ColorImage {
697 size: [w as usize, h as usize],
698 pixels: flipped,
699 }
700 }
701
702 pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
703 profiling::function_scope!();
704 let mut pixels = vec![0_u8; (w * h * 3) as usize];
705 unsafe {
706 self.gl.read_pixels(
707 0,
708 0,
709 w as _,
710 h as _,
711 glow::RGB,
712 glow::UNSIGNED_BYTE,
713 glow::PixelPackData::Slice(Some(&mut pixels)),
714 );
715 }
716 pixels
717 }
718
719 unsafe fn destroy_gl(&self) {
720 unsafe {
721 self.gl.delete_program(self.program);
722 for tex in self.textures.values() {
723 self.gl.delete_texture(*tex);
724 }
725 self.gl.delete_buffer(self.vbo);
726 self.gl.delete_buffer(self.element_array_buffer);
727 for t in &self.textures_to_destroy {
728 self.gl.delete_texture(*t);
729 }
730 }
731 }
732
733 pub fn destroy(&mut self) {
736 if !self.destroyed {
737 unsafe {
738 self.destroy_gl();
739 }
740 self.destroyed = true;
741 }
742 }
743
744 fn assert_not_destroyed(&self) {
745 assert!(!self.destroyed, "the egui glow has already been destroyed!");
746 }
747}
748
749pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: [f32; 4]) {
750 profiling::function_scope!();
751 unsafe {
752 gl.disable(glow::SCISSOR_TEST);
753
754 gl.viewport(
755 0,
756 0,
757 screen_size_in_pixels[0] as i32,
758 screen_size_in_pixels[1] as i32,
759 );
760 gl.clear_color(
761 clear_color[0],
762 clear_color[1],
763 clear_color[2],
764 clear_color[3],
765 );
766 gl.clear(glow::COLOR_BUFFER_BIT);
767 }
768}
769
770impl Drop for Painter {
771 fn drop(&mut self) {
772 if !self.destroyed {
773 log::warn!(
774 "You forgot to call destroy() on the egui glow painter. Resources will leak!"
775 );
776 }
777 }
778}
779
780fn set_clip_rect(
781 gl: &glow::Context,
782 [width_px, height_px]: [u32; 2],
783 pixels_per_point: f32,
784 clip_rect: Rect,
785) {
786 let clip_min_x = pixels_per_point * clip_rect.min.x;
788 let clip_min_y = pixels_per_point * clip_rect.min.y;
789 let clip_max_x = pixels_per_point * clip_rect.max.x;
790 let clip_max_y = pixels_per_point * clip_rect.max.y;
791
792 let clip_min_x = clip_min_x.round() as i32;
794 let clip_min_y = clip_min_y.round() as i32;
795 let clip_max_x = clip_max_x.round() as i32;
796 let clip_max_y = clip_max_y.round() as i32;
797
798 let clip_min_x = clip_min_x.clamp(0, width_px as i32);
800 let clip_min_y = clip_min_y.clamp(0, height_px as i32);
801 let clip_max_x = clip_max_x.clamp(clip_min_x, width_px as i32);
802 let clip_max_y = clip_max_y.clamp(clip_min_y, height_px as i32);
803
804 unsafe {
805 gl.scissor(
806 clip_min_x,
807 height_px as i32 - clip_max_y,
808 clip_max_x - clip_min_x,
809 clip_max_y - clip_min_y,
810 );
811 }
812}