egui_fltk_frontend/
backend.rs

1// source: https://github.com/emilk/egui/blob/master/crates/egui-wgpu/src/renderer.rs modified for FLTK
2
3use egui::{epaint::Primitive, PaintCallbackInfo};
4use fxhash::FxHashMap;
5use std::{borrow::Cow, num::NonZeroU32};
6use type_map::concurrent::TypeMap;
7use wgpu::util::DeviceExt as _;
8use wgpu::{self, TextureViewDescriptor};
9
10const EGUI_WGSL: &str = r#"
11// Vertex shader bindings
12
13struct VertexOutput {
14    @location(0) tex_coord: vec2<f32>,
15    @location(1) color: vec4<f32>,
16    @builtin(position) position: vec4<f32>,
17};
18
19struct Locals {
20    screen_size: vec2<f32>,
21    // Uniform buffers need to be at least 16 bytes in WebGL.
22    // See https://github.com/gfx-rs/wgpu/issues/2072
23    _padding: vec2<u32>,
24};
25@group(0) @binding(0) var<uniform> r_locals: Locals;
26
27// 0-1 from 0-255
28fn linear_from_srgb(srgb: vec3<f32>) -> vec3<f32> {
29    let cutoff = srgb < vec3<f32>(10.31475);
30    let lower = srgb / vec3<f32>(3294.6);
31    let higher = pow((srgb + vec3<f32>(14.025)) / vec3<f32>(269.025), vec3<f32>(2.4));
32    return select(higher, lower, cutoff);
33}
34
35// [u8; 4] SRGB as u32 -> [r, g, b, a]
36fn unpack_color(color: u32) -> vec4<f32> {
37    return vec4<f32>(
38        f32(color & 255u),
39        f32((color >> 8u) & 255u),
40        f32((color >> 16u) & 255u),
41        f32((color >> 24u) & 255u),
42    );
43}
44
45fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
46    return vec4<f32>(
47        2.0 * screen_pos.x / r_locals.screen_size.x - 1.0,
48        1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y,
49        0.0,
50        1.0,
51    );
52}
53
54@vertex
55fn vs_main(
56    @location(0) a_pos: vec2<f32>,
57    @location(1) a_tex_coord: vec2<f32>,
58    @location(2) a_color: u32,
59) -> VertexOutput {
60    var out: VertexOutput;
61    out.tex_coord = a_tex_coord;
62    let color = unpack_color(a_color);
63    out.color = vec4<f32>(linear_from_srgb(color.rgb), color.a / 255.0);
64    out.position = position_from_screen(a_pos);
65    return out;
66}
67
68@vertex
69fn vs_conv_main(
70    @location(0) a_pos: vec2<f32>,
71    @location(1) a_tex_coord: vec2<f32>,
72    @location(2) a_color: u32,
73) -> VertexOutput {
74    var out: VertexOutput;
75    out.tex_coord = a_tex_coord;
76    let color = unpack_color(a_color);
77    out.color = vec4<f32>(color.rgba / 255.0);
78    out.position = position_from_screen(a_pos);
79    return out;
80}
81
82// Fragment shader bindings
83
84@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
85@group(1) @binding(1) var r_tex_sampler: sampler;
86
87@fragment
88fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
89    return in.color * textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
90}
91"#;
92
93const IDX_BUF: &str = "egui_index_buffer";
94const VTX_BUF: &str = "egui_vertex_buffer";
95const UNI_BUF: &str = "egui_uniform_buffer";
96const WG_RPAS: &str = "wgpu render pass";
97const GRP_LAB: &str = "egui_pass";
98
99/// A callback function that can be used to compose an [`egui::PaintCallback`] for custom WGPU
100/// rendering.
101///
102/// The callback is composed of two functions: `prepare` and `paint`.
103///
104/// `prepare` is called every frame before `paint`, and can use the passed-in [`wgpu::Device`] and
105/// [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
106///
107/// `paint` is called after `prepare` and is given access to the the [`wgpu::RenderPass`] so that it
108/// can issue draw commands.
109///
110/// The final argument of both the `prepare` and `paint` callbacks is a the
111/// [`paint_callback_resources`][crate::renderer::RenderPass::paint_callback_resources].
112/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
113/// store buffers, pipelines, and other information that needs to be accessed during the render
114/// pass.
115///
116/// # Example
117///
118/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
119pub struct CallbackFn {
120    prepare: Box<PrepareCallback>,
121    paint: Box<PaintCallback>,
122}
123
124type PrepareCallback = dyn Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send;
125
126type PaintCallback =
127    dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
128
129impl Default for CallbackFn {
130    fn default() -> Self {
131        CallbackFn {
132            prepare: Box::new(|_, _, _| ()),
133            paint: Box::new(|_, _, _| ()),
134        }
135    }
136}
137
138#[allow(dead_code)]
139impl CallbackFn {
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    /// Set the prepare callback
145    pub fn prepare<F>(mut self, prepare: F) -> Self
146    where
147        F: Fn(&wgpu::Device, &wgpu::Queue, &mut TypeMap) + Sync + Send + 'static,
148    {
149        self.prepare = Box::new(prepare) as _;
150        self
151    }
152
153    /// Set the paint callback
154    pub fn paint<F>(mut self, paint: F) -> Self
155    where
156        F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap)
157            + Sync
158            + Send
159            + 'static,
160    {
161        self.paint = Box::new(paint) as _;
162        self
163    }
164}
165
166/// Enum for selecting the right buffer type.
167#[derive(Debug)]
168enum BufferType {
169    Uniform,
170    Index,
171    Vertex,
172}
173
174/// Information about the screen used for rendering.
175pub struct ScreenDescriptor {
176    /// Size of the window in physical pixels.
177    pub size_in_pixels: [u32; 2],
178
179    /// HiDPI scale factor (pixels per point).
180    pub pixels_per_point: f32,
181}
182
183impl ScreenDescriptor {
184    /// size in "logical" points
185    pub fn screen_size_in_points(&self) -> [f32; 2] {
186        [
187            self.size_in_pixels[0] as f32 / self.pixels_per_point,
188            self.size_in_pixels[1] as f32 / self.pixels_per_point,
189        ]
190    }
191}
192
193/// Uniform buffer used when rendering.
194#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
195#[repr(C)]
196struct UniformBuffer {
197    screen_size_in_points: [f32; 2],
198    // Uniform buffers need to be at least 16 bytes in WebGL.
199    // See https://github.com/gfx-rs/wgpu/issues/2072
200    _padding: [u32; 2],
201}
202
203/// Wraps the buffers and includes additional information.
204#[derive(Debug)]
205struct SizedBuffer {
206    buffer: wgpu::Buffer,
207    /// number of bytes
208    size: usize,
209}
210
211/// Render pass to render a egui based GUI.
212pub struct RenderPass<'a> {
213    render_pipeline: wgpu::RenderPipeline,
214    index_buffers: Vec<SizedBuffer>,
215    vertex_buffers: Vec<SizedBuffer>,
216    uniform_buffer: SizedBuffer,
217    uniform_bind_group: wgpu::BindGroup,
218    texture_bind_group_layout: wgpu::BindGroupLayout,
219    /// Map of egui texture IDs to textures and their associated bindgroups (texture view +
220    /// sampler). The texture may be None if the TextureId is just a handle to a user-provided
221    /// sampler.
222    textures: FxHashMap<egui::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
223    next_user_texture_id: u64,
224    /// Storage for use by [`egui::PaintCallback`]'s that need to store resources such as render
225    /// pipelines that must have the lifetime of the renderpass.
226    pub paint_callback_resources: TypeMap,
227    sampler: wgpu::Sampler,
228    texture_size: wgpu::Extent3d,
229    pub tex_view_desc: TextureViewDescriptor<'a>,
230}
231
232impl<'a> RenderPass<'a> {
233    /// Creates a new render pass to render a egui UI.
234    ///
235    /// If the format passed is not a *Srgb format, the shader will automatically convert to `sRGB` colors in the shader.
236    pub fn new(
237        device: &wgpu::Device,
238        texture_format: wgpu::TextureFormat,
239        msaa_samples: u32,
240    ) -> Self {
241        let shader = wgpu::ShaderModuleDescriptor {
242            label: Some("egui_shader"),
243            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(EGUI_WGSL)),
244        };
245        let module = device.create_shader_module(shader);
246
247        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
248            label: Some(UNI_BUF),
249            contents: bytemuck::cast_slice(&[UniformBuffer {
250                screen_size_in_points: [0.0, 0.0],
251                _padding: Default::default(),
252            }]),
253            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
254        });
255        let uniform_buffer = SizedBuffer {
256            buffer: uniform_buffer,
257            size: std::mem::size_of::<UniformBuffer>(),
258        };
259
260        let uniform_bind_group_layout =
261            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
262                label: Some("egui_uniform_bind_group_layout"),
263                entries: &[wgpu::BindGroupLayoutEntry {
264                    binding: 0,
265                    visibility: wgpu::ShaderStages::VERTEX,
266                    ty: wgpu::BindingType::Buffer {
267                        has_dynamic_offset: false,
268                        min_binding_size: None,
269                        ty: wgpu::BufferBindingType::Uniform,
270                    },
271                    count: None,
272                }],
273            });
274
275        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
276            label: Some("egui_uniform_bind_group"),
277            layout: &uniform_bind_group_layout,
278            entries: &[wgpu::BindGroupEntry {
279                binding: 0,
280                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
281                    buffer: &uniform_buffer.buffer,
282                    offset: 0,
283                    size: None,
284                }),
285            }],
286        });
287
288        let texture_bind_group_layout =
289            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
290                label: Some("egui_texture_bind_group_layout"),
291                entries: &[
292                    wgpu::BindGroupLayoutEntry {
293                        binding: 0,
294                        visibility: wgpu::ShaderStages::FRAGMENT,
295                        ty: wgpu::BindingType::Texture {
296                            multisampled: false,
297                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
298                            view_dimension: wgpu::TextureViewDimension::D2,
299                        },
300                        count: None,
301                    },
302                    wgpu::BindGroupLayoutEntry {
303                        binding: 1,
304                        visibility: wgpu::ShaderStages::FRAGMENT,
305                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
306                        count: None,
307                    },
308                ],
309            });
310
311        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
312            label: Some("egui_pipeline_layout"),
313            bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
314            push_constant_ranges: &[],
315        });
316
317        let mut multisample = wgpu::MultisampleState::default();
318        multisample.count = msaa_samples;
319
320        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
321            label: Some("egui_pipeline"),
322            layout: Some(&pipeline_layout),
323            vertex: wgpu::VertexState {
324                entry_point: if texture_format.describe().srgb {
325                    "vs_main"
326                } else {
327                    "vs_conv_main"
328                },
329                module: &module,
330                buffers: &[wgpu::VertexBufferLayout {
331                    array_stride: 5 * 4,
332                    step_mode: wgpu::VertexStepMode::Vertex,
333                    // 0: vec2 position
334                    // 1: vec2 texture coordinates
335                    // 2: uint color
336                    attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
337                }],
338            },
339            primitive: wgpu::PrimitiveState {
340                topology: wgpu::PrimitiveTopology::TriangleList,
341                unclipped_depth: false,
342                conservative: false,
343                cull_mode: None,
344                front_face: wgpu::FrontFace::default(),
345                polygon_mode: wgpu::PolygonMode::default(),
346                strip_index_format: None,
347            },
348            depth_stencil: None,
349            multisample,
350
351            fragment: Some(wgpu::FragmentState {
352                module: &module,
353                entry_point: "fs_main",
354                targets: &[Some(wgpu::ColorTargetState {
355                    format: texture_format,
356                    blend: Some(wgpu::BlendState {
357                        color: wgpu::BlendComponent {
358                            src_factor: wgpu::BlendFactor::One,
359                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
360                            operation: wgpu::BlendOperation::Add,
361                        },
362                        alpha: wgpu::BlendComponent {
363                            src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
364                            dst_factor: wgpu::BlendFactor::One,
365                            operation: wgpu::BlendOperation::Add,
366                        },
367                    }),
368                    write_mask: wgpu::ColorWrites::ALL,
369                })],
370            }),
371            multiview: None,
372        });
373
374        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
375            label: None,
376            mag_filter: wgpu::FilterMode::Linear,
377            min_filter: wgpu::FilterMode::Nearest,
378            ..Default::default()
379        });
380
381        let texture_size = wgpu::Extent3d {
382            width: 200,
383            height: 200,
384            depth_or_array_layers: 1,
385        };
386
387        Self {
388            render_pipeline,
389            vertex_buffers: Vec::with_capacity(64),
390            index_buffers: Vec::with_capacity(64),
391            uniform_buffer,
392            uniform_bind_group,
393            texture_bind_group_layout,
394            textures: FxHashMap::default(),
395            next_user_texture_id: 0,
396            paint_callback_resources: TypeMap::default(),
397            sampler,
398            texture_size,
399            tex_view_desc: TextureViewDescriptor::default(),
400        }
401    }
402
403    /// Executes the egui render pass.
404    pub fn execute(
405        &self,
406        encoder: &mut wgpu::CommandEncoder,
407        color_attachment: &wgpu::TextureView,
408        paint_jobs: Vec<egui::epaint::ClippedPrimitive>,
409        screen_descriptor: &ScreenDescriptor,
410        clear_color: Option<wgpu::Color>,
411    ) {
412        let load_operation = if let Some(color) = clear_color {
413            wgpu::LoadOp::Clear(color)
414        } else {
415            wgpu::LoadOp::Load
416        };
417
418        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
419            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
420                view: color_attachment,
421                resolve_target: None,
422                ops: wgpu::Operations {
423                    load: load_operation,
424                    store: true,
425                },
426            })],
427            depth_stencil_attachment: None,
428            label: Some(WG_RPAS),
429        });
430        rpass.push_debug_group(GRP_LAB);
431
432        self.execute_with_renderpass(&mut rpass, paint_jobs, screen_descriptor);
433
434        rpass.pop_debug_group();
435    }
436
437    /// Executes the egui render pass onto an existing wgpu renderpass.
438    pub fn execute_with_renderpass<'rpass>(
439        &'rpass self,
440        rpass: &mut wgpu::RenderPass<'rpass>,
441        paint_jobs: Vec<egui::epaint::ClippedPrimitive>,
442        screen_descriptor: &ScreenDescriptor,
443    ) {
444        let pixels_per_point = screen_descriptor.pixels_per_point;
445        let size_in_pixels = screen_descriptor.size_in_pixels;
446
447        // Whether or not we need to reset the renderpass state because a paint callback has just
448        // run.
449        let mut needs_reset = true;
450
451        let mut index_buffers = self.index_buffers.iter();
452        let mut vertex_buffers = self.vertex_buffers.iter();
453
454        for egui::ClippedPrimitive {
455            clip_rect,
456            primitive,
457        } in paint_jobs
458        {
459            if needs_reset {
460                rpass.set_viewport(
461                    0.0,
462                    0.0,
463                    size_in_pixels[0] as f32,
464                    size_in_pixels[1] as f32,
465                    0.0,
466                    1.0,
467                );
468                rpass.set_pipeline(&self.render_pipeline);
469                rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
470                needs_reset = false;
471            }
472
473            {
474                let rect = ScissorRect::new(&clip_rect, pixels_per_point, size_in_pixels);
475
476                if rect.width == 0 || rect.height == 0 {
477                    // Skip rendering with zero-sized clip areas.
478                    if let Primitive::Mesh(_) = primitive {
479                        // If this is a mesh, we need to advance the index and vertex buffer iterators
480                        index_buffers.next().unwrap();
481                        vertex_buffers.next().unwrap();
482                    }
483                    continue;
484                }
485
486                rpass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
487            }
488
489            match primitive {
490                Primitive::Mesh(mesh) => {
491                    let index_buffer = index_buffers.next().unwrap();
492                    let vertex_buffer = vertex_buffers.next().unwrap();
493
494                    if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
495                        rpass.set_bind_group(1, bind_group, &[]);
496                        rpass.set_index_buffer(
497                            index_buffer.buffer.slice(..),
498                            wgpu::IndexFormat::Uint32,
499                        );
500                        rpass.set_vertex_buffer(0, vertex_buffer.buffer.slice(..));
501                        rpass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
502                    } else {
503                        eprintln!("Missing texture: {:?}", mesh.texture_id);
504                    }
505                }
506                Primitive::Callback(callback) => {
507                    let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
508                        c
509                    } else {
510                        // We already warned in the `prepare` callback
511                        continue;
512                    };
513
514                    if callback.rect.is_positive() {
515                        needs_reset = true;
516
517                        {
518                            // Set the viewport rect
519                            // Transform callback rect to physical pixels:
520                            let rect_min_x = pixels_per_point * callback.rect.min.x;
521                            let rect_min_y = pixels_per_point * callback.rect.min.y;
522                            let rect_max_x = pixels_per_point * callback.rect.max.x;
523                            let rect_max_y = pixels_per_point * callback.rect.max.y;
524
525                            let rect_min_x = rect_min_x.round();
526                            let rect_min_y = rect_min_y.round();
527                            let rect_max_x = rect_max_x.round();
528                            let rect_max_y = rect_max_y.round();
529
530                            rpass.set_viewport(
531                                rect_min_x,
532                                rect_min_y,
533                                rect_max_x - rect_min_x,
534                                rect_max_y - rect_min_y,
535                                0.0,
536                                1.0,
537                            );
538                        }
539
540                        (cbfn.paint)(
541                            PaintCallbackInfo {
542                                viewport: callback.rect,
543                                clip_rect,
544                                pixels_per_point,
545                                screen_size_px: size_in_pixels,
546                            },
547                            rpass,
548                            &self.paint_callback_resources,
549                        );
550                    }
551                }
552            }
553        }
554
555        rpass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
556    }
557
558    /// Should be called before `execute()`.
559    pub fn update_texture(
560        &mut self,
561        device: &wgpu::Device,
562        queue: &wgpu::Queue,
563        id: egui::TextureId,
564        image_delta: &egui::epaint::ImageDelta,
565    ) {
566        let width = image_delta.image.width() as u32;
567        let height = image_delta.image.height() as u32;
568
569        self.texture_size.width = width;
570        self.texture_size.height = height;
571
572        let data_color32 = match &image_delta.image {
573            egui::ImageData::Color(image) => {
574                assert_eq!(
575                    width as usize * height as usize,
576                    image.pixels.len(),
577                    "Mismatch between texture size and texel count"
578                );
579                Cow::Borrowed(&image.pixels)
580            }
581            egui::ImageData::Font(image) => {
582                assert_eq!(
583                    width as usize * height as usize,
584                    image.pixels.len(),
585                    "Mismatch between texture size and texel count"
586                );
587                Cow::Owned(image.srgba_pixels(1.0).collect::<Vec<_>>())
588            }
589        };
590
591        let queue_write_data_to_texture = |texture, origin| {
592            queue.write_texture(
593                wgpu::ImageCopyTexture {
594                    texture,
595                    mip_level: 0,
596                    origin,
597                    aspect: wgpu::TextureAspect::All,
598                },
599                bytemuck::cast_slice(data_color32.as_slice()),
600                wgpu::ImageDataLayout {
601                    offset: 0,
602                    bytes_per_row: NonZeroU32::new(4 * width),
603                    rows_per_image: NonZeroU32::new(height),
604                },
605                self.texture_size,
606            );
607        };
608
609        if let Some(pos) = image_delta.pos {
610            // update the existing texture
611            let (texture, _bind_group) = self
612                .textures
613                .get(&id)
614                .expect("Tried to update a texture that has not been allocated yet.");
615            let origin = wgpu::Origin3d {
616                x: pos[0] as u32,
617                y: pos[1] as u32,
618                z: 0,
619            };
620            queue_write_data_to_texture(
621                texture.as_ref().expect("Tried to update user texture."),
622                origin,
623            );
624        } else {
625            // allocate a new texture
626            let texture = device.create_texture(&wgpu::TextureDescriptor {
627                label: None,
628                size: self.texture_size,
629                mip_level_count: 1,
630                sample_count: 1,
631                dimension: wgpu::TextureDimension::D2,
632                format: wgpu::TextureFormat::Rgba8UnormSrgb,
633                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
634            });
635
636            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
637                label: None,
638                layout: &self.texture_bind_group_layout,
639                entries: &[
640                    wgpu::BindGroupEntry {
641                        binding: 0,
642                        resource: wgpu::BindingResource::TextureView(
643                            &texture.create_view(&self.tex_view_desc),
644                        ),
645                    },
646                    wgpu::BindGroupEntry {
647                        binding: 1,
648                        resource: wgpu::BindingResource::Sampler(&self.sampler),
649                    },
650                ],
651            });
652
653            let origin = wgpu::Origin3d::ZERO;
654            queue_write_data_to_texture(&texture, origin);
655            self.textures.insert(id, (Some(texture), bind_group));
656        };
657    }
658
659    pub fn free_texture(&mut self, id: &egui::TextureId) {
660        self.textures.remove(id);
661    }
662
663    /// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
664    ///
665    /// This could be used by custom paint hooks to render images that have been added through with
666    /// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
667    /// or [`egui::Context::load_texture`].
668    pub fn texture(
669        &self,
670        id: &egui::TextureId,
671    ) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
672        self.textures.get(id)
673    }
674
675    /// Registers a `wgpu::Texture` with a `egui::TextureId`.
676    ///
677    /// This enables the application to reference the texture inside an image ui element.
678    /// This effectively enables off-screen rendering inside the egui UI. Texture must have
679    /// the texture format `TextureFormat::Rgba8UnormSrgb` and
680    /// Texture usage `TextureUsage::SAMPLED`.
681    pub fn register_native_texture(
682        &mut self,
683        device: &wgpu::Device,
684        texture: &wgpu::TextureView,
685        texture_filter: wgpu::FilterMode,
686    ) -> egui::TextureId {
687        self.register_native_texture_with_sampler_options(
688            device,
689            texture,
690            wgpu::SamplerDescriptor {
691                label: Some(
692                    format!(
693                        "egui_user_image_{}_texture_sampler",
694                        self.next_user_texture_id
695                    )
696                    .as_str(),
697                ),
698                mag_filter: texture_filter,
699                min_filter: texture_filter,
700                ..Default::default()
701            },
702        )
703    }
704
705    /// Registers a `wgpu::Texture` with a `egui::TextureId` while also accepting custom
706    /// `wgpu::SamplerDescriptor` options.
707    ///
708    /// This allows applications to specify individual minification/magnification filters as well as
709    /// custom mipmap and tiling options.
710    ///
711    /// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage
712    /// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be
713    /// ignored.
714    #[allow(clippy::needless_pass_by_value)] // false positive
715    pub fn register_native_texture_with_sampler_options(
716        &mut self,
717        device: &wgpu::Device,
718        texture: &wgpu::TextureView,
719        sampler_descriptor: wgpu::SamplerDescriptor<'_>,
720    ) -> egui::TextureId {
721        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
722            compare: None,
723            ..sampler_descriptor
724        });
725
726        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
727            label: Some(
728                format!(
729                    "egui_user_image_{}_texture_bind_group",
730                    self.next_user_texture_id
731                )
732                .as_str(),
733            ),
734            layout: &self.texture_bind_group_layout,
735            entries: &[
736                wgpu::BindGroupEntry {
737                    binding: 0,
738                    resource: wgpu::BindingResource::TextureView(texture),
739                },
740                wgpu::BindGroupEntry {
741                    binding: 1,
742                    resource: wgpu::BindingResource::Sampler(&sampler),
743                },
744            ],
745        });
746
747        let id = egui::TextureId::User(self.next_user_texture_id);
748        self.textures.insert(id, (None, bind_group));
749        self.next_user_texture_id += 1;
750
751        id
752    }
753
754    /// Uploads the uniform, vertex and index data used by the render pass.
755    /// Should be called before `execute()`.
756    pub fn update_buffers(
757        &mut self,
758        device: &wgpu::Device,
759        queue: &wgpu::Queue,
760        paint_jobs: &[egui::epaint::ClippedPrimitive],
761        screen_descriptor: &ScreenDescriptor,
762    ) {
763        let screen_size_in_points = screen_descriptor.screen_size_in_points();
764
765        self.update_buffer(
766            device,
767            queue,
768            &BufferType::Uniform,
769            0,
770            bytemuck::cast_slice(&[UniformBuffer {
771                screen_size_in_points,
772                _padding: Default::default(),
773            }]),
774        );
775
776        let mut mesh_idx = 0;
777        for egui::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
778            match primitive {
779                Primitive::Mesh(mesh) => {
780                    {
781                        let data: &[u8] = bytemuck::cast_slice(&mesh.indices);
782                        if mesh_idx < self.index_buffers.len() {
783                            self.update_buffer(device, queue, &BufferType::Index, mesh_idx, data);
784                        } else {
785                            let buffer =
786                                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
787                                    label: Some(IDX_BUF),
788                                    contents: data,
789                                    usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
790                                });
791                            self.index_buffers.push(SizedBuffer {
792                                buffer,
793                                size: data.len(),
794                            });
795                        }
796                    }
797
798                    let data: &[u8] = bytemuck::cast_slice(&mesh.vertices);
799                    if mesh_idx < self.vertex_buffers.len() {
800                        self.update_buffer(device, queue, &BufferType::Vertex, mesh_idx, data);
801                    } else {
802                        let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
803                            label: Some(VTX_BUF),
804                            contents: data,
805                            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
806                        });
807
808                        self.vertex_buffers.push(SizedBuffer {
809                            buffer,
810                            size: data.len(),
811                        });
812                    }
813
814                    mesh_idx += 1;
815                }
816                Primitive::Callback(callback) => {
817                    let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
818                        c
819                    } else {
820                        eprintln!("Unknown paint callback: expected `egui_gpu::CallbackFn`");
821                        continue;
822                    };
823
824                    (cbfn.prepare)(device, queue, &mut self.paint_callback_resources);
825                }
826            }
827        }
828    }
829
830    /// Updates the buffers used by egui. Will properly re-size the buffers if needed.
831    fn update_buffer(
832        &mut self,
833        device: &wgpu::Device,
834        queue: &wgpu::Queue,
835        buffer_type: &BufferType,
836        index: usize,
837        data: &[u8],
838    ) {
839        let (buffer, storage, label) = match buffer_type {
840            BufferType::Index => (
841                &mut self.index_buffers[index],
842                wgpu::BufferUsages::INDEX,
843                IDX_BUF,
844            ),
845            BufferType::Vertex => (
846                &mut self.vertex_buffers[index],
847                wgpu::BufferUsages::VERTEX,
848                VTX_BUF,
849            ),
850            BufferType::Uniform => (
851                &mut self.uniform_buffer,
852                wgpu::BufferUsages::UNIFORM,
853                UNI_BUF,
854            ),
855        };
856
857        if data.len() > buffer.size {
858            buffer.size = data.len();
859            buffer.buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
860                label: Some(label),
861                contents: bytemuck::cast_slice(data),
862                usage: storage | wgpu::BufferUsages::COPY_DST,
863            });
864        } else {
865            queue.write_buffer(&buffer.buffer, 0, data);
866        }
867    }
868}
869
870/// A Rect in physical pixel space, used for setting cliipping rectangles.
871struct ScissorRect {
872    x: u32,
873    y: u32,
874    width: u32,
875    height: u32,
876}
877
878impl ScissorRect {
879    fn new(clip_rect: &egui::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
880        // Transform clip rect to physical pixels:
881        let clip_min_x = (pixels_per_point * clip_rect.min.x).round() as u32;
882        let clip_min_y = (pixels_per_point * clip_rect.min.y).round() as u32;
883        let clip_max_x = (pixels_per_point * clip_rect.max.x).round() as u32;
884        let clip_max_y = (pixels_per_point * clip_rect.max.y).round() as u32;
885
886        // Clamp:
887        let clip_min_x = clip_min_x.clamp(0, target_size[0]);
888        let clip_min_y = clip_min_y.clamp(0, target_size[1]);
889        let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
890        let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
891
892        ScissorRect {
893            x: clip_min_x,
894            y: clip_min_y,
895            width: clip_max_x - clip_min_x,
896            height: clip_max_y - clip_min_y,
897        }
898    }
899}