1use crate::renderer::WgpuWrapper;
2use crate::{
3 render_resource::{SurfaceTexture, TextureView},
4 renderer::{RenderAdapter, RenderDevice, RenderInstance},
5 Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
6};
7use bevy_app::{App, Plugin};
8use bevy_ecs::{entity::EntityHashMap, prelude::*};
9use bevy_platform::collections::HashSet;
10use bevy_utils::default;
11use bevy_window::{
12 CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,
13};
14use core::{
15 num::NonZero,
16 ops::{Deref, DerefMut},
17};
18use tracing::{debug, info, warn};
19use wgpu::{
20 SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor,
21};
22
23pub mod screenshot;
24
25use screenshot::ScreenshotPlugin;
26
27pub struct WindowRenderPlugin;
28
29impl Plugin for WindowRenderPlugin {
30 fn build(&self, app: &mut App) {
31 app.add_plugins(ScreenshotPlugin);
32
33 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
34 render_app
35 .init_resource::<ExtractedWindows>()
36 .init_resource::<WindowSurfaces>()
37 .add_systems(ExtractSchedule, extract_windows)
38 .add_systems(
39 Render,
40 create_surfaces
41 .run_if(need_surface_configuration)
42 .before(prepare_windows),
43 )
44 .add_systems(Render, prepare_windows.in_set(RenderSystems::ManageViews));
45 }
46 }
47}
48
49pub struct ExtractedWindow {
50 pub entity: Entity,
52 pub handle: RawHandleWrapper,
53 pub physical_width: u32,
54 pub physical_height: u32,
55 pub present_mode: PresentMode,
56 pub desired_maximum_frame_latency: Option<NonZero<u32>>,
57 pub swap_chain_texture_view: Option<TextureView>,
61 pub swap_chain_texture: Option<SurfaceTexture>,
62 pub swap_chain_texture_format: Option<TextureFormat>,
63 pub swap_chain_texture_view_format: Option<TextureFormat>,
64 pub size_changed: bool,
65 pub present_mode_changed: bool,
66 pub alpha_mode: CompositeAlphaMode,
67 pub needs_initial_present: bool,
72}
73
74impl ExtractedWindow {
75 fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) {
76 self.swap_chain_texture_view_format = Some(frame.texture.format().add_srgb_suffix());
77 let texture_view_descriptor = TextureViewDescriptor {
78 format: self.swap_chain_texture_view_format,
79 ..default()
80 };
81 self.swap_chain_texture_view = Some(TextureView::from(
82 frame.texture.create_view(&texture_view_descriptor),
83 ));
84 self.swap_chain_texture = Some(SurfaceTexture::from(frame));
85 }
86
87 fn has_swapchain_texture(&self) -> bool {
88 self.swap_chain_texture_view.is_some() && self.swap_chain_texture.is_some()
89 }
90
91 pub fn present(&mut self) {
92 if let Some(surface_texture) = self.swap_chain_texture.take() {
93 surface_texture.present();
98 }
99 }
100}
101
102#[derive(Default, Resource)]
103pub struct ExtractedWindows {
104 pub primary: Option<Entity>,
105 pub windows: EntityHashMap<ExtractedWindow>,
106}
107
108impl Deref for ExtractedWindows {
109 type Target = EntityHashMap<ExtractedWindow>;
110
111 fn deref(&self) -> &Self::Target {
112 &self.windows
113 }
114}
115
116impl DerefMut for ExtractedWindows {
117 fn deref_mut(&mut self) -> &mut Self::Target {
118 &mut self.windows
119 }
120}
121
122fn extract_windows(
123 mut extracted_windows: ResMut<ExtractedWindows>,
124 mut closing: Extract<MessageReader<WindowClosing>>,
125 windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
126 mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
127 mut window_surfaces: ResMut<WindowSurfaces>,
128) {
129 for (entity, window, handle, primary) in windows.iter() {
130 if primary.is_some() {
131 extracted_windows.primary = Some(entity);
132 }
133
134 let (new_width, new_height) = (
135 window.resolution.physical_width().max(1),
136 window.resolution.physical_height().max(1),
137 );
138
139 let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
140 entity,
141 handle: handle.clone(),
142 physical_width: new_width,
143 physical_height: new_height,
144 present_mode: window.present_mode,
145 desired_maximum_frame_latency: window.desired_maximum_frame_latency,
146 swap_chain_texture: None,
147 swap_chain_texture_view: None,
148 size_changed: false,
149 swap_chain_texture_format: None,
150 swap_chain_texture_view_format: None,
151 present_mode_changed: false,
152 alpha_mode: window.composite_alpha_mode,
153 needs_initial_present: true,
154 });
155
156 if extracted_window.swap_chain_texture.is_none() {
157 extracted_window.swap_chain_texture_view = None;
162 }
163 extracted_window.size_changed = new_width != extracted_window.physical_width
164 || new_height != extracted_window.physical_height;
165 extracted_window.present_mode_changed =
166 window.present_mode != extracted_window.present_mode;
167
168 if extracted_window.size_changed {
169 debug!(
170 "Window size changed from {}x{} to {}x{}",
171 extracted_window.physical_width,
172 extracted_window.physical_height,
173 new_width,
174 new_height
175 );
176 extracted_window.physical_width = new_width;
177 extracted_window.physical_height = new_height;
178 }
179
180 if extracted_window.present_mode_changed {
181 debug!(
182 "Window Present Mode changed from {:?} to {:?}",
183 extracted_window.present_mode, window.present_mode
184 );
185 extracted_window.present_mode = window.present_mode;
186 }
187 }
188
189 for closing_window in closing.read() {
190 extracted_windows.remove(&closing_window.window);
191 window_surfaces.remove(&closing_window.window);
192 }
193 for removed_window in removed.read() {
194 extracted_windows.remove(&removed_window);
195 window_surfaces.remove(&removed_window);
196 }
197}
198
199struct SurfaceData {
200 surface: WgpuWrapper<wgpu::Surface<'static>>,
202 configuration: SurfaceConfiguration,
203 texture_view_format: Option<TextureFormat>,
204}
205
206#[derive(Resource, Default)]
207pub struct WindowSurfaces {
208 surfaces: EntityHashMap<SurfaceData>,
209 configured_windows: HashSet<Entity>,
211}
212
213impl WindowSurfaces {
214 fn remove(&mut self, window: &Entity) {
215 self.surfaces.remove(window);
216 self.configured_windows.remove(window);
217 }
218}
219
220pub fn prepare_windows(
242 mut windows: ResMut<ExtractedWindows>,
243 mut window_surfaces: ResMut<WindowSurfaces>,
244 render_device: Res<RenderDevice>,
245 #[cfg(target_os = "linux")] render_instance: Res<RenderInstance>,
246) {
247 for window in windows.windows.values_mut() {
248 let window_surfaces = window_surfaces.deref_mut();
249 let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else {
250 continue;
251 };
252
253 if window.has_swapchain_texture() && !window.size_changed && !window.present_mode_changed {
255 continue;
256 }
257
258 #[cfg(target_os = "linux")]
266 let may_erroneously_timeout = || {
267 render_instance
268 .enumerate_adapters(wgpu::Backends::VULKAN)
269 .iter()
270 .any(|adapter| {
271 let name = adapter.get_info().name;
272 name.starts_with("Radeon")
273 || name.starts_with("AMD")
274 || name.starts_with("Intel")
275 })
276 };
277
278 let surface = &surface_data.surface;
279 match surface.get_current_texture() {
280 Ok(frame) => {
281 window.set_swapchain_texture(frame);
282 }
283 Err(wgpu::SurfaceError::Outdated) => {
284 render_device.configure_surface(surface, &surface_data.configuration);
285 let frame = match surface.get_current_texture() {
286 Ok(frame) => frame,
287 Err(err) => {
288 warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'");
291 continue;
292 }
293 };
294 window.set_swapchain_texture(frame);
295 }
296 #[cfg(target_os = "linux")]
297 Err(wgpu::SurfaceError::Timeout) if may_erroneously_timeout() => {
298 tracing::trace!(
299 "Couldn't get swap chain texture. This is probably a quirk \
300 of your Linux GPU driver, so it can be safely ignored."
301 );
302 }
303 Err(err) => {
304 panic!("Couldn't get swap chain texture, operation unrecoverable: {err}");
305 }
306 }
307 window.swap_chain_texture_format = Some(surface_data.configuration.format);
308 }
309}
310
311pub fn need_surface_configuration(
312 windows: Res<ExtractedWindows>,
313 window_surfaces: Res<WindowSurfaces>,
314) -> bool {
315 for window in windows.windows.values() {
316 if !window_surfaces.configured_windows.contains(&window.entity)
317 || window.size_changed
318 || window.present_mode_changed
319 {
320 return true;
321 }
322 }
323 false
324}
325
326const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2;
331
332pub fn create_surfaces(
334 #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker,
337 mut windows: ResMut<ExtractedWindows>,
338 mut window_surfaces: ResMut<WindowSurfaces>,
339 render_instance: Res<RenderInstance>,
340 render_adapter: Res<RenderAdapter>,
341 render_device: Res<RenderDevice>,
342) {
343 for window in windows.windows.values_mut() {
344 let data = window_surfaces
345 .surfaces
346 .entry(window.entity)
347 .or_insert_with(|| {
348 let surface_target = SurfaceTargetUnsafe::RawHandle {
349 raw_display_handle: window.handle.get_display_handle(),
350 raw_window_handle: window.handle.get_window_handle(),
351 };
352 let surface = unsafe {
354 render_instance
357 .create_surface_unsafe(surface_target)
358 .expect("Failed to create wgpu surface")
359 };
360 let caps = surface.get_capabilities(&render_adapter);
361 let present_mode = present_mode(window, &caps);
362 let formats = caps.formats;
363 let mut format = *formats.first().expect("No supported formats for surface");
367 for available_format in formats {
368 if available_format == TextureFormat::Rgba8UnormSrgb
370 || available_format == TextureFormat::Bgra8UnormSrgb
371 {
372 format = available_format;
373 break;
374 }
375 }
376
377 let texture_view_format = if !format.is_srgb() {
378 Some(format.add_srgb_suffix())
379 } else {
380 None
381 };
382 let configuration = SurfaceConfiguration {
383 format,
384 width: window.physical_width,
385 height: window.physical_height,
386 usage: TextureUsages::RENDER_ATTACHMENT,
387 present_mode,
388 desired_maximum_frame_latency: window
389 .desired_maximum_frame_latency
390 .map(NonZero::<u32>::get)
391 .unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY),
392 alpha_mode: match window.alpha_mode {
393 CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto,
394 CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque,
395 CompositeAlphaMode::PreMultiplied => {
396 wgpu::CompositeAlphaMode::PreMultiplied
397 }
398 CompositeAlphaMode::PostMultiplied => {
399 wgpu::CompositeAlphaMode::PostMultiplied
400 }
401 CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,
402 },
403 view_formats: match texture_view_format {
404 Some(format) => vec![format],
405 None => vec![],
406 },
407 };
408
409 render_device.configure_surface(&surface, &configuration);
410
411 SurfaceData {
412 surface: WgpuWrapper::new(surface),
413 configuration,
414 texture_view_format,
415 }
416 });
417
418 if window.size_changed || window.present_mode_changed {
419 drop(window.swap_chain_texture.take());
422 #[cfg_attr(
423 target_arch = "wasm32",
424 expect(clippy::drop_non_drop, reason = "texture views are not drop on wasm")
425 )]
426 drop(window.swap_chain_texture_view.take());
427
428 data.configuration.width = window.physical_width;
429 data.configuration.height = window.physical_height;
430 let caps = data.surface.get_capabilities(&render_adapter);
431 data.configuration.present_mode = present_mode(window, &caps);
432 render_device.configure_surface(&data.surface, &data.configuration);
433 }
434
435 window_surfaces.configured_windows.insert(window.entity);
436 }
437}
438
439fn present_mode(
440 window: &mut ExtractedWindow,
441 caps: &wgpu::SurfaceCapabilities,
442) -> wgpu::PresentMode {
443 let present_mode = match window.present_mode {
444 PresentMode::Fifo => wgpu::PresentMode::Fifo,
445 PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
446 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
447 PresentMode::Immediate => wgpu::PresentMode::Immediate,
448 PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
449 PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
450 };
451 let fallbacks = match present_mode {
452 wgpu::PresentMode::AutoVsync => {
453 &[wgpu::PresentMode::FifoRelaxed, wgpu::PresentMode::Fifo][..]
454 }
455 wgpu::PresentMode::AutoNoVsync => &[
456 wgpu::PresentMode::Immediate,
457 wgpu::PresentMode::Mailbox,
458 wgpu::PresentMode::Fifo,
459 ][..],
460 wgpu::PresentMode::Mailbox => &[
461 wgpu::PresentMode::Mailbox,
462 wgpu::PresentMode::Immediate,
463 wgpu::PresentMode::Fifo,
464 ][..],
465 x => &[x, wgpu::PresentMode::Fifo][..],
467 };
468 let new_present_mode = fallbacks
469 .iter()
470 .copied()
471 .find(|fallback| caps.present_modes.contains(fallback))
472 .unwrap_or_else(|| {
473 unreachable!(
474 "Fallback system failed to choose present mode. \
475 This is a bug. Mode: {:?}, Options: {:?}",
476 window.present_mode, &caps.present_modes
477 );
478 });
479 if new_present_mode != present_mode && fallbacks.contains(&present_mode) {
480 info!("PresentMode {present_mode:?} requested but not available. Falling back to {new_present_mode:?}");
481 }
482 new_present_mode
483}