1use imgui::{
60 Context, DrawCmd::Elements, DrawData, DrawIdx, DrawList, DrawVert, TextureId, Textures,
61};
62use smallvec::SmallVec;
63use std::error::Error;
64use std::fmt;
65use std::mem::size_of;
66use std::sync::Arc;
67use wgpu::util::{BufferInitDescriptor, DeviceExt};
68use wgpu::*;
69
70static VS_ENTRY_POINT: &str = "vs_main";
71static FS_ENTRY_POINT_LINEAR: &str = "fs_main_linear";
72static FS_ENTRY_POINT_SRGB: &str = "fs_main_srgb";
73
74pub type RendererResult<T> = Result<T, RendererError>;
76
77#[repr(transparent)]
78#[derive(Debug, Copy, Clone)]
79struct DrawVertPod(DrawVert);
80
81unsafe impl bytemuck::Zeroable for DrawVertPod {}
82
83unsafe impl bytemuck::Pod for DrawVertPod {}
84
85#[derive(Clone, Debug)]
87pub enum RendererError {
88 BadTexture(TextureId),
91}
92
93impl fmt::Display for RendererError {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match *self {
96 RendererError::BadTexture(id) => {
97 write!(f, "imgui render error: bad texture id '{}'", id.id())
98 }
99 }
100 }
101}
102
103impl Error for RendererError {}
104
105#[allow(dead_code)]
106enum ShaderStage {
107 Vertex,
108 Fragment,
109 Compute,
110}
111
112#[derive(Clone)]
115pub struct RawTextureConfig<'a> {
116 pub label: Option<&'a str>,
118 pub sampler_desc: SamplerDescriptor<'a>,
120}
121
122#[derive(Clone)]
126pub struct TextureConfig<'a> {
127 pub size: Extent3d,
129 pub label: Option<&'a str>,
131 pub format: Option<TextureFormat>,
133 pub usage: TextureUsages,
135 pub mip_level_count: u32,
137 pub sample_count: u32,
139 pub dimension: TextureDimension,
141 pub sampler_desc: SamplerDescriptor<'a>,
143}
144
145impl Default for TextureConfig<'_> {
146 fn default() -> Self {
148 let sampler_desc = SamplerDescriptor {
149 label: Some("imgui-wgpu sampler"),
150 address_mode_u: AddressMode::ClampToEdge,
151 address_mode_v: AddressMode::ClampToEdge,
152 address_mode_w: AddressMode::ClampToEdge,
153 mag_filter: FilterMode::Linear,
154 min_filter: FilterMode::Linear,
155 mipmap_filter: MipmapFilterMode::Linear,
156 lod_min_clamp: 0.0,
157 lod_max_clamp: 100.0,
158 compare: None,
159 anisotropy_clamp: 1,
160 border_color: None,
161 };
162
163 Self {
164 size: Extent3d {
165 width: 0,
166 height: 0,
167 depth_or_array_layers: 1,
168 },
169 label: None,
170 format: None,
171 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
172 mip_level_count: 1,
173 sample_count: 1,
174 dimension: TextureDimension::D2,
175 sampler_desc,
176 }
177 }
178}
179
180pub struct Texture {
182 texture: Arc<wgpu::Texture>,
183 view: Arc<wgpu::TextureView>,
184 bind_group: Arc<BindGroup>,
185 size: Extent3d,
186}
187
188impl Texture {
189 pub fn from_raw_parts(
193 device: &Device,
194 renderer: &Renderer,
195 texture: Arc<wgpu::Texture>,
196 view: Arc<wgpu::TextureView>,
197 bind_group: Option<Arc<BindGroup>>,
198 config: Option<&RawTextureConfig>,
199 size: Extent3d,
200 ) -> Self {
201 let bind_group = bind_group.unwrap_or_else(|| {
202 let config = config.unwrap();
203
204 let sampler = device.create_sampler(&config.sampler_desc);
206
207 Arc::new(device.create_bind_group(&BindGroupDescriptor {
209 label: config.label,
210 layout: &renderer.texture_layout,
211 entries: &[
212 BindGroupEntry {
213 binding: 0,
214 resource: BindingResource::TextureView(&view),
215 },
216 BindGroupEntry {
217 binding: 1,
218 resource: BindingResource::Sampler(&sampler),
219 },
220 ],
221 }))
222 });
223
224 Self {
225 texture,
226 view,
227 bind_group,
228 size,
229 }
230 }
231
232 pub fn new(device: &Device, renderer: &Renderer, config: TextureConfig) -> Self {
234 let texture = Arc::new(device.create_texture(&TextureDescriptor {
236 label: config.label,
237 size: config.size,
238 mip_level_count: config.mip_level_count,
239 sample_count: config.sample_count,
240 dimension: config.dimension,
241 format: config.format.unwrap_or(renderer.config.texture_format),
242 usage: config.usage,
243 view_formats: &[config.format.unwrap_or(renderer.config.texture_format)],
244 }));
245
246 let view = Arc::new(texture.create_view(&TextureViewDescriptor::default()));
248
249 let sampler = device.create_sampler(&config.sampler_desc);
251
252 let bind_group = Arc::new(device.create_bind_group(&BindGroupDescriptor {
254 label: config.label,
255 layout: &renderer.texture_layout,
256 entries: &[
257 BindGroupEntry {
258 binding: 0,
259 resource: BindingResource::TextureView(&view),
260 },
261 BindGroupEntry {
262 binding: 1,
263 resource: BindingResource::Sampler(&sampler),
264 },
265 ],
266 }));
267
268 Self {
269 texture,
270 view,
271 bind_group,
272 size: config.size,
273 }
274 }
275
276 pub fn write(&self, queue: &Queue, data: &[u8], width: u32, height: u32) {
282 let bytes_per_block = self
283 .texture
284 .format()
285 .block_copy_size(None)
286 .expect("Texture format must have a valid block copy size");
287 queue.write_texture(
288 TexelCopyTextureInfo {
290 texture: &self.texture,
291 mip_level: 0,
292 origin: Origin3d { x: 0, y: 0, z: 0 },
293 aspect: TextureAspect::All,
294 },
295 data,
297 TexelCopyBufferLayout {
299 offset: 0,
300 bytes_per_row: Some(width * bytes_per_block),
301 rows_per_image: Some(height),
302 },
303 Extent3d {
305 width,
306 height,
307 depth_or_array_layers: 1,
308 },
309 );
310 }
311
312 pub fn width(&self) -> u32 {
314 self.size.width
315 }
316
317 pub fn height(&self) -> u32 {
319 self.size.height
320 }
321
322 pub fn depth(&self) -> u32 {
324 self.size.depth_or_array_layers
325 }
326
327 pub fn size(&self) -> Extent3d {
329 self.size
330 }
331
332 pub fn texture(&self) -> &wgpu::Texture {
334 &self.texture
335 }
336
337 pub fn view(&self) -> &wgpu::TextureView {
339 &self.view
340 }
341}
342
343pub struct RendererConfig<'s> {
348 pub texture_format: TextureFormat,
350 pub depth_format: Option<TextureFormat>,
352 pub sample_count: u32,
354 pub shader: Option<ShaderModuleDescriptor<'s>>,
356 pub vertex_shader_entry_point: Option<&'s str>,
358 pub fragment_shader_entry_point: Option<&'s str>,
360}
361
362impl<'s> RendererConfig<'s> {
363 pub fn with_shaders(shader: ShaderModuleDescriptor<'s>) -> Self {
365 RendererConfig {
366 texture_format: TextureFormat::Rgba8Unorm,
367 depth_format: None,
368 sample_count: 1,
369 shader: Some(shader),
370 vertex_shader_entry_point: Some(VS_ENTRY_POINT),
371 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
372 }
373 }
374}
375
376impl Default for RendererConfig<'_> {
377 fn default() -> Self {
381 Self::new()
382 }
383}
384
385impl RendererConfig<'_> {
386 pub fn new() -> Self {
390 RendererConfig {
391 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
392 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
393 }
394 }
395
396 pub fn new_srgb() -> Self {
400 RendererConfig {
401 fragment_shader_entry_point: Some(FS_ENTRY_POINT_SRGB),
402 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
403 }
404 }
405}
406
407pub struct RenderData {
414 fb_size: [f32; 2],
415 last_size: [f32; 2],
416 last_pos: [f32; 2],
417 vertex_buffer: Option<Buffer>,
418 vertex_buffer_size: usize,
419 index_buffer: Option<Buffer>,
420 index_buffer_size: usize,
421 draw_list_offsets: SmallVec<[(i32, u32); 4]>,
422 render: bool,
423}
424
425pub struct Renderer {
432 pipeline: RenderPipeline,
433 uniform_buffer: Buffer,
434 uniform_bind_group: BindGroup,
435 pub textures: Textures<Texture>,
440 texture_layout: BindGroupLayout,
441 render_data: Option<RenderData>,
442 config: RendererConfig<'static>,
443}
444
445impl Renderer {
446 pub fn new(
448 imgui: &mut Context,
449 device: &Device,
450 queue: &Queue,
451 config: RendererConfig,
452 ) -> Self {
453 let RendererConfig {
454 texture_format,
455 depth_format,
456 sample_count,
457 shader,
458 vertex_shader_entry_point,
459 fragment_shader_entry_point,
460 } = config;
461
462 let shader_module = device.create_shader_module(shader.unwrap());
464
465 let size = 64;
467 let uniform_buffer = device.create_buffer(&BufferDescriptor {
468 label: Some("imgui-wgpu uniform buffer"),
469 size,
470 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
471 mapped_at_creation: false,
472 });
473
474 let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
476 label: None,
477 entries: &[BindGroupLayoutEntry {
478 binding: 0,
479 visibility: wgpu::ShaderStages::VERTEX,
480 ty: BindingType::Buffer {
481 ty: BufferBindingType::Uniform,
482 has_dynamic_offset: false,
483 min_binding_size: None,
484 },
485 count: None,
486 }],
487 });
488
489 let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
491 label: Some("imgui-wgpu bind group"),
492 layout: &uniform_layout,
493 entries: &[BindGroupEntry {
494 binding: 0,
495 resource: uniform_buffer.as_entire_binding(),
496 }],
497 });
498
499 let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
501 label: Some("imgui-wgpu bind group layout"),
502 entries: &[
503 BindGroupLayoutEntry {
504 binding: 0,
505 visibility: wgpu::ShaderStages::FRAGMENT,
506 ty: BindingType::Texture {
507 multisampled: false,
508 sample_type: TextureSampleType::Float { filterable: true },
509 view_dimension: TextureViewDimension::D2,
510 },
511 count: None,
512 },
513 BindGroupLayoutEntry {
514 binding: 1,
515 visibility: wgpu::ShaderStages::FRAGMENT,
516 ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
517 count: None,
518 },
519 ],
520 });
521
522 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
524 label: Some("imgui-wgpu pipeline layout"),
525 bind_group_layouts: &[Some(&uniform_layout), Some(&texture_layout)],
526 immediate_size: 0,
527 });
528
529 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
532 label: Some("imgui-wgpu pipeline"),
533 layout: Some(&pipeline_layout),
534 vertex: VertexState {
535 module: &shader_module,
536 entry_point: vertex_shader_entry_point,
537 compilation_options: Default::default(),
538 buffers: &[VertexBufferLayout {
539 array_stride: size_of::<DrawVert>() as BufferAddress,
540 step_mode: VertexStepMode::Vertex,
541 attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Unorm8x4],
542 }],
543 },
544 primitive: PrimitiveState {
545 topology: PrimitiveTopology::TriangleList,
546 strip_index_format: None,
547 front_face: FrontFace::Cw,
548 cull_mode: None,
549 polygon_mode: PolygonMode::Fill,
550 unclipped_depth: false,
551 conservative: false,
552 },
553 depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
554 format,
555 depth_write_enabled: Some(false),
556 depth_compare: None,
557 stencil: wgpu::StencilState::default(),
558 bias: DepthBiasState::default(),
559 }),
560 multisample: MultisampleState {
561 count: sample_count,
562 ..Default::default()
563 },
564 fragment: Some(FragmentState {
565 module: &shader_module,
566 entry_point: fragment_shader_entry_point,
567 compilation_options: Default::default(),
568 targets: &[Some(ColorTargetState {
569 format: texture_format,
570 blend: Some(BlendState {
571 color: BlendComponent {
572 src_factor: BlendFactor::SrcAlpha,
573 dst_factor: BlendFactor::OneMinusSrcAlpha,
574 operation: BlendOperation::Add,
575 },
576 alpha: BlendComponent {
577 src_factor: BlendFactor::OneMinusDstAlpha,
578 dst_factor: BlendFactor::One,
579 operation: BlendOperation::Add,
580 },
581 }),
582 write_mask: ColorWrites::ALL,
583 })],
584 }),
585 multiview_mask: None,
586 cache: None,
587 });
588
589 let mut renderer = Self {
590 pipeline,
591 uniform_buffer,
592 uniform_bind_group,
593 textures: Textures::new(),
594 texture_layout,
595 render_data: None,
596 config: RendererConfig {
597 texture_format,
598 depth_format,
599 sample_count,
600 shader: None,
601 vertex_shader_entry_point: None,
602 fragment_shader_entry_point: None,
603 },
604 };
605
606 renderer.reload_font_texture(imgui, device, queue);
608
609 renderer
610 }
611
612 pub fn prepare(
616 &self,
617 draw_data: &DrawData,
618 render_data: Option<RenderData>,
619 queue: &Queue,
620 device: &Device,
621 ) -> RenderData {
622 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
623 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
624
625 let mut render_data = render_data.unwrap_or_else(|| RenderData {
626 fb_size: [fb_width, fb_height],
627 last_size: [0.0, 0.0],
628 last_pos: [0.0, 0.0],
629 vertex_buffer: None,
630 vertex_buffer_size: 0,
631 index_buffer: None,
632 index_buffer_size: 0,
633 draw_list_offsets: SmallVec::<[_; 4]>::new(),
634 render: false,
635 });
636
637 if fb_width <= 0.0 || fb_height <= 0.0 {
639 render_data.render = false;
640 return render_data;
641 } else {
642 render_data.render = true;
643 }
644
645 if draw_data.draw_lists_count() == 0 {
647 render_data.render = false;
648 return render_data;
649 }
650
651 if (render_data.last_size[0] - draw_data.display_size[0]).abs() > f32::EPSILON
653 || (render_data.last_size[1] - draw_data.display_size[1]).abs() > f32::EPSILON
654 || (render_data.last_pos[0] - draw_data.display_pos[0]).abs() > f32::EPSILON
655 || (render_data.last_pos[1] - draw_data.display_pos[1]).abs() > f32::EPSILON
656 {
657 render_data.fb_size = [fb_width, fb_height];
658 render_data.last_size = draw_data.display_size;
659 render_data.last_pos = draw_data.display_pos;
660
661 let width = draw_data.display_size[0];
662 let height = draw_data.display_size[1];
663
664 let offset_x = draw_data.display_pos[0] / width;
665 let offset_y = draw_data.display_pos[1] / height;
666
667 let matrix = [
670 [2.0 / width, 0.0, 0.0, 0.0],
671 [0.0, 2.0 / -height, 0.0, 0.0],
672 [0.0, 0.0, 1.0, 0.0],
673 [-1.0 - offset_x * 2.0, 1.0 + offset_y * 2.0, 0.0, 1.0],
674 ];
675 self.update_uniform_buffer(queue, &matrix);
676 }
677
678 render_data.draw_list_offsets.clear();
679
680 let mut vertex_count = 0;
681 let mut index_count = 0;
682 for draw_list in draw_data.draw_lists() {
683 render_data
684 .draw_list_offsets
685 .push((vertex_count as i32, index_count as u32));
686 vertex_count += draw_list.vtx_buffer().len();
687 index_count += draw_list.idx_buffer().len();
688 }
689
690 let mut vertices = Vec::with_capacity(vertex_count * std::mem::size_of::<DrawVertPod>());
691 let mut indices = Vec::with_capacity(index_count * std::mem::size_of::<DrawIdx>());
692
693 for draw_list in draw_data.draw_lists() {
694 let vertices_pod: &[DrawVertPod] = unsafe { draw_list.transmute_vtx_buffer() };
696 vertices.extend_from_slice(bytemuck::cast_slice(vertices_pod));
697 indices.extend_from_slice(bytemuck::cast_slice(draw_list.idx_buffer()));
698 }
699
700 indices.resize(
702 indices.len() + COPY_BUFFER_ALIGNMENT as usize
703 - indices.len() % COPY_BUFFER_ALIGNMENT as usize,
704 0,
705 );
706
707 if render_data.index_buffer.is_none() || render_data.index_buffer_size < indices.len() {
709 let buffer = device.create_buffer_init(&BufferInitDescriptor {
710 label: Some("imgui-wgpu index buffer"),
711 contents: &indices,
712 usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
713 });
714 render_data.index_buffer = Some(buffer);
715 render_data.index_buffer_size = indices.len();
716 } else if let Some(buffer) = render_data.index_buffer.as_ref() {
717 queue.write_buffer(buffer, 0, &indices);
719 } else {
720 unreachable!()
721 }
722
723 if render_data.vertex_buffer.is_none() || render_data.vertex_buffer_size < vertices.len() {
725 let buffer = device.create_buffer_init(&BufferInitDescriptor {
726 label: Some("imgui-wgpu vertex buffer"),
727 contents: &vertices,
728 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
729 });
730 render_data.vertex_buffer = Some(buffer);
731 render_data.vertex_buffer_size = vertices.len();
732 } else if let Some(buffer) = render_data.vertex_buffer.as_ref() {
733 queue.write_buffer(buffer, 0, &vertices);
735 } else {
736 unreachable!()
737 }
738
739 render_data
740 }
741
742 pub fn split_render(
746 &self,
747 draw_data: &DrawData,
748 render_data: &RenderData,
749 rpass: &mut RenderPass<'_>,
750 ) -> RendererResult<()> {
751 if !render_data.render {
752 return Ok(());
753 }
754
755 let vertex_buffer = render_data.vertex_buffer.as_ref().unwrap();
756 if vertex_buffer.size() == 0 {
757 return Ok(());
758 }
759
760 let index_buffer = render_data.index_buffer.as_ref().unwrap();
761 if index_buffer.size() == 0 {
762 return Ok(());
763 }
764
765 rpass.set_pipeline(&self.pipeline);
766 rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
767 rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
768 rpass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint16);
769
770 for (draw_list, bases) in draw_data
772 .draw_lists()
773 .zip(render_data.draw_list_offsets.iter())
774 {
775 self.render_draw_list(
776 rpass,
777 draw_list,
778 render_data.fb_size,
779 draw_data.display_pos,
780 draw_data.framebuffer_scale,
781 *bases,
782 )?;
783 }
784
785 Ok(())
786 }
787
788 pub fn render(
790 &mut self,
791 draw_data: &DrawData,
792 queue: &Queue,
793 device: &Device,
794 rpass: &mut RenderPass<'_>,
795 ) -> RendererResult<()> {
796 let render_data = self.render_data.take();
797 self.render_data = Some(self.prepare(draw_data, render_data, queue, device));
798 self.split_render(draw_data, self.render_data.as_ref().unwrap(), rpass)
799 }
800
801 fn render_draw_list(
803 &self,
804 rpass: &mut RenderPass<'_>,
805 draw_list: &DrawList,
806 fb_size: [f32; 2],
807 clip_off: [f32; 2],
808 clip_scale: [f32; 2],
809 (vertex_base, index_base): (i32, u32),
810 ) -> RendererResult<()> {
811 for cmd in draw_list.commands() {
812 if let Elements { count, cmd_params } = cmd {
813 let clip_rect = [
814 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0],
815 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1],
816 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0],
817 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1],
818 ];
819
820 let texture_id = cmd_params.texture_id;
822 let tex = self
823 .textures
824 .get(texture_id)
825 .ok_or(RendererError::BadTexture(texture_id))?;
826 rpass.set_bind_group(1, Some(tex.bind_group.as_ref()), &[]);
827
828 let start = index_base + cmd_params.idx_offset as u32;
830 let end = start + count as u32;
831 if clip_rect[0] < fb_size[0]
832 && clip_rect[1] < fb_size[1]
833 && clip_rect[2] >= 0.0
834 && clip_rect[3] >= 0.0
835 {
836 let scissors = (
837 clip_rect[0].max(0.0).floor() as u32,
838 clip_rect[1].max(0.0).floor() as u32,
839 (clip_rect[2].min(fb_size[0]) - clip_rect[0].max(0.0))
840 .abs()
841 .ceil() as u32,
842 (clip_rect[3].min(fb_size[1]) - clip_rect[1].max(0.0))
843 .abs()
844 .ceil() as u32,
845 );
846
847 if scissors.2 > 0 && scissors.3 > 0 {
853 rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3);
854
855 rpass.draw_indexed(
857 start..end,
858 vertex_base + cmd_params.vtx_offset as i32,
859 0..1,
860 );
861 }
862 }
863 }
864 }
865 Ok(())
866 }
867
868 fn update_uniform_buffer(&self, queue: &Queue, matrix: &[[f32; 4]; 4]) {
870 let data = bytemuck::bytes_of(matrix);
871 queue.write_buffer(&self.uniform_buffer, 0, data);
872 }
873
874 pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &Queue) {
878 let fonts = imgui.fonts();
879 self.textures.remove(fonts.tex_id);
881
882 let handle = fonts.build_rgba32_texture();
884 let font_texture_cnfig = TextureConfig {
885 label: Some("imgui-wgpu font atlas"),
886 size: Extent3d {
887 width: handle.width,
888 height: handle.height,
889 ..Default::default()
890 },
891 format: Some(TextureFormat::Rgba8Unorm),
892 ..Default::default()
893 };
894
895 let font_texture = Texture::new(device, self, font_texture_cnfig);
896 font_texture.write(queue, handle.data, handle.width, handle.height);
897 fonts.tex_id = self.textures.insert(font_texture);
898 fonts.clear_tex_data();
900 }
901}