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