imgui_opengl/
lib.rs

1extern crate imgui;
2
3use imgui::Context;
4use std::mem;
5
6mod gl {
7    #![cfg_attr(
8        feature = "cargo-clippy",
9        allow(
10            clippy::unreadable_literal,
11            clippy::too_many_arguments,
12            clippy::unused_unit
13        )
14    )]
15
16    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
17}
18
19use gl::types::*;
20
21pub struct Renderer {
22    gl: gl::Gl,
23    program: GLuint,
24    locs: Locs,
25    vbo: GLuint,
26    ebo: GLuint,
27    font_texture: GLuint,
28}
29
30struct Locs {
31    texture: GLint,
32    proj_mtx: GLint,
33    position: GLuint,
34    uv: GLuint,
35    color: GLuint,
36}
37
38impl Renderer {
39    pub fn new<F>(imgui: &mut Context, load_fn: F) -> Self
40    where
41        F: FnMut(&'static str) -> *const ::std::os::raw::c_void,
42    {
43        let gl = gl::Gl::load_with(load_fn);
44
45        unsafe {
46            #[cfg(target_os = "macos")]
47            let glsl_version = b"#version 150\n\0";
48            #[cfg(not(target_os = "macos"))]
49            let glsl_version = b"#version 130\n\0";
50
51            let vert_source = b"
52        uniform mat4 ProjMtx;
53        in vec2 Position;
54        in vec2 UV;
55        in vec4 Color;
56        out vec2 Frag_UV;
57        out vec4 Frag_Color;
58        void main()
59        {
60          Frag_UV = UV;
61          Frag_Color = Color;
62          gl_Position = ProjMtx * vec4(Position.xy,0,1);
63        }
64      \0";
65
66            let frag_source = b"
67        uniform sampler2D Texture;
68        in vec2 Frag_UV;
69        in vec4 Frag_Color;
70        out vec4 Out_Color;
71        void main()
72        {
73          Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
74        }
75      \0";
76
77            let vert_sources = [
78                glsl_version.as_ptr() as *const GLchar,
79                vert_source.as_ptr() as *const GLchar,
80            ];
81            let vert_sources_len = [
82                glsl_version.len() as GLint - 1,
83                vert_source.len() as GLint - 1,
84            ];
85            let frag_sources = [
86                glsl_version.as_ptr() as *const GLchar,
87                frag_source.as_ptr() as *const GLchar,
88            ];
89            let frag_sources_len = [
90                glsl_version.len() as GLint - 1,
91                frag_source.len() as GLint - 1,
92            ];
93
94            let program = gl.CreateProgram();
95            let vert_shader = gl.CreateShader(gl::VERTEX_SHADER);
96            let frag_shader = gl.CreateShader(gl::FRAGMENT_SHADER);
97            gl.ShaderSource(
98                vert_shader,
99                2,
100                vert_sources.as_ptr(),
101                vert_sources_len.as_ptr(),
102            );
103            gl.ShaderSource(
104                frag_shader,
105                2,
106                frag_sources.as_ptr(),
107                frag_sources_len.as_ptr(),
108            );
109            gl.CompileShader(vert_shader);
110            gl.CompileShader(frag_shader);
111            gl.AttachShader(program, vert_shader);
112            gl.AttachShader(program, frag_shader);
113            gl.LinkProgram(program);
114            gl.DeleteShader(vert_shader);
115            gl.DeleteShader(frag_shader);
116
117            let locs = Locs {
118                texture: gl.GetUniformLocation(program, b"Texture\0".as_ptr() as _),
119                proj_mtx: gl.GetUniformLocation(program, b"ProjMtx\0".as_ptr() as _),
120                position: gl.GetAttribLocation(program, b"Position\0".as_ptr() as _) as _,
121                uv: gl.GetAttribLocation(program, b"UV\0".as_ptr() as _) as _,
122                color: gl.GetAttribLocation(program, b"Color\0".as_ptr() as _) as _,
123            };
124
125            let vbo = return_param(|x| gl.GenBuffers(1, x));
126            let ebo = return_param(|x| gl.GenBuffers(1, x));
127
128            let mut current_texture = 0;
129            gl.GetIntegerv(gl::TEXTURE_BINDING_2D, &mut current_texture);
130
131            let font_texture = return_param(|x| gl.GenTextures(1, x));
132            gl.BindTexture(gl::TEXTURE_2D, font_texture);
133            gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
134            gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
135            gl.PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
136
137            {
138                let mut atlas = imgui.fonts();
139
140                let texture = atlas.build_rgba32_texture();
141                gl.TexImage2D(
142                    gl::TEXTURE_2D,
143                    0,
144                    gl::RGBA as _,
145                    texture.width as _,
146                    texture.height as _,
147                    0,
148                    gl::RGBA,
149                    gl::UNSIGNED_BYTE,
150                    texture.data.as_ptr() as _,
151                );
152
153                atlas.tex_id = (font_texture as usize).into();
154            }
155
156            gl.BindTexture(gl::TEXTURE_2D, current_texture as _);
157
158            Self {
159                gl,
160                program,
161                locs,
162                vbo,
163                ebo,
164                font_texture,
165            }
166        }
167    }
168
169    pub fn render(&self, ctx: &mut Context) {
170        use imgui::{DrawCmd, DrawCmdParams, DrawIdx, DrawVert};
171
172        let gl = &self.gl;
173
174        unsafe {
175            let last_active_texture = return_param(|x| gl.GetIntegerv(gl::ACTIVE_TEXTURE, x));
176            gl.ActiveTexture(gl::TEXTURE0);
177            let last_program = return_param(|x| gl.GetIntegerv(gl::CURRENT_PROGRAM, x));
178            let last_texture = return_param(|x| gl.GetIntegerv(gl::TEXTURE_BINDING_2D, x));
179            let last_sampler = if gl.BindSampler.is_loaded() {
180                return_param(|x| gl.GetIntegerv(gl::SAMPLER_BINDING, x))
181            } else {
182                0
183            };
184            let last_array_buffer = return_param(|x| gl.GetIntegerv(gl::ARRAY_BUFFER_BINDING, x));
185            let last_element_array_buffer =
186                return_param(|x| gl.GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, x));
187            let last_vertex_array = return_param(|x| gl.GetIntegerv(gl::VERTEX_ARRAY_BINDING, x));
188            let last_polygon_mode =
189                return_param(|x: &mut [GLint; 2]| gl.GetIntegerv(gl::POLYGON_MODE, x.as_mut_ptr()));
190            let last_viewport =
191                return_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::VIEWPORT, x.as_mut_ptr()));
192            let last_scissor_box =
193                return_param(|x: &mut [GLint; 4]| gl.GetIntegerv(gl::SCISSOR_BOX, x.as_mut_ptr()));
194            let last_blend_src_rgb = return_param(|x| gl.GetIntegerv(gl::BLEND_SRC_RGB, x));
195            let last_blend_dst_rgb = return_param(|x| gl.GetIntegerv(gl::BLEND_DST_RGB, x));
196            let last_blend_src_alpha = return_param(|x| gl.GetIntegerv(gl::BLEND_SRC_ALPHA, x));
197            let last_blend_dst_alpha = return_param(|x| gl.GetIntegerv(gl::BLEND_DST_ALPHA, x));
198            let last_blend_equation_rgb =
199                return_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_RGB, x));
200            let last_blend_equation_alpha =
201                return_param(|x| gl.GetIntegerv(gl::BLEND_EQUATION_ALPHA, x));
202            let last_enable_blend = gl.IsEnabled(gl::BLEND) == gl::TRUE;
203            let last_enable_cull_face = gl.IsEnabled(gl::CULL_FACE) == gl::TRUE;
204            let last_enable_depth_test = gl.IsEnabled(gl::DEPTH_TEST) == gl::TRUE;
205            let last_enable_scissor_test = gl.IsEnabled(gl::SCISSOR_TEST) == gl::TRUE;
206
207            gl.Enable(gl::BLEND);
208            gl.BlendEquation(gl::FUNC_ADD);
209            gl.BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
210            gl.Disable(gl::CULL_FACE);
211            gl.Disable(gl::DEPTH_TEST);
212            gl.Enable(gl::SCISSOR_TEST);
213            gl.PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
214
215            let [width, height] = ctx.io().display_size;
216            let [scale_w, scale_h] = ctx.io().display_framebuffer_scale;
217
218            let fb_width = width * scale_w;
219            let fb_height = height * scale_h;
220
221            gl.Viewport(0, 0, fb_width as _, fb_height as _);
222            let matrix = [
223                [2.0 / width as f32, 0.0, 0.0, 0.0],
224                [0.0, 2.0 / -(height as f32), 0.0, 0.0],
225                [0.0, 0.0, -1.0, 0.0],
226                [-1.0, 1.0, 0.0, 1.0],
227            ];
228            gl.UseProgram(self.program);
229            gl.Uniform1i(self.locs.texture, 0);
230            gl.UniformMatrix4fv(self.locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
231            if gl.BindSampler.is_loaded() {
232                gl.BindSampler(0, 0);
233            }
234
235            let vao = return_param(|x| gl.GenVertexArrays(1, x));
236            gl.BindVertexArray(vao);
237            gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);
238            gl.EnableVertexAttribArray(self.locs.position);
239            gl.EnableVertexAttribArray(self.locs.uv);
240            gl.EnableVertexAttribArray(self.locs.color);
241            gl.VertexAttribPointer(
242                self.locs.position,
243                2,
244                gl::FLOAT,
245                gl::FALSE,
246                mem::size_of::<DrawVert>() as _,
247                field_offset::<DrawVert, _, _>(|v| &v.pos) as _,
248            );
249            gl.VertexAttribPointer(
250                self.locs.uv,
251                2,
252                gl::FLOAT,
253                gl::FALSE,
254                mem::size_of::<DrawVert>() as _,
255                field_offset::<DrawVert, _, _>(|v| &v.uv) as _,
256            );
257            gl.VertexAttribPointer(
258                self.locs.color,
259                4,
260                gl::UNSIGNED_BYTE,
261                gl::TRUE,
262                mem::size_of::<DrawVert>() as _,
263                field_offset::<DrawVert, _, _>(|v| &v.col) as _,
264            );
265
266            let draw_data = ctx.render();
267
268            for draw_list in draw_data.draw_lists() {
269                let vtx_buffer = draw_list.vtx_buffer();
270                let idx_buffer = draw_list.idx_buffer();
271
272                gl.BindBuffer(gl::ARRAY_BUFFER, self.vbo);
273                gl.BufferData(
274                    gl::ARRAY_BUFFER,
275                    (vtx_buffer.len() * mem::size_of::<DrawVert>()) as _,
276                    vtx_buffer.as_ptr() as _,
277                    gl::STREAM_DRAW,
278                );
279
280                gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
281                gl.BufferData(
282                    gl::ELEMENT_ARRAY_BUFFER,
283                    (idx_buffer.len() * mem::size_of::<DrawIdx>()) as _,
284                    idx_buffer.as_ptr() as _,
285                    gl::STREAM_DRAW,
286                );
287
288                for cmd in draw_list.commands() {
289                    match cmd {
290                        DrawCmd::Elements {
291                            count,
292                            cmd_params:
293                                DrawCmdParams {
294                                    clip_rect: [x, y, z, w],
295                                    texture_id,
296                                    idx_offset,
297                                    ..
298                                },
299                        } => {
300                            gl.BindTexture(gl::TEXTURE_2D, texture_id.id() as _);
301
302                            gl.Scissor(
303                                (x * scale_w) as GLint,
304                                (fb_height - w * scale_h) as GLint,
305                                ((z - x) * scale_w) as GLint,
306                                ((w - y) * scale_h) as GLint,
307                            );
308
309                            let idx_size = if mem::size_of::<DrawIdx>() == 2 {
310                                gl::UNSIGNED_SHORT
311                            } else {
312                                gl::UNSIGNED_INT
313                            };
314
315                            gl.DrawElements(
316                                gl::TRIANGLES,
317                                count as _,
318                                idx_size,
319                                (idx_offset * mem::size_of::<DrawIdx>()) as _,
320                            );
321                        }
322                        DrawCmd::ResetRenderState => {
323                            unimplemented!("Haven't implemented DrawCmd::ResetRenderState yet");
324                        }
325                        DrawCmd::RawCallback { .. } => {
326                            unimplemented!("Haven't implemented user callbacks yet");
327                        }
328                    }
329                }
330            }
331
332            gl.DeleteVertexArrays(1, &vao);
333
334            gl.UseProgram(last_program as _);
335            gl.BindTexture(gl::TEXTURE_2D, last_texture as _);
336            if gl.BindSampler.is_loaded() {
337                gl.BindSampler(0, last_sampler as _);
338            }
339            gl.ActiveTexture(last_active_texture as _);
340            gl.BindVertexArray(last_vertex_array as _);
341            gl.BindBuffer(gl::ARRAY_BUFFER, last_array_buffer as _);
342            gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, last_element_array_buffer as _);
343            gl.BlendEquationSeparate(last_blend_equation_rgb as _, last_blend_equation_alpha as _);
344            gl.BlendFuncSeparate(
345                last_blend_src_rgb as _,
346                last_blend_dst_rgb as _,
347                last_blend_src_alpha as _,
348                last_blend_dst_alpha as _,
349            );
350            if last_enable_blend {
351                gl.Enable(gl::BLEND)
352            } else {
353                gl.Disable(gl::BLEND)
354            };
355            if last_enable_cull_face {
356                gl.Enable(gl::CULL_FACE)
357            } else {
358                gl.Disable(gl::CULL_FACE)
359            };
360            if last_enable_depth_test {
361                gl.Enable(gl::DEPTH_TEST)
362            } else {
363                gl.Disable(gl::DEPTH_TEST)
364            };
365            if last_enable_scissor_test {
366                gl.Enable(gl::SCISSOR_TEST)
367            } else {
368                gl.Disable(gl::SCISSOR_TEST)
369            };
370            gl.PolygonMode(gl::FRONT_AND_BACK, last_polygon_mode[0] as _);
371            gl.Viewport(
372                last_viewport[0] as _,
373                last_viewport[1] as _,
374                last_viewport[2] as _,
375                last_viewport[3] as _,
376            );
377            gl.Scissor(
378                last_scissor_box[0] as _,
379                last_scissor_box[1] as _,
380                last_scissor_box[2] as _,
381                last_scissor_box[3] as _,
382            );
383        }
384    }
385}
386
387impl Drop for Renderer {
388    fn drop(&mut self) {
389        let gl = &self.gl;
390
391        unsafe {
392            gl.DeleteBuffers(1, &self.vbo);
393            gl.DeleteBuffers(1, &self.ebo);
394
395            gl.DeleteProgram(self.program);
396
397            gl.DeleteTextures(1, &self.font_texture);
398        }
399    }
400}
401
402fn field_offset<T, U, F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> usize {
403    unsafe {
404        let instance = mem::zeroed::<T>();
405
406        let offset = {
407            let field: &U = f(&instance);
408            field as *const U as usize - &instance as *const T as usize
409        };
410
411        mem::forget(instance);
412
413        offset
414    }
415}
416
417fn return_param<T, F>(f: F) -> T
418where
419    F: FnOnce(&mut T),
420{
421    let mut val = unsafe { mem::zeroed() };
422    f(&mut val);
423    val
424}