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