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}