Skip to main content

egui_wgpu/
renderer.rs

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