imgui_opengl_renderer/
lib.rs

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