egui_wgpu/
renderer.rs

1#![allow(unsafe_code)]
2
3use std::{borrow::Cow, num::NonZeroU64, ops::Range};
4
5use ahash::HashMap;
6use bytemuck::Zeroable as _;
7use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _};
8
9use wgpu::util::DeviceExt as _;
10
11// Only implements Send + Sync on wasm32 in order to allow storing wgpu resources on the type map.
12#[cfg(not(all(
13    target_arch = "wasm32",
14    not(feature = "fragile-send-sync-non-atomic-wasm"),
15)))]
16/// You can use this for storage when implementing [`CallbackTrait`].
17pub type CallbackResources = type_map::concurrent::TypeMap;
18#[cfg(all(
19    target_arch = "wasm32",
20    not(feature = "fragile-send-sync-non-atomic-wasm"),
21))]
22/// You can use this for storage when implementing [`CallbackTrait`].
23pub type CallbackResources = type_map::TypeMap;
24
25/// You can use this to do custom [`wgpu`] rendering in an egui app.
26///
27/// Implement [`CallbackTrait`] and call [`Callback::new_paint_callback`].
28///
29/// This can be turned into a [`epaint::PaintCallback`] and [`epaint::Shape`].
30pub struct Callback(Box<dyn CallbackTrait>);
31
32impl Callback {
33    /// Creates a new [`epaint::PaintCallback`] from a callback trait instance.
34    pub fn new_paint_callback(
35        rect: epaint::emath::Rect,
36        callback: impl CallbackTrait + 'static,
37    ) -> epaint::PaintCallback {
38        epaint::PaintCallback {
39            rect,
40            callback: std::sync::Arc::new(Self(Box::new(callback))),
41        }
42    }
43}
44
45/// A callback trait that can be used to compose an [`epaint::PaintCallback`] via [`Callback`]
46/// for custom WGPU rendering.
47///
48/// Callbacks in [`Renderer`] are done in three steps:
49/// * [`CallbackTrait::prepare`]: called for all registered callbacks before the main egui render pass.
50/// * [`CallbackTrait::finish_prepare`]: called for all registered callbacks after all callbacks finished calling prepare.
51/// * [`CallbackTrait::paint`]: called for all registered callbacks during the main egui render pass.
52///
53/// Each callback has access to an instance of [`CallbackResources`] that is stored in the [`Renderer`].
54/// This can be used to store wgpu resources that need to be accessed during the [`CallbackTrait::paint`] step.
55///
56/// The callbacks implementing [`CallbackTrait`] itself must always be Send + Sync, but resources stored in
57/// [`Renderer::callback_resources`] are not required to implement Send + Sync when building for wasm.
58/// (this is because wgpu stores references to the JS heap in most of its resources which can not be shared with other threads).
59///
60///
61/// # Command submission
62///
63/// ## Command Encoder
64///
65/// The passed-in [`wgpu::CommandEncoder`] is egui's and can be used directly to register
66/// wgpu commands for simple use cases.
67/// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui
68/// rendering itself.
69///
70/// ## Command Buffers
71///
72/// For more complicated use cases, one can also return a list of arbitrary
73/// [`wgpu::CommandBuffer`]s and have complete control over how they get created and fed.
74/// In particular, this gives an opportunity to parallelize command registration and
75/// prevents a faulty callback from poisoning the main wgpu pipeline.
76///
77/// When using eframe, the main egui command buffer, as well as all user-defined
78/// command buffers returned by this function, are guaranteed to all be submitted
79/// at once in a single call.
80///
81/// Command Buffers returned by [`CallbackTrait::finish_prepare`] will always be issued *after*
82/// those returned by [`CallbackTrait::prepare`].
83/// Order within command buffers returned by [`CallbackTrait::prepare`] is dependent
84/// on the order the respective [`epaint::Shape::Callback`]s were submitted in.
85///
86/// # Example
87///
88/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/main/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
89pub trait CallbackTrait: Send + Sync {
90    fn prepare(
91        &self,
92        _device: &wgpu::Device,
93        _queue: &wgpu::Queue,
94        _screen_descriptor: &ScreenDescriptor,
95        _egui_encoder: &mut wgpu::CommandEncoder,
96        _callback_resources: &mut CallbackResources,
97    ) -> Vec<wgpu::CommandBuffer> {
98        Vec::new()
99    }
100
101    /// Called after all [`CallbackTrait::prepare`] calls are done.
102    fn finish_prepare(
103        &self,
104        _device: &wgpu::Device,
105        _queue: &wgpu::Queue,
106        _egui_encoder: &mut wgpu::CommandEncoder,
107        _callback_resources: &mut CallbackResources,
108    ) -> Vec<wgpu::CommandBuffer> {
109        Vec::new()
110    }
111
112    /// Called after all [`CallbackTrait::finish_prepare`] calls are done.
113    ///
114    /// It is given access to the [`wgpu::RenderPass`] so that it can issue draw commands
115    /// into the same [`wgpu::RenderPass`] that is used for all other egui elements.
116    fn paint(
117        &self,
118        info: PaintCallbackInfo,
119        render_pass: &mut wgpu::RenderPass<'static>,
120        callback_resources: &CallbackResources,
121    );
122}
123
124/// Information about the screen used for rendering.
125pub struct ScreenDescriptor {
126    /// Size of the window in physical pixels.
127    pub size_in_pixels: [u32; 2],
128
129    /// High-DPI scale factor (pixels per point).
130    pub pixels_per_point: f32,
131}
132
133impl ScreenDescriptor {
134    /// size in "logical" points
135    fn screen_size_in_points(&self) -> [f32; 2] {
136        [
137            self.size_in_pixels[0] as f32 / self.pixels_per_point,
138            self.size_in_pixels[1] as f32 / self.pixels_per_point,
139        ]
140    }
141}
142
143/// Uniform buffer used when rendering.
144#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
145#[repr(C)]
146struct UniformBuffer {
147    screen_size_in_points: [f32; 2],
148    dithering: u32,
149
150    /// 1 to do manual filtering for more predictable kittest snapshot images.
151    ///
152    /// See also <https://github.com/emilk/egui/issues/5295>.
153    predictable_texture_filtering: u32,
154}
155
156struct SlicedBuffer {
157    buffer: wgpu::Buffer,
158    slices: Vec<Range<usize>>,
159    capacity: wgpu::BufferAddress,
160}
161
162pub struct Texture {
163    /// The texture may be None if the `TextureId` is just a handle to a user-provided bind-group.
164    pub texture: Option<wgpu::Texture>,
165
166    /// Bindgroup for the texture + sampler.
167    pub bind_group: wgpu::BindGroup,
168
169    /// Options describing the sampler used in the bind group. This may be None if the `TextureId`
170    /// is just a handle to a user-provided bind-group.
171    pub options: Option<epaint::textures::TextureOptions>,
172}
173
174/// Ways to configure [`Renderer`] during creation.
175#[derive(Clone, Copy, Debug)]
176pub struct RendererOptions {
177    /// Set the level of the multisampling anti-aliasing (MSAA).
178    ///
179    /// Must be a power-of-two. Higher = more smooth 3D.
180    ///
181    /// A value of `0` or `1` turns it off (default).
182    ///
183    /// `egui` already performs anti-aliasing via "feathering"
184    /// (controlled by [`egui::epaint::TessellationOptions`]),
185    /// but if you are embedding 3D in egui you may want to turn on multisampling.
186    pub msaa_samples: u32,
187
188    /// What format to use for the depth and stencil buffers,
189    /// e.g. [`wgpu::TextureFormat::Depth32FloatStencil8`].
190    ///
191    /// egui doesn't need depth/stencil, so the default value is `None` (no depth or stancil buffers).
192    pub depth_stencil_format: Option<wgpu::TextureFormat>,
193
194    /// Controls whether to apply dithering to minimize banding artifacts.
195    ///
196    /// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
197    /// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
198    /// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
199    ///
200    /// Defaults to true.
201    pub dithering: bool,
202
203    /// Perform texture filtering in software?
204    ///
205    /// This is useful when you want predictable rendering across
206    /// different hardware, e.g. for kittest snapshots.
207    ///
208    /// Default is `false`.
209    ///
210    /// See also <https://github.com/emilk/egui/issues/5295>.
211    pub predictable_texture_filtering: bool,
212}
213
214impl RendererOptions {
215    /// Set options that produce the most predicatable output.
216    ///
217    /// Useful for image snapshot tests.
218    pub const PREDICTABLE: Self = Self {
219        msaa_samples: 1,
220        depth_stencil_format: None,
221        dithering: false,
222        predictable_texture_filtering: true,
223    };
224}
225
226impl Default for RendererOptions {
227    fn default() -> Self {
228        Self {
229            msaa_samples: 0,
230            depth_stencil_format: None,
231            dithering: true,
232            predictable_texture_filtering: false,
233        }
234    }
235}
236
237/// Renderer for a egui based GUI.
238pub struct Renderer {
239    pipeline: wgpu::RenderPipeline,
240
241    index_buffer: SlicedBuffer,
242    vertex_buffer: SlicedBuffer,
243
244    uniform_buffer: wgpu::Buffer,
245    previous_uniform_buffer_content: UniformBuffer,
246    uniform_bind_group: wgpu::BindGroup,
247    texture_bind_group_layout: wgpu::BindGroupLayout,
248
249    /// Map of egui texture IDs to textures and their associated bindgroups (texture view +
250    /// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided
251    /// sampler.
252    textures: HashMap<epaint::TextureId, Texture>,
253    next_user_texture_id: u64,
254    samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
255
256    options: RendererOptions,
257
258    /// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods.
259    ///
260    /// See also [`CallbackTrait`].
261    pub callback_resources: CallbackResources,
262}
263
264impl Renderer {
265    /// Creates a renderer for a egui UI.
266    ///
267    /// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or
268    /// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space.
269    pub fn new(
270        device: &wgpu::Device,
271        output_color_format: wgpu::TextureFormat,
272        options: RendererOptions,
273    ) -> Self {
274        profiling::function_scope!();
275
276        let shader = wgpu::ShaderModuleDescriptor {
277            label: Some("egui"),
278            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
279        };
280        let module = {
281            profiling::scope!("create_shader_module");
282            device.create_shader_module(shader)
283        };
284
285        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
286            label: Some("egui_uniform_buffer"),
287            contents: bytemuck::cast_slice(&[UniformBuffer {
288                screen_size_in_points: [0.0, 0.0],
289                dithering: u32::from(options.dithering),
290                predictable_texture_filtering: u32::from(options.predictable_texture_filtering),
291            }]),
292            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
293        });
294
295        let uniform_bind_group_layout = {
296            profiling::scope!("create_bind_group_layout");
297            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
298                label: Some("egui_uniform_bind_group_layout"),
299                entries: &[wgpu::BindGroupLayoutEntry {
300                    binding: 0,
301                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
302                    ty: wgpu::BindingType::Buffer {
303                        has_dynamic_offset: false,
304                        min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
305                        ty: wgpu::BufferBindingType::Uniform,
306                    },
307                    count: None,
308                }],
309            })
310        };
311
312        let uniform_bind_group = {
313            profiling::scope!("create_bind_group");
314            device.create_bind_group(&wgpu::BindGroupDescriptor {
315                label: Some("egui_uniform_bind_group"),
316                layout: &uniform_bind_group_layout,
317                entries: &[wgpu::BindGroupEntry {
318                    binding: 0,
319                    resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
320                        buffer: &uniform_buffer,
321                        offset: 0,
322                        size: None,
323                    }),
324                }],
325            })
326        };
327
328        let texture_bind_group_layout = {
329            profiling::scope!("create_bind_group_layout");
330            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
331                label: Some("egui_texture_bind_group_layout"),
332                entries: &[
333                    wgpu::BindGroupLayoutEntry {
334                        binding: 0,
335                        visibility: wgpu::ShaderStages::FRAGMENT,
336                        ty: wgpu::BindingType::Texture {
337                            multisampled: false,
338                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
339                            view_dimension: wgpu::TextureViewDimension::D2,
340                        },
341                        count: None,
342                    },
343                    wgpu::BindGroupLayoutEntry {
344                        binding: 1,
345                        visibility: wgpu::ShaderStages::FRAGMENT,
346                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
347                        count: None,
348                    },
349                ],
350            })
351        };
352
353        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
354            label: Some("egui_pipeline_layout"),
355            bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
356            push_constant_ranges: &[],
357        });
358
359        let depth_stencil = options
360            .depth_stencil_format
361            .map(|format| wgpu::DepthStencilState {
362                format,
363                depth_write_enabled: false,
364                depth_compare: wgpu::CompareFunction::Always,
365                stencil: wgpu::StencilState::default(),
366                bias: wgpu::DepthBiasState::default(),
367            });
368
369        let pipeline = {
370            profiling::scope!("create_render_pipeline");
371            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
372                label: Some("egui_pipeline"),
373                layout: Some(&pipeline_layout),
374                vertex: wgpu::VertexState {
375                    entry_point: Some("vs_main"),
376                    module: &module,
377                    buffers: &[wgpu::VertexBufferLayout {
378                        array_stride: 5 * 4,
379                        step_mode: wgpu::VertexStepMode::Vertex,
380                        // 0: vec2 position
381                        // 1: vec2 texture coordinates
382                        // 2: uint color
383                        attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
384                    }],
385                    compilation_options: wgpu::PipelineCompilationOptions::default()
386                },
387                primitive: wgpu::PrimitiveState {
388                    topology: wgpu::PrimitiveTopology::TriangleList,
389                    unclipped_depth: false,
390                    conservative: false,
391                    cull_mode: None,
392                    front_face: wgpu::FrontFace::default(),
393                    polygon_mode: wgpu::PolygonMode::default(),
394                    strip_index_format: None,
395                },
396                depth_stencil,
397                multisample: wgpu::MultisampleState {
398                    alpha_to_coverage_enabled: false,
399                    count: options.msaa_samples.max(1),
400                    mask: !0,
401                },
402
403                fragment: Some(wgpu::FragmentState {
404                    module: &module,
405                    entry_point: Some(if output_color_format.is_srgb() {
406                        log::warn!("Detected a linear (sRGBA aware) framebuffer {output_color_format:?}. egui prefers Rgba8Unorm or Bgra8Unorm");
407                        "fs_main_linear_framebuffer"
408                    } else {
409                        "fs_main_gamma_framebuffer" // this is what we prefer
410                    }),
411                    targets: &[Some(wgpu::ColorTargetState {
412                        format: output_color_format,
413                        blend: Some(wgpu::BlendState {
414                            color: wgpu::BlendComponent {
415                                src_factor: wgpu::BlendFactor::One,
416                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
417                                operation: wgpu::BlendOperation::Add,
418                            },
419                            alpha: wgpu::BlendComponent {
420                                src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
421                                dst_factor: wgpu::BlendFactor::One,
422                                operation: wgpu::BlendOperation::Add,
423                            },
424                        }),
425                        write_mask: wgpu::ColorWrites::ALL,
426                    })],
427                    compilation_options: wgpu::PipelineCompilationOptions::default()
428                }),
429                multiview: None,
430                cache: None,
431            }
432        )
433        };
434
435        const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
436            (std::mem::size_of::<Vertex>() * 1024) as _;
437        const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
438            (std::mem::size_of::<u32>() * 1024 * 3) as _;
439
440        Self {
441            pipeline,
442            vertex_buffer: SlicedBuffer {
443                buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
444                slices: Vec::with_capacity(64),
445                capacity: VERTEX_BUFFER_START_CAPACITY,
446            },
447            index_buffer: SlicedBuffer {
448                buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
449                slices: Vec::with_capacity(64),
450                capacity: INDEX_BUFFER_START_CAPACITY,
451            },
452            uniform_buffer,
453            // Buffers on wgpu are zero initialized, so this is indeed its current state!
454            previous_uniform_buffer_content: UniformBuffer::zeroed(),
455            uniform_bind_group,
456            texture_bind_group_layout,
457            textures: HashMap::default(),
458            next_user_texture_id: 0,
459            samplers: HashMap::default(),
460            options,
461            callback_resources: CallbackResources::default(),
462        }
463    }
464
465    /// Executes the egui renderer onto an existing wgpu renderpass.
466    ///
467    /// Note that the lifetime of `render_pass` is `'static` which requires a call to [`wgpu::RenderPass::forget_lifetime`].
468    /// This allows users to pass resources that live outside of the callback resources to the render pass.
469    /// The render pass internally keeps all referenced resources alive as long as necessary.
470    /// The only consequence of `forget_lifetime` is that any operation on the parent encoder will cause a runtime error
471    /// instead of a compile time error.
472    pub fn render(
473        &self,
474        render_pass: &mut wgpu::RenderPass<'static>,
475        paint_jobs: &[epaint::ClippedPrimitive],
476        screen_descriptor: &ScreenDescriptor,
477    ) {
478        profiling::function_scope!();
479
480        let pixels_per_point = screen_descriptor.pixels_per_point;
481        let size_in_pixels = screen_descriptor.size_in_pixels;
482
483        // Whether or not we need to reset the render pass because a paint callback has just
484        // run.
485        let mut needs_reset = true;
486
487        let mut index_buffer_slices = self.index_buffer.slices.iter();
488        let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
489
490        for epaint::ClippedPrimitive {
491            clip_rect,
492            primitive,
493        } in paint_jobs
494        {
495            if needs_reset {
496                render_pass.set_viewport(
497                    0.0,
498                    0.0,
499                    size_in_pixels[0] as f32,
500                    size_in_pixels[1] as f32,
501                    0.0,
502                    1.0,
503                );
504                render_pass.set_pipeline(&self.pipeline);
505                render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
506                needs_reset = false;
507            }
508
509            {
510                let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
511
512                if rect.width == 0 || rect.height == 0 {
513                    // Skip rendering zero-sized clip areas.
514                    if let Primitive::Mesh(_) = primitive {
515                        // If this is a mesh, we need to advance the index and vertex buffer iterators:
516                        index_buffer_slices.next().unwrap();
517                        vertex_buffer_slices.next().unwrap();
518                    }
519                    continue;
520                }
521
522                render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
523            }
524
525            match primitive {
526                Primitive::Mesh(mesh) => {
527                    let index_buffer_slice = index_buffer_slices.next().unwrap();
528                    let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
529
530                    if let Some(Texture { bind_group, .. }) = self.textures.get(&mesh.texture_id) {
531                        render_pass.set_bind_group(1, bind_group, &[]);
532                        render_pass.set_index_buffer(
533                            self.index_buffer.buffer.slice(
534                                index_buffer_slice.start as u64..index_buffer_slice.end as u64,
535                            ),
536                            wgpu::IndexFormat::Uint32,
537                        );
538                        render_pass.set_vertex_buffer(
539                            0,
540                            self.vertex_buffer.buffer.slice(
541                                vertex_buffer_slice.start as u64..vertex_buffer_slice.end as u64,
542                            ),
543                        );
544                        render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
545                    } else {
546                        log::warn!("Missing texture: {:?}", mesh.texture_id);
547                    }
548                }
549                Primitive::Callback(callback) => {
550                    let Some(cbfn) = callback.callback.downcast_ref::<Callback>() else {
551                        // We already warned in the `prepare` callback
552                        continue;
553                    };
554
555                    let info = PaintCallbackInfo {
556                        viewport: callback.rect,
557                        clip_rect: *clip_rect,
558                        pixels_per_point,
559                        screen_size_px: size_in_pixels,
560                    };
561
562                    let viewport_px = info.viewport_in_pixels();
563                    if viewport_px.width_px > 0 && viewport_px.height_px > 0 {
564                        profiling::scope!("callback");
565
566                        needs_reset = true;
567
568                        // We're setting a default viewport for the render pass as a
569                        // courtesy for the user, so that they don't have to think about
570                        // it in the simple case where they just want to fill the whole
571                        // paint area.
572                        //
573                        // The user still has the possibility of setting their own custom
574                        // viewport during the paint callback, effectively overriding this
575                        // one.
576                        render_pass.set_viewport(
577                            viewport_px.left_px as f32,
578                            viewport_px.top_px as f32,
579                            viewport_px.width_px as f32,
580                            viewport_px.height_px as f32,
581                            0.0,
582                            1.0,
583                        );
584
585                        cbfn.0.paint(info, render_pass, &self.callback_resources);
586                    }
587                }
588            }
589        }
590
591        render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
592    }
593
594    /// Should be called before [`Self::render`].
595    pub fn update_texture(
596        &mut self,
597        device: &wgpu::Device,
598        queue: &wgpu::Queue,
599        id: epaint::TextureId,
600        image_delta: &epaint::ImageDelta,
601    ) {
602        profiling::function_scope!();
603
604        let width = image_delta.image.width() as u32;
605        let height = image_delta.image.height() as u32;
606
607        let size = wgpu::Extent3d {
608            width,
609            height,
610            depth_or_array_layers: 1,
611        };
612
613        let data_color32 = match &image_delta.image {
614            epaint::ImageData::Color(image) => {
615                assert_eq!(
616                    width as usize * height as usize,
617                    image.pixels.len(),
618                    "Mismatch between texture size and texel count"
619                );
620                Cow::Borrowed(&image.pixels)
621            }
622        };
623        let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
624
625        let queue_write_data_to_texture = |texture, origin| {
626            profiling::scope!("write_texture");
627            queue.write_texture(
628                wgpu::TexelCopyTextureInfo {
629                    texture,
630                    mip_level: 0,
631                    origin,
632                    aspect: wgpu::TextureAspect::All,
633                },
634                data_bytes,
635                wgpu::TexelCopyBufferLayout {
636                    offset: 0,
637                    bytes_per_row: Some(4 * width),
638                    rows_per_image: Some(height),
639                },
640                size,
641            );
642        };
643
644        // Use same label for all resources associated with this texture id (no point in retyping the type)
645        let label_str = format!("egui_texid_{id:?}");
646        let label = Some(label_str.as_str());
647
648        let (texture, origin, bind_group) = if let Some(pos) = image_delta.pos {
649            // update the existing texture
650            let Texture {
651                texture,
652                bind_group,
653                options,
654            } = self
655                .textures
656                .remove(&id)
657                .expect("Tried to update a texture that has not been allocated yet.");
658            let texture = texture.expect("Tried to update user texture.");
659            let options = options.expect("Tried to update user texture.");
660            let origin = wgpu::Origin3d {
661                x: pos[0] as u32,
662                y: pos[1] as u32,
663                z: 0,
664            };
665
666            (
667                texture,
668                origin,
669                // If the TextureOptions are the same as the previous ones, we can reuse the bind group. Otherwise we
670                // have to recreate it.
671                if image_delta.options == options {
672                    Some(bind_group)
673                } else {
674                    None
675                },
676            )
677        } else {
678            // allocate a new texture
679            let texture = {
680                profiling::scope!("create_texture");
681                device.create_texture(&wgpu::TextureDescriptor {
682                    label,
683                    size,
684                    mip_level_count: 1,
685                    sample_count: 1,
686                    dimension: wgpu::TextureDimension::D2,
687                    format: wgpu::TextureFormat::Rgba8Unorm,
688                    usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
689                    view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
690                })
691            };
692            let origin = wgpu::Origin3d::ZERO;
693            (texture, origin, None)
694        };
695
696        let bind_group = bind_group.unwrap_or_else(|| {
697            let sampler = self
698                .samplers
699                .entry(image_delta.options)
700                .or_insert_with(|| create_sampler(image_delta.options, device));
701            device.create_bind_group(&wgpu::BindGroupDescriptor {
702                label,
703                layout: &self.texture_bind_group_layout,
704                entries: &[
705                    wgpu::BindGroupEntry {
706                        binding: 0,
707                        resource: wgpu::BindingResource::TextureView(
708                            &texture.create_view(&wgpu::TextureViewDescriptor::default()),
709                        ),
710                    },
711                    wgpu::BindGroupEntry {
712                        binding: 1,
713                        resource: wgpu::BindingResource::Sampler(sampler),
714                    },
715                ],
716            })
717        });
718
719        queue_write_data_to_texture(&texture, origin);
720        self.textures.insert(
721            id,
722            Texture {
723                texture: Some(texture),
724                bind_group,
725                options: Some(image_delta.options),
726            },
727        );
728    }
729
730    pub fn free_texture(&mut self, id: &epaint::TextureId) {
731        if let Some(texture) = self.textures.remove(id).and_then(|t| t.texture) {
732            texture.destroy();
733        }
734    }
735
736    /// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
737    ///
738    /// This could be used by custom paint hooks to render images that have been added through
739    /// [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture).
740    pub fn texture(&self, id: &epaint::TextureId) -> Option<&Texture> {
741        self.textures.get(id)
742    }
743
744    /// Registers a [`wgpu::Texture`] with a [`epaint::TextureId`].
745    ///
746    /// This enables the application to reference the texture inside an image ui element.
747    /// This effectively enables off-screen rendering inside the egui UI. Texture must have
748    /// the texture format [`wgpu::TextureFormat::Rgba8Unorm`].
749    pub fn register_native_texture(
750        &mut self,
751        device: &wgpu::Device,
752        texture: &wgpu::TextureView,
753        texture_filter: wgpu::FilterMode,
754    ) -> epaint::TextureId {
755        self.register_native_texture_with_sampler_options(
756            device,
757            texture,
758            wgpu::SamplerDescriptor {
759                label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
760                mag_filter: texture_filter,
761                min_filter: texture_filter,
762                ..Default::default()
763            },
764        )
765    }
766
767    /// Registers a [`wgpu::Texture`] with an existing [`epaint::TextureId`].
768    ///
769    /// This enables applications to reuse [`epaint::TextureId`]s.
770    pub fn update_egui_texture_from_wgpu_texture(
771        &mut self,
772        device: &wgpu::Device,
773        texture: &wgpu::TextureView,
774        texture_filter: wgpu::FilterMode,
775        id: epaint::TextureId,
776    ) {
777        self.update_egui_texture_from_wgpu_texture_with_sampler_options(
778            device,
779            texture,
780            wgpu::SamplerDescriptor {
781                label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
782                mag_filter: texture_filter,
783                min_filter: texture_filter,
784                ..Default::default()
785            },
786            id,
787        );
788    }
789
790    /// Registers a [`wgpu::Texture`] with a [`epaint::TextureId`] while also accepting custom
791    /// [`wgpu::SamplerDescriptor`] options.
792    ///
793    /// This allows applications to specify individual minification/magnification filters as well as
794    /// custom mipmap and tiling options.
795    ///
796    /// The texture must have the format [`wgpu::TextureFormat::Rgba8Unorm`].
797    /// Any compare function supplied in the [`wgpu::SamplerDescriptor`] will be ignored.
798    #[expect(clippy::needless_pass_by_value)] // false positive
799    pub fn register_native_texture_with_sampler_options(
800        &mut self,
801        device: &wgpu::Device,
802        texture: &wgpu::TextureView,
803        sampler_descriptor: wgpu::SamplerDescriptor<'_>,
804    ) -> epaint::TextureId {
805        profiling::function_scope!();
806
807        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
808            compare: None,
809            ..sampler_descriptor
810        });
811
812        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
813            label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
814            layout: &self.texture_bind_group_layout,
815            entries: &[
816                wgpu::BindGroupEntry {
817                    binding: 0,
818                    resource: wgpu::BindingResource::TextureView(texture),
819                },
820                wgpu::BindGroupEntry {
821                    binding: 1,
822                    resource: wgpu::BindingResource::Sampler(&sampler),
823                },
824            ],
825        });
826
827        let id = epaint::TextureId::User(self.next_user_texture_id);
828        self.textures.insert(
829            id,
830            Texture {
831                texture: None,
832                bind_group,
833                options: None,
834            },
835        );
836        self.next_user_texture_id += 1;
837
838        id
839    }
840
841    /// Registers a [`wgpu::Texture`] with an existing [`epaint::TextureId`] while also accepting custom
842    /// [`wgpu::SamplerDescriptor`] options.
843    ///
844    /// This allows applications to reuse [`epaint::TextureId`]s created with custom sampler options.
845    #[expect(clippy::needless_pass_by_value)] // false positive
846    pub fn update_egui_texture_from_wgpu_texture_with_sampler_options(
847        &mut self,
848        device: &wgpu::Device,
849        texture: &wgpu::TextureView,
850        sampler_descriptor: wgpu::SamplerDescriptor<'_>,
851        id: epaint::TextureId,
852    ) {
853        profiling::function_scope!();
854
855        let Texture {
856            bind_group: user_texture_binding,
857            ..
858        } = self
859            .textures
860            .get_mut(&id)
861            .expect("Tried to update a texture that has not been allocated yet.");
862
863        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
864            compare: None,
865            ..sampler_descriptor
866        });
867
868        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
869            label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
870            layout: &self.texture_bind_group_layout,
871            entries: &[
872                wgpu::BindGroupEntry {
873                    binding: 0,
874                    resource: wgpu::BindingResource::TextureView(texture),
875                },
876                wgpu::BindGroupEntry {
877                    binding: 1,
878                    resource: wgpu::BindingResource::Sampler(&sampler),
879                },
880            ],
881        });
882
883        *user_texture_binding = bind_group;
884    }
885
886    /// Uploads the uniform, vertex and index data used by the renderer.
887    /// Should be called before [`Self::render`].
888    ///
889    /// Returns all user-defined command buffers gathered from [`CallbackTrait::prepare`] & [`CallbackTrait::finish_prepare`] callbacks.
890    pub fn update_buffers(
891        &mut self,
892        device: &wgpu::Device,
893        queue: &wgpu::Queue,
894        encoder: &mut wgpu::CommandEncoder,
895        paint_jobs: &[epaint::ClippedPrimitive],
896        screen_descriptor: &ScreenDescriptor,
897    ) -> Vec<wgpu::CommandBuffer> {
898        profiling::function_scope!();
899
900        let screen_size_in_points = screen_descriptor.screen_size_in_points();
901
902        let uniform_buffer_content = UniformBuffer {
903            screen_size_in_points,
904            dithering: u32::from(self.options.dithering),
905            predictable_texture_filtering: u32::from(self.options.predictable_texture_filtering),
906        };
907        if uniform_buffer_content != self.previous_uniform_buffer_content {
908            profiling::scope!("update uniforms");
909            queue.write_buffer(
910                &self.uniform_buffer,
911                0,
912                bytemuck::cast_slice(&[uniform_buffer_content]),
913            );
914            self.previous_uniform_buffer_content = uniform_buffer_content;
915        }
916
917        // Determine how many vertices & indices need to be rendered, and gather prepare callbacks
918        let mut callbacks = Vec::new();
919        let (vertex_count, index_count) = {
920            profiling::scope!("count_vertices_indices");
921            paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
922                match &clipped_primitive.primitive {
923                    Primitive::Mesh(mesh) => {
924                        (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
925                    }
926                    Primitive::Callback(callback) => {
927                        if let Some(c) = callback.callback.downcast_ref::<Callback>() {
928                            callbacks.push(c.0.as_ref());
929                        } else {
930                            log::warn!("Unknown paint callback: expected `egui_wgpu::Callback`");
931                        }
932                        acc
933                    }
934                }
935            })
936        };
937
938        if index_count > 0 {
939            profiling::scope!("indices", index_count.to_string().as_str());
940
941            self.index_buffer.slices.clear();
942
943            let required_index_buffer_size = (std::mem::size_of::<u32>() * index_count) as u64;
944            if self.index_buffer.capacity < required_index_buffer_size {
945                // Resize index buffer if needed.
946                self.index_buffer.capacity =
947                    (self.index_buffer.capacity * 2).at_least(required_index_buffer_size);
948                self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
949            }
950
951            let index_buffer_staging = queue.write_buffer_with(
952                &self.index_buffer.buffer,
953                0,
954                NonZeroU64::new(required_index_buffer_size).unwrap(),
955            );
956
957            let Some(mut index_buffer_staging) = index_buffer_staging else {
958                panic!(
959                    "Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)",
960                    self.index_buffer.buffer.size(),
961                    self.index_buffer.capacity
962                );
963            };
964
965            let mut index_offset = 0;
966            for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
967                match primitive {
968                    Primitive::Mesh(mesh) => {
969                        let size = mesh.indices.len() * std::mem::size_of::<u32>();
970                        let slice = index_offset..(size + index_offset);
971                        index_buffer_staging[slice.clone()]
972                            .copy_from_slice(bytemuck::cast_slice(&mesh.indices));
973                        self.index_buffer.slices.push(slice);
974                        index_offset += size;
975                    }
976                    Primitive::Callback(_) => {}
977                }
978            }
979        }
980        if vertex_count > 0 {
981            profiling::scope!("vertices", vertex_count.to_string().as_str());
982
983            self.vertex_buffer.slices.clear();
984
985            let required_vertex_buffer_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
986            if self.vertex_buffer.capacity < required_vertex_buffer_size {
987                // Resize vertex buffer if needed.
988                self.vertex_buffer.capacity =
989                    (self.vertex_buffer.capacity * 2).at_least(required_vertex_buffer_size);
990                self.vertex_buffer.buffer =
991                    create_vertex_buffer(device, self.vertex_buffer.capacity);
992            }
993
994            let vertex_buffer_staging = queue.write_buffer_with(
995                &self.vertex_buffer.buffer,
996                0,
997                NonZeroU64::new(required_vertex_buffer_size).unwrap(),
998            );
999
1000            let Some(mut vertex_buffer_staging) = vertex_buffer_staging else {
1001                panic!(
1002                    "Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)",
1003                    self.vertex_buffer.buffer.size(),
1004                    self.vertex_buffer.capacity
1005                );
1006            };
1007
1008            let mut vertex_offset = 0;
1009            for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
1010                match primitive {
1011                    Primitive::Mesh(mesh) => {
1012                        let size = mesh.vertices.len() * std::mem::size_of::<Vertex>();
1013                        let slice = vertex_offset..(size + vertex_offset);
1014                        vertex_buffer_staging[slice.clone()]
1015                            .copy_from_slice(bytemuck::cast_slice(&mesh.vertices));
1016                        self.vertex_buffer.slices.push(slice);
1017                        vertex_offset += size;
1018                    }
1019                    Primitive::Callback(_) => {}
1020                }
1021            }
1022        }
1023
1024        let mut user_cmd_bufs = Vec::new();
1025        {
1026            profiling::scope!("prepare callbacks");
1027            for callback in &callbacks {
1028                user_cmd_bufs.extend(callback.prepare(
1029                    device,
1030                    queue,
1031                    screen_descriptor,
1032                    encoder,
1033                    &mut self.callback_resources,
1034                ));
1035            }
1036        }
1037        {
1038            profiling::scope!("finish prepare callbacks");
1039            for callback in &callbacks {
1040                user_cmd_bufs.extend(callback.finish_prepare(
1041                    device,
1042                    queue,
1043                    encoder,
1044                    &mut self.callback_resources,
1045                ));
1046            }
1047        }
1048
1049        user_cmd_bufs
1050    }
1051}
1052
1053fn create_sampler(
1054    options: epaint::textures::TextureOptions,
1055    device: &wgpu::Device,
1056) -> wgpu::Sampler {
1057    let mag_filter = match options.magnification {
1058        epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
1059        epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
1060    };
1061    let min_filter = match options.minification {
1062        epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
1063        epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
1064    };
1065    let address_mode = match options.wrap_mode {
1066        epaint::textures::TextureWrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
1067        epaint::textures::TextureWrapMode::Repeat => wgpu::AddressMode::Repeat,
1068        epaint::textures::TextureWrapMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
1069    };
1070    device.create_sampler(&wgpu::SamplerDescriptor {
1071        label: Some(&format!(
1072            "egui sampler (mag: {mag_filter:?}, min {min_filter:?})"
1073        )),
1074        mag_filter,
1075        min_filter,
1076        address_mode_u: address_mode,
1077        address_mode_v: address_mode,
1078        ..Default::default()
1079    })
1080}
1081
1082fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
1083    profiling::function_scope!();
1084    device.create_buffer(&wgpu::BufferDescriptor {
1085        label: Some("egui_vertex_buffer"),
1086        usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1087        size,
1088        mapped_at_creation: false,
1089    })
1090}
1091
1092fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
1093    profiling::function_scope!();
1094    device.create_buffer(&wgpu::BufferDescriptor {
1095        label: Some("egui_index_buffer"),
1096        usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1097        size,
1098        mapped_at_creation: false,
1099    })
1100}
1101
1102/// A Rect in physical pixel space, used for setting clipping rectangles.
1103struct ScissorRect {
1104    x: u32,
1105    y: u32,
1106    width: u32,
1107    height: u32,
1108}
1109
1110impl ScissorRect {
1111    fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
1112        // Transform clip rect to physical pixels:
1113        let clip_min_x = pixels_per_point * clip_rect.min.x;
1114        let clip_min_y = pixels_per_point * clip_rect.min.y;
1115        let clip_max_x = pixels_per_point * clip_rect.max.x;
1116        let clip_max_y = pixels_per_point * clip_rect.max.y;
1117
1118        // Round to integer:
1119        let clip_min_x = clip_min_x.round() as u32;
1120        let clip_min_y = clip_min_y.round() as u32;
1121        let clip_max_x = clip_max_x.round() as u32;
1122        let clip_max_y = clip_max_y.round() as u32;
1123
1124        // Clamp:
1125        let clip_min_x = clip_min_x.clamp(0, target_size[0]);
1126        let clip_min_y = clip_min_y.clamp(0, target_size[1]);
1127        let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
1128        let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
1129
1130        Self {
1131            x: clip_min_x,
1132            y: clip_min_y,
1133            width: clip_max_x - clip_min_x,
1134            height: clip_max_y - clip_min_y,
1135        }
1136    }
1137}
1138
1139// Look at the feature flag for an explanation.
1140#[cfg(not(all(
1141    target_arch = "wasm32",
1142    not(feature = "fragile-send-sync-non-atomic-wasm"),
1143)))]
1144#[test]
1145fn renderer_impl_send_sync() {
1146    fn assert_send_sync<T: Send + Sync>() {}
1147    assert_send_sync::<Renderer>();
1148}