Skip to main content

imgui_wgpu/
lib.rs

1//! # imgui-wgpu
2//!
3//! A [wgpu](https://crates.io/crates/wgpu) render backend for
4//! [imgui-rs](https://crates.io/crates/imgui).
5//!
6//! ## Quick Start
7//!
8//! ```rust,no_run
9//! # use imgui::Context;
10//! # use imgui_wgpu::{Renderer, RendererConfig};
11//! # fn example(device: &wgpu::Device, queue: &wgpu::Queue, surface_format: wgpu::TextureFormat) {
12//! // During setup:
13//! let mut imgui = Context::create();
14//! let renderer_config = RendererConfig {
15//!     texture_format: surface_format,
16//!     ..RendererConfig::new()
17//! };
18//! let mut renderer = Renderer::new(&mut imgui, &device, &queue, renderer_config);
19//!
20//! // Each frame:
21//! // let draw_data = imgui_context.render();
22//! // let mut rpass = encoder.begin_render_pass(&render_pass_desc);
23//! // renderer.render(draw_data, &queue, &device, &mut rpass).unwrap();
24//! # }
25//! ```
26//!
27//! ## Color Space
28//!
29//! The default [`RendererConfig::new`] outputs linear color, appropriate for sRGB
30//! framebuffers (e.g. `Bgra8UnormSrgb`). If you are rendering to a linear framebuffer
31//! (e.g. `Bgra8Unorm`), use [`RendererConfig::new_srgb`] instead.
32//!
33//! ## Custom Textures
34//!
35//! Create a [`Texture`], then insert it into [`Renderer::textures`] to get a
36//! [`imgui::TextureId`] for use with imgui image widgets:
37//!
38//! ```rust,no_run
39//! # use imgui_wgpu::{Renderer, Texture, TextureConfig};
40//! # fn example(device: &wgpu::Device, queue: &wgpu::Queue, renderer: &mut Renderer) {
41//! let config = TextureConfig {
42//!     size: wgpu::Extent3d { width: 64, height: 64, depth_or_array_layers: 1 },
43//!     ..Default::default()
44//! };
45//! # let pixel_data = vec![0u8; 64 * 64 * 4];
46//! let texture = Texture::new(&device, &renderer, config);
47//! texture.write(&queue, &pixel_data, 64, 64);
48//! let tex_id = renderer.textures.insert(texture);
49//! # }
50//! ```
51//!
52//! ## Split Rendering
53//!
54//! For more control over buffer lifetime, use [`Renderer::prepare`] and
55//! [`Renderer::split_render`] separately instead of [`Renderer::render`].
56//! This lets you reuse [`RenderData`] across frames and control when GPU
57//! buffers are created.
58
59use 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
74/// Result type for renderer operations.
75pub 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/// Errors that can occur during rendering.
86#[derive(Clone, Debug)]
87pub enum RendererError {
88    /// A draw command referenced a [`TextureId`] that is not present in
89    /// [`Renderer::textures`].
90    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/// Config for creating a texture from raw parts
113///
114#[derive(Clone)]
115pub struct RawTextureConfig<'a> {
116    /// An optional label for the bind group used for debugging.
117    pub label: Option<&'a str>,
118    /// The sampler descriptor of the texture.
119    pub sampler_desc: SamplerDescriptor<'a>,
120}
121
122/// Config for creating a texture.
123///
124/// Uses the builder pattern.
125#[derive(Clone)]
126pub struct TextureConfig<'a> {
127    /// The size of the texture.
128    pub size: Extent3d,
129    /// An optional label for the texture used for debugging.
130    pub label: Option<&'a str>,
131    /// The format of the texture, if not set uses the format from the renderer.
132    pub format: Option<TextureFormat>,
133    /// The usage of the texture.
134    pub usage: TextureUsages,
135    /// The mip level of the texture.
136    pub mip_level_count: u32,
137    /// The sample count of the texture.
138    pub sample_count: u32,
139    /// The dimension of the texture.
140    pub dimension: TextureDimension,
141    /// The sampler descriptor of the texture.
142    pub sampler_desc: SamplerDescriptor<'a>,
143}
144
145impl Default for TextureConfig<'_> {
146    /// Create a new texture config.
147    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
180/// A container for a bindable texture.
181pub 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    /// Create a `Texture` from its raw parts.
190    /// - `bind_group`: The bind group used by the texture. If it is `None`, the bind group will be created like in `Self::new`.
191    /// - `config`: The config used for creating the bind group. If `bind_group` is `Some(_)`, it will be ignored
192    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            // Create the texture sampler.
205            let sampler = device.create_sampler(&config.sampler_desc);
206
207            // Create the texture bind group from the layout.
208            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    /// Create a new GPU texture width the specified `config`.
233    pub fn new(device: &Device, renderer: &Renderer, config: TextureConfig) -> Self {
234        // Create the wgpu texture.
235        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        // Extract the texture view.
247        let view = Arc::new(texture.create_view(&TextureViewDescriptor::default()));
248
249        // Create the texture sampler.
250        let sampler = device.create_sampler(&config.sampler_desc);
251
252        // Create the texture bind group from the layout.
253        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    /// Write `data` to the texture.
277    ///
278    /// - `data`: 32-bit RGBA bitmap data.
279    /// - `width`: The width of the source bitmap (`data`) in pixels.
280    /// - `height`: The height of the source bitmap (`data`) in pixels.
281    pub fn write(&self, queue: &Queue, data: &[u8], width: u32, height: u32) {
282        queue.write_texture(
283            // destination (sub)texture
284            TexelCopyTextureInfo {
285                texture: &self.texture,
286                mip_level: 0,
287                origin: Origin3d { x: 0, y: 0, z: 0 },
288                aspect: TextureAspect::All,
289            },
290            // source bitmap data
291            data,
292            // layout of the source bitmap
293            TexelCopyBufferLayout {
294                offset: 0,
295                bytes_per_row: Some(width * 4),
296                rows_per_image: Some(height),
297            },
298            // size of the source bitmap
299            Extent3d {
300                width,
301                height,
302                depth_or_array_layers: 1,
303            },
304        );
305    }
306
307    /// The width of the texture in pixels.
308    pub fn width(&self) -> u32 {
309        self.size.width
310    }
311
312    /// The height of the texture in pixels.
313    pub fn height(&self) -> u32 {
314        self.size.height
315    }
316
317    /// The depth of the texture.
318    pub fn depth(&self) -> u32 {
319        self.size.depth_or_array_layers
320    }
321
322    /// The size of the texture in pixels.
323    pub fn size(&self) -> Extent3d {
324        self.size
325    }
326
327    /// The underlying `wgpu::Texture`.
328    pub fn texture(&self) -> &wgpu::Texture {
329        &self.texture
330    }
331
332    /// The `wgpu::TextureView` of the underlying texture.
333    pub fn view(&self) -> &wgpu::TextureView {
334        &self.view
335    }
336}
337
338/// Configuration for the [`Renderer`].
339///
340/// Use [`RendererConfig::new`] for sRGB framebuffers, [`RendererConfig::new_srgb`]
341/// for linear framebuffers, or [`RendererConfig::with_shaders`] for custom shaders.
342pub struct RendererConfig<'s> {
343    /// The output texture format. Must match your render target (e.g. the surface format).
344    pub texture_format: TextureFormat,
345    /// Optional depth format. Set this if your render pass uses a depth attachment.
346    pub depth_format: Option<TextureFormat>,
347    /// MSAA sample count. Defaults to `1` (no MSAA). Must match your render pass.
348    pub sample_count: u32,
349    /// Custom shader module. If `None`, the built-in imgui shaders are used.
350    pub shader: Option<ShaderModuleDescriptor<'s>>,
351    /// Vertex shader entry point name. Only needed with custom shaders.
352    pub vertex_shader_entry_point: Option<&'s str>,
353    /// Fragment shader entry point name. Only needed with custom shaders.
354    pub fragment_shader_entry_point: Option<&'s str>,
355}
356
357impl<'s> RendererConfig<'s> {
358    /// Create a new renderer config with custom shaders.
359    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    /// Create a new renderer config with precompiled default shaders outputting linear color.
373    ///
374    /// If you write to a Bgra8UnormSrgb framebuffer, this is what you want.
375    fn default() -> Self {
376        Self::new()
377    }
378}
379
380impl RendererConfig<'_> {
381    /// Create a new renderer config with precompiled default shaders outputting linear color.
382    ///
383    /// If you write to a Bgra8UnormSrgb framebuffer, this is what you want.
384    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    /// Create a new renderer config with precompiled default shaders outputting srgb color.
392    ///
393    /// If you write to a Bgra8Unorm framebuffer, this is what you want.
394    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
402/// Intermediate render state produced by [`Renderer::prepare`] and consumed by
403/// [`Renderer::split_render`].
404///
405/// Holds GPU buffers and draw-list metadata for a single frame. Can be reused
406/// across frames by passing the previous frame's `RenderData` back into
407/// [`Renderer::prepare`], which avoids reallocating buffers when capacity is sufficient.
408pub 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
420/// The main imgui-wgpu renderer.
421///
422/// Manages the wgpu render pipeline, GPU buffers, and texture storage needed to
423/// render imgui draw data. Create one with [`Renderer::new`], then call
424/// [`Renderer::render`] (or [`Renderer::prepare`] + [`Renderer::split_render`])
425/// each frame inside a wgpu render pass.
426pub struct Renderer {
427    pipeline: RenderPipeline,
428    uniform_buffer: Buffer,
429    uniform_bind_group: BindGroup,
430    /// Texture storage for the font atlas and user-registered images.
431    ///
432    /// Insert custom [`Texture`]s here to get a [`TextureId`] for use with imgui
433    /// image widgets. The font atlas texture is inserted automatically.
434    pub textures: Textures<Texture>,
435    texture_layout: BindGroupLayout,
436    render_data: Option<RenderData>,
437    config: RendererConfig<'static>,
438}
439
440impl Renderer {
441    /// Create an entirely new imgui wgpu renderer.
442    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        // Load shaders.
458        let shader_module = device.create_shader_module(shader.unwrap());
459
460        // Create the uniform matrix buffer.
461        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        // Create the uniform matrix buffer bind group layout.
470        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        // Create the uniform matrix buffer bind group.
485        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        // Create the texture layout for further usage.
495        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        // Create the render pipeline layout.
518        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        // Create the render pipeline.
525        // Create the render pipeline.
526        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        // Immediately load the font texture to the GPU.
602        renderer.reload_font_texture(imgui, device, queue);
603
604        renderer
605    }
606
607    /// Prepares buffers for the current imgui frame.  This must be
608    /// called before `Renderer::split_render`, and its output must
609    /// be passed to the render call.
610    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 the render area is <= 0, exit here and now.
633        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 there are no draw lists, exit here
641        if draw_data.draw_lists_count() == 0 {
642            render_data.render = false;
643            return render_data;
644        }
645
646        // Only update matrices if the size or position changes
647        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            // Create and update the transform matrix for the current frame.
663            // This is required to adapt to vulkan coordinates.
664            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            // Safety: DrawVertPod is #[repr(transparent)] over DrawVert and DrawVert _should_ be Pod.
690            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        // Copies in wgpu must be padded to 4 byte alignment
696        indices.resize(
697            indices.len() + COPY_BUFFER_ALIGNMENT as usize
698                - indices.len() % COPY_BUFFER_ALIGNMENT as usize,
699            0,
700        );
701
702        // If the buffer is not created or is too small for the new indices, create a new buffer
703        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            // The buffer is large enough for the new indices, so reuse it
713            queue.write_buffer(buffer, 0, &indices);
714        } else {
715            unreachable!()
716        }
717
718        // If the buffer is not created or is too small for the new vertices, create a new buffer
719        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            // The buffer is large enough for the new vertices, so reuse it
729            queue.write_buffer(buffer, 0, &vertices);
730        } else {
731            unreachable!()
732        }
733
734        render_data
735    }
736
737    /// Render the current imgui frame.  `Renderer::prepare` must be
738    /// called first, and the output render data must be kept for the
739    /// lifetime of the renderpass.
740    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        // Execute all the imgui render work.
766        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    /// Render the current imgui frame.
784    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    /// Render a given `DrawList` from imgui onto a wgpu frame.
797    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                // Set the current texture bind group on the renderpass.
816                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                // Set scissors on the renderpass.
824                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                    // XXX: Work-around for wgpu issue [1] by only issuing draw
843                    // calls if the scissor rect is valid (by wgpu's flawed
844                    // logic). Regardless, a zero-width or zero-height scissor
845                    // is essentially a no-op render anyway, so just skip it.
846                    // [1]: https://github.com/gfx-rs/wgpu/issues/1750
847                    if scissors.2 > 0 && scissors.3 > 0 {
848                        rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3);
849
850                        // Draw the current batch of vertices with the renderpass.
851                        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    /// Updates the current uniform buffer containing the transform matrix.
864    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    /// Updates the texture on the GPU corresponding to the current imgui font atlas.
870    ///
871    /// This has to be called after loading a font.
872    pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &Queue) {
873        let fonts = imgui.fonts();
874        // Remove possible font atlas texture.
875        self.textures.remove(fonts.tex_id);
876
877        // Create font texture and upload it.
878        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        // Clear imgui texture data to save memory.
893        fonts.clear_tex_data();
894    }
895}