1use std::mem::size_of;
2
3use crate::glow::{self, HasContext};
4use anyhow::{anyhow, Result};
5use cgmath::{EuclideanSpace, Matrix3, Point2, Transform};
6use easy_imgui as imgui;
7use easy_imgui_opengl as glr;
8use easy_imgui_sys::*;
9use imgui::{Color, TextureId, Vector2};
10
11pub struct Renderer {
13 imgui: imgui::Context,
14 gl: glr::GlContext,
15 bg_color: Option<imgui::Color>,
16 matrix: Option<Matrix3<f32>>,
17 objs: GlObjects,
18}
19
20struct GlObjects {
21 atlas: glr::Texture,
22 program: glr::Program,
23 vao: glr::VertexArray,
24 vbuf: glr::Buffer,
25 ibuf: glr::Buffer,
26 a_pos_location: u32,
27 a_uv_location: u32,
28 a_color_location: u32,
29 u_matrix_location: glow::UniformLocation,
30 u_tex_location: glow::UniformLocation,
31}
32
33impl Renderer {
34 pub fn new(gl: glr::GlContext) -> Result<Renderer> {
38 let atlas;
39 let program;
40 let vao;
41 let (vbuf, ibuf);
42 let a_pos_location;
43 let a_uv_location;
44 let a_color_location;
45 let u_matrix_location;
46 let u_tex_location;
47
48 let imgui = unsafe { imgui::Context::new() };
49
50 unsafe {
51 if !cfg!(target_arch = "wasm32") {
52 let io = &mut *ImGui_GetIO();
53 io.BackendFlags |= (imgui::BackendFlags::HasMouseCursors
54 | imgui::BackendFlags::HasSetMousePos
55 | imgui::BackendFlags::RendererHasVtxOffset)
56 .bits();
57 }
58
59 atlas = glr::Texture::generate(&gl)?;
60 let glsl_version = if cfg!(not(target_arch = "wasm32")) {
61 "#version 150\n"
62 } else {
63 "#version 300 es\n"
64 };
65 program = gl_program_from_source(&gl, Some(glsl_version), include_str!("shader.glsl"))?;
66 vao = glr::VertexArray::generate(&gl)?;
67 gl.bind_vertex_array(Some(vao.id()));
68
69 let a_pos = program.attrib_by_name("pos").unwrap();
70 a_pos_location = a_pos.location();
71 gl.enable_vertex_attrib_array(a_pos_location);
72
73 let a_uv = program.attrib_by_name("uv").unwrap();
74 a_uv_location = a_uv.location();
75 gl.enable_vertex_attrib_array(a_uv_location);
76
77 let a_color = program.attrib_by_name("color").unwrap();
78 a_color_location = a_color.location();
79 gl.enable_vertex_attrib_array(a_color_location);
80
81 let u_matrix = program.uniform_by_name("matrix").unwrap();
82 u_matrix_location = u_matrix.location();
83
84 let u_tex = program.uniform_by_name("tex").unwrap();
85 u_tex_location = u_tex.location();
86
87 vbuf = glr::Buffer::generate(&gl)?;
88 ibuf = glr::Buffer::generate(&gl)?;
89 }
90 Ok(Renderer {
91 imgui,
92 gl,
93 bg_color: Some(Color::new(0.45, 0.55, 0.60, 1.0)),
94 matrix: None,
95 objs: GlObjects {
96 atlas,
97 program,
98 vao,
99 vbuf,
100 ibuf,
101 a_pos_location,
102 a_uv_location,
103 a_color_location,
104 u_matrix_location,
105 u_tex_location,
106 },
107 })
108 }
109 pub fn gl_context(&self) -> &glr::GlContext {
111 &self.gl
112 }
113 pub fn set_background_color(&mut self, color: Option<Color>) {
119 self.bg_color = color;
120 }
121 pub fn set_matrix(&mut self, matrix: Option<Matrix3<f32>>) {
125 self.matrix = matrix;
126 }
127 pub fn background_color(&self) -> Option<Color> {
129 self.bg_color
130 }
131 pub fn imgui(&mut self) -> &mut imgui::Context {
133 &mut self.imgui
134 }
135 pub fn set_size(&mut self, size: Vector2, scale: f32) {
137 unsafe {
138 self.imgui.set_current().set_size(size, scale);
139 }
140 }
141 pub fn size(&mut self) -> Vector2 {
143 unsafe { self.imgui.set_current().size() }
144 }
145 pub fn do_frame<A: imgui::UiBuilder>(&mut self, app: &mut A) {
147 unsafe {
148 let mut imgui = self.imgui.set_current();
149
150 if imgui.update_atlas(app) {
151 Self::update_atlas(&self.gl, &self.objs.atlas);
152 }
153
154 imgui.do_frame(
155 app,
156 || {
157 let io = &*ImGui_GetIO();
158 if self.matrix.is_none() {
159 self.gl.viewport(
160 0,
161 0,
162 (io.DisplaySize.x * io.DisplayFramebufferScale.x) as i32,
163 (io.DisplaySize.y * io.DisplayFramebufferScale.y) as i32,
164 );
165 }
166 if let Some(bg) = self.bg_color {
167 self.gl.clear_color(bg.r, bg.g, bg.b, bg.a);
168 self.gl.clear(glow::COLOR_BUFFER_BIT);
169 }
170 },
171 |draw_data| {
172 Self::render(&self.gl, &self.objs, draw_data, self.matrix.as_ref());
173 },
174 );
175 }
176 }
177 unsafe fn update_atlas(gl: &glr::GlContext, atlas_tex: &glr::Texture) {
178 let io = ImGui_GetIO();
179 let mut data = std::ptr::null_mut();
180 let mut width = 0;
181 let mut height = 0;
182 let mut pixel_size = 0;
183 ImFontAtlas_GetTexDataAsRGBA32(
184 (*io).Fonts,
185 &mut data,
186 &mut width,
187 &mut height,
188 &mut pixel_size,
189 );
190
191 gl.bind_texture(glow::TEXTURE_2D, Some(atlas_tex.id()));
192
193 gl.tex_parameter_i32(
194 glow::TEXTURE_2D,
195 glow::TEXTURE_WRAP_S,
196 glow::CLAMP_TO_EDGE as i32,
197 );
198 gl.tex_parameter_i32(
199 glow::TEXTURE_2D,
200 glow::TEXTURE_WRAP_T,
201 glow::CLAMP_TO_EDGE as i32,
202 );
203 gl.tex_parameter_i32(
204 glow::TEXTURE_2D,
205 glow::TEXTURE_MIN_FILTER,
206 glow::LINEAR as i32,
207 );
208 gl.tex_parameter_i32(
209 glow::TEXTURE_2D,
210 glow::TEXTURE_MAG_FILTER,
211 glow::LINEAR as i32,
212 );
213 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAX_LEVEL, 0);
214 gl.tex_image_2d(
215 glow::TEXTURE_2D,
216 0,
217 glow::RGBA as i32, width,
219 height,
220 0,
221 glow::RGBA,
222 glow::UNSIGNED_BYTE,
223 glow::PixelUnpackData::Slice(Some(std::slice::from_raw_parts(
225 data,
226 (width * height * pixel_size) as usize,
227 ))),
228 );
229 gl.bind_texture(glow::TEXTURE_2D, None);
230
231 (*(*io).Fonts).TexID = Self::map_tex(atlas_tex.id()).id();
233
234 ImFontAtlas_ClearTexData((*io).Fonts);
236 }
237 unsafe fn render(
238 gl: &glow::Context,
239 objs: &GlObjects,
240 draw_data: &ImDrawData,
241 matrix: Option<&Matrix3<f32>>,
242 ) {
243 enum ScissorViewportMatrix {
244 Default,
245 Custom(Matrix3<f32>),
246 None,
247 }
248 let default_matrix;
249 let (matrix, viewport_matrix) = match matrix {
250 None => {
251 let ImVec2 { x: left, y: top } = draw_data.DisplayPos;
252 let ImVec2 {
253 x: width,
254 y: height,
255 } = draw_data.DisplaySize;
256 let right = left + width;
257 let bottom = top + height;
258 gl.enable(glow::SCISSOR_TEST);
259 default_matrix = Matrix3::new(
260 2.0 / width,
261 0.0,
262 0.0,
263 0.0,
264 -2.0 / height,
265 0.0,
266 -(right + left) / width,
267 (top + bottom) / height,
268 1.0,
269 );
270 (&default_matrix, ScissorViewportMatrix::Default)
271 }
272 Some(matrix) => {
273 if (matrix[0][0].abs() < f32::EPSILON && matrix[1][1].abs() < f32::EPSILON)
280 || (matrix[1][0].abs() < f32::EPSILON && matrix[0][1].abs() < f32::EPSILON)
281 {
282 let mut viewport = [0; 4];
283 gl.get_parameter_i32_slice(glow::VIEWPORT, &mut viewport);
284 let viewport_x = viewport[0] as f32;
285 let viewport_y = viewport[1] as f32;
286 let viewport_w2 = viewport[2] as f32 / 2.0;
287 let viewport_h2 = viewport[3] as f32 / 2.0;
288 let vm = Matrix3::new(
289 viewport_w2,
290 0.0,
291 0.0,
292 0.0,
293 viewport_h2,
294 0.0,
295 viewport_x + viewport_w2,
296 viewport_y + viewport_h2,
297 1.0,
298 );
299 gl.enable(glow::SCISSOR_TEST);
300 (matrix, ScissorViewportMatrix::Custom(vm * matrix))
301 } else {
302 gl.disable(glow::SCISSOR_TEST);
303 (matrix, ScissorViewportMatrix::None)
304 }
305 }
306 };
307
308 gl.bind_vertex_array(Some(objs.vao.id()));
309 gl.use_program(Some(objs.program.id()));
310 gl.bind_buffer(glow::ARRAY_BUFFER, Some(objs.vbuf.id()));
311 gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(objs.ibuf.id()));
312 gl.enable(glow::BLEND);
313 gl.blend_func_separate(
314 glow::SRC_ALPHA,
315 glow::ONE_MINUS_SRC_ALPHA,
316 glow::ONE,
317 glow::ONE_MINUS_SRC_ALPHA,
318 );
319 gl.disable(glow::CULL_FACE);
320 gl.disable(glow::DEPTH_TEST);
321
322 gl.active_texture(glow::TEXTURE0);
323 gl.uniform_1_i32(Some(&objs.u_tex_location), 0);
324
325 gl.uniform_matrix_3_f32_slice(
326 Some(&objs.u_matrix_location),
327 false,
328 AsRef::<[f32; 9]>::as_ref(matrix),
329 );
330
331 for cmd_list in &draw_data.CmdLists {
332 let cmd_list = &**cmd_list;
333
334 gl.buffer_data_u8_slice(
335 glow::ARRAY_BUFFER,
336 glr::as_u8_slice(&cmd_list.VtxBuffer),
337 glow::DYNAMIC_DRAW,
338 );
339 gl.buffer_data_u8_slice(
340 glow::ELEMENT_ARRAY_BUFFER,
341 glr::as_u8_slice(&cmd_list.IdxBuffer),
342 glow::DYNAMIC_DRAW,
343 );
344 let stride = size_of::<ImDrawVert>() as i32;
345 gl.vertex_attrib_pointer_f32(
346 objs.a_pos_location,
347 2, glow::FLOAT,
349 false,
350 stride,
351 0,
352 );
353 gl.vertex_attrib_pointer_f32(
354 objs.a_uv_location,
355 2, glow::FLOAT,
357 false,
358 stride,
359 8,
360 );
361 gl.vertex_attrib_pointer_f32(
362 objs.a_color_location,
363 4, glow::UNSIGNED_BYTE,
365 true,
366 stride,
367 16,
368 );
369
370 for cmd in &cmd_list.CmdBuffer {
371 match viewport_matrix {
372 ScissorViewportMatrix::Default => {
373 let clip_x = cmd.ClipRect.x - draw_data.DisplayPos.x;
374 let clip_y = cmd.ClipRect.y - draw_data.DisplayPos.y;
375 let clip_w = cmd.ClipRect.z - cmd.ClipRect.x;
376 let clip_h = cmd.ClipRect.w - cmd.ClipRect.y;
377 let scale = draw_data.FramebufferScale.x;
378 gl.scissor(
379 (clip_x * scale) as i32,
380 ((draw_data.DisplaySize.y - (clip_y + clip_h)) * scale) as i32,
381 (clip_w * scale) as i32,
382 (clip_h * scale) as i32,
383 );
384 }
385 ScissorViewportMatrix::Custom(vm) => {
386 let pos = Vector2::new(draw_data.DisplayPos.x, draw_data.DisplayPos.y);
387 let clip_aa = Vector2::new(cmd.ClipRect.x, cmd.ClipRect.y) - pos;
388 let clip_bb = Vector2::new(cmd.ClipRect.z, cmd.ClipRect.w) - pos;
389 let clip_aa = vm.transform_point(Point2::from_vec(clip_aa));
390 let clip_bb = vm.transform_point(Point2::from_vec(clip_bb));
391 gl.scissor(
392 clip_aa.x.min(clip_bb.x).round() as i32,
393 clip_aa.y.min(clip_bb.y).round() as i32,
394 (clip_bb.x - clip_aa.x).abs().round() as i32,
395 (clip_bb.y - clip_aa.y).abs().round() as i32,
396 );
397 }
398 ScissorViewportMatrix::None => {}
399 }
400
401 match cmd.UserCallback {
402 Some(cb) => {
403 cb(cmd_list, cmd);
404 }
405 None => {
406 gl.bind_texture(
407 glow::TEXTURE_2D,
408 Self::unmap_tex(TextureId::from_id(cmd.TextureId)),
409 );
410
411 if cfg!(target_arch = "wasm32") {
412 gl.draw_elements(
413 glow::TRIANGLES,
414 cmd.ElemCount as i32,
415 if size_of::<ImDrawIdx>() == 2 {
416 glow::UNSIGNED_SHORT
417 } else {
418 glow::UNSIGNED_INT
419 },
420 (size_of::<ImDrawIdx>() * cmd.IdxOffset as usize) as i32,
421 );
422 } else {
423 gl.draw_elements_base_vertex(
424 glow::TRIANGLES,
425 cmd.ElemCount as i32,
426 if size_of::<ImDrawIdx>() == 2 {
427 glow::UNSIGNED_SHORT
428 } else {
429 glow::UNSIGNED_INT
430 },
431 (size_of::<ImDrawIdx>() * cmd.IdxOffset as usize) as i32,
432 cmd.VtxOffset as i32,
433 );
434 }
435 }
436 }
437 }
438 }
439 gl.use_program(None);
440 gl.bind_vertex_array(None);
441 gl.disable(glow::SCISSOR_TEST);
442 }
443 pub fn map_tex(ntex: glow::Texture) -> TextureId {
445 #[cfg(target_arch = "wasm32")]
446 {
447 let mut tex_map = WASM_TEX_MAP.lock().unwrap();
448 let id = tex_map.len();
449 tex_map.push(ntex);
450 unsafe { TextureId::from_id(id as *mut std::ffi::c_void) }
451 }
452 #[cfg(not(target_arch = "wasm32"))]
453 {
454 unsafe { TextureId::from_id(ntex.0.get() as ImTextureID) }
455 }
456 }
457 pub fn unmap_tex(tex: TextureId) -> Option<glow::Texture> {
459 #[cfg(target_arch = "wasm32")]
460 {
461 let tex_map = WASM_TEX_MAP.lock().unwrap();
462 let id = tex.id() as usize;
463 tex_map.get(id).cloned()
464 }
465 #[cfg(not(target_arch = "wasm32"))]
466 {
467 Some(glow::NativeTexture(std::num::NonZeroU32::new(
468 tex.id() as u32
469 )?))
470 }
471 }
472}
473
474#[cfg(target_arch = "wasm32")]
475static WASM_TEX_MAP: std::sync::Mutex<Vec<glow::Texture>> = std::sync::Mutex::new(Vec::new());
476
477impl Drop for Renderer {
478 fn drop(&mut self) {
479 unsafe {
480 let io = ImGui_GetIO();
481 ImFontAtlas_Clear((*io).Fonts);
482 }
483 }
484}
485
486pub fn gl_program_from_source(
487 gl: &glr::GlContext,
488 prefix: Option<&str>,
489 shaders: &str,
490) -> Result<glr::Program> {
491 let split = shaders
492 .find("###")
493 .ok_or_else(|| anyhow!("shader marker not found"))?;
494 let vertex = &shaders[..split];
495 let frag = &shaders[split..];
496 let split_2 = frag
497 .find('\n')
498 .ok_or_else(|| anyhow!("shader marker not valid"))?;
499
500 let mut frag = &frag[split_2 + 1..];
501
502 let geom = if let Some(split) = frag.find("###") {
503 let geom = &frag[split..];
504 frag = &frag[..split];
505 let split_2 = geom
506 .find('\n')
507 .ok_or_else(|| anyhow!("shader marker not valid"))?;
508 Some(&geom[split_2 + 1..])
509 } else {
510 None
511 };
512
513 use std::borrow::Cow;
514
515 let (vertex, frag, geom) = match prefix {
516 None => (
517 Cow::Borrowed(vertex),
518 Cow::Borrowed(frag),
519 geom.map(Cow::Borrowed),
520 ),
521 Some(prefix) => (
522 Cow::Owned(format!("{0}{1}", prefix, vertex)),
523 Cow::Owned(format!("{0}{1}", prefix, frag)),
524 geom.map(|s| Cow::Owned(format!("{0}{1}", prefix, s))),
525 ),
526 };
527 let prg = glr::Program::from_source(gl, &vertex, &frag, geom.as_deref())?;
528 Ok(prg)
529}