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