1use imgui::{
2 Context, DrawCmd::Elements, DrawData, DrawIdx, DrawList, DrawVert, TextureId, Textures,
3};
4use smallvec::SmallVec;
5use std::error::Error;
6use std::fmt;
7use std::mem::size_of;
8use std::sync::Arc;
9use wgpu::util::{BufferInitDescriptor, DeviceExt};
10use wgpu::*;
11
12static VS_ENTRY_POINT: &str = "vs_main";
13static FS_ENTRY_POINT_LINEAR: &str = "fs_main_linear";
14static FS_ENTRY_POINT_SRGB: &str = "fs_main_srgb";
15
16pub type RendererResult<T> = Result<T, RendererError>;
17
18#[repr(transparent)]
19#[derive(Debug, Copy, Clone)]
20struct DrawVertPod(DrawVert);
21
22unsafe impl bytemuck::Zeroable for DrawVertPod {}
23
24unsafe impl bytemuck::Pod for DrawVertPod {}
25
26#[derive(Clone, Debug)]
27pub enum RendererError {
28 BadTexture(TextureId),
29}
30
31impl fmt::Display for RendererError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match *self {
34 RendererError::BadTexture(id) => {
35 write!(f, "imgui render error: bad texture id '{}'", id.id())
36 }
37 }
38 }
39}
40
41impl Error for RendererError {}
42
43#[allow(dead_code)]
44enum ShaderStage {
45 Vertex,
46 Fragment,
47 Compute,
48}
49
50#[derive(Clone)]
53pub struct RawTextureConfig<'a> {
54 pub label: Option<&'a str>,
56 pub sampler_desc: SamplerDescriptor<'a>,
58}
59
60#[derive(Clone)]
64pub struct TextureConfig<'a> {
65 pub size: Extent3d,
67 pub label: Option<&'a str>,
69 pub format: Option<TextureFormat>,
71 pub usage: TextureUsages,
73 pub mip_level_count: u32,
75 pub sample_count: u32,
77 pub dimension: TextureDimension,
79 pub sampler_desc: SamplerDescriptor<'a>,
81}
82
83impl Default for TextureConfig<'_> {
84 fn default() -> Self {
86 let sampler_desc = SamplerDescriptor {
87 label: Some("imgui-wgpu sampler"),
88 address_mode_u: AddressMode::ClampToEdge,
89 address_mode_v: AddressMode::ClampToEdge,
90 address_mode_w: AddressMode::ClampToEdge,
91 mag_filter: FilterMode::Linear,
92 min_filter: FilterMode::Linear,
93 mipmap_filter: FilterMode::Linear,
94 lod_min_clamp: 0.0,
95 lod_max_clamp: 100.0,
96 compare: None,
97 anisotropy_clamp: 1,
98 border_color: None,
99 };
100
101 Self {
102 size: Extent3d {
103 width: 0,
104 height: 0,
105 depth_or_array_layers: 1,
106 },
107 label: None,
108 format: None,
109 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
110 mip_level_count: 1,
111 sample_count: 1,
112 dimension: TextureDimension::D2,
113 sampler_desc,
114 }
115 }
116}
117
118pub struct Texture {
120 texture: Arc<wgpu::Texture>,
121 view: Arc<wgpu::TextureView>,
122 bind_group: Arc<BindGroup>,
123 size: Extent3d,
124}
125
126impl Texture {
127 pub fn from_raw_parts(
131 device: &Device,
132 renderer: &Renderer,
133 texture: Arc<wgpu::Texture>,
134 view: Arc<wgpu::TextureView>,
135 bind_group: Option<Arc<BindGroup>>,
136 config: Option<&RawTextureConfig>,
137 size: Extent3d,
138 ) -> Self {
139 let bind_group = bind_group.unwrap_or_else(|| {
140 let config = config.unwrap();
141
142 let sampler = device.create_sampler(&config.sampler_desc);
144
145 Arc::new(device.create_bind_group(&BindGroupDescriptor {
147 label: config.label,
148 layout: &renderer.texture_layout,
149 entries: &[
150 BindGroupEntry {
151 binding: 0,
152 resource: BindingResource::TextureView(&view),
153 },
154 BindGroupEntry {
155 binding: 1,
156 resource: BindingResource::Sampler(&sampler),
157 },
158 ],
159 }))
160 });
161
162 Self {
163 texture,
164 view,
165 bind_group,
166 size,
167 }
168 }
169
170 pub fn new(device: &Device, renderer: &Renderer, config: TextureConfig) -> Self {
172 let texture = Arc::new(device.create_texture(&TextureDescriptor {
174 label: config.label,
175 size: config.size,
176 mip_level_count: config.mip_level_count,
177 sample_count: config.sample_count,
178 dimension: config.dimension,
179 format: config.format.unwrap_or(renderer.config.texture_format),
180 usage: config.usage,
181 view_formats: &[config.format.unwrap_or(renderer.config.texture_format)],
182 }));
183
184 let view = Arc::new(texture.create_view(&TextureViewDescriptor::default()));
186
187 let sampler = device.create_sampler(&config.sampler_desc);
189
190 let bind_group = Arc::new(device.create_bind_group(&BindGroupDescriptor {
192 label: config.label,
193 layout: &renderer.texture_layout,
194 entries: &[
195 BindGroupEntry {
196 binding: 0,
197 resource: BindingResource::TextureView(&view),
198 },
199 BindGroupEntry {
200 binding: 1,
201 resource: BindingResource::Sampler(&sampler),
202 },
203 ],
204 }));
205
206 Self {
207 texture,
208 view,
209 bind_group,
210 size: config.size,
211 }
212 }
213
214 pub fn write(&self, queue: &Queue, data: &[u8], width: u32, height: u32) {
220 queue.write_texture(
221 TexelCopyTextureInfo {
223 texture: &self.texture,
224 mip_level: 0,
225 origin: Origin3d { x: 0, y: 0, z: 0 },
226 aspect: TextureAspect::All,
227 },
228 data,
230 TexelCopyBufferLayout {
232 offset: 0,
233 bytes_per_row: Some(width * 4),
234 rows_per_image: Some(height),
235 },
236 Extent3d {
238 width,
239 height,
240 depth_or_array_layers: 1,
241 },
242 );
243 }
244
245 pub fn width(&self) -> u32 {
247 self.size.width
248 }
249
250 pub fn height(&self) -> u32 {
252 self.size.height
253 }
254
255 pub fn depth(&self) -> u32 {
257 self.size.depth_or_array_layers
258 }
259
260 pub fn size(&self) -> Extent3d {
262 self.size
263 }
264
265 pub fn texture(&self) -> &wgpu::Texture {
267 &self.texture
268 }
269
270 pub fn view(&self) -> &wgpu::TextureView {
272 &self.view
273 }
274}
275
276pub struct RendererConfig<'s> {
278 pub texture_format: TextureFormat,
279 pub depth_format: Option<TextureFormat>,
280 pub sample_count: u32,
281 pub shader: Option<ShaderModuleDescriptor<'s>>,
282 pub vertex_shader_entry_point: Option<&'s str>,
283 pub fragment_shader_entry_point: Option<&'s str>,
284}
285
286impl<'s> RendererConfig<'s> {
287 pub fn with_shaders(shader: ShaderModuleDescriptor<'s>) -> Self {
289 RendererConfig {
290 texture_format: TextureFormat::Rgba8Unorm,
291 depth_format: None,
292 sample_count: 1,
293 shader: Some(shader),
294 vertex_shader_entry_point: Some(VS_ENTRY_POINT),
295 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
296 }
297 }
298}
299
300impl Default for RendererConfig<'_> {
301 fn default() -> Self {
305 Self::new()
306 }
307}
308
309impl RendererConfig<'_> {
310 pub fn new() -> Self {
314 RendererConfig {
315 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
316 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
317 }
318 }
319
320 pub fn new_srgb() -> Self {
324 RendererConfig {
325 fragment_shader_entry_point: Some(FS_ENTRY_POINT_SRGB),
326 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
327 }
328 }
329}
330
331pub struct RenderData {
332 fb_size: [f32; 2],
333 last_size: [f32; 2],
334 last_pos: [f32; 2],
335 vertex_buffer: Option<Buffer>,
336 vertex_buffer_size: usize,
337 index_buffer: Option<Buffer>,
338 index_buffer_size: usize,
339 draw_list_offsets: SmallVec<[(i32, u32); 4]>,
340 render: bool,
341}
342
343pub struct Renderer {
344 pipeline: RenderPipeline,
345 uniform_buffer: Buffer,
346 uniform_bind_group: BindGroup,
347 pub textures: Textures<Texture>,
349 texture_layout: BindGroupLayout,
350 render_data: Option<RenderData>,
351 config: RendererConfig<'static>,
352}
353
354impl Renderer {
355 pub fn new(
357 imgui: &mut Context,
358 device: &Device,
359 queue: &Queue,
360 config: RendererConfig,
361 ) -> Self {
362 let RendererConfig {
363 texture_format,
364 depth_format,
365 sample_count,
366 shader,
367 vertex_shader_entry_point,
368 fragment_shader_entry_point,
369 } = config;
370
371 let shader_module = device.create_shader_module(shader.unwrap());
373
374 let size = 64;
376 let uniform_buffer = device.create_buffer(&BufferDescriptor {
377 label: Some("imgui-wgpu uniform buffer"),
378 size,
379 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
380 mapped_at_creation: false,
381 });
382
383 let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
385 label: None,
386 entries: &[BindGroupLayoutEntry {
387 binding: 0,
388 visibility: wgpu::ShaderStages::VERTEX,
389 ty: BindingType::Buffer {
390 ty: BufferBindingType::Uniform,
391 has_dynamic_offset: false,
392 min_binding_size: None,
393 },
394 count: None,
395 }],
396 });
397
398 let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
400 label: Some("imgui-wgpu bind group"),
401 layout: &uniform_layout,
402 entries: &[BindGroupEntry {
403 binding: 0,
404 resource: uniform_buffer.as_entire_binding(),
405 }],
406 });
407
408 let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
410 label: Some("imgui-wgpu bind group layout"),
411 entries: &[
412 BindGroupLayoutEntry {
413 binding: 0,
414 visibility: wgpu::ShaderStages::FRAGMENT,
415 ty: BindingType::Texture {
416 multisampled: false,
417 sample_type: TextureSampleType::Float { filterable: true },
418 view_dimension: TextureViewDimension::D2,
419 },
420 count: None,
421 },
422 BindGroupLayoutEntry {
423 binding: 1,
424 visibility: wgpu::ShaderStages::FRAGMENT,
425 ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
426 count: None,
427 },
428 ],
429 });
430
431 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
433 label: Some("imgui-wgpu pipeline layout"),
434 bind_group_layouts: &[&uniform_layout, &texture_layout],
435 push_constant_ranges: &[],
436 });
437
438 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
441 label: Some("imgui-wgpu pipeline"),
442 layout: Some(&pipeline_layout),
443 vertex: VertexState {
444 module: &shader_module,
445 entry_point: vertex_shader_entry_point,
446 compilation_options: Default::default(),
447 buffers: &[VertexBufferLayout {
448 array_stride: size_of::<DrawVert>() as BufferAddress,
449 step_mode: VertexStepMode::Vertex,
450 attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Unorm8x4],
451 }],
452 },
453 primitive: PrimitiveState {
454 topology: PrimitiveTopology::TriangleList,
455 strip_index_format: None,
456 front_face: FrontFace::Cw,
457 cull_mode: None,
458 polygon_mode: PolygonMode::Fill,
459 unclipped_depth: false,
460 conservative: false,
461 },
462 depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
463 format,
464 depth_write_enabled: false,
465 depth_compare: wgpu::CompareFunction::Always,
466 stencil: wgpu::StencilState::default(),
467 bias: DepthBiasState::default(),
468 }),
469 multisample: MultisampleState {
470 count: sample_count,
471 ..Default::default()
472 },
473 fragment: Some(FragmentState {
474 module: &shader_module,
475 entry_point: fragment_shader_entry_point,
476 compilation_options: Default::default(),
477 targets: &[Some(ColorTargetState {
478 format: texture_format,
479 blend: Some(BlendState {
480 color: BlendComponent {
481 src_factor: BlendFactor::SrcAlpha,
482 dst_factor: BlendFactor::OneMinusSrcAlpha,
483 operation: BlendOperation::Add,
484 },
485 alpha: BlendComponent {
486 src_factor: BlendFactor::OneMinusDstAlpha,
487 dst_factor: BlendFactor::One,
488 operation: BlendOperation::Add,
489 },
490 }),
491 write_mask: ColorWrites::ALL,
492 })],
493 }),
494 multiview: None,
495 cache: None,
496 });
497
498 let mut renderer = Self {
499 pipeline,
500 uniform_buffer,
501 uniform_bind_group,
502 textures: Textures::new(),
503 texture_layout,
504 render_data: None,
505 config: RendererConfig {
506 texture_format,
507 depth_format,
508 sample_count,
509 shader: None,
510 vertex_shader_entry_point: None,
511 fragment_shader_entry_point: None,
512 },
513 };
514
515 renderer.reload_font_texture(imgui, device, queue);
517
518 renderer
519 }
520
521 pub fn prepare(
525 &self,
526 draw_data: &DrawData,
527 render_data: Option<RenderData>,
528 queue: &Queue,
529 device: &Device,
530 ) -> RenderData {
531 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
532 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
533
534 let mut render_data = render_data.unwrap_or_else(|| RenderData {
535 fb_size: [fb_width, fb_height],
536 last_size: [0.0, 0.0],
537 last_pos: [0.0, 0.0],
538 vertex_buffer: None,
539 vertex_buffer_size: 0,
540 index_buffer: None,
541 index_buffer_size: 0,
542 draw_list_offsets: SmallVec::<[_; 4]>::new(),
543 render: false,
544 });
545
546 if fb_width <= 0.0 || fb_height <= 0.0 {
548 render_data.render = false;
549 return render_data;
550 } else {
551 render_data.render = true;
552 }
553
554 if (render_data.last_size[0] - draw_data.display_size[0]).abs() > f32::EPSILON
556 || (render_data.last_size[1] - draw_data.display_size[1]).abs() > f32::EPSILON
557 || (render_data.last_pos[0] - draw_data.display_pos[0]).abs() > f32::EPSILON
558 || (render_data.last_pos[1] - draw_data.display_pos[1]).abs() > f32::EPSILON
559 {
560 render_data.fb_size = [fb_width, fb_height];
561 render_data.last_size = draw_data.display_size;
562 render_data.last_pos = draw_data.display_pos;
563
564 let width = draw_data.display_size[0];
565 let height = draw_data.display_size[1];
566
567 let offset_x = draw_data.display_pos[0] / width;
568 let offset_y = draw_data.display_pos[1] / height;
569
570 let matrix = [
573 [2.0 / width, 0.0, 0.0, 0.0],
574 [0.0, 2.0 / -height, 0.0, 0.0],
575 [0.0, 0.0, 1.0, 0.0],
576 [-1.0 - offset_x * 2.0, 1.0 + offset_y * 2.0, 0.0, 1.0],
577 ];
578 self.update_uniform_buffer(queue, &matrix);
579 }
580
581 render_data.draw_list_offsets.clear();
582
583 let mut vertex_count = 0;
584 let mut index_count = 0;
585 for draw_list in draw_data.draw_lists() {
586 render_data
587 .draw_list_offsets
588 .push((vertex_count as i32, index_count as u32));
589 vertex_count += draw_list.vtx_buffer().len();
590 index_count += draw_list.idx_buffer().len();
591 }
592
593 let mut vertices = Vec::with_capacity(vertex_count * std::mem::size_of::<DrawVertPod>());
594 let mut indices = Vec::with_capacity(index_count * std::mem::size_of::<DrawIdx>());
595
596 for draw_list in draw_data.draw_lists() {
597 let vertices_pod: &[DrawVertPod] = unsafe { draw_list.transmute_vtx_buffer() };
599 vertices.extend_from_slice(bytemuck::cast_slice(vertices_pod));
600 indices.extend_from_slice(bytemuck::cast_slice(draw_list.idx_buffer()));
601 }
602
603 indices.resize(
605 indices.len() + COPY_BUFFER_ALIGNMENT as usize
606 - indices.len() % COPY_BUFFER_ALIGNMENT as usize,
607 0,
608 );
609
610 if render_data.index_buffer.is_none() || render_data.index_buffer_size < indices.len() {
612 let buffer = device.create_buffer_init(&BufferInitDescriptor {
613 label: Some("imgui-wgpu index buffer"),
614 contents: &indices,
615 usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
616 });
617 render_data.index_buffer = Some(buffer);
618 render_data.index_buffer_size = indices.len();
619 } else if let Some(buffer) = render_data.index_buffer.as_ref() {
620 queue.write_buffer(buffer, 0, &indices);
622 } else {
623 unreachable!()
624 }
625
626 if render_data.vertex_buffer.is_none() || render_data.vertex_buffer_size < vertices.len() {
628 let buffer = device.create_buffer_init(&BufferInitDescriptor {
629 label: Some("imgui-wgpu vertex buffer"),
630 contents: &vertices,
631 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
632 });
633 render_data.vertex_buffer = Some(buffer);
634 render_data.vertex_buffer_size = vertices.len();
635 } else if let Some(buffer) = render_data.vertex_buffer.as_ref() {
636 queue.write_buffer(buffer, 0, &vertices);
638 } else {
639 unreachable!()
640 }
641
642 render_data
643 }
644
645 pub fn split_render<'r>(
649 &'r self,
650 draw_data: &DrawData,
651 render_data: &'r RenderData,
652 rpass: &mut RenderPass<'r>,
653 ) -> RendererResult<()> {
654 if !render_data.render {
655 return Ok(());
656 }
657
658 rpass.set_pipeline(&self.pipeline);
659 rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
660 rpass.set_vertex_buffer(0, render_data.vertex_buffer.as_ref().unwrap().slice(..));
661 rpass.set_index_buffer(
662 render_data.index_buffer.as_ref().unwrap().slice(..),
663 IndexFormat::Uint16,
664 );
665
666 for (draw_list, bases) in draw_data
668 .draw_lists()
669 .zip(render_data.draw_list_offsets.iter())
670 {
671 self.render_draw_list(
672 rpass,
673 draw_list,
674 render_data.fb_size,
675 draw_data.display_pos,
676 draw_data.framebuffer_scale,
677 *bases,
678 )?;
679 }
680
681 Ok(())
682 }
683
684 pub fn render<'r>(
686 &'r mut self,
687 draw_data: &DrawData,
688 queue: &Queue,
689 device: &Device,
690 rpass: &mut RenderPass<'r>,
691 ) -> RendererResult<()> {
692 let render_data = self.render_data.take();
693 self.render_data = Some(self.prepare(draw_data, render_data, queue, device));
694 self.split_render(draw_data, self.render_data.as_ref().unwrap(), rpass)
695 }
696
697 fn render_draw_list<'render>(
699 &'render self,
700 rpass: &mut RenderPass<'render>,
701 draw_list: &DrawList,
702 fb_size: [f32; 2],
703 clip_off: [f32; 2],
704 clip_scale: [f32; 2],
705 (vertex_base, index_base): (i32, u32),
706 ) -> RendererResult<()> {
707 let mut start = index_base;
708
709 for cmd in draw_list.commands() {
710 if let Elements { count, cmd_params } = cmd {
711 let clip_rect = [
712 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0],
713 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1],
714 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0],
715 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1],
716 ];
717
718 let texture_id = cmd_params.texture_id;
720 let tex = self
721 .textures
722 .get(texture_id)
723 .ok_or(RendererError::BadTexture(texture_id))?;
724 rpass.set_bind_group(1, Some(tex.bind_group.as_ref()), &[]);
725
726 let end = start + count as u32;
728 if clip_rect[0] < fb_size[0]
729 && clip_rect[1] < fb_size[1]
730 && clip_rect[2] >= 0.0
731 && clip_rect[3] >= 0.0
732 {
733 let scissors = (
734 clip_rect[0].max(0.0).floor() as u32,
735 clip_rect[1].max(0.0).floor() as u32,
736 (clip_rect[2].min(fb_size[0]) - clip_rect[0].max(0.0))
737 .abs()
738 .ceil() as u32,
739 (clip_rect[3].min(fb_size[1]) - clip_rect[1].max(0.0))
740 .abs()
741 .ceil() as u32,
742 );
743
744 if scissors.2 > 0 && scissors.3 > 0 {
750 rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3);
751
752 rpass.draw_indexed(start..end, vertex_base, 0..1);
754 }
755 }
756
757 start = end;
760 }
761 }
762 Ok(())
763 }
764
765 fn update_uniform_buffer(&self, queue: &Queue, matrix: &[[f32; 4]; 4]) {
767 let data = bytemuck::bytes_of(matrix);
768 queue.write_buffer(&self.uniform_buffer, 0, data);
769 }
770
771 pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &Queue) {
775 let fonts = imgui.fonts();
776 self.textures.remove(fonts.tex_id);
778
779 let handle = fonts.build_rgba32_texture();
781 let font_texture_cnfig = TextureConfig {
782 label: Some("imgui-wgpu font atlas"),
783 size: Extent3d {
784 width: handle.width,
785 height: handle.height,
786 ..Default::default()
787 },
788 ..Default::default()
789 };
790
791 let font_texture = Texture::new(device, self, font_texture_cnfig);
792 font_texture.write(queue, handle.data, handle.width, handle.height);
793 fonts.tex_id = self.textures.insert(font_texture);
794 fonts.clear_tex_data();
796 }
797}