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 queue.write_texture(
283 TexelCopyTextureInfo {
285 texture: &self.texture,
286 mip_level: 0,
287 origin: Origin3d { x: 0, y: 0, z: 0 },
288 aspect: TextureAspect::All,
289 },
290 data,
292 TexelCopyBufferLayout {
294 offset: 0,
295 bytes_per_row: Some(width * 4),
296 rows_per_image: Some(height),
297 },
298 Extent3d {
300 width,
301 height,
302 depth_or_array_layers: 1,
303 },
304 );
305 }
306
307 pub fn width(&self) -> u32 {
309 self.size.width
310 }
311
312 pub fn height(&self) -> u32 {
314 self.size.height
315 }
316
317 pub fn depth(&self) -> u32 {
319 self.size.depth_or_array_layers
320 }
321
322 pub fn size(&self) -> Extent3d {
324 self.size
325 }
326
327 pub fn texture(&self) -> &wgpu::Texture {
329 &self.texture
330 }
331
332 pub fn view(&self) -> &wgpu::TextureView {
334 &self.view
335 }
336}
337
338pub struct RendererConfig<'s> {
343 pub texture_format: TextureFormat,
345 pub depth_format: Option<TextureFormat>,
347 pub sample_count: u32,
349 pub shader: Option<ShaderModuleDescriptor<'s>>,
351 pub vertex_shader_entry_point: Option<&'s str>,
353 pub fragment_shader_entry_point: Option<&'s str>,
355}
356
357impl<'s> RendererConfig<'s> {
358 pub fn with_shaders(shader: ShaderModuleDescriptor<'s>) -> Self {
360 RendererConfig {
361 texture_format: TextureFormat::Rgba8Unorm,
362 depth_format: None,
363 sample_count: 1,
364 shader: Some(shader),
365 vertex_shader_entry_point: Some(VS_ENTRY_POINT),
366 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
367 }
368 }
369}
370
371impl Default for RendererConfig<'_> {
372 fn default() -> Self {
376 Self::new()
377 }
378}
379
380impl RendererConfig<'_> {
381 pub fn new() -> Self {
385 RendererConfig {
386 fragment_shader_entry_point: Some(FS_ENTRY_POINT_LINEAR),
387 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
388 }
389 }
390
391 pub fn new_srgb() -> Self {
395 RendererConfig {
396 fragment_shader_entry_point: Some(FS_ENTRY_POINT_SRGB),
397 ..Self::with_shaders(include_wgsl!("imgui.wgsl"))
398 }
399 }
400}
401
402pub struct RenderData {
409 fb_size: [f32; 2],
410 last_size: [f32; 2],
411 last_pos: [f32; 2],
412 vertex_buffer: Option<Buffer>,
413 vertex_buffer_size: usize,
414 index_buffer: Option<Buffer>,
415 index_buffer_size: usize,
416 draw_list_offsets: SmallVec<[(i32, u32); 4]>,
417 render: bool,
418}
419
420pub struct Renderer {
427 pipeline: RenderPipeline,
428 uniform_buffer: Buffer,
429 uniform_bind_group: BindGroup,
430 pub textures: Textures<Texture>,
435 texture_layout: BindGroupLayout,
436 render_data: Option<RenderData>,
437 config: RendererConfig<'static>,
438}
439
440impl Renderer {
441 pub fn new(
443 imgui: &mut Context,
444 device: &Device,
445 queue: &Queue,
446 config: RendererConfig,
447 ) -> Self {
448 let RendererConfig {
449 texture_format,
450 depth_format,
451 sample_count,
452 shader,
453 vertex_shader_entry_point,
454 fragment_shader_entry_point,
455 } = config;
456
457 let shader_module = device.create_shader_module(shader.unwrap());
459
460 let size = 64;
462 let uniform_buffer = device.create_buffer(&BufferDescriptor {
463 label: Some("imgui-wgpu uniform buffer"),
464 size,
465 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
466 mapped_at_creation: false,
467 });
468
469 let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
471 label: None,
472 entries: &[BindGroupLayoutEntry {
473 binding: 0,
474 visibility: wgpu::ShaderStages::VERTEX,
475 ty: BindingType::Buffer {
476 ty: BufferBindingType::Uniform,
477 has_dynamic_offset: false,
478 min_binding_size: None,
479 },
480 count: None,
481 }],
482 });
483
484 let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
486 label: Some("imgui-wgpu bind group"),
487 layout: &uniform_layout,
488 entries: &[BindGroupEntry {
489 binding: 0,
490 resource: uniform_buffer.as_entire_binding(),
491 }],
492 });
493
494 let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
496 label: Some("imgui-wgpu bind group layout"),
497 entries: &[
498 BindGroupLayoutEntry {
499 binding: 0,
500 visibility: wgpu::ShaderStages::FRAGMENT,
501 ty: BindingType::Texture {
502 multisampled: false,
503 sample_type: TextureSampleType::Float { filterable: true },
504 view_dimension: TextureViewDimension::D2,
505 },
506 count: None,
507 },
508 BindGroupLayoutEntry {
509 binding: 1,
510 visibility: wgpu::ShaderStages::FRAGMENT,
511 ty: BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
512 count: None,
513 },
514 ],
515 });
516
517 let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
519 label: Some("imgui-wgpu pipeline layout"),
520 bind_group_layouts: &[&uniform_layout, &texture_layout],
521 immediate_size: 0,
522 });
523
524 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
527 label: Some("imgui-wgpu pipeline"),
528 layout: Some(&pipeline_layout),
529 vertex: VertexState {
530 module: &shader_module,
531 entry_point: vertex_shader_entry_point,
532 compilation_options: Default::default(),
533 buffers: &[VertexBufferLayout {
534 array_stride: size_of::<DrawVert>() as BufferAddress,
535 step_mode: VertexStepMode::Vertex,
536 attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Unorm8x4],
537 }],
538 },
539 primitive: PrimitiveState {
540 topology: PrimitiveTopology::TriangleList,
541 strip_index_format: None,
542 front_face: FrontFace::Cw,
543 cull_mode: None,
544 polygon_mode: PolygonMode::Fill,
545 unclipped_depth: false,
546 conservative: false,
547 },
548 depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
549 format,
550 depth_write_enabled: false,
551 depth_compare: wgpu::CompareFunction::Always,
552 stencil: wgpu::StencilState::default(),
553 bias: DepthBiasState::default(),
554 }),
555 multisample: MultisampleState {
556 count: sample_count,
557 ..Default::default()
558 },
559 fragment: Some(FragmentState {
560 module: &shader_module,
561 entry_point: fragment_shader_entry_point,
562 compilation_options: Default::default(),
563 targets: &[Some(ColorTargetState {
564 format: texture_format,
565 blend: Some(BlendState {
566 color: BlendComponent {
567 src_factor: BlendFactor::SrcAlpha,
568 dst_factor: BlendFactor::OneMinusSrcAlpha,
569 operation: BlendOperation::Add,
570 },
571 alpha: BlendComponent {
572 src_factor: BlendFactor::OneMinusDstAlpha,
573 dst_factor: BlendFactor::One,
574 operation: BlendOperation::Add,
575 },
576 }),
577 write_mask: ColorWrites::ALL,
578 })],
579 }),
580 multiview_mask: None,
581 cache: None,
582 });
583
584 let mut renderer = Self {
585 pipeline,
586 uniform_buffer,
587 uniform_bind_group,
588 textures: Textures::new(),
589 texture_layout,
590 render_data: None,
591 config: RendererConfig {
592 texture_format,
593 depth_format,
594 sample_count,
595 shader: None,
596 vertex_shader_entry_point: None,
597 fragment_shader_entry_point: None,
598 },
599 };
600
601 renderer.reload_font_texture(imgui, device, queue);
603
604 renderer
605 }
606
607 pub fn prepare(
611 &self,
612 draw_data: &DrawData,
613 render_data: Option<RenderData>,
614 queue: &Queue,
615 device: &Device,
616 ) -> RenderData {
617 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
618 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
619
620 let mut render_data = render_data.unwrap_or_else(|| RenderData {
621 fb_size: [fb_width, fb_height],
622 last_size: [0.0, 0.0],
623 last_pos: [0.0, 0.0],
624 vertex_buffer: None,
625 vertex_buffer_size: 0,
626 index_buffer: None,
627 index_buffer_size: 0,
628 draw_list_offsets: SmallVec::<[_; 4]>::new(),
629 render: false,
630 });
631
632 if fb_width <= 0.0 || fb_height <= 0.0 {
634 render_data.render = false;
635 return render_data;
636 } else {
637 render_data.render = true;
638 }
639
640 if draw_data.draw_lists_count() == 0 {
642 render_data.render = false;
643 return render_data;
644 }
645
646 if (render_data.last_size[0] - draw_data.display_size[0]).abs() > f32::EPSILON
648 || (render_data.last_size[1] - draw_data.display_size[1]).abs() > f32::EPSILON
649 || (render_data.last_pos[0] - draw_data.display_pos[0]).abs() > f32::EPSILON
650 || (render_data.last_pos[1] - draw_data.display_pos[1]).abs() > f32::EPSILON
651 {
652 render_data.fb_size = [fb_width, fb_height];
653 render_data.last_size = draw_data.display_size;
654 render_data.last_pos = draw_data.display_pos;
655
656 let width = draw_data.display_size[0];
657 let height = draw_data.display_size[1];
658
659 let offset_x = draw_data.display_pos[0] / width;
660 let offset_y = draw_data.display_pos[1] / height;
661
662 let matrix = [
665 [2.0 / width, 0.0, 0.0, 0.0],
666 [0.0, 2.0 / -height, 0.0, 0.0],
667 [0.0, 0.0, 1.0, 0.0],
668 [-1.0 - offset_x * 2.0, 1.0 + offset_y * 2.0, 0.0, 1.0],
669 ];
670 self.update_uniform_buffer(queue, &matrix);
671 }
672
673 render_data.draw_list_offsets.clear();
674
675 let mut vertex_count = 0;
676 let mut index_count = 0;
677 for draw_list in draw_data.draw_lists() {
678 render_data
679 .draw_list_offsets
680 .push((vertex_count as i32, index_count as u32));
681 vertex_count += draw_list.vtx_buffer().len();
682 index_count += draw_list.idx_buffer().len();
683 }
684
685 let mut vertices = Vec::with_capacity(vertex_count * std::mem::size_of::<DrawVertPod>());
686 let mut indices = Vec::with_capacity(index_count * std::mem::size_of::<DrawIdx>());
687
688 for draw_list in draw_data.draw_lists() {
689 let vertices_pod: &[DrawVertPod] = unsafe { draw_list.transmute_vtx_buffer() };
691 vertices.extend_from_slice(bytemuck::cast_slice(vertices_pod));
692 indices.extend_from_slice(bytemuck::cast_slice(draw_list.idx_buffer()));
693 }
694
695 indices.resize(
697 indices.len() + COPY_BUFFER_ALIGNMENT as usize
698 - indices.len() % COPY_BUFFER_ALIGNMENT as usize,
699 0,
700 );
701
702 if render_data.index_buffer.is_none() || render_data.index_buffer_size < indices.len() {
704 let buffer = device.create_buffer_init(&BufferInitDescriptor {
705 label: Some("imgui-wgpu index buffer"),
706 contents: &indices,
707 usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
708 });
709 render_data.index_buffer = Some(buffer);
710 render_data.index_buffer_size = indices.len();
711 } else if let Some(buffer) = render_data.index_buffer.as_ref() {
712 queue.write_buffer(buffer, 0, &indices);
714 } else {
715 unreachable!()
716 }
717
718 if render_data.vertex_buffer.is_none() || render_data.vertex_buffer_size < vertices.len() {
720 let buffer = device.create_buffer_init(&BufferInitDescriptor {
721 label: Some("imgui-wgpu vertex buffer"),
722 contents: &vertices,
723 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
724 });
725 render_data.vertex_buffer = Some(buffer);
726 render_data.vertex_buffer_size = vertices.len();
727 } else if let Some(buffer) = render_data.vertex_buffer.as_ref() {
728 queue.write_buffer(buffer, 0, &vertices);
730 } else {
731 unreachable!()
732 }
733
734 render_data
735 }
736
737 pub fn split_render<'r>(
741 &'r self,
742 draw_data: &DrawData,
743 render_data: &'r RenderData,
744 rpass: &mut RenderPass<'r>,
745 ) -> RendererResult<()> {
746 if !render_data.render {
747 return Ok(());
748 }
749
750 let vertex_buffer = render_data.vertex_buffer.as_ref().unwrap();
751 if vertex_buffer.size() == 0 {
752 return Ok(());
753 }
754
755 let index_buffer = render_data.index_buffer.as_ref().unwrap();
756 if index_buffer.size() == 0 {
757 return Ok(());
758 }
759
760 rpass.set_pipeline(&self.pipeline);
761 rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
762 rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
763 rpass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint16);
764
765 for (draw_list, bases) in draw_data
767 .draw_lists()
768 .zip(render_data.draw_list_offsets.iter())
769 {
770 self.render_draw_list(
771 rpass,
772 draw_list,
773 render_data.fb_size,
774 draw_data.display_pos,
775 draw_data.framebuffer_scale,
776 *bases,
777 )?;
778 }
779
780 Ok(())
781 }
782
783 pub fn render<'r>(
785 &'r mut self,
786 draw_data: &DrawData,
787 queue: &Queue,
788 device: &Device,
789 rpass: &mut RenderPass<'r>,
790 ) -> RendererResult<()> {
791 let render_data = self.render_data.take();
792 self.render_data = Some(self.prepare(draw_data, render_data, queue, device));
793 self.split_render(draw_data, self.render_data.as_ref().unwrap(), rpass)
794 }
795
796 fn render_draw_list<'render>(
798 &'render self,
799 rpass: &mut RenderPass<'render>,
800 draw_list: &DrawList,
801 fb_size: [f32; 2],
802 clip_off: [f32; 2],
803 clip_scale: [f32; 2],
804 (vertex_base, index_base): (i32, u32),
805 ) -> RendererResult<()> {
806 for cmd in draw_list.commands() {
807 if let Elements { count, cmd_params } = cmd {
808 let clip_rect = [
809 (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0],
810 (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1],
811 (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0],
812 (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1],
813 ];
814
815 let texture_id = cmd_params.texture_id;
817 let tex = self
818 .textures
819 .get(texture_id)
820 .ok_or(RendererError::BadTexture(texture_id))?;
821 rpass.set_bind_group(1, Some(tex.bind_group.as_ref()), &[]);
822
823 let start = index_base + cmd_params.idx_offset as u32;
825 let end = start + count as u32;
826 if clip_rect[0] < fb_size[0]
827 && clip_rect[1] < fb_size[1]
828 && clip_rect[2] >= 0.0
829 && clip_rect[3] >= 0.0
830 {
831 let scissors = (
832 clip_rect[0].max(0.0).floor() as u32,
833 clip_rect[1].max(0.0).floor() as u32,
834 (clip_rect[2].min(fb_size[0]) - clip_rect[0].max(0.0))
835 .abs()
836 .ceil() as u32,
837 (clip_rect[3].min(fb_size[1]) - clip_rect[1].max(0.0))
838 .abs()
839 .ceil() as u32,
840 );
841
842 if scissors.2 > 0 && scissors.3 > 0 {
848 rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3);
849
850 rpass.draw_indexed(
852 start..end,
853 vertex_base + cmd_params.vtx_offset as i32,
854 0..1,
855 );
856 }
857 }
858 }
859 }
860 Ok(())
861 }
862
863 fn update_uniform_buffer(&self, queue: &Queue, matrix: &[[f32; 4]; 4]) {
865 let data = bytemuck::bytes_of(matrix);
866 queue.write_buffer(&self.uniform_buffer, 0, data);
867 }
868
869 pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &Queue) {
873 let fonts = imgui.fonts();
874 self.textures.remove(fonts.tex_id);
876
877 let handle = fonts.build_rgba32_texture();
879 let font_texture_cnfig = TextureConfig {
880 label: Some("imgui-wgpu font atlas"),
881 size: Extent3d {
882 width: handle.width,
883 height: handle.height,
884 ..Default::default()
885 },
886 ..Default::default()
887 };
888
889 let font_texture = Texture::new(device, self, font_texture_cnfig);
890 font_texture.write(queue, handle.data, handle.width, handle.height);
891 fonts.tex_id = self.textures.insert(font_texture);
892 fonts.clear_tex_data();
894 }
895}