egui_render_wgpu/
lib.rs

1mod painter;
2mod surface;
3use std::sync::Arc;
4use tracing::{debug, info};
5use wgpu::*;
6
7pub use painter::*;
8pub use surface::SurfaceManager;
9pub use wgpu;
10
11pub struct WgpuConfig {
12    pub backends: Backends,
13    pub power_preference: PowerPreference,
14    pub device_descriptor: DeviceDescriptor<'static>,
15    /// If not empty, We will try to iterate over this vector and use the first format that is supported by the surface.
16    /// If this is empty or none of the formats in this vector are supported, we will just use the first supported format of the surface.
17    pub surface_formats_priority: Vec<TextureFormat>,
18    /// we will try to use this config if supported. otherwise, the surface recommended options will be used.   
19    pub surface_config: SurfaceConfiguration,
20    pub transparent_surface: Option<bool>,
21}
22impl Default for WgpuConfig {
23    fn default() -> Self {
24        Self {
25            backends: Backends::all(),
26            power_preference: PowerPreference::default(),
27            device_descriptor: DeviceDescriptor {
28                label: Some("my wgpu device"),
29                required_features: Default::default(),
30                required_limits: Limits::downlevel_defaults(),
31                memory_hints: MemoryHints::default(),
32            },
33            surface_config: SurfaceConfiguration {
34                usage: TextureUsages::RENDER_ATTACHMENT,
35                format: TextureFormat::Bgra8UnormSrgb,
36                width: 0,
37                height: 0,
38                present_mode: PresentMode::Fifo,
39                alpha_mode: wgpu::CompositeAlphaMode::Auto,
40                view_formats: vec![],
41                desired_maximum_frame_latency: 2,
42            },
43            surface_formats_priority: vec![],
44            transparent_surface: Some(true),
45        }
46    }
47}
48/// This provides a Gfx backend for egui using wgpu as the backend
49/// If you are making your own wgpu integration, then you can reuse the `EguiPainter` instead which contains only egui render specific data.
50pub struct WgpuBackend {
51    /// wgpu instance
52    pub instance: Arc<Instance>,
53    /// wgpu adapter
54    pub adapter: Arc<Adapter>,
55    /// wgpu device.
56    pub device: Arc<Device>,
57    /// wgpu queue. if you have commands that you would like to submit, instead push them into `Self::command_encoders`
58    pub queue: Arc<Queue>,
59    /// contains egui specific wgpu data like textures or buffers or pipelines etc..
60    pub painter: EguiPainter,
61    pub surface_manager: SurfaceManager,
62    /// this is where we store our command encoders. we will create one during the `prepare_frame` fn.
63    /// users can just use this. or create new encoders, and push them into this vec.
64    /// `wgpu::Queue::submit` is very expensive, so we will submit ALL command encoders at the same time during the `present_frame` method
65    /// just before presenting the swapchain image (surface texture).
66    pub command_encoders: Vec<CommandEncoder>,
67}
68impl Drop for WgpuBackend {
69    fn drop(&mut self) {
70        tracing::warn!("dropping wgpu backend");
71    }
72}
73impl WgpuBackend {
74    /// Both surface target and window are basically the same.
75    /// But we try to create the surface *twice*. First, with just instance, and if surface
76    pub async fn new_async(
77        config: WgpuConfig,
78        window: Option<Box<dyn WindowHandle>>,
79        latest_fb_size: [u32; 2],
80    ) -> Self {
81        let WgpuConfig {
82            power_preference,
83            device_descriptor,
84            surface_formats_priority,
85            surface_config,
86            backends,
87            transparent_surface,
88        } = config;
89        debug!("using wgpu backends: {:?}", backends);
90        let instance = Arc::new(Instance::new(InstanceDescriptor {
91            backends,
92            dx12_shader_compiler: Default::default(),
93            flags: InstanceFlags::from_build_config(),
94            gles_minor_version: Gles3MinorVersion::Automatic,
95        }));
96        debug!("iterating over all adapters");
97        #[cfg(not(target_arch = "wasm32"))]
98        for adapter in instance.enumerate_adapters(Backends::all()) {
99            debug!("adapter: {:#?}", adapter.get_info());
100        }
101
102        let surface = window.map(|w| {
103            tracing::debug!("creating a surface");
104            instance
105                .create_surface(SurfaceTarget::Window(w))
106                .expect("failed to create surface")
107        });
108
109        info!("is surfaced created at startup?: {}", surface.is_some());
110
111        debug!("using power preference: {:?}", config.power_preference);
112        let adapter = Arc::new(
113            instance
114                .request_adapter(&RequestAdapterOptions {
115                    power_preference,
116                    force_fallback_adapter: false,
117                    compatible_surface: surface.as_ref(),
118                })
119                .await
120                .expect("failed to get adapter"),
121        );
122
123        info!("chosen adapter details: {:?}", adapter.get_info());
124        let (device, queue) = adapter
125            .request_device(&device_descriptor, Default::default())
126            .await
127            .expect("failed to create wgpu device");
128
129        let device = Arc::new(device);
130        let queue = Arc::new(queue);
131
132        let surface_manager = SurfaceManager::new(
133            None,
134            transparent_surface,
135            latest_fb_size,
136            &instance,
137            &adapter,
138            &device,
139            surface,
140            surface_formats_priority,
141            surface_config,
142        );
143
144        debug!("device features: {:#?}", device.features());
145        debug!("device limits: {:#?}", device.limits());
146
147        let painter = EguiPainter::new(&device, surface_manager.surface_config.format);
148
149        Self {
150            instance,
151            adapter,
152            device,
153            queue,
154            painter,
155            command_encoders: Vec::new(),
156            surface_manager,
157        }
158    }
159}
160impl WgpuBackend {
161    pub fn new(
162        config: WgpuConfig,
163        window: Option<Box<dyn WindowHandle>>,
164        latest_fb_size: [u32; 2],
165    ) -> Self {
166        pollster::block_on(Self::new_async(config, window, latest_fb_size))
167    }
168
169    pub fn resume(
170        &mut self,
171        window: Option<Box<dyn WindowHandle>>,
172        latest_fb_size: [u32; 2],
173        transparent: Option<bool>,
174    ) {
175        self.surface_manager.reconfigure_surface(
176            window,
177            transparent,
178            latest_fb_size,
179            &self.instance,
180            &self.adapter,
181            &self.device,
182        );
183        self.painter.on_resume(
184            &self.device,
185            self.surface_manager
186                .surface_config
187                .view_formats
188                .first()
189                .copied()
190                .unwrap(),
191        );
192    }
193
194    pub fn prepare_frame(&mut self, latest_framebuffer_size_getter: impl FnMut() -> [u32; 2]) {
195        self.surface_manager
196            .create_current_surface_texture_view(latest_framebuffer_size_getter, &self.device);
197        if let Some(view) = self.surface_manager.surface_view.as_ref() {
198            let mut ce = self
199                .device
200                .create_command_encoder(&CommandEncoderDescriptor {
201                    label: "surface clear ce".into(),
202                });
203            ce.begin_render_pass(&RenderPassDescriptor {
204                label: "surface clear rpass".into(),
205                color_attachments: &[Some(RenderPassColorAttachment {
206                    view,
207                    resolve_target: None,
208                    ops: Operations {
209                        load: LoadOp::Clear(wgpu::Color::TRANSPARENT),
210                        store: StoreOp::Store,
211                    },
212                })],
213                ..Default::default()
214            });
215            self.command_encoders.push(ce);
216        }
217    }
218
219    pub fn render_egui(
220        &mut self,
221        meshes: Vec<egui::ClippedPrimitive>,
222        textures_delta: egui::TexturesDelta,
223        logical_screen_size: [f32; 2],
224    ) {
225        let mut command_encoder = self
226            .device
227            .create_command_encoder(&CommandEncoderDescriptor {
228                label: Some("egui command encoder"),
229            });
230        let draw_calls = self.painter.upload_egui_data(
231            &self.device,
232            &self.queue,
233            meshes,
234            textures_delta,
235            logical_screen_size,
236            [
237                self.surface_manager.surface_config.width,
238                self.surface_manager.surface_config.height,
239            ],
240            &mut command_encoder,
241        );
242        {
243            let mut egui_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
244                label: Some("egui render pass"),
245                color_attachments: &[Some(RenderPassColorAttachment {
246                    view: self
247                        .surface_manager
248                        .surface_view
249                        .as_ref()
250                        .expect("failed ot get surface view for egui render pass creation"),
251                    resolve_target: None,
252                    ops: Operations {
253                        load: LoadOp::Load,
254                        store: StoreOp::Store,
255                    },
256                })],
257                ..Default::default()
258            });
259            self.painter
260                .draw_egui_with_renderpass(&mut egui_pass, draw_calls);
261        }
262        self.command_encoders.push(command_encoder);
263    }
264
265    pub fn present(&mut self) {
266        assert!(self.surface_manager.surface_view.is_some());
267        self.queue.submit(
268            std::mem::take(&mut self.command_encoders)
269                .into_iter()
270                .map(|encoder| encoder.finish()),
271        );
272        {
273            self.surface_manager
274                .surface_view
275                .take()
276                .expect("failed to get surface view to present");
277        }
278        self.surface_manager
279            .surface_current_image
280            .take()
281            .expect("failed to surface texture to preset")
282            .present();
283    }
284
285    pub fn resize_framebuffer(&mut self, latest_fb_size: [u32; 2]) {
286        self.surface_manager
287            .resize_framebuffer(&self.device, latest_fb_size);
288    }
289
290    pub fn suspend(&mut self) {
291        self.surface_manager.suspend();
292    }
293}
294/// input: clip rectangle in logical pixels, scale and framebuffer size in physical pixels
295/// we will get [x, y, width, height] of the scissor rectangle.
296///
297/// internally, it will
298/// 1. multiply clip rect and scale  to convert the logical rectangle to a physical rectangle in framebuffer space.
299/// 2. clamp the rectangle between 0..width and 0..height of the frambuffer. make sure that width/height are positive/zero.
300/// 3. return Some only if width/height of scissor region are not zero.
301///
302/// This fn is for wgpu/metal/directx.
303pub fn scissor_from_clip_rect(
304    clip_rect: &egui::Rect,
305    scale: f32,
306    physical_framebuffer_size: [u32; 2],
307) -> Option<[u32; 4]> {
308    // copy paste from official egui impl because i have no idea what this is :D
309
310    // first, we turn the clip rectangle into physical framebuffer coordinates
311    // clip_min is top left point and clip_max is bottom right.
312    let clip_min_x = scale * clip_rect.min.x;
313    let clip_min_y = scale * clip_rect.min.y;
314    let clip_max_x = scale * clip_rect.max.x;
315    let clip_max_y = scale * clip_rect.max.y;
316
317    // round to integers
318    let clip_min_x = clip_min_x.round() as i32;
319    let clip_min_y = clip_min_y.round() as i32;
320    let clip_max_x = clip_max_x.round() as i32;
321    let clip_max_y = clip_max_y.round() as i32;
322
323    // clamp top_left of clip rect to be within framebuffer bounds
324    let clip_min_x = clip_min_x.clamp(0, physical_framebuffer_size[0] as i32);
325    let clip_min_y = clip_min_y.clamp(0, physical_framebuffer_size[1] as i32);
326    // clamp bottom right of clip rect to be between top_left of clip rect and framebuffer bottom right bounds
327    let clip_max_x = clip_max_x.clamp(clip_min_x, physical_framebuffer_size[0] as i32);
328    let clip_max_y = clip_max_y.clamp(clip_min_y, physical_framebuffer_size[1] as i32);
329    // x,y are simply top left coords
330    let x = clip_min_x as u32;
331    let y = clip_min_y as u32;
332    // width height by subtracting bottom right with top left coords.
333    let width = (clip_max_x - clip_min_x) as u32;
334    let height = (clip_max_y - clip_min_y) as u32;
335    // return only if scissor width/height are not zero. otherwise, no need for a scissor rect at all
336    (width != 0 && height != 0).then_some([x, y, width, height])
337}