Skip to main content

egui_baseview/renderer/opengl/
renderer.rs

1use baseview::{PhySize, Window, gl::GlConfig};
2use egui::FullOutput;
3use egui_glow::Painter;
4use std::sync::Arc;
5
6use super::OpenGlError;
7
8#[derive(Debug, Clone)]
9pub struct GraphicsConfig {
10    pub gl_config: GlConfig,
11
12    /// Controls whether to apply dithering to minimize banding artifacts.
13    ///
14    /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
15    /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
16    /// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
17    ///
18    /// Defaults to true.
19    pub dithering: bool,
20
21    /// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture.
22    /// See <https://github.com/emilk/egui/pull/1993>.
23    ///
24    /// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader").
25    pub shader_version: Option<egui_glow::ShaderVersion>,
26}
27
28impl Default for GraphicsConfig {
29    fn default() -> Self {
30        Self {
31            gl_config: GlConfig::default(),
32            shader_version: None,
33            dithering: true,
34        }
35    }
36}
37
38pub struct Renderer {
39    glow_context: Arc<egui_glow::glow::Context>,
40    painter: Painter,
41}
42
43impl Renderer {
44    pub fn new(window: &Window, config: GraphicsConfig) -> Result<Self, OpenGlError> {
45        let context = window.gl_context().ok_or(OpenGlError::NoContext)?;
46        unsafe {
47            context.make_current();
48        }
49
50        #[allow(clippy::arc_with_non_send_sync)]
51        let glow_context = Arc::new(unsafe {
52            egui_glow::glow::Context::from_loader_function(|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: &Window,
80        bg_color: egui::Rgba,
81        physical_size: PhySize,
82        pixels_per_point: f32,
83        egui_ctx: &mut egui::Context,
84        full_output: &mut FullOutput,
85    ) {
86        let PhySize {
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();
99        }
100
101        unsafe {
102            use egui_glow::glow::HasContext as _;
103            self.glow_context
104                .clear_color(bg_color.r(), bg_color.g(), bg_color.b(), bg_color.a());
105            self.glow_context.clear(egui_glow::glow::COLOR_BUFFER_BIT);
106        }
107
108        for (id, image_delta) in &textures_delta.set {
109            self.painter.set_texture(*id, image_delta);
110        }
111
112        let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
113        let dimensions: [u32; 2] = [canvas_width, canvas_height];
114
115        self.painter
116            .paint_primitives(dimensions, pixels_per_point, &clipped_primitives);
117
118        for id in textures_delta.free.drain(..) {
119            self.painter.free_texture(id);
120        }
121
122        unsafe {
123            context.swap_buffers();
124            context.make_not_current();
125        }
126    }
127}
128
129impl Drop for Renderer {
130    fn drop(&mut self) {
131        self.painter.destroy()
132    }
133}