1#![expect(clippy::missing_errors_doc)]
2#![expect(clippy::undocumented_unsafe_blocks)]
3#![expect(clippy::unwrap_used)] #![expect(unsafe_code)]
5
6use crate::{RenderState, SurfaceConfig, SurfaceErrorAction, WgpuConfiguration, renderer};
7use crate::{
8 RendererOptions,
9 capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
10};
11use core::num::NonZeroU32;
12use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
13use std::sync::Arc;
14
15struct SurfaceState {
16 surface: wgpu::Surface<'static>,
17 alpha_mode: wgpu::CompositeAlphaMode,
18 width: u32,
19 height: u32,
20 resizing: bool,
21 needs_reconfigure: bool,
22 needs_recreate: bool,
23}
24
25pub struct Painter {
31 context: Context,
32 config: WgpuConfiguration,
33 options: RendererOptions,
34 support_transparent_backbuffer: bool,
35 screen_capture_state: Option<CaptureState>,
36
37 instance: wgpu::Instance,
38 render_state: Option<RenderState>,
39
40 depth_texture_view: ViewportIdMap<wgpu::TextureView>,
42 msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
43 surfaces: ViewportIdMap<SurfaceState>,
44 capture_tx: CaptureSender,
45 capture_rx: CaptureReceiver,
46}
47
48impl Painter {
49 pub async fn new(
62 context: Context,
63 config: WgpuConfiguration,
64 support_transparent_backbuffer: bool,
65 options: RendererOptions,
66 ) -> Self {
67 let (capture_tx, capture_rx) = capture_channel();
68 let instance = config.wgpu_setup.new_instance().await;
69
70 Self {
71 context,
72 config,
73 options,
74 support_transparent_backbuffer,
75 screen_capture_state: None,
76
77 instance,
78 render_state: None,
79
80 depth_texture_view: Default::default(),
81 surfaces: Default::default(),
82 msaa_texture_view: Default::default(),
83
84 capture_tx,
85 capture_rx,
86 }
87 }
88
89 pub fn render_state(&self) -> Option<RenderState> {
93 self.render_state.clone()
94 }
95
96 fn configure_surface(
97 surface_state: &SurfaceState,
98 render_state: &RenderState,
99 config: &SurfaceConfig,
100 ) {
101 profiling::function_scope!();
102
103 let SurfaceConfig {
104 present_mode,
105 desired_maximum_frame_latency,
106 } = *config;
107
108 #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))]
111 let desired_maximum_frame_latency = if surface_state.resizing {
112 Some(desired_maximum_frame_latency.unwrap_or(2).max(2))
113 } else {
114 desired_maximum_frame_latency
115 };
116
117 let width = surface_state.width;
118 let height = surface_state.height;
119
120 let mut surf_config = wgpu::SurfaceConfiguration {
121 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
122 format: render_state.target_format,
123 present_mode,
124 alpha_mode: surface_state.alpha_mode,
125 view_formats: vec![render_state.target_format],
126 ..surface_state
127 .surface
128 .get_default_config(&render_state.adapter, width, height)
129 .expect("The surface isn't supported by this adapter")
130 };
131
132 if let Some(desired_maximum_frame_latency) = desired_maximum_frame_latency {
133 surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
134 }
135
136 surface_state
137 .surface
138 .configure(&render_state.device, &surf_config);
139 }
140
141 fn recreate_surface(
147 &mut self,
148 viewport_id: ViewportId,
149 window: &Arc<winit::window::Window>,
150 ) -> Result<(), crate::WgpuError> {
151 profiling::function_scope!();
152
153 let Some(old_state) = self.surfaces.remove(&viewport_id) else {
154 return Ok(());
155 };
156
157 let surface = self.instance.create_surface(Arc::clone(window))?;
158 self.install_surface(
159 surface,
160 viewport_id,
161 old_state.width,
162 old_state.height,
163 old_state.resizing,
164 );
165 Ok(())
166 }
167
168 pub async fn set_window(
190 &mut self,
191 viewport_id: ViewportId,
192 window: Option<Arc<winit::window::Window>>,
193 ) -> Result<(), crate::WgpuError> {
194 profiling::scope!("Painter::set_window"); if let Some(window) = window {
197 let size = window.inner_size();
198 if !self.surfaces.contains_key(&viewport_id) {
199 let surface = self.instance.create_surface(window)?;
200 self.add_surface(surface, viewport_id, size).await?;
201 }
202 } else {
203 log::warn!("No window - clearing all surfaces");
204 self.surfaces.clear();
205 }
206 Ok(())
207 }
208
209 pub async unsafe fn set_window_unsafe(
216 &mut self,
217 viewport_id: ViewportId,
218 window: Option<&winit::window::Window>,
219 ) -> Result<(), crate::WgpuError> {
220 profiling::scope!("Painter::set_window_unsafe"); if let Some(window) = window {
223 let size = window.inner_size();
224 if !self.surfaces.contains_key(&viewport_id) {
225 let surface = unsafe {
226 self.instance
227 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)?
228 };
229 self.add_surface(surface, viewport_id, size).await?;
230 }
231 } else {
232 log::warn!("No window - clearing all surfaces");
233 self.surfaces.clear();
234 }
235 Ok(())
236 }
237
238 async fn add_surface(
239 &mut self,
240 surface: wgpu::Surface<'static>,
241 viewport_id: ViewportId,
242 size: winit::dpi::PhysicalSize<u32>,
243 ) -> Result<(), crate::WgpuError> {
244 if self.render_state.is_none() {
245 let render_state =
246 RenderState::create(&self.config, &self.instance, Some(&surface), self.options)
247 .await?;
248 self.render_state = Some(render_state);
249 }
250 self.install_surface(surface, viewport_id, size.width, size.height, false);
251 Ok(())
252 }
253
254 fn install_surface(
259 &mut self,
260 surface: wgpu::Surface<'static>,
261 viewport_id: ViewportId,
262 width: u32,
263 height: u32,
264 resizing: bool,
265 ) {
266 let alpha_mode = {
267 let render_state = self
269 .render_state
270 .as_ref()
271 .expect("install_surface called before render_state initialization");
272 if self.support_transparent_backbuffer {
273 let supported_alpha_modes =
274 surface.get_capabilities(&render_state.adapter).alpha_modes;
275 if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
277 wgpu::CompositeAlphaMode::PreMultiplied
278 } else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
279 {
280 wgpu::CompositeAlphaMode::PostMultiplied
281 } else {
282 log::warn!(
283 "Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."
284 );
285 wgpu::CompositeAlphaMode::Auto
286 }
287 } else {
288 wgpu::CompositeAlphaMode::Auto
289 }
290 };
291 self.surfaces.insert(
292 viewport_id,
293 SurfaceState {
294 surface,
295 width,
296 height,
297 alpha_mode,
298 resizing,
299 needs_reconfigure: false,
300 needs_recreate: false,
301 },
302 );
303 let Some(width) = NonZeroU32::new(width) else {
304 log::debug!("The window width was zero; skipping generate textures");
305 return;
306 };
307 let Some(height) = NonZeroU32::new(height) else {
308 log::debug!("The window height was zero; skipping generate textures");
309 return;
310 };
311 self.resize_and_generate_depth_texture_view_and_msaa_view(viewport_id, width, height);
312 }
313
314 pub fn max_texture_side(&self) -> Option<usize> {
320 self.render_state
321 .as_ref()
322 .map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
323 }
324
325 fn resize_and_generate_depth_texture_view_and_msaa_view(
326 &mut self,
327 viewport_id: ViewportId,
328 width_in_pixels: NonZeroU32,
329 height_in_pixels: NonZeroU32,
330 ) {
331 profiling::function_scope!();
332
333 let width = width_in_pixels.get();
334 let height = height_in_pixels.get();
335
336 let render_state = self.render_state.as_ref().unwrap();
337 let surface_state = self.surfaces.get_mut(&viewport_id).unwrap();
338
339 surface_state.width = width;
340 surface_state.height = height;
341
342 Self::configure_surface(surface_state, render_state, &self.config.surface);
343
344 if let Some(depth_format) = self.options.depth_stencil_format {
345 self.depth_texture_view.insert(
346 viewport_id,
347 render_state
348 .device
349 .create_texture(&wgpu::TextureDescriptor {
350 label: Some("egui_depth_texture"),
351 size: wgpu::Extent3d {
352 width,
353 height,
354 depth_or_array_layers: 1,
355 },
356 mip_level_count: 1,
357 sample_count: self.options.msaa_samples.max(1),
358 dimension: wgpu::TextureDimension::D2,
359 format: depth_format,
360 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
361 | wgpu::TextureUsages::TEXTURE_BINDING,
362 view_formats: &[depth_format],
363 })
364 .create_view(&wgpu::TextureViewDescriptor::default()),
365 );
366 }
367
368 if let Some(render_state) = (self.options.msaa_samples > 1)
369 .then_some(self.render_state.as_ref())
370 .flatten()
371 {
372 let texture_format = render_state.target_format;
373 self.msaa_texture_view.insert(
374 viewport_id,
375 render_state
376 .device
377 .create_texture(&wgpu::TextureDescriptor {
378 label: Some("egui_msaa_texture"),
379 size: wgpu::Extent3d {
380 width,
381 height,
382 depth_or_array_layers: 1,
383 },
384 mip_level_count: 1,
385 sample_count: self.options.msaa_samples.max(1),
386 dimension: wgpu::TextureDimension::D2,
387 format: texture_format,
388 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
389 view_formats: &[texture_format],
390 })
391 .create_view(&wgpu::TextureViewDescriptor::default()),
392 );
393 }
394 }
395
396 pub fn on_window_resize_state_change(&mut self, viewport_id: ViewportId, resizing: bool) {
401 profiling::function_scope!();
402
403 let Some(state) = self.surfaces.get_mut(&viewport_id) else {
404 return;
405 };
406 if state.resizing == resizing {
407 if resizing {
408 log::debug!(
409 "Painter::on_window_resize_state_change() redundant call while resizing"
410 );
411 } else {
412 log::debug!(
413 "Painter::on_window_resize_state_change() redundant call after resizing"
414 );
415 }
416 return;
417 }
418
419 state.resizing = resizing;
421
422 #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))]
429 {
430 unsafe {
433 if let (Some(render_state), Some(hal_surface)) = (
434 self.render_state.as_ref(),
435 state.surface.as_hal::<wgpu::hal::api::Metal>(),
436 ) {
437 hal_surface
438 .render_layer()
439 .lock()
440 .setPresentsWithTransaction(resizing);
441
442 Self::configure_surface(state, render_state, &self.config.surface);
443 }
444 }
445 }
446 }
447
448 pub fn on_window_resized(
449 &mut self,
450 viewport_id: ViewportId,
451 width_in_pixels: NonZeroU32,
452 height_in_pixels: NonZeroU32,
453 ) {
454 profiling::function_scope!();
455
456 if self.surfaces.contains_key(&viewport_id) {
457 self.resize_and_generate_depth_texture_view_and_msaa_view(
458 viewport_id,
459 width_in_pixels,
460 height_in_pixels,
461 );
462 } else {
463 log::warn!(
464 "Ignoring window resize notification with no surface created via Painter::set_window()"
465 );
466 }
467 }
468
469 #[expect(clippy::too_many_arguments)]
476 pub fn paint_and_update_textures(
477 &mut self,
478 viewport_id: ViewportId,
479 pixels_per_point: f32,
480 clear_color: [f32; 4],
481 clipped_primitives: &[epaint::ClippedPrimitive],
482 textures_delta: &mut epaint::textures::TexturesDelta,
483 capture_data: Vec<UserData>,
484 window: &Arc<winit::window::Window>,
485 ) -> f32 {
486 profiling::function_scope!();
487
488 struct RendererQueueGuard<'q> {
496 queue: &'q wgpu::Queue,
497 commands_submitted: bool,
498 }
499
500 impl Drop for RendererQueueGuard<'_> {
501 fn drop(&mut self) {
502 if !self.commands_submitted {
505 self.queue.submit([]);
506 }
507 }
508 }
509
510 let capture = !capture_data.is_empty();
511 let mut vsync_sec = 0.0;
512
513 if self
517 .surfaces
518 .get(&viewport_id)
519 .is_some_and(|s| s.needs_recreate)
520 && let Err(err) = self.recreate_surface(viewport_id, window)
521 {
522 log::error!("Failed to recreate surface for {viewport_id:?}: {err}");
523 return vsync_sec;
524 }
525
526 if let Some(render_state) = self.render_state.as_ref()
531 && render_state.surface_config != self.config.surface
532 {
533 self.config.surface = render_state.surface_config;
534 #[expect(clippy::iter_over_hash_type)]
535 for surface in self.surfaces.values_mut() {
536 surface.needs_reconfigure = true;
537 }
538 }
539
540 let Some(render_state) = self.render_state.as_mut() else {
541 return vsync_sec;
542 };
543
544 let mut render_queue_guard = RendererQueueGuard {
545 queue: &render_state.queue,
546 commands_submitted: false,
547 };
548
549 let Some(surface_state) = self.surfaces.get_mut(&viewport_id) else {
550 return vsync_sec;
551 };
552
553 let mut encoder =
554 render_state
555 .device
556 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
557 label: Some("encoder"),
558 });
559
560 let screen_descriptor = renderer::ScreenDescriptor {
562 size_in_pixels: [surface_state.width, surface_state.height],
563 pixels_per_point,
564 };
565
566 let user_cmd_bufs = {
567 let mut renderer = render_state.renderer.write();
568 #[expect(clippy::iter_over_hash_type)] for (id, image_deltas) in textures_delta.set.drain() {
570 for image_delta in image_deltas {
571 renderer.update_texture(
572 &render_state.device,
573 &render_state.queue,
574 id,
575 &image_delta,
576 );
577 }
578 }
579
580 renderer.update_buffers(
581 &render_state.device,
582 &render_state.queue,
583 &mut encoder,
584 clipped_primitives,
585 &screen_descriptor,
586 )
587 };
588
589 if surface_state.needs_reconfigure {
590 Self::configure_surface(surface_state, render_state, &self.config.surface);
591 surface_state.needs_reconfigure = false;
592 }
593
594 let output_frame = {
595 profiling::scope!("get_current_texture");
596 let start = web_time::Instant::now();
598 let output_frame = surface_state.surface.get_current_texture();
599 vsync_sec += start.elapsed().as_secs_f32();
600 output_frame
601 };
602
603 let output_frame = match output_frame {
604 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
605 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
606 surface_state.needs_reconfigure = true;
607 frame
608 }
609 other => {
610 match (*self.config.on_surface_status)(&other) {
611 SurfaceErrorAction::Reconfigure => {
612 Self::configure_surface(surface_state, render_state, &self.config.surface);
613 self.context.request_repaint_of(viewport_id);
614 }
615 SurfaceErrorAction::RecreateSurface => {
616 surface_state.needs_recreate = true;
622 self.context.request_repaint_of(viewport_id);
623 }
624 SurfaceErrorAction::SkipFrame => {}
625 }
626 return vsync_sec;
627 }
628 };
629
630 let mut capture_buffer = None;
631 {
632 let renderer = render_state.renderer.read();
633
634 let target_texture = if capture {
635 let capture_state = self.screen_capture_state.get_or_insert_with(|| {
636 CaptureState::new(&render_state.device, &output_frame.texture)
637 });
638 capture_state.update(&render_state.device, &output_frame.texture);
639
640 &capture_state.texture
641 } else {
642 &output_frame.texture
643 };
644 let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
645
646 let (view, resolve_target) = (self.options.msaa_samples > 1)
647 .then_some(self.msaa_texture_view.get(&viewport_id))
648 .flatten()
649 .map_or((&target_view, None), |texture_view| {
650 (texture_view, Some(&target_view))
651 });
652
653 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
654 label: Some("egui_render"),
655 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
656 view,
657 resolve_target,
658 ops: wgpu::Operations {
659 load: wgpu::LoadOp::Clear(wgpu::Color {
660 r: clear_color[0] as f64,
661 g: clear_color[1] as f64,
662 b: clear_color[2] as f64,
663 a: clear_color[3] as f64,
664 }),
665 store: wgpu::StoreOp::Store,
666 },
667 depth_slice: None,
668 })],
669 depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
670 wgpu::RenderPassDepthStencilAttachment {
671 view,
672 depth_ops: self
673 .options
674 .depth_stencil_format
675 .is_some_and(|depth_stencil_format| {
676 depth_stencil_format.has_depth_aspect()
677 })
678 .then_some(wgpu::Operations {
679 load: wgpu::LoadOp::Clear(1.0),
680 store: wgpu::StoreOp::Discard,
683 }),
684 stencil_ops: self
685 .options
686 .depth_stencil_format
687 .is_some_and(|depth_stencil_format| {
688 depth_stencil_format.has_stencil_aspect()
689 })
690 .then_some(wgpu::Operations {
691 load: wgpu::LoadOp::Clear(0),
692 store: wgpu::StoreOp::Discard,
693 }),
694 }
695 }),
696 timestamp_writes: None,
697 occlusion_query_set: None,
698 multiview_mask: None,
699 });
700
701 renderer.render(
705 &mut render_pass.forget_lifetime(),
706 clipped_primitives,
707 &screen_descriptor,
708 );
709
710 if capture && let Some(capture_state) = &mut self.screen_capture_state {
711 capture_buffer = Some(capture_state.copy_textures(
712 &render_state.device,
713 &output_frame,
714 &mut encoder,
715 ));
716 }
717 }
718
719 let encoded = {
720 profiling::scope!("CommandEncoder::finish");
721 encoder.finish()
722 };
723
724 {
726 profiling::scope!("Queue::submit");
727 let start = web_time::Instant::now();
729 render_state
730 .queue
731 .submit(core::iter::chain(user_cmd_bufs, [encoded]));
732 vsync_sec += start.elapsed().as_secs_f32();
733 };
734
735 render_queue_guard.commands_submitted = true;
737
738 {
742 let mut renderer = render_state.renderer.write();
743 #[expect(clippy::iter_over_hash_type)] for id in textures_delta.free.drain() {
745 renderer.free_texture(&id);
746 }
747 }
748
749 if let Some(capture_buffer) = capture_buffer
750 && let Some(screen_capture_state) = &mut self.screen_capture_state
751 {
752 screen_capture_state.read_screen_rgba(
753 self.context.clone(),
754 capture_buffer,
755 capture_data,
756 self.capture_tx.clone(),
757 viewport_id,
758 );
759 }
760
761 window.pre_present_notify();
762
763 {
764 profiling::scope!("present");
765 let start = web_time::Instant::now();
767 render_state.queue.present(output_frame);
768 vsync_sec += start.elapsed().as_secs_f32();
769 }
770
771 vsync_sec
772 }
773
774 pub fn handle_screenshots(&self, events: &mut Vec<Event>) {
776 for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
777 let screenshot = Arc::new(screenshot);
778 for data in user_data {
779 events.push(Event::Screenshot {
780 viewport_id,
781 user_data: data,
782 image: Arc::clone(&screenshot),
783 });
784 }
785 }
786 }
787
788 pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
789 self.surfaces.retain(|id, _| active_viewports.contains(id));
790 self.depth_texture_view
791 .retain(|id, _| active_viewports.contains(id));
792 self.msaa_texture_view
793 .retain(|id, _| active_viewports.contains(id));
794 }
795
796 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
797 pub fn destroy(&mut self) {
798 }
800}