Skip to main content

egui_baseview/renderer/opengl/
renderer.rs

1use super::OpenGlError;
2use baseview::WindowContext;
3use baseview::dpi::PhysicalSize;
4use baseview::gl::GlConfig;
5use egui::FullOutput;
6use egui_glow::Painter;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct GraphicsConfig {
11    pub gl_config: GlConfig,
12
13    /// Controls whether to apply dithering to minimize banding artifacts.
14    ///
15    /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
16    /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
17    /// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
18    ///
19    /// Defaults to true.
20    pub dithering: bool,
21
22    /// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture.
23    /// See <https://github.com/emilk/egui/pull/1993>.
24    ///
25    /// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader").
26    pub shader_version: Option<egui_glow::ShaderVersion>,
27}
28
29impl Default for GraphicsConfig {
30    fn default() -> Self {
31        Self {
32            gl_config: GlConfig::default(),
33            shader_version: None,
34            dithering: true,
35        }
36    }
37}
38
39pub struct Renderer {
40    pub glow_context: Arc<egui_glow::glow::Context>,
41    painter: Painter,
42}
43
44impl Renderer {
45    pub fn new(window: WindowContext, config: GraphicsConfig) -> Result<Self, OpenGlError> {
46        let context = window.gl_context().ok_or(OpenGlError::NoContext)?;
47        unsafe {
48            context.make_current()?;
49        }
50
51        let glow_context = Arc::new(unsafe {
52            egui_glow::glow::Context::from_loader_function_cstr(|s| context.get_proc_address(s))
53        });
54
55        let painter = egui_glow::Painter::new(
56            Arc::clone(&glow_context),
57            "",
58            config.shader_version,
59            config.dithering,
60        )
61        .map_err(OpenGlError::CreatePainter)?;
62
63        unsafe {
64            context.make_not_current()?;
65        }
66
67        Ok(Self {
68            glow_context,
69            painter,
70        })
71    }
72
73    pub fn max_texture_side(&self) -> usize {
74        self.painter.max_texture_side()
75    }
76
77    pub fn render(
78        &mut self,
79        window: &WindowContext,
80        clear_color: egui::Rgba,
81        physical_size: PhysicalSize<u32>,
82        pixels_per_point: f32,
83        egui_ctx: &mut egui::Context,
84        full_output: &mut FullOutput,
85    ) {
86        let PhysicalSize {
87            width: canvas_width,
88            height: canvas_height,
89        } = physical_size;
90
91        let shapes = std::mem::take(&mut full_output.shapes);
92        let textures_delta = &mut full_output.textures_delta;
93
94        let context = window
95            .gl_context()
96            .expect("failed to get baseview gl context");
97        unsafe {
98            context.make_current().unwrap();
99        }
100
101        unsafe {
102            use egui_glow::glow::HasContext as _;
103            self.glow_context.clear_color(
104                clear_color.r(),
105                clear_color.g(),
106                clear_color.b(),
107                clear_color.a(),
108            );
109            self.glow_context.clear(egui_glow::glow::COLOR_BUFFER_BIT);
110        }
111
112        #[expect(clippy::iter_over_hash_type)] // Order doesn't matter here
113        for (id, image_deltas) in textures_delta.set.drain() {
114            for image_delta in image_deltas {
115                self.painter.set_texture(id, &image_delta);
116            }
117        }
118
119        let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
120        let dimensions: [u32; 2] = [canvas_width, canvas_height];
121
122        self.painter
123            .paint_primitives(dimensions, pixels_per_point, &clipped_primitives);
124
125        #[expect(clippy::iter_over_hash_type)] // Order doesn't matter here
126        for id in textures_delta.free.drain() {
127            self.painter.free_texture(id);
128        }
129
130        unsafe {
131            context.swap_buffers().unwrap();
132            context.make_not_current().unwrap();
133        }
134    }
135}
136
137impl Drop for Renderer {
138    fn drop(&mut self) {
139        self.painter.destroy()
140    }
141}