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