imgui_glium_renderer/
lib.rs

1use glium::backend::{Context, Facade};
2use glium::index::{self, PrimitiveType};
3use glium::program::ProgramChooserCreationError;
4use glium::texture::{ClientFormat, MipmapsOption, RawImage2d, TextureCreationError};
5use glium::uniforms::{
6    MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction,
7};
8use glium::{
9    program, uniform, vertex, Blend, BlendingFunction, DrawError, DrawParameters, IndexBuffer,
10    LinearBlendingFactor, Program, Rect, Surface, Texture2d, VertexBuffer,
11};
12
13use imgui::internal::RawWrapper;
14use imgui::{BackendFlags, DrawCmd, DrawCmdParams, DrawData, TextureId, Textures};
15use std::borrow::Cow;
16use std::error::Error;
17use std::fmt;
18use std::rc::Rc;
19
20#[derive(Clone, Debug)]
21pub enum RendererError {
22    Vertex(vertex::BufferCreationError),
23    Index(index::BufferCreationError),
24    Program(ProgramChooserCreationError),
25    Texture(TextureCreationError),
26    Draw(DrawError),
27    BadTexture(TextureId),
28}
29
30impl Error for RendererError {
31    fn source(&self) -> Option<&(dyn Error + 'static)> {
32        use self::RendererError::*;
33        match *self {
34            Vertex(ref e) => Some(e),
35            Index(ref e) => Some(e),
36            Program(ref e) => Some(e),
37            Texture(ref e) => Some(e),
38            Draw(ref e) => Some(e),
39            BadTexture(_) => None,
40        }
41    }
42}
43
44impl fmt::Display for RendererError {
45    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46        use self::RendererError::*;
47        match *self {
48            Vertex(_) => write!(f, "Vertex buffer creation failed"),
49            Index(_) => write!(f, "Index buffer creation failed"),
50            Program(ref e) => write!(f, "Program creation failed: {}", e),
51            Texture(_) => write!(f, "Texture creation failed"),
52            Draw(ref e) => write!(f, "Drawing failed: {}", e),
53            BadTexture(ref t) => write!(f, "Bad texture ID: {}", t.id()),
54        }
55    }
56}
57
58impl From<vertex::BufferCreationError> for RendererError {
59    fn from(e: vertex::BufferCreationError) -> RendererError {
60        RendererError::Vertex(e)
61    }
62}
63
64impl From<index::BufferCreationError> for RendererError {
65    fn from(e: index::BufferCreationError) -> RendererError {
66        RendererError::Index(e)
67    }
68}
69
70impl From<ProgramChooserCreationError> for RendererError {
71    fn from(e: ProgramChooserCreationError) -> RendererError {
72        RendererError::Program(e)
73    }
74}
75
76impl From<TextureCreationError> for RendererError {
77    fn from(e: TextureCreationError) -> RendererError {
78        RendererError::Texture(e)
79    }
80}
81
82impl From<DrawError> for RendererError {
83    fn from(e: DrawError) -> RendererError {
84        RendererError::Draw(e)
85    }
86}
87
88pub struct Texture {
89    pub texture: Rc<Texture2d>,
90    pub sampler: SamplerBehavior,
91}
92
93pub struct Renderer {
94    ctx: Rc<Context>,
95    program: Program,
96    font_texture: Texture,
97    textures: Textures<Texture>,
98}
99
100#[repr(C)]
101#[derive(Copy, Clone, Debug, PartialEq)]
102pub struct GliumDrawVert {
103    pub pos: [f32; 2],
104    pub uv: [f32; 2],
105    pub col: [u8; 4],
106}
107
108// manual impl to avoid an allocation, and to reduce macro wonkiness.
109impl glium::vertex::Vertex for GliumDrawVert {
110    #[inline]
111    fn build_bindings() -> glium::vertex::VertexFormat {
112        use std::borrow::Cow::*;
113        &[
114            (
115                Borrowed("pos"),
116                0,
117                -1,
118                glium::vertex::AttributeType::F32F32,
119                false,
120            ),
121            (
122                Borrowed("uv"),
123                8,
124                -1,
125                glium::vertex::AttributeType::F32F32,
126                false,
127            ),
128            (
129                Borrowed("col"),
130                16,
131                -1,
132                glium::vertex::AttributeType::U8U8U8U8,
133                false,
134            ),
135        ]
136    }
137}
138
139impl Renderer {
140    /// Creates a new [`Renderer`].
141    pub fn new<F: Facade>(ctx: &mut imgui::Context, facade: &F) -> Result<Renderer, RendererError> {
142        let program = compile_default_program(facade)?;
143        let font_texture = upload_font_texture(ctx.fonts(), facade.get_context())?;
144        ctx.set_renderer_name(Some(format!(
145            "imgui-glium-renderer {}",
146            env!("CARGO_PKG_VERSION")
147        )));
148        ctx.io_mut()
149            .backend_flags
150            .insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
151        Ok(Renderer {
152            ctx: Rc::clone(facade.get_context()),
153            program,
154            font_texture,
155            textures: Textures::new(),
156        })
157    }
158
159    /// Creates a new [`Renderer`]
160    #[deprecated(since = "0.13.0", note = "use `new` instead")]
161    pub fn init<F: Facade>(
162        ctx: &mut imgui::Context,
163        facade: &F,
164    ) -> Result<Renderer, RendererError> {
165        Self::new(ctx, facade)
166    }
167
168    pub fn reload_font_texture(&mut self, ctx: &mut imgui::Context) -> Result<(), RendererError> {
169        self.font_texture = upload_font_texture(ctx.fonts(), &self.ctx)?;
170        Ok(())
171    }
172    pub fn textures(&mut self) -> &mut Textures<Texture> {
173        &mut self.textures
174    }
175    fn lookup_texture(&self, texture_id: TextureId) -> Result<&Texture, RendererError> {
176        if texture_id.id() == usize::MAX {
177            Ok(&self.font_texture)
178        } else if let Some(texture) = self.textures.get(texture_id) {
179            Ok(texture)
180        } else {
181            Err(RendererError::BadTexture(texture_id))
182        }
183    }
184    pub fn render<T: Surface>(
185        &mut self,
186        target: &mut T,
187        draw_data: &DrawData,
188    ) -> Result<(), RendererError> {
189        let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
190        let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
191        if !(fb_width > 0.0 && fb_height > 0.0) {
192            return Ok(());
193        }
194        let _ = self.ctx.insert_debug_marker("imgui-rs: starting rendering");
195        let left = draw_data.display_pos[0];
196        let right = draw_data.display_pos[0] + draw_data.display_size[0];
197        let top = draw_data.display_pos[1];
198        let bottom = draw_data.display_pos[1] + draw_data.display_size[1];
199        let matrix = [
200            [(2.0 / (right - left)), 0.0, 0.0, 0.0],
201            [0.0, (2.0 / (top - bottom)), 0.0, 0.0],
202            [0.0, 0.0, -1.0, 0.0],
203            [
204                (right + left) / (left - right),
205                (top + bottom) / (bottom - top),
206                0.0,
207                1.0,
208            ],
209        ];
210        let clip_off = draw_data.display_pos;
211        let clip_scale = draw_data.framebuffer_scale;
212        for draw_list in draw_data.draw_lists() {
213            let vtx_buffer = VertexBuffer::immutable(&self.ctx, unsafe {
214                draw_list.transmute_vtx_buffer::<GliumDrawVert>()
215            })?;
216            let idx_buffer = IndexBuffer::immutable(
217                &self.ctx,
218                PrimitiveType::TrianglesList,
219                draw_list.idx_buffer(),
220            )?;
221            for cmd in draw_list.commands() {
222                match cmd {
223                    DrawCmd::Elements {
224                        count,
225                        cmd_params:
226                            DrawCmdParams {
227                                clip_rect,
228                                texture_id,
229                                vtx_offset,
230                                idx_offset,
231                                ..
232                            },
233                    } => {
234                        let clip_rect = [
235                            (clip_rect[0] - clip_off[0]) * clip_scale[0],
236                            (clip_rect[1] - clip_off[1]) * clip_scale[1],
237                            (clip_rect[2] - clip_off[0]) * clip_scale[0],
238                            (clip_rect[3] - clip_off[1]) * clip_scale[1],
239                        ];
240
241                        if clip_rect[0] < fb_width
242                            && clip_rect[1] < fb_height
243                            && clip_rect[2] >= 0.0
244                            && clip_rect[3] >= 0.0
245                        {
246                            let texture = self.lookup_texture(texture_id)?;
247
248                            target.draw(
249                                vtx_buffer
250                                    .slice(vtx_offset..)
251                                    .expect("Invalid vertex buffer range"),
252                                idx_buffer
253                                    .slice(idx_offset..(idx_offset + count))
254                                    .expect("Invalid index buffer range"),
255                                &self.program,
256                                &uniform! {
257                                    matrix: matrix,
258                                    tex: Sampler(texture.texture.as_ref(), texture.sampler)
259                                },
260                                &DrawParameters {
261                                    blend: Blend {
262                                        alpha: BlendingFunction::Addition {
263                                            source: LinearBlendingFactor::One,
264                                            destination: LinearBlendingFactor::OneMinusSourceAlpha,
265                                        },
266                                        ..Blend::alpha_blending()
267                                    },
268                                    scissor: Some(Rect {
269                                        left: f32::max(0.0, clip_rect[0]).floor() as u32,
270                                        bottom: f32::max(0.0, fb_height - clip_rect[3]).floor()
271                                            as u32,
272                                        width: (clip_rect[2] - clip_rect[0]).abs().ceil() as u32,
273                                        height: (clip_rect[3] - clip_rect[1]).abs().ceil() as u32,
274                                    }),
275                                    ..DrawParameters::default()
276                                },
277                            )?;
278                        }
279                    }
280                    DrawCmd::ResetRenderState => (), // TODO
281                    DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
282                        callback(draw_list.raw(), raw_cmd)
283                    },
284                }
285            }
286        }
287        let _ = self.ctx.insert_debug_marker("imgui-rs: rendering finished");
288        Ok(())
289    }
290}
291
292fn upload_font_texture(
293    fonts: &mut imgui::FontAtlas,
294    ctx: &Rc<Context>,
295) -> Result<Texture, RendererError> {
296    let texture = fonts.build_rgba32_texture();
297    let data = RawImage2d {
298        data: Cow::Borrowed(texture.data),
299        width: texture.width,
300        height: texture.height,
301        format: ClientFormat::U8U8U8U8,
302    };
303    let font_texture = Texture2d::with_mipmaps(ctx, data, MipmapsOption::NoMipmap)?;
304    fonts.tex_id = TextureId::from(usize::MAX);
305    Ok(Texture {
306        texture: Rc::new(font_texture),
307        sampler: SamplerBehavior {
308            minify_filter: MinifySamplerFilter::Linear,
309            magnify_filter: MagnifySamplerFilter::Linear,
310            wrap_function: (
311                SamplerWrapFunction::BorderClamp,
312                SamplerWrapFunction::BorderClamp,
313                SamplerWrapFunction::BorderClamp,
314            ),
315            ..Default::default()
316        },
317    })
318}
319
320fn compile_default_program<F: Facade>(facade: &F) -> Result<Program, ProgramChooserCreationError> {
321    program!(
322        facade,
323        400 => {
324            vertex: include_str!("shader/glsl_400.vert"),
325            fragment: include_str!("shader/glsl_400.frag"),
326            outputs_srgb: true,
327        },
328        150 => {
329            vertex: include_str!("shader/glsl_150.vert"),
330            fragment: include_str!("shader/glsl_150.frag"),
331            outputs_srgb: true,
332        },
333        130 => {
334            vertex: include_str!("shader/glsl_130.vert"),
335            fragment: include_str!("shader/glsl_130.frag"),
336            outputs_srgb: true,
337        },
338        110 => {
339            vertex: include_str!("shader/glsl_110.vert"),
340            fragment: include_str!("shader/glsl_110.frag"),
341            outputs_srgb: true,
342        },
343        300 es => {
344            vertex: include_str!("shader/glsles_300.vert"),
345            fragment: include_str!("shader/glsles_300.frag"),
346            outputs_srgb: true,
347        },
348        100 es => {
349            vertex: include_str!("shader/glsles_100.vert"),
350            fragment: include_str!("shader/glsles_100.frag"),
351            outputs_srgb: true,
352        },
353    )
354}