1#![expect(clippy::missing_errors_doc)]
2#![expect(clippy::undocumented_unsafe_blocks)]
3#![expect(clippy::unwrap_used)] #![expect(unsafe_code)]
5
6use crate::{RenderState, 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
22 needs_recreate: bool,
25
26 window_for_surface_recreation: Option<Arc<winit::window::Window>>,
32}
33
34pub struct Painter {
40 context: Context,
41 configuration: WgpuConfiguration,
42 options: RendererOptions,
43 support_transparent_backbuffer: bool,
44 screen_capture_state: Option<CaptureState>,
45
46 instance: wgpu::Instance,
47 render_state: Option<RenderState>,
48
49 depth_texture_view: ViewportIdMap<wgpu::TextureView>,
51 msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
52 surfaces: ViewportIdMap<SurfaceState>,
53 capture_tx: CaptureSender,
54 capture_rx: CaptureReceiver,
55}
56
57impl Painter {
58 pub async fn new(
71 context: Context,
72 configuration: WgpuConfiguration,
73 support_transparent_backbuffer: bool,
74 options: RendererOptions,
75 ) -> Self {
76 let (capture_tx, capture_rx) = capture_channel();
77 let instance = configuration.wgpu_setup.new_instance().await;
78
79 Self {
80 context,
81 configuration,
82 options,
83 support_transparent_backbuffer,
84 screen_capture_state: None,
85
86 instance,
87 render_state: None,
88
89 depth_texture_view: Default::default(),
90 surfaces: Default::default(),
91 msaa_texture_view: Default::default(),
92
93 capture_tx,
94 capture_rx,
95 }
96 }
97
98 pub fn render_state(&self) -> Option<RenderState> {
102 self.render_state.clone()
103 }
104
105 fn configure_surface(
106 surface_state: &SurfaceState,
107 render_state: &RenderState,
108 config: &WgpuConfiguration,
109 ) {
110 profiling::function_scope!();
111
112 let width = surface_state.width;
113 let height = surface_state.height;
114
115 let mut surf_config = wgpu::SurfaceConfiguration {
116 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
117 format: render_state.target_format,
118 present_mode: config.present_mode,
119 alpha_mode: surface_state.alpha_mode,
120 view_formats: vec![render_state.target_format],
121 ..surface_state
122 .surface
123 .get_default_config(&render_state.adapter, width, height)
124 .expect("The surface isn't supported by this adapter")
125 };
126
127 if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency {
128 surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
129 }
130
131 surface_state
132 .surface
133 .configure(&render_state.device, &surf_config);
134 }
135
136 pub async fn set_window(
158 &mut self,
159 viewport_id: ViewportId,
160 window: Option<Arc<winit::window::Window>>,
161 ) -> Result<(), crate::WgpuError> {
162 profiling::scope!("Painter::set_window"); if let Some(window) = window {
165 let size = window.inner_size();
166 if !self.surfaces.contains_key(&viewport_id) {
167 let surface = self.instance.create_surface(Arc::clone(&window))?;
168 self.add_surface(surface, viewport_id, size, Some(window))
169 .await?;
170 }
171 } else {
172 log::warn!("No window - clearing all surfaces");
173 self.surfaces.clear();
174 }
175 Ok(())
176 }
177
178 pub async unsafe fn set_window_unsafe(
185 &mut self,
186 viewport_id: ViewportId,
187 window: Option<&winit::window::Window>,
188 ) -> Result<(), crate::WgpuError> {
189 profiling::scope!("Painter::set_window_unsafe"); if let Some(window) = window {
192 let size = window.inner_size();
193 if !self.surfaces.contains_key(&viewport_id) {
194 let surface = unsafe {
195 self.instance
196 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)?
197 };
198 self.add_surface(surface, viewport_id, size, None).await?;
199 }
200 } else {
201 log::warn!("No window - clearing all surfaces");
202 self.surfaces.clear();
203 }
204 Ok(())
205 }
206
207 async fn add_surface(
208 &mut self,
209 surface: wgpu::Surface<'static>,
210 viewport_id: ViewportId,
211 size: winit::dpi::PhysicalSize<u32>,
212 window_for_surface_recreation: Option<Arc<winit::window::Window>>,
213 ) -> Result<(), crate::WgpuError> {
214 if self.render_state.is_none() {
215 let render_state = RenderState::create(
216 &self.configuration,
217 &self.instance,
218 Some(&surface),
219 self.options,
220 )
221 .await?;
222 self.render_state = Some(render_state);
223 }
224 self.install_surface(
225 surface,
226 viewport_id,
227 size.width,
228 size.height,
229 false,
230 window_for_surface_recreation,
231 );
232 Ok(())
233 }
234
235 fn install_surface(
239 &mut self,
240 surface: wgpu::Surface<'static>,
241 viewport_id: ViewportId,
242 width: u32,
243 height: u32,
244 resizing: bool,
245 window_for_surface_recreation: Option<Arc<winit::window::Window>>,
246 ) {
247 let alpha_mode = {
248 let render_state = self
249 .render_state
250 .as_ref()
251 .expect("install_surface called before render_state initialization");
252 if self.support_transparent_backbuffer {
253 let supported_alpha_modes =
254 surface.get_capabilities(&render_state.adapter).alpha_modes;
255
256 if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
258 wgpu::CompositeAlphaMode::PreMultiplied
259 } else if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
260 {
261 wgpu::CompositeAlphaMode::PostMultiplied
262 } else {
263 log::warn!(
264 "Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency."
265 );
266 wgpu::CompositeAlphaMode::Auto
267 }
268 } else {
269 wgpu::CompositeAlphaMode::Auto
270 }
271 };
272 self.surfaces.insert(
273 viewport_id,
274 SurfaceState {
275 surface,
276 width,
277 height,
278 alpha_mode,
279 resizing,
280 needs_reconfigure: false,
281 needs_recreate: false,
282 window_for_surface_recreation,
283 },
284 );
285 let Some(width) = NonZeroU32::new(width) else {
286 log::debug!("The window width was zero; skipping generate textures");
287 return;
288 };
289 let Some(height) = NonZeroU32::new(height) else {
290 log::debug!("The window height was zero; skipping generate textures");
291 return;
292 };
293 self.resize_and_generate_depth_texture_view_and_msaa_view(viewport_id, width, height);
294 }
295
296 fn recreate_surface(&mut self, viewport_id: ViewportId) -> Result<(), crate::WgpuError> {
303 profiling::function_scope!();
304
305 let Some(old_state) = self.surfaces.get(&viewport_id) else {
306 return Ok(());
307 };
308 let Some(window) = old_state.window_for_surface_recreation.clone() else {
309 return Ok(());
311 };
312 let width = old_state.width;
313 let height = old_state.height;
314 let resizing = old_state.resizing;
315
316 self.surfaces.remove(&viewport_id);
318
319 let surface = self.instance.create_surface(Arc::clone(&window))?;
320 self.install_surface(surface, viewport_id, width, height, resizing, Some(window));
321 Ok(())
322 }
323
324 pub fn max_texture_side(&self) -> Option<usize> {
330 self.render_state
331 .as_ref()
332 .map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
333 }
334
335 fn resize_and_generate_depth_texture_view_and_msaa_view(
336 &mut self,
337 viewport_id: ViewportId,
338 width_in_pixels: NonZeroU32,
339 height_in_pixels: NonZeroU32,
340 ) {
341 profiling::function_scope!();
342
343 let width = width_in_pixels.get();
344 let height = height_in_pixels.get();
345
346 let render_state = self.render_state.as_ref().unwrap();
347 let surface_state = self.surfaces.get_mut(&viewport_id).unwrap();
348
349 surface_state.width = width;
350 surface_state.height = height;
351
352 Self::configure_surface(surface_state, render_state, &self.configuration);
353
354 if let Some(depth_format) = self.options.depth_stencil_format {
355 self.depth_texture_view.insert(
356 viewport_id,
357 render_state
358 .device
359 .create_texture(&wgpu::TextureDescriptor {
360 label: Some("egui_depth_texture"),
361 size: wgpu::Extent3d {
362 width,
363 height,
364 depth_or_array_layers: 1,
365 },
366 mip_level_count: 1,
367 sample_count: self.options.msaa_samples.max(1),
368 dimension: wgpu::TextureDimension::D2,
369 format: depth_format,
370 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
371 | wgpu::TextureUsages::TEXTURE_BINDING,
372 view_formats: &[depth_format],
373 })
374 .create_view(&wgpu::TextureViewDescriptor::default()),
375 );
376 }
377
378 if let Some(render_state) = (self.options.msaa_samples > 1)
379 .then_some(self.render_state.as_ref())
380 .flatten()
381 {
382 let texture_format = render_state.target_format;
383 self.msaa_texture_view.insert(
384 viewport_id,
385 render_state
386 .device
387 .create_texture(&wgpu::TextureDescriptor {
388 label: Some("egui_msaa_texture"),
389 size: wgpu::Extent3d {
390 width,
391 height,
392 depth_or_array_layers: 1,
393 },
394 mip_level_count: 1,
395 sample_count: self.options.msaa_samples.max(1),
396 dimension: wgpu::TextureDimension::D2,
397 format: texture_format,
398 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
399 view_formats: &[texture_format],
400 })
401 .create_view(&wgpu::TextureViewDescriptor::default()),
402 );
403 }
404 }
405
406 pub fn on_window_resize_state_change(&mut self, viewport_id: ViewportId, resizing: bool) {
411 profiling::function_scope!();
412
413 let Some(state) = self.surfaces.get_mut(&viewport_id) else {
414 return;
415 };
416 if state.resizing == resizing {
417 if resizing {
418 log::debug!(
419 "Painter::on_window_resize_state_change() redundant call while resizing"
420 );
421 } else {
422 log::debug!(
423 "Painter::on_window_resize_state_change() redundant call after resizing"
424 );
425 }
426 return;
427 }
428
429 #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))]
436 {
437 unsafe {
440 if let Some(hal_surface) = state.surface.as_hal::<wgpu::hal::api::Metal>() {
441 hal_surface
442 .render_layer()
443 .lock()
444 .setPresentsWithTransaction(resizing);
445
446 Self::configure_surface(
447 state,
448 self.render_state.as_ref().unwrap(),
449 &self.configuration,
450 );
451 }
452 }
453 }
454
455 state.resizing = resizing;
456 }
457
458 pub fn on_window_resized(
459 &mut self,
460 viewport_id: ViewportId,
461 width_in_pixels: NonZeroU32,
462 height_in_pixels: NonZeroU32,
463 ) {
464 profiling::function_scope!();
465
466 if self.surfaces.contains_key(&viewport_id) {
467 self.resize_and_generate_depth_texture_view_and_msaa_view(
468 viewport_id,
469 width_in_pixels,
470 height_in_pixels,
471 );
472 } else {
473 log::warn!(
474 "Ignoring window resize notification with no surface created via Painter::set_window()"
475 );
476 }
477 }
478
479 pub fn paint_and_update_textures(
486 &mut self,
487 viewport_id: ViewportId,
488 pixels_per_point: f32,
489 clear_color: [f32; 4],
490 clipped_primitives: &[epaint::ClippedPrimitive],
491 textures_delta: &epaint::textures::TexturesDelta,
492 capture_data: Vec<UserData>,
493 ) -> f32 {
494 profiling::function_scope!();
495
496 struct RendererQueueGuard<'q> {
504 queue: &'q wgpu::Queue,
505 commands_submitted: bool,
506 }
507
508 impl Drop for RendererQueueGuard<'_> {
509 fn drop(&mut self) {
510 if !self.commands_submitted {
513 self.queue.submit([]);
514 }
515 }
516 }
517
518 let capture = !capture_data.is_empty();
519 let mut vsync_sec = 0.0;
520
521 if self
525 .surfaces
526 .get(&viewport_id)
527 .is_some_and(|s| s.needs_recreate)
528 && let Err(err) = self.recreate_surface(viewport_id)
529 {
530 log::error!("Failed to recreate surface for {viewport_id:?}: {err}");
531 return vsync_sec;
532 }
533
534 let Some(render_state) = self.render_state.as_mut() else {
535 return vsync_sec;
536 };
537
538 let mut render_queue_guard = RendererQueueGuard {
539 queue: &render_state.queue,
540 commands_submitted: false,
541 };
542
543 let Some(surface_state) = self.surfaces.get_mut(&viewport_id) else {
544 return vsync_sec;
545 };
546
547 let mut encoder =
548 render_state
549 .device
550 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
551 label: Some("encoder"),
552 });
553
554 let screen_descriptor = renderer::ScreenDescriptor {
556 size_in_pixels: [surface_state.width, surface_state.height],
557 pixels_per_point,
558 };
559
560 let user_cmd_bufs = {
561 let mut renderer = render_state.renderer.write();
562 for (id, image_delta) in &textures_delta.set {
563 renderer.update_texture(
564 &render_state.device,
565 &render_state.queue,
566 *id,
567 image_delta,
568 );
569 }
570
571 renderer.update_buffers(
572 &render_state.device,
573 &render_state.queue,
574 &mut encoder,
575 clipped_primitives,
576 &screen_descriptor,
577 )
578 };
579
580 if surface_state.needs_reconfigure {
581 Self::configure_surface(surface_state, render_state, &self.configuration);
582 surface_state.needs_reconfigure = false;
583 }
584
585 let output_frame = {
586 profiling::scope!("get_current_texture");
587 let start = web_time::Instant::now();
589 let output_frame = surface_state.surface.get_current_texture();
590 vsync_sec += start.elapsed().as_secs_f32();
591 output_frame
592 };
593
594 let output_frame = match output_frame {
595 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
596 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
597 surface_state.needs_reconfigure = true;
598 frame
599 }
600 other => {
601 match (*self.configuration.on_surface_status)(&other) {
602 SurfaceErrorAction::RecreateSurface => {
603 if matches!(other, wgpu::CurrentSurfaceTexture::Lost) {
604 surface_state.needs_recreate = true;
609 } else {
610 Self::configure_surface(
613 surface_state,
614 render_state,
615 &self.configuration,
616 );
617 }
618 self.context.request_repaint_of(viewport_id);
619 }
620 SurfaceErrorAction::SkipFrame => {}
621 }
622 return vsync_sec;
623 }
624 };
625
626 let mut capture_buffer = None;
627 {
628 let renderer = render_state.renderer.read();
629
630 let target_texture = if capture {
631 let capture_state = self.screen_capture_state.get_or_insert_with(|| {
632 CaptureState::new(&render_state.device, &output_frame.texture)
633 });
634 capture_state.update(&render_state.device, &output_frame.texture);
635
636 &capture_state.texture
637 } else {
638 &output_frame.texture
639 };
640 let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
641
642 let (view, resolve_target) = (self.options.msaa_samples > 1)
643 .then_some(self.msaa_texture_view.get(&viewport_id))
644 .flatten()
645 .map_or((&target_view, None), |texture_view| {
646 (texture_view, Some(&target_view))
647 });
648
649 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
650 label: Some("egui_render"),
651 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
652 view,
653 resolve_target,
654 ops: wgpu::Operations {
655 load: wgpu::LoadOp::Clear(wgpu::Color {
656 r: clear_color[0] as f64,
657 g: clear_color[1] as f64,
658 b: clear_color[2] as f64,
659 a: clear_color[3] as f64,
660 }),
661 store: wgpu::StoreOp::Store,
662 },
663 depth_slice: None,
664 })],
665 depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
666 wgpu::RenderPassDepthStencilAttachment {
667 view,
668 depth_ops: self
669 .options
670 .depth_stencil_format
671 .is_some_and(|depth_stencil_format| {
672 depth_stencil_format.has_depth_aspect()
673 })
674 .then_some(wgpu::Operations {
675 load: wgpu::LoadOp::Clear(1.0),
676 store: wgpu::StoreOp::Discard,
679 }),
680 stencil_ops: self
681 .options
682 .depth_stencil_format
683 .is_some_and(|depth_stencil_format| {
684 depth_stencil_format.has_stencil_aspect()
685 })
686 .then_some(wgpu::Operations {
687 load: wgpu::LoadOp::Clear(0),
688 store: wgpu::StoreOp::Discard,
689 }),
690 }
691 }),
692 timestamp_writes: None,
693 occlusion_query_set: None,
694 multiview_mask: None,
695 });
696
697 renderer.render(
701 &mut render_pass.forget_lifetime(),
702 clipped_primitives,
703 &screen_descriptor,
704 );
705
706 if capture && let Some(capture_state) = &mut self.screen_capture_state {
707 capture_buffer = Some(capture_state.copy_textures(
708 &render_state.device,
709 &output_frame,
710 &mut encoder,
711 ));
712 }
713 }
714
715 let encoded = {
716 profiling::scope!("CommandEncoder::finish");
717 encoder.finish()
718 };
719
720 {
722 profiling::scope!("Queue::submit");
723 let start = web_time::Instant::now();
725 render_state
726 .queue
727 .submit(user_cmd_bufs.into_iter().chain([encoded]));
728 vsync_sec += start.elapsed().as_secs_f32();
729 };
730
731 render_queue_guard.commands_submitted = true;
733
734 {
738 let mut renderer = render_state.renderer.write();
739 for id in &textures_delta.free {
740 renderer.free_texture(id);
741 }
742 }
743
744 if let Some(capture_buffer) = capture_buffer
745 && let Some(screen_capture_state) = &mut self.screen_capture_state
746 {
747 screen_capture_state.read_screen_rgba(
748 self.context.clone(),
749 capture_buffer,
750 capture_data,
751 self.capture_tx.clone(),
752 viewport_id,
753 );
754 }
755
756 {
757 profiling::scope!("present");
758 let start = web_time::Instant::now();
760 output_frame.present();
761 vsync_sec += start.elapsed().as_secs_f32();
762 }
763
764 vsync_sec
765 }
766
767 pub fn handle_screenshots(&self, events: &mut Vec<Event>) {
769 for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
770 let screenshot = Arc::new(screenshot);
771 for data in user_data {
772 events.push(Event::Screenshot {
773 viewport_id,
774 user_data: data,
775 image: Arc::clone(&screenshot),
776 });
777 }
778 }
779 }
780
781 pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
782 self.surfaces.retain(|id, _| active_viewports.contains(id));
783 self.depth_texture_view
784 .retain(|id, _| active_viewports.contains(id));
785 self.msaa_texture_view
786 .retain(|id, _| active_viewports.contains(id));
787 }
788
789 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
790 pub fn destroy(&mut self) {
791 }
793}