1use std::mem::size_of;
2
3use crate::glow::{self, HasContext};
4use anyhow::{Result, anyhow};
5use cgmath::{EuclideanSpace, Matrix3, Point2, Transform};
6use easy_imgui::{self as imgui, PlatformIo, TextureId};
7use easy_imgui_opengl::{self as glr};
8use easy_imgui_sys::*;
9use imgui::{Color, 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 program: glr::Program,
22 vao: glr::VertexArray,
23 vbuf: glr::Buffer,
24 ibuf: glr::Buffer,
25 a_pos_location: u32,
26 a_uv_location: u32,
27 a_color_location: u32,
28 u_matrix_location: glow::UniformLocation,
29 u_tex_location: glow::UniformLocation,
30}
31
32impl Renderer {
33 pub fn new(gl: glr::GlContext) -> Result<Renderer> {
37 Self::with_builder(gl, &imgui::ContextBuilder::new())
38 }
39
40 pub fn with_builder(gl: glr::GlContext, builder: &imgui::ContextBuilder) -> Result<Renderer> {
44 let program;
45 let vao;
46 let (vbuf, ibuf);
47 let a_pos_location;
48 let a_uv_location;
49 let a_color_location;
50 let u_matrix_location;
51 let u_tex_location;
52
53 let mut imgui = unsafe { builder.build() };
54
55 unsafe {
56 if !cfg!(target_arch = "wasm32") {
57 imgui.io_mut().inner().add_backend_flags(
58 imgui::BackendFlags::HasMouseCursors
59 | imgui::BackendFlags::HasSetMousePos
60 | imgui::BackendFlags::RendererHasVtxOffset,
61 );
62 }
63 imgui
64 .io_mut()
65 .inner()
66 .add_backend_flags(imgui::BackendFlags::RendererHasTextures);
67
68 let pio = imgui.platform_io_mut();
69 let max_tex_size = gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE);
70 log::info!("Max texture size {max_tex_size}");
71 if pio.Renderer_TextureMaxWidth == 0 || pio.Renderer_TextureMaxWidth > max_tex_size {
72 pio.Renderer_TextureMaxWidth = max_tex_size;
73 pio.Renderer_TextureMaxHeight = max_tex_size;
74 }
75 let fonts = imgui.io_mut().font_atlas_mut().inner();
76 if fonts.TexMaxWidth == 0 || fonts.TexMaxWidth > max_tex_size {
77 fonts.TexMaxWidth = max_tex_size;
78 fonts.TexMaxHeight = max_tex_size;
79 }
80
81 let glsl_version = if cfg!(not(target_arch = "wasm32")) {
82 "#version 150\n"
83 } else {
84 "#version 300 es\n"
85 };
86 program = gl_program_from_source(&gl, Some(glsl_version), include_str!("shader.glsl"))?;
87 vao = glr::VertexArray::generate(&gl)?;
88 gl.bind_vertex_array(Some(vao.id()));
89
90 let a_pos = program.attrib_by_name("pos").unwrap();
91 a_pos_location = a_pos.location();
92 gl.enable_vertex_attrib_array(a_pos_location);
93
94 let a_uv = program.attrib_by_name("uv").unwrap();
95 a_uv_location = a_uv.location();
96 gl.enable_vertex_attrib_array(a_uv_location);
97
98 let a_color = program.attrib_by_name("color").unwrap();
99 a_color_location = a_color.location();
100 gl.enable_vertex_attrib_array(a_color_location);
101
102 let u_matrix = program.uniform_by_name("matrix").unwrap();
103 u_matrix_location = u_matrix.location();
104
105 let u_tex = program.uniform_by_name("tex").unwrap();
106 u_tex_location = u_tex.location();
107
108 vbuf = glr::Buffer::generate(&gl)?;
109 ibuf = glr::Buffer::generate(&gl)?;
110 }
111 Ok(Renderer {
112 imgui,
113 gl,
114 bg_color: Some(Color::new(0.45, 0.55, 0.60, 1.0)),
115 matrix: None,
116 objs: GlObjects {
117 program,
118 vao,
119 vbuf,
120 ibuf,
121 a_pos_location,
122 a_uv_location,
123 a_color_location,
124 u_matrix_location,
125 u_tex_location,
126 },
127 })
128 }
129 pub fn gl_context(&self) -> &glr::GlContext {
131 &self.gl
132 }
133 pub fn set_background_color(&mut self, color: Option<Color>) {
139 self.bg_color = color;
140 }
141 pub fn set_matrix(&mut self, matrix: Option<Matrix3<f32>>) {
145 self.matrix = matrix;
146 }
147 pub fn background_color(&self) -> Option<Color> {
149 self.bg_color
150 }
151 pub fn imgui(&mut self) -> &mut imgui::Context {
153 &mut self.imgui
154 }
155 pub fn set_size(&mut self, size: Vector2, scale: f32) {
157 unsafe {
158 self.imgui.set_size(size, scale);
159 }
160 }
161 pub fn size(&mut self) -> Vector2 {
163 self.imgui.io().display_size()
164 }
165 pub fn do_frame<A: imgui::UiBuilder>(&mut self, app: &mut A) {
167 unsafe {
168 let mut imgui = self.imgui.set_current();
169
170 imgui.do_frame(
171 app,
172 |ctx| {
173 let display_size = ctx.io().display_size();
174 let scale = ctx.io().display_scale();
175 if self.matrix.is_none() {
176 self.gl.viewport(
177 0,
178 0,
179 (display_size.x * scale) as i32,
180 (display_size.y * scale) as i32,
181 );
182 }
183 if let Some(bg) = self.bg_color {
184 self.gl.clear_color(bg.r, bg.g, bg.b, bg.a);
185 self.gl.clear(glow::COLOR_BUFFER_BIT);
186 }
187 Self::update_textures(&self.gl, ctx.platform_io_mut());
188 },
189 |draw_data| {
190 Self::render(&self.gl, &self.objs, draw_data, self.matrix.as_ref());
191 },
192 );
193 }
194 }
195
196 unsafe fn update_textures(gl: &glr::GlContext, pio: &mut PlatformIo) {
197 unsafe {
198 for tex in pio.textures_mut() {
199 Self::update_texture(gl, tex);
200 }
201 }
202 }
203 unsafe fn update_texture(gl: &glr::GlContext, tex: &mut ImTextureData) {
204 unsafe {
205 match tex.Status {
206 ImTextureStatus::ImTextureStatus_WantCreate => {
207 log::debug!("Texture create {}", tex.UniqueID);
208 let pixels = tex.Pixels;
209 let tex_id = glr::Texture::generate(gl).unwrap();
210 gl.bind_texture(glow::TEXTURE_2D, Some(tex_id.id()));
211 gl.tex_parameter_i32(
212 glow::TEXTURE_2D,
213 glow::TEXTURE_WRAP_S,
214 glow::CLAMP_TO_EDGE as i32,
215 );
216 gl.tex_parameter_i32(
217 glow::TEXTURE_2D,
218 glow::TEXTURE_WRAP_T,
219 glow::CLAMP_TO_EDGE as i32,
220 );
221 gl.tex_parameter_i32(
222 glow::TEXTURE_2D,
223 glow::TEXTURE_MIN_FILTER,
224 glow::LINEAR as i32,
225 );
226 gl.tex_parameter_i32(
227 glow::TEXTURE_2D,
228 glow::TEXTURE_MAG_FILTER,
229 glow::LINEAR as i32,
230 );
231 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAX_LEVEL, 0);
232 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
233 gl.tex_image_2d(
234 glow::TEXTURE_2D,
235 0,
236 glow::RGBA as i32,
237 tex.Width,
238 tex.Height,
239 0,
240 glow::RGBA,
241 glow::UNSIGNED_BYTE,
242 glow::PixelUnpackData::Slice(Some(std::slice::from_raw_parts(
243 pixels,
244 (tex.Width * tex.Height * 4) as usize,
245 ))),
246 );
247 gl.bind_texture(glow::TEXTURE_2D, None);
248 tex.Status = ImTextureStatus::ImTextureStatus_OK;
249 tex.TexID = Self::map_tex(tex_id.id()).id();
251 std::mem::forget(tex_id);
252 }
253 ImTextureStatus::ImTextureStatus_WantUpdates => {
254 log::debug!("Texture update {}", tex.UniqueID);
255 let tex_id = Self::unmap_tex(TextureId::from_id(tex.TexID)).unwrap();
256 gl.bind_texture(glow::TEXTURE_2D, Some(tex_id));
257 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, tex.Width);
258 for r in &tex.Updates {
260 let ptr = tex.Pixels.offset(
261 ((i32::from(r.x) + i32::from(r.y) * tex.Width) * tex.BytesPerPixel)
262 as isize,
263 );
264 let mem = std::slice::from_raw_parts(
265 ptr,
266 (4 * i32::from(r.h) * tex.Width) as usize,
267 );
268 gl.tex_sub_image_2d(
269 glow::TEXTURE_2D,
270 0,
271 i32::from(r.x),
272 i32::from(r.y),
273 i32::from(r.w),
274 i32::from(r.h),
275 glow::RGBA,
276 glow::UNSIGNED_BYTE,
277 glow::PixelUnpackData::Slice(Some(mem)),
278 );
279 }
280 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
281 tex.Status = ImTextureStatus::ImTextureStatus_OK;
282 gl.bind_texture(glow::TEXTURE_2D, None);
283 }
284 ImTextureStatus::ImTextureStatus_WantDestroy => {
285 log::debug!("Texture destroy {}", tex.UniqueID);
286 if let Some(tex_id) = Self::delete_tex(TextureId::from_id(tex.TexID)) {
287 gl.delete_texture(tex_id);
288 tex.TexID = 0;
289 }
290 tex.Status = ImTextureStatus::ImTextureStatus_Destroyed;
291 }
292 _ => {}
293 }
294 }
295 }
296
297 unsafe fn render(
298 gl: &glow::Context,
299 objs: &GlObjects,
300 draw_data: &ImDrawData,
301 matrix: Option<&Matrix3<f32>>,
302 ) {
303 unsafe {
304 enum ScissorViewportMatrix {
305 Default,
306 Custom(Matrix3<f32>),
307 None,
308 }
309 let default_matrix;
310 let (matrix, viewport_matrix) = match matrix {
311 None => {
312 let ImVec2 { x: left, y: top } = draw_data.DisplayPos;
313 let ImVec2 {
314 x: width,
315 y: height,
316 } = draw_data.DisplaySize;
317 let right = left + width;
318 let bottom = top + height;
319 gl.enable(glow::SCISSOR_TEST);
320 default_matrix = Matrix3::new(
321 2.0 / width,
322 0.0,
323 0.0,
324 0.0,
325 -2.0 / height,
326 0.0,
327 -(right + left) / width,
328 (top + bottom) / height,
329 1.0,
330 );
331 (&default_matrix, ScissorViewportMatrix::Default)
332 }
333 Some(matrix) => {
334 if (matrix[0][0].abs() < f32::EPSILON && matrix[1][1].abs() < f32::EPSILON)
341 || (matrix[1][0].abs() < f32::EPSILON && matrix[0][1].abs() < f32::EPSILON)
342 {
343 let mut viewport = [0; 4];
344 gl.get_parameter_i32_slice(glow::VIEWPORT, &mut viewport);
345 let viewport_x = viewport[0] as f32;
346 let viewport_y = viewport[1] as f32;
347 let viewport_w2 = viewport[2] as f32 / 2.0;
348 let viewport_h2 = viewport[3] as f32 / 2.0;
349 let vm = Matrix3::new(
350 viewport_w2,
351 0.0,
352 0.0,
353 0.0,
354 viewport_h2,
355 0.0,
356 viewport_x + viewport_w2,
357 viewport_y + viewport_h2,
358 1.0,
359 );
360 gl.enable(glow::SCISSOR_TEST);
361 (matrix, ScissorViewportMatrix::Custom(vm * matrix))
362 } else {
363 gl.disable(glow::SCISSOR_TEST);
364 (matrix, ScissorViewportMatrix::None)
365 }
366 }
367 };
368
369 gl.bind_vertex_array(Some(objs.vao.id()));
370 gl.use_program(Some(objs.program.id()));
371 gl.bind_buffer(glow::ARRAY_BUFFER, Some(objs.vbuf.id()));
372 gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(objs.ibuf.id()));
373 gl.enable(glow::BLEND);
374 gl.blend_func_separate(
375 glow::SRC_ALPHA,
376 glow::ONE_MINUS_SRC_ALPHA,
377 glow::ONE,
378 glow::ONE_MINUS_SRC_ALPHA,
379 );
380 gl.disable(glow::CULL_FACE);
381 gl.disable(glow::DEPTH_TEST);
382
383 gl.active_texture(glow::TEXTURE0);
384 gl.uniform_1_i32(Some(&objs.u_tex_location), 0);
385
386 gl.uniform_matrix_3_f32_slice(
387 Some(&objs.u_matrix_location),
388 false,
389 AsRef::<[f32; 9]>::as_ref(matrix),
390 );
391
392 for cmd_list in &draw_data.CmdLists {
393 let cmd_list = &**cmd_list;
394
395 gl.buffer_data_u8_slice(
396 glow::ARRAY_BUFFER,
397 glr::as_u8_slice(&cmd_list.VtxBuffer),
398 glow::DYNAMIC_DRAW,
399 );
400 gl.buffer_data_u8_slice(
401 glow::ELEMENT_ARRAY_BUFFER,
402 glr::as_u8_slice(&cmd_list.IdxBuffer),
403 glow::DYNAMIC_DRAW,
404 );
405 let stride = size_of::<ImDrawVert>() as i32;
406 gl.vertex_attrib_pointer_f32(
407 objs.a_pos_location,
408 2, glow::FLOAT,
410 false,
411 stride,
412 0,
413 );
414 gl.vertex_attrib_pointer_f32(
415 objs.a_uv_location,
416 2, glow::FLOAT,
418 false,
419 stride,
420 8,
421 );
422 gl.vertex_attrib_pointer_f32(
423 objs.a_color_location,
424 4, glow::UNSIGNED_BYTE,
426 true,
427 stride,
428 16,
429 );
430
431 for cmd in &cmd_list.CmdBuffer {
432 match viewport_matrix {
433 ScissorViewportMatrix::Default => {
434 let clip_x = cmd.ClipRect.x - draw_data.DisplayPos.x;
435 let clip_y = cmd.ClipRect.y - draw_data.DisplayPos.y;
436 let clip_w = cmd.ClipRect.z - cmd.ClipRect.x;
437 let clip_h = cmd.ClipRect.w - cmd.ClipRect.y;
438 let scale = draw_data.FramebufferScale.x;
439 gl.scissor(
440 (clip_x * scale) as i32,
441 ((draw_data.DisplaySize.y - (clip_y + clip_h)) * scale) as i32,
442 (clip_w * scale) as i32,
443 (clip_h * scale) as i32,
444 );
445 }
446 ScissorViewportMatrix::Custom(vm) => {
447 let pos = Vector2::new(draw_data.DisplayPos.x, draw_data.DisplayPos.y);
448 let clip_aa = Vector2::new(cmd.ClipRect.x, cmd.ClipRect.y) - pos;
449 let clip_bb = Vector2::new(cmd.ClipRect.z, cmd.ClipRect.w) - pos;
450 let clip_aa = vm.transform_point(Point2::from_vec(clip_aa));
451 let clip_bb = vm.transform_point(Point2::from_vec(clip_bb));
452 gl.scissor(
453 clip_aa.x.min(clip_bb.x).round() as i32,
454 clip_aa.y.min(clip_bb.y).round() as i32,
455 (clip_bb.x - clip_aa.x).abs().round() as i32,
456 (clip_bb.y - clip_aa.y).abs().round() as i32,
457 );
458 }
459 ScissorViewportMatrix::None => {}
460 }
461
462 match cmd.UserCallback {
463 Some(cb) => {
464 cb(cmd_list, cmd);
465 }
466 None => {
467 let tex_id = if cmd.TexRef._TexData.is_null() {
469 cmd.TexRef._TexID
470 } else {
471 (*cmd.TexRef._TexData).TexID
472 };
473 let tex_id = TextureId::from_id(tex_id);
474 gl.bind_texture(glow::TEXTURE_2D, Self::unmap_tex(tex_id));
475
476 if cfg!(target_arch = "wasm32") {
477 gl.draw_elements(
478 glow::TRIANGLES,
479 cmd.ElemCount as i32,
480 if size_of::<ImDrawIdx>() == 2 {
481 glow::UNSIGNED_SHORT
482 } else {
483 glow::UNSIGNED_INT
484 },
485 (size_of::<ImDrawIdx>() * cmd.IdxOffset as usize) as i32,
486 );
487 } else {
488 gl.draw_elements_base_vertex(
489 glow::TRIANGLES,
490 cmd.ElemCount as i32,
491 if size_of::<ImDrawIdx>() == 2 {
492 glow::UNSIGNED_SHORT
493 } else {
494 glow::UNSIGNED_INT
495 },
496 (size_of::<ImDrawIdx>() * cmd.IdxOffset as usize) as i32,
497 cmd.VtxOffset as i32,
498 );
499 }
500 }
501 }
502 }
503 }
504 gl.use_program(None);
505 gl.bind_vertex_array(None);
506 gl.disable(glow::SCISSOR_TEST);
507 }
508 }
509 pub fn map_tex(ntex: glow::Texture) -> TextureId {
511 #[cfg(target_arch = "wasm32")]
512 {
513 let mut tex_map = WASM_TEX_MAP.lock().unwrap();
514 let id = match tex_map.iter().position(|t| t.is_none()) {
515 Some(free) => {
516 tex_map[free] = Some(ntex);
517 free
518 }
519 None => {
520 let id = tex_map.len();
521 tex_map.push(Some(ntex));
522 id
523 }
524 };
525 unsafe { TextureId::from_id(id as u64) }
526 }
527 #[cfg(not(target_arch = "wasm32"))]
528 {
529 unsafe { TextureId::from_id(ntex.0.get() as ImTextureID) }
530 }
531 }
532 pub fn unmap_tex(tex: TextureId) -> Option<glow::Texture> {
534 #[cfg(target_arch = "wasm32")]
535 {
536 let tex_map = WASM_TEX_MAP.lock().unwrap();
537 let id = tex.id() as usize;
538 tex_map.get(id).cloned().flatten()
539 }
540 #[cfg(not(target_arch = "wasm32"))]
541 {
542 Some(glow::NativeTexture(std::num::NonZeroU32::new(
543 tex.id() as u32
544 )?))
545 }
546 }
547
548 pub fn delete_tex(tex: TextureId) -> Option<glow::Texture> {
549 #[cfg(target_arch = "wasm32")]
550 {
551 let mut tex_map = WASM_TEX_MAP.lock().unwrap();
552 let id = tex.id() as usize;
553 tex_map.get_mut(id).map(|x| x.take()).flatten()
554 }
555
556 #[cfg(not(target_arch = "wasm32"))]
557 {
558 Self::unmap_tex(tex)
559 }
560 }
561}
562
563#[cfg(target_arch = "wasm32")]
564static WASM_TEX_MAP: std::sync::Mutex<Vec<Option<glow::Texture>>> =
565 std::sync::Mutex::new(Vec::new());
566
567impl Drop for Renderer {
568 fn drop(&mut self) {
569 unsafe {
570 let gl = self.gl.clone();
571 let imgui = self.imgui();
572 imgui.io_mut().font_atlas_mut().inner().Clear();
573
574 for tex in imgui.platform_io_mut().textures_mut() {
576 if tex.RefCount == 1 {
577 tex.Status = ImTextureStatus::ImTextureStatus_WantDestroy;
578 Self::update_texture(&gl, tex);
579 }
580 }
581 }
582 }
583}
584
585pub fn gl_program_from_source(
586 gl: &glr::GlContext,
587 prefix: Option<&str>,
588 shaders: &str,
589) -> Result<glr::Program> {
590 let split = shaders
591 .find("###")
592 .ok_or_else(|| anyhow!("shader marker not found"))?;
593 let vertex = &shaders[..split];
594 let frag = &shaders[split..];
595 let split_2 = frag
596 .find('\n')
597 .ok_or_else(|| anyhow!("shader marker not valid"))?;
598
599 let mut frag = &frag[split_2 + 1..];
600
601 let geom = if let Some(split) = frag.find("###") {
602 let geom = &frag[split..];
603 frag = &frag[..split];
604 let split_2 = geom
605 .find('\n')
606 .ok_or_else(|| anyhow!("shader marker not valid"))?;
607 Some(&geom[split_2 + 1..])
608 } else {
609 None
610 };
611
612 use std::borrow::Cow;
613
614 let (vertex, frag, geom) = match prefix {
615 None => (
616 Cow::Borrowed(vertex),
617 Cow::Borrowed(frag),
618 geom.map(Cow::Borrowed),
619 ),
620 Some(prefix) => (
621 Cow::Owned(format!("{prefix}{vertex}")),
622 Cow::Owned(format!("{prefix}{frag}")),
623 geom.map(|s| Cow::Owned(format!("{prefix}{s}"))),
624 ),
625 };
626 let prg = glr::Program::from_source(gl, &vertex, &frag, geom.as_deref())?;
627 Ok(prg)
628}