1#![doc = include_str!("../README.md")]
2#![deny(rust_2018_idioms)]
3use 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
14pub 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
26pub struct AutoRenderer {
34 gl: Rc<glow::Context>,
35 texture_map: SimpleTextureMap,
36 renderer: Renderer,
37}
38
39impl AutoRenderer {
40 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 #[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 #[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 #[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
101pub 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 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 out.configure_imgui_context(imgui_context);
200
201 Ok(out)
202 }
203
204 #[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 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 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 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 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
524pub 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#[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
561impl 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#[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 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
774struct 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 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#[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}