Skip to main content

egui_baseview/renderer/opengl/
renderer.rs

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