egui_wgpu/
renderer.rs

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