radiance_egui_wgpu/
renderer.rs

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