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, 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 size_changed: bool,
64 pub present_mode_changed: bool,
65 pub alpha_mode: CompositeAlphaMode,
66}
67
68impl ExtractedWindow {
69 fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) {
70 let texture_view_descriptor = TextureViewDescriptor {
71 format: Some(frame.texture.format().add_srgb_suffix()),
72 ..default()
73 };
74 self.swap_chain_texture_view = Some(TextureView::from(
75 frame.texture.create_view(&texture_view_descriptor),
76 ));
77 self.swap_chain_texture = Some(SurfaceTexture::from(frame));
78 }
79}
80
81#[derive(Default, Resource)]
82pub struct ExtractedWindows {
83 pub primary: Option<Entity>,
84 pub windows: EntityHashMap<ExtractedWindow>,
85}
86
87impl Deref for ExtractedWindows {
88 type Target = EntityHashMap<ExtractedWindow>;
89
90 fn deref(&self) -> &Self::Target {
91 &self.windows
92 }
93}
94
95impl DerefMut for ExtractedWindows {
96 fn deref_mut(&mut self) -> &mut Self::Target {
97 &mut self.windows
98 }
99}
100
101fn extract_windows(
102 mut extracted_windows: ResMut<ExtractedWindows>,
103 mut closing: Extract<MessageReader<WindowClosing>>,
104 windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
105 mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
106 mut window_surfaces: ResMut<WindowSurfaces>,
107) {
108 for (entity, window, handle, primary) in windows.iter() {
109 if primary.is_some() {
110 extracted_windows.primary = Some(entity);
111 }
112
113 let (new_width, new_height) = (
114 window.resolution.physical_width().max(1),
115 window.resolution.physical_height().max(1),
116 );
117
118 let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
119 entity,
120 handle: handle.clone(),
121 physical_width: new_width,
122 physical_height: new_height,
123 present_mode: window.present_mode,
124 desired_maximum_frame_latency: window.desired_maximum_frame_latency,
125 swap_chain_texture: None,
126 swap_chain_texture_view: None,
127 size_changed: false,
128 swap_chain_texture_format: None,
129 present_mode_changed: false,
130 alpha_mode: window.composite_alpha_mode,
131 });
132
133 extracted_window.swap_chain_texture_view = None;
135 extracted_window.size_changed = new_width != extracted_window.physical_width
136 || new_height != extracted_window.physical_height;
137 extracted_window.present_mode_changed =
138 window.present_mode != extracted_window.present_mode;
139
140 if extracted_window.size_changed {
141 debug!(
142 "Window size changed from {}x{} to {}x{}",
143 extracted_window.physical_width,
144 extracted_window.physical_height,
145 new_width,
146 new_height
147 );
148 extracted_window.physical_width = new_width;
149 extracted_window.physical_height = new_height;
150 }
151
152 if extracted_window.present_mode_changed {
153 debug!(
154 "Window Present Mode changed from {:?} to {:?}",
155 extracted_window.present_mode, window.present_mode
156 );
157 extracted_window.present_mode = window.present_mode;
158 }
159 }
160
161 for closing_window in closing.read() {
162 extracted_windows.remove(&closing_window.window);
163 window_surfaces.remove(&closing_window.window);
164 }
165 for removed_window in removed.read() {
166 extracted_windows.remove(&removed_window);
167 window_surfaces.remove(&removed_window);
168 }
169}
170
171struct SurfaceData {
172 surface: WgpuWrapper<wgpu::Surface<'static>>,
174 configuration: SurfaceConfiguration,
175}
176
177#[derive(Resource, Default)]
178pub struct WindowSurfaces {
179 surfaces: EntityHashMap<SurfaceData>,
180 configured_windows: HashSet<Entity>,
182}
183
184impl WindowSurfaces {
185 fn remove(&mut self, window: &Entity) {
186 self.surfaces.remove(window);
187 self.configured_windows.remove(window);
188 }
189}
190
191pub fn prepare_windows(
213 mut windows: ResMut<ExtractedWindows>,
214 mut window_surfaces: ResMut<WindowSurfaces>,
215 render_device: Res<RenderDevice>,
216 #[cfg(target_os = "linux")] render_instance: Res<RenderInstance>,
217) {
218 for window in windows.windows.values_mut() {
219 let window_surfaces = window_surfaces.deref_mut();
220 let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else {
221 continue;
222 };
223
224 #[cfg(target_os = "linux")]
232 let may_erroneously_timeout = || {
233 render_instance
234 .enumerate_adapters(wgpu::Backends::VULKAN)
235 .iter()
236 .any(|adapter| {
237 let name = adapter.get_info().name;
238 name.starts_with("Radeon")
239 || name.starts_with("AMD")
240 || name.starts_with("Intel")
241 })
242 };
243
244 let surface = &surface_data.surface;
245 match surface.get_current_texture() {
246 Ok(frame) => {
247 window.set_swapchain_texture(frame);
248 }
249 Err(wgpu::SurfaceError::Outdated) => {
250 render_device.configure_surface(surface, &surface_data.configuration);
251 let frame = match surface.get_current_texture() {
252 Ok(frame) => frame,
253 Err(err) => {
254 warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'");
257 continue;
258 }
259 };
260 window.set_swapchain_texture(frame);
261 }
262 #[cfg(target_os = "linux")]
263 Err(wgpu::SurfaceError::Timeout) if may_erroneously_timeout() => {
264 tracing::trace!(
265 "Couldn't get swap chain texture. This is probably a quirk \
266 of your Linux GPU driver, so it can be safely ignored."
267 );
268 }
269 Err(err) => {
270 panic!("Couldn't get swap chain texture, operation unrecoverable: {err}");
271 }
272 }
273 window.swap_chain_texture_format = Some(surface_data.configuration.format);
274 }
275}
276
277pub fn need_surface_configuration(
278 windows: Res<ExtractedWindows>,
279 window_surfaces: Res<WindowSurfaces>,
280) -> bool {
281 for window in windows.windows.values() {
282 if !window_surfaces.configured_windows.contains(&window.entity)
283 || window.size_changed
284 || window.present_mode_changed
285 {
286 return true;
287 }
288 }
289 false
290}
291
292const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2;
297
298pub fn create_surfaces(
300 #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker,
303 windows: Res<ExtractedWindows>,
304 mut window_surfaces: ResMut<WindowSurfaces>,
305 render_instance: Res<RenderInstance>,
306 render_adapter: Res<RenderAdapter>,
307 render_device: Res<RenderDevice>,
308) {
309 for window in windows.windows.values() {
310 let data = window_surfaces
311 .surfaces
312 .entry(window.entity)
313 .or_insert_with(|| {
314 let surface_target = SurfaceTargetUnsafe::RawHandle {
315 raw_display_handle: window.handle.get_display_handle(),
316 raw_window_handle: window.handle.get_window_handle(),
317 };
318 let surface = unsafe {
320 render_instance
323 .create_surface_unsafe(surface_target)
324 .expect("Failed to create wgpu surface")
325 };
326 let caps = surface.get_capabilities(&render_adapter);
327 let formats = caps.formats;
328 let mut format = *formats.first().expect("No supported formats for surface");
332 for available_format in formats {
333 if available_format == TextureFormat::Rgba8UnormSrgb
335 || available_format == TextureFormat::Bgra8UnormSrgb
336 {
337 format = available_format;
338 break;
339 }
340 }
341
342 let configuration = SurfaceConfiguration {
343 format,
344 width: window.physical_width,
345 height: window.physical_height,
346 usage: TextureUsages::RENDER_ATTACHMENT,
347 present_mode: match window.present_mode {
348 PresentMode::Fifo => wgpu::PresentMode::Fifo,
349 PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
350 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
351 PresentMode::Immediate => wgpu::PresentMode::Immediate,
352 PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
353 PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
354 },
355 desired_maximum_frame_latency: window
356 .desired_maximum_frame_latency
357 .map(NonZero::<u32>::get)
358 .unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY),
359 alpha_mode: match window.alpha_mode {
360 CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto,
361 CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque,
362 CompositeAlphaMode::PreMultiplied => {
363 wgpu::CompositeAlphaMode::PreMultiplied
364 }
365 CompositeAlphaMode::PostMultiplied => {
366 wgpu::CompositeAlphaMode::PostMultiplied
367 }
368 CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,
369 },
370 view_formats: if !format.is_srgb() {
371 vec![format.add_srgb_suffix()]
372 } else {
373 vec![]
374 },
375 };
376
377 render_device.configure_surface(&surface, &configuration);
378
379 SurfaceData {
380 surface: WgpuWrapper::new(surface),
381 configuration,
382 }
383 });
384
385 if window.size_changed || window.present_mode_changed {
386 data.configuration.width = window.physical_width;
387 data.configuration.height = window.physical_height;
388 data.configuration.present_mode = match window.present_mode {
389 PresentMode::Fifo => wgpu::PresentMode::Fifo,
390 PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
391 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
392 PresentMode::Immediate => wgpu::PresentMode::Immediate,
393 PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
394 PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
395 };
396 render_device.configure_surface(&data.surface, &data.configuration);
397 }
398
399 window_surfaces.configured_windows.insert(window.entity);
400 }
401}