1#![allow(clippy::missing_errors_doc)]
2#![allow(clippy::undocumented_unsafe_blocks)]
3
4use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
5use crate::{
6 RendererOptions,
7 capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
8};
9use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
10use std::{num::NonZeroU32, sync::Arc};
11
12struct SurfaceState {
13 surface: wgpu::Surface<'static>,
14 alpha_mode: wgpu::CompositeAlphaMode,
15 width: u32,
16 height: u32,
17 resizing: bool,
18}
19
20pub struct Painter {
26 context: Context,
27 configuration: WgpuConfiguration,
28 options: RendererOptions,
29 support_transparent_backbuffer: bool,
30 screen_capture_state: Option<CaptureState>,
31
32 instance: wgpu::Instance,
33 render_state: Option<RenderState>,
34
35 depth_texture_view: ViewportIdMap<wgpu::TextureView>,
37 msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
38 surfaces: ViewportIdMap<SurfaceState>,
39 capture_tx: CaptureSender,
40 capture_rx: CaptureReceiver,
41}
42
43impl Painter {
44 pub async fn new(
57 context: Context,
58 configuration: WgpuConfiguration,
59 support_transparent_backbuffer: bool,
60 options: RendererOptions,
61 ) -> Self {
62 let (capture_tx, capture_rx) = capture_channel();
63 let instance = configuration.wgpu_setup.new_instance().await;
64
65 Self {
66 context,
67 configuration,
68 options,
69 support_transparent_backbuffer,
70 screen_capture_state: None,
71
72 instance,
73 render_state: None,
74
75 depth_texture_view: Default::default(),
76 surfaces: Default::default(),
77 msaa_texture_view: Default::default(),
78
79 capture_tx,
80 capture_rx,
81 }
82 }
83
84 pub fn render_state(&self) -> Option<RenderState> {
88 self.render_state.clone()
89 }
90
91 fn configure_surface(
92 surface_state: &SurfaceState,
93 render_state: &RenderState,
94 config: &WgpuConfiguration,
95 ) {
96 profiling::function_scope!();
97
98 let width = surface_state.width;
99 let height = surface_state.height;
100
101 let mut surf_config = wgpu::SurfaceConfiguration {
102 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
103 format: render_state.target_format,
104 present_mode: config.present_mode,
105 alpha_mode: surface_state.alpha_mode,
106 view_formats: vec![render_state.target_format],
107 ..surface_state
108 .surface
109 .get_default_config(&render_state.adapter, width, height)
110 .expect("The surface isn't supported by this adapter")
111 };
112
113 if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency {
114 surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
115 }
116
117 surface_state
118 .surface
119 .configure(&render_state.device, &surf_config);
120 }
121
122 pub async fn set_window(
144 &mut self,
145 viewport_id: ViewportId,
146 window: Option<Arc<winit::window::Window>>,
147 ) -> Result<(), crate::WgpuError> {
148 profiling::scope!("Painter::set_window"); if let Some(window) = window {
151 let size = window.inner_size();
152 if !self.surfaces.contains_key(&viewport_id) {
153 let surface = self.instance.create_surface(window)?;
154 self.add_surface(surface, viewport_id, size).await?;
155 }
156 } else {
157 log::warn!("No window - clearing all surfaces");
158 self.surfaces.clear();
159 }
160 Ok(())
161 }
162
163 pub async unsafe fn set_window_unsafe(
170 &mut self,
171 viewport_id: ViewportId,
172 window: Option<&winit::window::Window>,
173 ) -> Result<(), crate::WgpuError> {
174 profiling::scope!("Painter::set_window_unsafe"); if let Some(window) = window {
177 let size = window.inner_size();
178 if !self.surfaces.contains_key(&viewport_id) {
179 let surface = unsafe {
180 self.instance
181 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)?
182 };
183 self.add_surface(surface, viewport_id, size).await?;
184 }
185 } else {
186 log::warn!("No window - clearing all surfaces");
187 self.surfaces.clear();
188 }
189 Ok(())
190 }
191
192 async fn add_surface(
193 &mut self,
194 surface: wgpu::Surface<'static>,
195 viewport_id: ViewportId,
196 size: winit::dpi::PhysicalSize<u32>,
197 ) -> Result<(), crate::WgpuError> {
198 let render_state = if let Some(render_state) = &self.render_state {
199 render_state
200 } else {
201 let render_state = RenderState::create(
202 &self.configuration,
203 &self.instance,
204 Some(&surface),
205 self.options,
206 )
207 .await?;
208 self.render_state.get_or_insert(render_state)
209 };
210 let alpha_mode = if self.support_transparent_backbuffer {
211 let supported_alpha_modes = surface.get_capabilities(&render_state.adapter).alpha_modes;
212
213 if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
215 wgpu::CompositeAlphaMode::PreMultiplied
216 } else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) {
217 wgpu::CompositeAlphaMode::PostMultiplied
218 } else {
219 log::warn!(
220 "Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."
221 );
222 wgpu::CompositeAlphaMode::Auto
223 }
224 } else {
225 wgpu::CompositeAlphaMode::Auto
226 };
227 self.surfaces.insert(
228 viewport_id,
229 SurfaceState {
230 surface,
231 width: size.width,
232 height: size.height,
233 alpha_mode,
234 resizing: false,
235 },
236 );
237 let Some(width) = NonZeroU32::new(size.width) else {
238 log::debug!("The window width was zero; skipping generate textures");
239 return Ok(());
240 };
241 let Some(height) = NonZeroU32::new(size.height) else {
242 log::debug!("The window height was zero; skipping generate textures");
243 return Ok(());
244 };
245 self.resize_and_generate_depth_texture_view_and_msaa_view(viewport_id, width, height);
246 Ok(())
247 }
248
249 pub fn max_texture_side(&self) -> Option<usize> {
255 self.render_state
256 .as_ref()
257 .map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
258 }
259
260 fn resize_and_generate_depth_texture_view_and_msaa_view(
261 &mut self,
262 viewport_id: ViewportId,
263 width_in_pixels: NonZeroU32,
264 height_in_pixels: NonZeroU32,
265 ) {
266 profiling::function_scope!();
267
268 let width = width_in_pixels.get();
269 let height = height_in_pixels.get();
270
271 let render_state = self.render_state.as_ref().unwrap();
272 let surface_state = self.surfaces.get_mut(&viewport_id).unwrap();
273
274 surface_state.width = width;
275 surface_state.height = height;
276
277 Self::configure_surface(surface_state, render_state, &self.configuration);
278
279 if let Some(depth_format) = self.options.depth_stencil_format {
280 self.depth_texture_view.insert(
281 viewport_id,
282 render_state
283 .device
284 .create_texture(&wgpu::TextureDescriptor {
285 label: Some("egui_depth_texture"),
286 size: wgpu::Extent3d {
287 width,
288 height,
289 depth_or_array_layers: 1,
290 },
291 mip_level_count: 1,
292 sample_count: self.options.msaa_samples.max(1),
293 dimension: wgpu::TextureDimension::D2,
294 format: depth_format,
295 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
296 | wgpu::TextureUsages::TEXTURE_BINDING,
297 view_formats: &[depth_format],
298 })
299 .create_view(&wgpu::TextureViewDescriptor::default()),
300 );
301 }
302
303 if let Some(render_state) = (self.options.msaa_samples > 1)
304 .then_some(self.render_state.as_ref())
305 .flatten()
306 {
307 let texture_format = render_state.target_format;
308 self.msaa_texture_view.insert(
309 viewport_id,
310 render_state
311 .device
312 .create_texture(&wgpu::TextureDescriptor {
313 label: Some("egui_msaa_texture"),
314 size: wgpu::Extent3d {
315 width,
316 height,
317 depth_or_array_layers: 1,
318 },
319 mip_level_count: 1,
320 sample_count: self.options.msaa_samples.max(1),
321 dimension: wgpu::TextureDimension::D2,
322 format: texture_format,
323 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
324 view_formats: &[texture_format],
325 })
326 .create_view(&wgpu::TextureViewDescriptor::default()),
327 );
328 }
329 }
330
331 pub fn on_window_resize_state_change(&mut self, viewport_id: ViewportId, resizing: bool) {
336 profiling::function_scope!();
337
338 let Some(state) = self.surfaces.get_mut(&viewport_id) else {
339 return;
340 };
341 if state.resizing == resizing {
342 if resizing {
343 log::debug!(
344 "Painter::on_window_resize_state_change() redundant call while resizing"
345 );
346 } else {
347 log::debug!(
348 "Painter::on_window_resize_state_change() redundant call after resizing"
349 );
350 }
351 return;
352 }
353
354 #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))]
361 {
362 unsafe {
366 if let Some(hal_surface) = state.surface.as_hal::<wgpu::hal::api::Metal>() {
367 let raw =
368 std::ptr::from_ref::<wgpu::hal::metal::Surface>(&*hal_surface).cast_mut();
369
370 (*raw).present_with_transaction = resizing;
371
372 Self::configure_surface(
373 state,
374 self.render_state.as_ref().unwrap(),
375 &self.configuration,
376 );
377 }
378 }
379 }
380
381 state.resizing = resizing;
382 }
383
384 pub fn on_window_resized(
385 &mut self,
386 viewport_id: ViewportId,
387 width_in_pixels: NonZeroU32,
388 height_in_pixels: NonZeroU32,
389 ) {
390 profiling::function_scope!();
391
392 if self.surfaces.contains_key(&viewport_id) {
393 self.resize_and_generate_depth_texture_view_and_msaa_view(
394 viewport_id,
395 width_in_pixels,
396 height_in_pixels,
397 );
398 } else {
399 log::warn!(
400 "Ignoring window resize notification with no surface created via Painter::set_window()"
401 );
402 }
403 }
404
405 pub fn paint_and_update_textures(
412 &mut self,
413 viewport_id: ViewportId,
414 pixels_per_point: f32,
415 clear_color: [f32; 4],
416 clipped_primitives: &[epaint::ClippedPrimitive],
417 textures_delta: &epaint::textures::TexturesDelta,
418 capture_data: Vec<UserData>,
419 ) -> f32 {
420 profiling::function_scope!();
421
422 let capture = !capture_data.is_empty();
423 let mut vsync_sec = 0.0;
424
425 let Some(render_state) = self.render_state.as_mut() else {
426 return vsync_sec;
427 };
428 let Some(surface_state) = self.surfaces.get(&viewport_id) else {
429 return vsync_sec;
430 };
431
432 let mut encoder =
433 render_state
434 .device
435 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
436 label: Some("encoder"),
437 });
438
439 let screen_descriptor = renderer::ScreenDescriptor {
441 size_in_pixels: [surface_state.width, surface_state.height],
442 pixels_per_point,
443 };
444
445 let user_cmd_bufs = {
446 let mut renderer = render_state.renderer.write();
447 for (id, image_delta) in &textures_delta.set {
448 renderer.update_texture(
449 &render_state.device,
450 &render_state.queue,
451 *id,
452 image_delta,
453 );
454 }
455
456 renderer.update_buffers(
457 &render_state.device,
458 &render_state.queue,
459 &mut encoder,
460 clipped_primitives,
461 &screen_descriptor,
462 )
463 };
464
465 let output_frame = {
466 profiling::scope!("get_current_texture");
467 let start = web_time::Instant::now();
469 let output_frame = surface_state.surface.get_current_texture();
470 vsync_sec += start.elapsed().as_secs_f32();
471 output_frame
472 };
473
474 let output_frame = match output_frame {
475 Ok(frame) => frame,
476 Err(err) => match (*self.configuration.on_surface_error)(err) {
477 SurfaceErrorAction::RecreateSurface => {
478 Self::configure_surface(surface_state, render_state, &self.configuration);
479 return vsync_sec;
480 }
481 SurfaceErrorAction::SkipFrame => {
482 return vsync_sec;
483 }
484 },
485 };
486
487 let mut capture_buffer = None;
488 {
489 let renderer = render_state.renderer.read();
490
491 let target_texture = if capture {
492 let capture_state = self.screen_capture_state.get_or_insert_with(|| {
493 CaptureState::new(&render_state.device, &output_frame.texture)
494 });
495 capture_state.update(&render_state.device, &output_frame.texture);
496
497 &capture_state.texture
498 } else {
499 &output_frame.texture
500 };
501 let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
502
503 let (view, resolve_target) = (self.options.msaa_samples > 1)
504 .then_some(self.msaa_texture_view.get(&viewport_id))
505 .flatten()
506 .map_or((&target_view, None), |texture_view| {
507 (texture_view, Some(&target_view))
508 });
509
510 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
511 label: Some("egui_render"),
512 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
513 view,
514 resolve_target,
515 ops: wgpu::Operations {
516 load: wgpu::LoadOp::Clear(wgpu::Color {
517 r: clear_color[0] as f64,
518 g: clear_color[1] as f64,
519 b: clear_color[2] as f64,
520 a: clear_color[3] as f64,
521 }),
522 store: wgpu::StoreOp::Store,
523 },
524 depth_slice: None,
525 })],
526 depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
527 wgpu::RenderPassDepthStencilAttachment {
528 view,
529 depth_ops: Some(wgpu::Operations {
530 load: wgpu::LoadOp::Clear(1.0),
531 store: wgpu::StoreOp::Discard,
534 }),
535 stencil_ops: None,
536 }
537 }),
538 timestamp_writes: None,
539 occlusion_query_set: None,
540 });
541
542 renderer.render(
546 &mut render_pass.forget_lifetime(),
547 clipped_primitives,
548 &screen_descriptor,
549 );
550
551 if capture && let Some(capture_state) = &mut self.screen_capture_state {
552 capture_buffer = Some(capture_state.copy_textures(
553 &render_state.device,
554 &output_frame,
555 &mut encoder,
556 ));
557 }
558 }
559
560 let encoded = {
561 profiling::scope!("CommandEncoder::finish");
562 encoder.finish()
563 };
564
565 {
567 profiling::scope!("Queue::submit");
568 let start = web_time::Instant::now();
570 render_state
571 .queue
572 .submit(user_cmd_bufs.into_iter().chain([encoded]));
573 vsync_sec += start.elapsed().as_secs_f32();
574 };
575
576 {
580 let mut renderer = render_state.renderer.write();
581 for id in &textures_delta.free {
582 renderer.free_texture(id);
583 }
584 }
585
586 if let Some(capture_buffer) = capture_buffer
587 && let Some(screen_capture_state) = &mut self.screen_capture_state
588 {
589 screen_capture_state.read_screen_rgba(
590 self.context.clone(),
591 capture_buffer,
592 capture_data,
593 self.capture_tx.clone(),
594 viewport_id,
595 );
596 }
597
598 {
599 profiling::scope!("present");
600 let start = web_time::Instant::now();
602 output_frame.present();
603 vsync_sec += start.elapsed().as_secs_f32();
604 }
605
606 vsync_sec
607 }
608
609 pub fn handle_screenshots(&self, events: &mut Vec<Event>) {
611 for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
612 let screenshot = Arc::new(screenshot);
613 for data in user_data {
614 events.push(Event::Screenshot {
615 viewport_id,
616 user_data: data,
617 image: screenshot.clone(),
618 });
619 }
620 }
621 }
622
623 pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
624 self.surfaces.retain(|id, _| active_viewports.contains(id));
625 self.depth_texture_view
626 .retain(|id, _| active_viewports.contains(id));
627 self.msaa_texture_view
628 .retain(|id, _| active_viewports.contains(id));
629 }
630
631 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
632 pub fn destroy(&mut self) {
633 }
635}