tiny_game_framework/gui/
renderer.rs

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