Skip to main content

egui_wgpu/
renderer.rs

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