imgui_glow_renderer/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(rust_2018_idioms)]
3// #![deny(missing_docs)]
4
5use std::{borrow::Cow, error::Error, fmt::Display, mem::size_of, num::NonZeroU32, rc::Rc};
6
7use imgui::{
8    internal::RawWrapper, Context as ImGuiContext, DrawCmd, DrawCmdParams, DrawData, DrawIdx,
9    DrawVert, FontAtlas, TextureId, Textures,
10};
11
12use crate::versions::{GlVersion, GlslVersion};
13
14// Re-export glow to make it easier for users to use the correct version.
15pub use glow;
16use glow::{Context, HasContext};
17
18pub mod versions;
19
20pub type GlBuffer = <Context as HasContext>::Buffer;
21pub type GlTexture = <Context as HasContext>::Texture;
22pub type GlVertexArray = <Context as HasContext>::VertexArray;
23type GlProgram = <Context as HasContext>::Program;
24type GlUniformLocation = <Context as HasContext>::UniformLocation;
25
26/// Renderer which owns the OpenGL context and handles textures itself. Also
27/// converts all output colors to sRGB for display. Useful for simple applications,
28/// but more complicated applications may prefer to use [`Renderer`], or even
29/// write their own renderer based on this code.
30///
31/// OpenGL context is still available to the rest of the application through
32/// the [`gl_context`](Self::gl_context) method.
33pub struct AutoRenderer {
34    gl: Rc<glow::Context>,
35    texture_map: SimpleTextureMap,
36    renderer: Renderer,
37}
38
39impl AutoRenderer {
40    /// Creates a new [`AutoRenderer`] for simple rendering.
41    ///
42    /// # Errors
43    /// Any error initialising the OpenGL objects (including shaders) will
44    /// result in an error.
45    pub fn new(gl: glow::Context, imgui_context: &mut ImGuiContext) -> Result<Self, InitError> {
46        let mut texture_map = SimpleTextureMap::default();
47        let renderer = Renderer::new(&gl, imgui_context, &mut texture_map, true)?;
48        Ok(Self {
49            gl: Rc::new(gl),
50            texture_map,
51            renderer,
52        })
53    }
54
55    /// Creates a new [`AutoRenderer`] for simple rendering.
56    #[deprecated(since = "0.13.0", note = "use `new` instead")]
57    pub fn initialize(
58        gl: glow::Context,
59        imgui_context: &mut ImGuiContext,
60    ) -> Result<Self, InitError> {
61        Self::new(gl, imgui_context)
62    }
63
64    /// Note: no need to provide a `mut` version of this, as all methods on
65    /// [`glow::HasContext`] are immutable.
66    #[inline]
67    pub fn gl_context(&self) -> &Rc<glow::Context> {
68        &self.gl
69    }
70
71    #[inline]
72    pub fn texture_map(&self) -> &SimpleTextureMap {
73        &self.texture_map
74    }
75
76    #[inline]
77    pub fn texture_map_mut(&mut self) -> &mut SimpleTextureMap {
78        &mut self.texture_map
79    }
80
81    #[inline]
82    pub fn renderer(&self) -> &Renderer {
83        &self.renderer
84    }
85
86    /// # Errors
87    /// Some OpenGL errors trigger an error (few are explicitly checked,
88    /// however)
89    #[inline]
90    pub fn render(&mut self, draw_data: &DrawData) -> Result<(), RenderError> {
91        self.renderer.render(&self.gl, &self.texture_map, draw_data)
92    }
93}
94
95impl Drop for AutoRenderer {
96    fn drop(&mut self) {
97        self.renderer.destroy(&self.gl);
98    }
99}
100
101/// Main renderer. Borrows the OpenGL context and [texture map](TextureMap)
102/// when required.
103pub struct Renderer {
104    shaders: Shaders,
105    state_backup: GlStateBackup,
106    pub vbo_handle: Option<GlBuffer>,
107    pub ebo_handle: Option<GlBuffer>,
108    pub font_atlas_texture: Option<GlTexture>,
109    #[cfg(feature = "bind_vertex_array_support")]
110    pub vertex_array_object: Option<GlVertexArray>,
111    pub gl_version: GlVersion,
112    pub has_clip_origin_support: bool,
113    pub is_destroyed: bool,
114}
115
116impl Renderer {
117    /// Create the renderer, initialising OpenGL objects and shaders.
118    ///
119    /// `output_srgb` controls whether the shader outputs sRGB colors, or linear
120    /// RGB colors. In short:
121    /// - If you're outputting to a display and haven't specified the framebuffer
122    ///   is sRGB (e.g. with `gl.enable(glow::FRAMEBUFFER_SRGB)`), then you probably
123    ///   want `output_srgb=true`.
124    /// - OpenGL ES doesn't support sRGB framebuffers, so you almost always
125    ///   want `output_srgb=true` if you're using OpenGL ES and you're outputting
126    ///   to a display.
127    /// - If you're outputting to a display with an sRGB framebuffer (e.g. with
128    ///   `gl.enable(glow::FRAMEBUFFER_SRGB)`), then you probably want
129    ///   `output_srgb=false`, as OpenGL will convert to sRGB itself.
130    /// - If you're not outputting to a display, but instead to some intermediate
131    ///   framebuffer, then you probably want `output_srgb=false` to keep the
132    ///   colors in linear color space, and then convert them to sRGB at some
133    ///   later stage.
134    ///
135    /// # Errors
136    /// Any error initialising the OpenGL objects (including shaders) will
137    /// result in an error.
138    pub fn new<T: TextureMap>(
139        gl: &Context,
140        imgui_context: &mut ImGuiContext,
141        texture_map: &mut T,
142        output_srgb: bool,
143    ) -> Result<Self, InitError> {
144        #![allow(
145            clippy::similar_names,
146            clippy::cast_sign_loss,
147            clippy::shadow_unrelated
148        )]
149
150        let gl_version = GlVersion::read(gl);
151
152        #[cfg(feature = "clip_origin_support")]
153        let has_clip_origin_support = {
154            let support = gl_version.clip_origin_support();
155
156            #[cfg(feature = "gl_extensions_support")]
157            if support {
158                support
159            } else {
160                let extensions_count = unsafe { gl.get_parameter_i32(glow::NUM_EXTENSIONS) } as u32;
161                (0..extensions_count).any(|index| {
162                    let extension_name =
163                        unsafe { gl.get_parameter_indexed_string(glow::EXTENSIONS, index) };
164                    extension_name == "GL_ARB_clip_control"
165                })
166            }
167            #[cfg(not(feature = "gl_extensions_support"))]
168            support
169        };
170        #[cfg(not(feature = "clip_origin_support"))]
171        let has_clip_origin_support = false;
172
173        let mut state_backup = GlStateBackup::default();
174        state_backup.pre_init(gl);
175
176        let font_atlas_texture = prepare_font_atlas(gl, imgui_context.fonts(), texture_map)?;
177
178        let shaders = Shaders::new(gl, gl_version, output_srgb)?;
179        let vbo_handle = unsafe { gl.create_buffer() }.map_err(InitError::CreateBufferObject)?;
180        let ebo_handle = unsafe { gl.create_buffer() }.map_err(InitError::CreateBufferObject)?;
181
182        state_backup.post_init(gl);
183
184        let out = Self {
185            shaders,
186            state_backup,
187            vbo_handle: Some(vbo_handle),
188            ebo_handle: Some(ebo_handle),
189            font_atlas_texture: Some(font_atlas_texture),
190            #[cfg(feature = "bind_vertex_array_support")]
191            vertex_array_object: None,
192            gl_version,
193            has_clip_origin_support,
194            is_destroyed: false,
195        };
196
197        // Leave this until the end of the function to avoid changing state if
198        // there was ever an error above
199        out.configure_imgui_context(imgui_context);
200
201        Ok(out)
202    }
203
204    /// Create the renderer, initialising OpenGL objects and shaders.
205    #[deprecated(since = "0.13.0", note = "use `new` instead")]
206    pub fn initialize<T: TextureMap>(
207        gl: &Context,
208        imgui_context: &mut ImGuiContext,
209        texture_map: &mut T,
210        output_srgb: bool,
211    ) -> Result<Self, InitError> {
212        Self::new(gl, imgui_context, texture_map, output_srgb)
213    }
214
215    /// This must be called before being dropped to properly free OpenGL
216    /// resources.
217    pub fn destroy(&mut self, gl: &Context) {
218        if self.is_destroyed {
219            return;
220        }
221
222        if let Some(h) = self.vbo_handle {
223            unsafe { gl.delete_buffer(h) };
224            self.vbo_handle = None;
225        }
226        if let Some(h) = self.ebo_handle {
227            unsafe { gl.delete_buffer(h) };
228            self.ebo_handle = None;
229        }
230        if let Some(p) = self.shaders.program {
231            unsafe { gl.delete_program(p) };
232            self.shaders.program = None;
233        }
234        if let Some(h) = self.font_atlas_texture {
235            unsafe { gl.delete_texture(h) };
236            self.font_atlas_texture = None;
237        }
238
239        self.is_destroyed = true;
240    }
241
242    /// # Errors
243    /// Some OpenGL errors trigger an error (few are explicitly checked,
244    /// however)
245    pub fn render<T: TextureMap>(
246        &mut self,
247        gl: &Context,
248        texture_map: &T,
249        draw_data: &DrawData,
250    ) -> Result<(), RenderError> {
251        if self.is_destroyed {
252            return Err(Self::renderer_destroyed());
253        }
254
255        let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
256        let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
257        if !(fb_width > 0.0 && fb_height > 0.0) {
258            return Ok(());
259        }
260
261        gl_debug_message(gl, "imgui-rs-glow: start render");
262        self.state_backup.pre_render(gl, self.gl_version);
263
264        #[cfg(feature = "bind_vertex_array_support")]
265        if self.gl_version.bind_vertex_array_support() {
266            unsafe {
267                self.vertex_array_object = Some(
268                    gl.create_vertex_array()
269                        .map_err(|err| format!("Error creating vertex array object: {}", err))?,
270                );
271                gl.bind_vertex_array(self.vertex_array_object);
272            }
273        }
274
275        self.set_up_render_state(gl, draw_data, fb_width, fb_height)?;
276
277        gl_debug_message(gl, "start loop over draw lists");
278        for draw_list in draw_data.draw_lists() {
279            unsafe {
280                gl.buffer_data_u8_slice(
281                    glow::ARRAY_BUFFER,
282                    to_byte_slice(draw_list.vtx_buffer()),
283                    glow::STREAM_DRAW,
284                );
285                gl.buffer_data_u8_slice(
286                    glow::ELEMENT_ARRAY_BUFFER,
287                    to_byte_slice(draw_list.idx_buffer()),
288                    glow::STREAM_DRAW,
289                );
290            }
291
292            gl_debug_message(gl, "start loop over commands");
293            for command in draw_list.commands() {
294                match command {
295                    DrawCmd::Elements { count, cmd_params } => self.render_elements(
296                        gl,
297                        texture_map,
298                        count,
299                        cmd_params,
300                        draw_data,
301                        fb_width,
302                        fb_height,
303                    ),
304                    DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
305                        callback(draw_list.raw(), raw_cmd)
306                    },
307                    DrawCmd::ResetRenderState => {
308                        self.set_up_render_state(gl, draw_data, fb_width, fb_height)?
309                    }
310                }
311            }
312        }
313
314        #[cfg(feature = "bind_vertex_array_support")]
315        if self.gl_version.bind_vertex_array_support() {
316            unsafe { gl.delete_vertex_array(self.vertex_array_object.unwrap()) };
317            self.vertex_array_object = None;
318        }
319
320        self.state_backup.post_render(gl, self.gl_version);
321        gl_debug_message(gl, "imgui-rs-glow: complete render");
322        Ok(())
323    }
324
325    /// # Errors
326    /// Few GL calls are checked for errors, but any that are found will result
327    /// in an error. Errors from the state manager lifecycle callbacks will also
328    /// result in an error.
329    pub fn set_up_render_state(
330        &mut self,
331        gl: &Context,
332        draw_data: &DrawData,
333        fb_width: f32,
334        fb_height: f32,
335    ) -> Result<(), RenderError> {
336        #![allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
337
338        if self.is_destroyed {
339            return Err(Self::renderer_destroyed());
340        }
341
342        unsafe {
343            gl.active_texture(glow::TEXTURE0);
344            gl.enable(glow::BLEND);
345            gl.blend_equation(glow::FUNC_ADD);
346            gl.blend_func_separate(
347                glow::SRC_ALPHA,
348                glow::ONE_MINUS_SRC_ALPHA,
349                glow::ONE,
350                glow::ONE_MINUS_SRC_ALPHA,
351            );
352            gl.disable(glow::CULL_FACE);
353            gl.disable(glow::DEPTH_TEST);
354            gl.disable(glow::STENCIL_TEST);
355            gl.enable(glow::SCISSOR_TEST);
356
357            #[cfg(feature = "primitive_restart_support")]
358            if self.gl_version.primitive_restart_support() {
359                gl.disable(glow::PRIMITIVE_RESTART);
360            }
361
362            #[cfg(feature = "polygon_mode_support")]
363            if self.gl_version.polygon_mode_support() {
364                gl.polygon_mode(glow::FRONT_AND_BACK, glow::FILL);
365            }
366
367            gl.viewport(0, 0, fb_width as _, fb_height as _);
368        }
369
370        #[cfg(feature = "clip_origin_support")]
371        let clip_origin_is_lower_left = if self.has_clip_origin_support {
372            unsafe { gl.get_parameter_i32(glow::CLIP_ORIGIN) != glow::UPPER_LEFT as i32 }
373        } else {
374            true
375        };
376        #[cfg(not(feature = "clip_origin_support"))]
377        let clip_origin_is_lower_left = true;
378
379        let projection_matrix = calculate_matrix(draw_data, clip_origin_is_lower_left);
380
381        unsafe {
382            gl.use_program(self.shaders.program);
383            gl.uniform_1_i32(Some(&self.shaders.texture_uniform_location), 0);
384            gl.uniform_matrix_4_f32_slice(
385                Some(&self.shaders.matrix_uniform_location),
386                false,
387                &projection_matrix,
388            );
389        }
390
391        #[cfg(feature = "bind_sampler_support")]
392        if self.gl_version.bind_sampler_support() {
393            unsafe { gl.bind_sampler(0, None) };
394        }
395
396        // TODO: soon it should be possible for these to be `const` functions
397        let position_field_offset = memoffset::offset_of!(DrawVert, pos) as _;
398        let uv_field_offset = memoffset::offset_of!(DrawVert, uv) as _;
399        let color_field_offset = memoffset::offset_of!(DrawVert, col) as _;
400
401        unsafe {
402            gl.bind_buffer(glow::ARRAY_BUFFER, self.vbo_handle);
403            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, self.ebo_handle);
404            gl.enable_vertex_attrib_array(self.shaders.position_attribute_index);
405            gl.vertex_attrib_pointer_f32(
406                self.shaders.position_attribute_index,
407                2,
408                glow::FLOAT,
409                false,
410                size_of::<DrawVert>() as _,
411                position_field_offset,
412            );
413            gl.enable_vertex_attrib_array(self.shaders.uv_attribute_index);
414            gl.vertex_attrib_pointer_f32(
415                self.shaders.uv_attribute_index,
416                2,
417                glow::FLOAT,
418                false,
419                size_of::<DrawVert>() as _,
420                uv_field_offset,
421            );
422            gl.enable_vertex_attrib_array(self.shaders.color_attribute_index);
423            gl.vertex_attrib_pointer_f32(
424                self.shaders.color_attribute_index,
425                4,
426                glow::UNSIGNED_BYTE,
427                true,
428                size_of::<DrawVert>() as _,
429                color_field_offset,
430            );
431        }
432
433        Ok(())
434    }
435
436    #[allow(clippy::too_many_arguments)]
437    fn render_elements<T: TextureMap>(
438        &self,
439        gl: &Context,
440        texture_map: &T,
441        element_count: usize,
442        element_params: DrawCmdParams,
443        draw_data: &DrawData,
444        fb_width: f32,
445        fb_height: f32,
446    ) {
447        #![allow(
448            clippy::similar_names,
449            clippy::cast_possible_truncation,
450            clippy::cast_possible_wrap
451        )]
452
453        let DrawCmdParams {
454            clip_rect,
455            texture_id,
456            vtx_offset,
457            idx_offset,
458        } = element_params;
459        let clip_off = draw_data.display_pos;
460        let scale = draw_data.framebuffer_scale;
461
462        let clip_x1 = (clip_rect[0] - clip_off[0]) * scale[0];
463        let clip_y1 = (clip_rect[1] - clip_off[1]) * scale[1];
464        let clip_x2 = (clip_rect[2] - clip_off[0]) * scale[0];
465        let clip_y2 = (clip_rect[3] - clip_off[1]) * scale[1];
466
467        if clip_x1 >= fb_width || clip_y1 >= fb_height || clip_x2 < 0.0 || clip_y2 < 0.0 {
468            return;
469        }
470
471        unsafe {
472            gl.scissor(
473                clip_x1 as i32,
474                (fb_height - clip_y2) as i32,
475                (clip_x2 - clip_x1) as i32,
476                (clip_y2 - clip_y1) as i32,
477            );
478            gl.bind_texture(glow::TEXTURE_2D, texture_map.gl_texture(texture_id));
479
480            #[cfg(feature = "vertex_offset_support")]
481            let with_offset = self.gl_version.vertex_offset_support();
482            #[cfg(not(feature = "vertex_offset_support"))]
483            let with_offset = false;
484
485            if with_offset {
486                gl.draw_elements_base_vertex(
487                    glow::TRIANGLES,
488                    element_count as _,
489                    imgui_index_type_as_gl(),
490                    (idx_offset * size_of::<DrawIdx>()) as _,
491                    vtx_offset as _,
492                );
493            } else {
494                gl.draw_elements(
495                    glow::TRIANGLES,
496                    element_count as _,
497                    imgui_index_type_as_gl(),
498                    (idx_offset * size_of::<DrawIdx>()) as _,
499                );
500            }
501        }
502    }
503
504    fn configure_imgui_context(&self, imgui_context: &mut ImGuiContext) {
505        imgui_context.set_renderer_name(Some(format!(
506            "imgui-rs-glow-render {}",
507            env!("CARGO_PKG_VERSION")
508        )));
509
510        #[cfg(feature = "vertex_offset_support")]
511        if self.gl_version.vertex_offset_support() {
512            imgui_context
513                .io_mut()
514                .backend_flags
515                .insert(imgui::BackendFlags::RENDERER_HAS_VTX_OFFSET);
516        }
517    }
518
519    fn renderer_destroyed() -> RenderError {
520        "Renderer is destroyed".into()
521    }
522}
523
524/// Trait for mapping imgui texture IDs to OpenGL textures.
525///
526/// [`register`] should be called after uploading a texture to OpenGL to get a
527/// [`TextureId`] corresponding to it.
528///
529/// [`register`]: Self::register
530///
531/// Then [`gl_texture`] can be called to find the OpenGL texture corresponding to
532/// that [`TextureId`].
533///
534/// [`gl_texture`]: Self::gl_texture
535pub trait TextureMap {
536    fn register(&mut self, gl_texture: GlTexture) -> Option<TextureId>;
537
538    fn gl_texture(&self, imgui_texture: TextureId) -> Option<GlTexture>;
539}
540
541/// Texture map where the imgui texture ID is simply numerically equal to the
542/// OpenGL texture ID.
543#[derive(Default)]
544pub struct SimpleTextureMap();
545
546impl TextureMap for SimpleTextureMap {
547    #[inline(always)]
548    fn register(&mut self, gl_texture: glow::Texture) -> Option<TextureId> {
549        Some(TextureId::new(gl_texture.0.get() as _))
550    }
551
552    #[inline(always)]
553    fn gl_texture(&self, imgui_texture: TextureId) -> Option<glow::Texture> {
554        #[allow(clippy::cast_possible_truncation)]
555        Some(glow::NativeTexture(
556            NonZeroU32::new(imgui_texture.id() as _).unwrap(),
557        ))
558    }
559}
560
561/// [`Textures`] is a simple choice for a texture map.
562impl TextureMap for Textures<glow::Texture> {
563    fn register(&mut self, gl_texture: glow::Texture) -> Option<TextureId> {
564        Some(self.insert(gl_texture))
565    }
566
567    fn gl_texture(&self, imgui_texture: TextureId) -> Option<glow::Texture> {
568        self.get(imgui_texture).copied()
569    }
570}
571
572/// This OpenGL state backup is based on the upstream OpenGL example from
573/// imgui, where an attempt is made to save and restore the OpenGL context state
574/// before and after rendering.
575///
576/// If you're writing your own renderer, you can likely streamline most of this.
577///
578/// It is unlikely that any such attempt will be comprehensive for all possible
579/// applications, due to the complexity of OpenGL and the possibility of
580/// arbitrary extensions. However, it remains as a useful tool for quickly
581/// getting started. If your application needs more state to be backed up and
582/// restored, it is probably best to do this manually before/after calling
583/// the render method rather than opening an issue to add more to this
584/// struct.
585#[allow(clippy::struct_excessive_bools)]
586#[derive(Default)]
587pub struct GlStateBackup {
588    active_texture: u32,
589    program: u32,
590    texture: u32,
591    #[cfg(feature = "bind_sampler_support")]
592    sampler: Option<u32>,
593    array_buffer: u32,
594    #[cfg(feature = "polygon_mode_support")]
595    polygon_mode: Option<[i32; 2]>,
596    viewport: [i32; 4],
597    scissor_box: [i32; 4],
598    blend_src_rgb: i32,
599    blend_dst_rgb: i32,
600    blend_src_alpha: i32,
601    blend_dst_alpha: i32,
602    blend_equation_rgb: i32,
603    blend_equation_alpha: i32,
604    blend_enabled: bool,
605    cull_face_enabled: bool,
606    depth_test_enabled: bool,
607    stencil_test_enabled: bool,
608    scissor_test_enabled: bool,
609    #[cfg(feature = "primitive_restart_support")]
610    primitive_restart_enabled: Option<bool>,
611    #[cfg(feature = "bind_vertex_array_support")]
612    vertex_array_object: Option<u32>,
613}
614
615fn to_native_gl<T>(handle: u32, constructor: fn(NonZeroU32) -> T) -> Option<T> {
616    if handle != 0 {
617        Some(constructor(NonZeroU32::new(handle).unwrap()))
618    } else {
619        None
620    }
621}
622
623impl GlStateBackup {
624    fn pre_init(&mut self, gl: &Context) {
625        self.texture = unsafe { gl.get_parameter_i32(glow::TEXTURE_BINDING_2D) as _ };
626    }
627
628    fn post_init(&mut self, gl: &Context) {
629        #[allow(clippy::cast_sign_loss)]
630        unsafe {
631            gl.bind_texture(
632                glow::TEXTURE_2D,
633                to_native_gl(self.texture, glow::NativeTexture),
634            );
635        }
636    }
637
638    // note: the CFG is because `gl_version` is unused in `no-default-features`
639    fn pre_render(&mut self, gl: &Context, #[allow(unused_variables)] gl_version: GlVersion) {
640        #[allow(clippy::cast_sign_loss)]
641        unsafe {
642            self.active_texture = gl.get_parameter_i32(glow::ACTIVE_TEXTURE) as _;
643            self.program = gl.get_parameter_i32(glow::CURRENT_PROGRAM) as _;
644            self.texture = gl.get_parameter_i32(glow::TEXTURE_BINDING_2D) as _;
645            #[cfg(feature = "bind_sampler_support")]
646            if gl_version.bind_sampler_support() {
647                self.sampler = Some(gl.get_parameter_i32(glow::SAMPLER_BINDING) as _);
648            } else {
649                self.sampler = None;
650            }
651            self.array_buffer = gl.get_parameter_i32(glow::ARRAY_BUFFER_BINDING) as _;
652
653            #[cfg(feature = "bind_vertex_array_support")]
654            if gl_version.bind_vertex_array_support() {
655                self.vertex_array_object =
656                    Some(gl.get_parameter_i32(glow::VERTEX_ARRAY_BINDING) as _);
657            }
658
659            #[cfg(feature = "polygon_mode_support")]
660            if gl_version.polygon_mode_support() {
661                if self.polygon_mode.is_none() {
662                    self.polygon_mode = Some(Default::default());
663                }
664                gl.get_parameter_i32_slice(glow::POLYGON_MODE, self.polygon_mode.as_mut().unwrap());
665            } else {
666                self.polygon_mode = None;
667            }
668            gl.get_parameter_i32_slice(glow::VIEWPORT, &mut self.viewport);
669            gl.get_parameter_i32_slice(glow::SCISSOR_BOX, &mut self.scissor_box);
670            self.blend_src_rgb = gl.get_parameter_i32(glow::BLEND_SRC_RGB);
671            self.blend_dst_rgb = gl.get_parameter_i32(glow::BLEND_DST_RGB);
672            self.blend_src_alpha = gl.get_parameter_i32(glow::BLEND_SRC_ALPHA);
673            self.blend_dst_alpha = gl.get_parameter_i32(glow::BLEND_DST_ALPHA);
674            self.blend_equation_rgb = gl.get_parameter_i32(glow::BLEND_EQUATION_RGB);
675            self.blend_equation_alpha = gl.get_parameter_i32(glow::BLEND_EQUATION_ALPHA);
676            self.blend_enabled = gl.is_enabled(glow::BLEND);
677            self.cull_face_enabled = gl.is_enabled(glow::CULL_FACE);
678            self.depth_test_enabled = gl.is_enabled(glow::DEPTH_TEST);
679            self.stencil_test_enabled = gl.is_enabled(glow::STENCIL_TEST);
680            self.scissor_test_enabled = gl.is_enabled(glow::SCISSOR_TEST);
681            #[cfg(feature = "primitive_restart_support")]
682            if gl_version.primitive_restart_support() {
683                self.primitive_restart_enabled = Some(gl.is_enabled(glow::PRIMITIVE_RESTART));
684            } else {
685                self.primitive_restart_enabled = None;
686            }
687        }
688    }
689
690    fn post_render(&mut self, gl: &Context, _gl_version: GlVersion) {
691        #![allow(clippy::cast_sign_loss)]
692        unsafe {
693            gl.use_program(to_native_gl(self.program, glow::NativeProgram));
694            gl.bind_texture(
695                glow::TEXTURE_2D,
696                to_native_gl(self.texture, glow::NativeTexture),
697            );
698            #[cfg(feature = "bind_sampler_support")]
699            if let Some(sampler) = self.sampler {
700                gl.bind_sampler(0, to_native_gl(sampler, glow::NativeSampler));
701            }
702            gl.active_texture(self.active_texture as _);
703            #[cfg(feature = "bind_vertex_array_support")]
704            if let Some(vao) = self.vertex_array_object {
705                gl.bind_vertex_array(to_native_gl(vao, glow::NativeVertexArray));
706            }
707            gl.bind_buffer(
708                glow::ARRAY_BUFFER,
709                to_native_gl(self.array_buffer, glow::NativeBuffer),
710            );
711            gl.blend_equation_separate(
712                self.blend_equation_rgb as _,
713                self.blend_equation_alpha as _,
714            );
715            gl.blend_func_separate(
716                self.blend_src_rgb as _,
717                self.blend_dst_rgb as _,
718                self.blend_src_alpha as _,
719                self.blend_dst_alpha as _,
720            );
721            if self.blend_enabled {
722                gl.enable(glow::BLEND)
723            } else {
724                gl.disable(glow::BLEND);
725            }
726            if self.cull_face_enabled {
727                gl.enable(glow::CULL_FACE)
728            } else {
729                gl.disable(glow::CULL_FACE)
730            }
731            if self.depth_test_enabled {
732                gl.enable(glow::DEPTH_TEST)
733            } else {
734                gl.disable(glow::DEPTH_TEST)
735            }
736            if self.stencil_test_enabled {
737                gl.enable(glow::STENCIL_TEST)
738            } else {
739                gl.disable(glow::STENCIL_TEST)
740            }
741            if self.scissor_test_enabled {
742                gl.enable(glow::SCISSOR_TEST)
743            } else {
744                gl.disable(glow::SCISSOR_TEST)
745            }
746            #[cfg(feature = "primitive_restart_support")]
747            if let Some(restart_enabled) = self.primitive_restart_enabled {
748                if restart_enabled {
749                    gl.enable(glow::PRIMITIVE_RESTART)
750                } else {
751                    gl.disable(glow::PRIMITIVE_RESTART)
752                }
753            }
754            #[cfg(feature = "polygon_mode_support")]
755            if let Some([mode, _]) = self.polygon_mode {
756                gl.polygon_mode(glow::FRONT_AND_BACK, mode as _);
757            }
758            gl.viewport(
759                self.viewport[0],
760                self.viewport[1],
761                self.viewport[2],
762                self.viewport[3],
763            );
764            gl.scissor(
765                self.scissor_box[0],
766                self.scissor_box[1],
767                self.scissor_box[2],
768                self.scissor_box[3],
769            );
770        }
771    }
772}
773
774/// Parses `GL_VERSION` and `GL_SHADING_LANGUAGE_VERSION` at runtime in order to
775/// generate shaders which should work on a wide variety of modern devices
776/// (GL >= 3.3 and GLES >= 2.0 are expected to work).
777struct Shaders {
778    program: Option<GlProgram>,
779    texture_uniform_location: GlUniformLocation,
780    matrix_uniform_location: GlUniformLocation,
781    position_attribute_index: u32,
782    uv_attribute_index: u32,
783    color_attribute_index: u32,
784}
785
786impl Shaders {
787    fn new(gl: &Context, gl_version: GlVersion, output_srgb: bool) -> Result<Self, ShaderError> {
788        let (vertex_source, fragment_source) =
789            Self::get_shader_sources(gl, gl_version, output_srgb)?;
790
791        let vertex_shader =
792            unsafe { gl.create_shader(glow::VERTEX_SHADER) }.map_err(ShaderError::CreateShader)?;
793        unsafe {
794            gl.shader_source(vertex_shader, &vertex_source);
795            gl.compile_shader(vertex_shader);
796            if !gl.get_shader_compile_status(vertex_shader) {
797                return Err(ShaderError::CompileShader(
798                    gl.get_shader_info_log(vertex_shader),
799                ));
800            }
801        }
802
803        let fragment_shader = unsafe { gl.create_shader(glow::FRAGMENT_SHADER) }
804            .map_err(ShaderError::CreateShader)?;
805        unsafe {
806            gl.shader_source(fragment_shader, &fragment_source);
807            gl.compile_shader(fragment_shader);
808            if !gl.get_shader_compile_status(fragment_shader) {
809                return Err(ShaderError::CompileShader(
810                    gl.get_shader_info_log(fragment_shader),
811                ));
812            }
813        }
814
815        let program = unsafe { gl.create_program() }.map_err(ShaderError::CreateProgram)?;
816        unsafe {
817            gl.attach_shader(program, vertex_shader);
818            gl.attach_shader(program, fragment_shader);
819            gl.link_program(program);
820
821            if !gl.get_program_link_status(program) {
822                return Err(ShaderError::LinkProgram(gl.get_program_info_log(program)));
823            }
824
825            gl.detach_shader(program, vertex_shader);
826            gl.detach_shader(program, fragment_shader);
827            gl.delete_shader(vertex_shader);
828            gl.delete_shader(fragment_shader);
829        }
830
831        Ok(unsafe {
832            Self {
833                program: Some(program),
834                texture_uniform_location: gl
835                    .get_uniform_location(program, "tex")
836                    .ok_or_else(|| ShaderError::UniformNotFound("tex".into()))?,
837                matrix_uniform_location: gl
838                    .get_uniform_location(program, "matrix")
839                    .ok_or_else(|| ShaderError::UniformNotFound("matrix".into()))?,
840                position_attribute_index: gl
841                    .get_attrib_location(program, "position")
842                    .ok_or_else(|| ShaderError::AttributeNotFound("position".into()))?,
843                uv_attribute_index: gl
844                    .get_attrib_location(program, "uv")
845                    .ok_or_else(|| ShaderError::AttributeNotFound("uv".into()))?,
846                color_attribute_index: gl
847                    .get_attrib_location(program, "color")
848                    .ok_or_else(|| ShaderError::AttributeNotFound("color".into()))?,
849            }
850        })
851    }
852
853    fn get_shader_sources(
854        gl: &Context,
855        gl_version: GlVersion,
856        output_srgb: bool,
857    ) -> Result<(String, String), ShaderError> {
858        const VERTEX_BODY: &str = r#"
859layout (location = 0) in vec2 position;
860layout (location = 1) in vec2 uv;
861layout (location = 2) in vec4 color;
862
863uniform mat4 matrix;
864out vec2 fragment_uv;
865out vec4 fragment_color;
866
867// Because imgui only specifies sRGB colors
868vec4 srgb_to_linear(vec4 srgb_color) {
869    // Calcuation as documented by OpenGL
870    vec3 srgb = srgb_color.rgb;
871    vec3 selector = ceil(srgb - 0.04045);
872    vec3 less_than_branch = srgb / 12.92;
873    vec3 greater_than_branch = pow((srgb + 0.055) / 1.055, vec3(2.4));
874    return vec4(
875        mix(less_than_branch, greater_than_branch, selector),
876        srgb_color.a
877    );
878}
879
880void main() {
881    fragment_uv = uv;
882    fragment_color = srgb_to_linear(color);
883    gl_Position = matrix * vec4(position.xy, 0, 1);
884}
885"#;
886        const FRAGMENT_BODY: &str = r#"
887in vec2 fragment_uv;
888in vec4 fragment_color;
889
890uniform sampler2D tex;
891layout (location = 0) out vec4 out_color;
892
893vec4 linear_to_srgb(vec4 linear_color) {
894    vec3 linear = linear_color.rgb;
895    vec3 selector = ceil(linear - 0.0031308);
896    vec3 less_than_branch = linear * 12.92;
897    vec3 greater_than_branch = pow(linear, vec3(1.0/2.4)) * 1.055 - 0.055;
898    return vec4(
899        mix(less_than_branch, greater_than_branch, selector),
900        linear_color.a
901    );
902}
903
904void main() {
905    vec4 linear_color = fragment_color * texture(tex, fragment_uv.st);
906#ifdef OUTPUT_SRGB
907    out_color = linear_to_srgb(linear_color);
908#else
909    out_color = linear_color;
910#endif
911}
912"#;
913
914        let glsl_version = GlslVersion::read(gl);
915
916        // Find the lowest common denominator version
917        let is_gles = gl_version.is_gles || glsl_version.is_gles;
918        let (major, minor) = if let std::cmp::Ordering::Less = gl_version
919            .major
920            .cmp(&glsl_version.major)
921            .then(gl_version.minor.cmp(&glsl_version.minor))
922        {
923            (gl_version.major, gl_version.minor)
924        } else {
925            (glsl_version.major, glsl_version.minor)
926        };
927
928        if is_gles && major < 2 {
929            return Err(ShaderError::IncompatibleVersion(format!(
930                "This auto-shader OpenGL version 3.0 or OpenGL ES version 2.0 or higher, found: ES {}.{}",
931                major, minor
932            )));
933        }
934        if !is_gles && major < 3 {
935            return Err(ShaderError::IncompatibleVersion(format!(
936                "This auto-shader OpenGL version 3.0 or OpenGL ES version 2.0 or higher, found: {}.{}",
937                major, minor
938            )));
939        }
940
941        let vertex_source = format!(
942            "#version {version}{es_extras}\n{body}",
943            version = major * 100 + minor * 10,
944            es_extras = if is_gles {
945                " es\nprecision mediump float;"
946            } else {
947                ""
948            },
949            body = VERTEX_BODY,
950        );
951        let fragment_source = format!(
952            "#version {version}{es_extras}{defines}\n{body}",
953            version = major * 100 + minor * 10,
954            es_extras = if is_gles {
955                " es\nprecision mediump float;"
956            } else {
957                ""
958            },
959            defines = if output_srgb {
960                "\n#define OUTPUT_SRGB"
961            } else {
962                ""
963            },
964            body = FRAGMENT_BODY,
965        );
966
967        Ok((vertex_source, fragment_source))
968    }
969}
970
971#[derive(Debug)]
972pub enum ShaderError {
973    IncompatibleVersion(String),
974    CreateShader(String),
975    CreateProgram(String),
976    CompileShader(String),
977    LinkProgram(String),
978    UniformNotFound(Cow<'static, str>),
979    AttributeNotFound(Cow<'static, str>),
980}
981
982impl Error for ShaderError {}
983
984impl Display for ShaderError {
985    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
986        match self {
987            Self::IncompatibleVersion(msg) => write!(
988                f,
989                "Shader not compatible with OpenGL version found in the context: {}",
990                msg
991            ),
992            Self::CreateShader(msg) => write!(f, "Error creating shader object: {}", msg),
993            Self::CreateProgram(msg) => write!(f, "Error creating program object: {}", msg),
994            Self::CompileShader(msg) => write!(f, "Error compiling shader: {}", msg),
995            Self::LinkProgram(msg) => write!(f, "Error linking shader program: {}", msg),
996            Self::UniformNotFound(uniform_name) => {
997                write!(f, "Uniform `{}` not found in shader program", uniform_name)
998            }
999            Self::AttributeNotFound(attribute_name) => {
1000                write!(
1001                    f,
1002                    "Attribute `{}` not found in shader program",
1003                    attribute_name
1004                )
1005            }
1006        }
1007    }
1008}
1009
1010#[derive(Debug)]
1011pub enum InitError {
1012    Shader(ShaderError),
1013    CreateBufferObject(String),
1014    CreateTexture(String),
1015    RegisterTexture,
1016    UserError(String),
1017}
1018
1019impl Error for InitError {
1020    fn source(&self) -> Option<&(dyn Error + 'static)> {
1021        match self {
1022            Self::Shader(error) => Some(error),
1023            _ => None,
1024        }
1025    }
1026}
1027
1028impl Display for InitError {
1029    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1030        match self {
1031            Self::Shader(error) => write!(f, "Shader initialisation error: {}", error),
1032            Self::CreateBufferObject(msg) => write!(f, "Error creating buffer object: {}", msg),
1033            Self::CreateTexture(msg) => write!(f, "Error creating texture object: {}", msg),
1034            Self::RegisterTexture => write!(f, "Error registering texture in texture map"),
1035            Self::UserError(msg) => write!(f, "Initialization error: {}", msg),
1036        }
1037    }
1038}
1039
1040impl From<ShaderError> for InitError {
1041    fn from(error: ShaderError) -> Self {
1042        Self::Shader(error)
1043    }
1044}
1045
1046pub type RenderError = String;
1047
1048fn prepare_font_atlas<T: TextureMap>(
1049    gl: &Context,
1050    fonts: &mut FontAtlas,
1051    texture_map: &mut T,
1052) -> Result<GlTexture, InitError> {
1053    #![allow(clippy::cast_possible_wrap)]
1054
1055    let atlas_texture = fonts.build_rgba32_texture();
1056
1057    let gl_texture = unsafe { gl.create_texture() }.map_err(InitError::CreateTexture)?;
1058
1059    unsafe {
1060        gl.bind_texture(glow::TEXTURE_2D, Some(gl_texture));
1061        gl.tex_parameter_i32(
1062            glow::TEXTURE_2D,
1063            glow::TEXTURE_MIN_FILTER,
1064            glow::LINEAR as _,
1065        );
1066        gl.tex_parameter_i32(
1067            glow::TEXTURE_2D,
1068            glow::TEXTURE_MAG_FILTER,
1069            glow::LINEAR as _,
1070        );
1071        gl.tex_image_2d(
1072            glow::TEXTURE_2D,
1073            0,
1074            glow::SRGB8_ALPHA8 as _,
1075            atlas_texture.width as _,
1076            atlas_texture.height as _,
1077            0,
1078            glow::RGBA,
1079            glow::UNSIGNED_BYTE,
1080            Some(atlas_texture.data),
1081        );
1082    }
1083
1084    fonts.tex_id = texture_map
1085        .register(gl_texture)
1086        .ok_or(InitError::RegisterTexture)?;
1087
1088    Ok(gl_texture)
1089}
1090
1091// this CFG guard disables apple usage of this function -- apple only has supported up to opengl 3.3
1092#[cfg(all(not(target_vendor = "apple"), feature = "debug_message_insert_support"))]
1093fn gl_debug_message<G: glow::HasContext>(gl: &G, message: impl AsRef<str>) {
1094    unsafe {
1095        gl.debug_message_insert(
1096            glow::DEBUG_SOURCE_APPLICATION,
1097            glow::DEBUG_TYPE_MARKER,
1098            0,
1099            glow::DEBUG_SEVERITY_NOTIFICATION,
1100            message,
1101        )
1102    };
1103}
1104
1105#[cfg(any(target_vendor = "apple", not(feature = "debug_message_insert_support")))]
1106fn gl_debug_message<G: glow::HasContext>(_gl: &G, _message: impl AsRef<str>) {}
1107
1108#[cfg_attr(not(feature = "clip_origin_support"), allow(unused_variables))]
1109#[allow(clippy::deprecated_cfg_attr)]
1110fn calculate_matrix(draw_data: &DrawData, clip_origin_is_lower_left: bool) -> [f32; 16] {
1111    let left = draw_data.display_pos[0];
1112    let right = draw_data.display_pos[0] + draw_data.display_size[0];
1113    let top = draw_data.display_pos[1];
1114    let bottom = draw_data.display_pos[1] + draw_data.display_size[1];
1115
1116    #[cfg(feature = "clip_origin_support")]
1117    let (top, bottom) = if clip_origin_is_lower_left {
1118        (top, bottom)
1119    } else {
1120        (bottom, top)
1121    };
1122
1123    #[cfg_attr(rustfmt, rustfmt::skip)]
1124    {
1125        [
1126        2.0 / (right - left)           , 0.0                            , 0.0 , 0.0,
1127        0.0                            , (2.0 / (top - bottom))         , 0.0 , 0.0,
1128        0.0                            , 0.0                            , -1.0, 0.0,
1129        (right + left) / (left - right), (top + bottom) / (bottom - top), 0.0 , 1.0,
1130        ]
1131    }
1132}
1133
1134unsafe fn to_byte_slice<T>(slice: &[T]) -> &[u8] {
1135    std::slice::from_raw_parts(slice.as_ptr().cast(), std::mem::size_of_val(slice))
1136}
1137
1138const fn imgui_index_type_as_gl() -> u32 {
1139    match size_of::<DrawIdx>() {
1140        1 => glow::UNSIGNED_BYTE,
1141        2 => glow::UNSIGNED_SHORT,
1142        _ => glow::UNSIGNED_INT,
1143    }
1144}