1use crate::{
2 render_resource::{SurfaceTexture, TextureView},
3 renderer::{RenderAdapter, RenderDevice, RenderInstance},
4 Extract, ExtractSchedule, Render, RenderApp, RenderSet, WgpuWrapper,
5};
6use bevy_app::{App, Plugin};
7use bevy_ecs::{entity::EntityHashMap, prelude::*};
8use bevy_utils::{
9 default,
10 tracing::{debug, warn},
11 HashSet,
12};
13use bevy_window::{
14 CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,
15};
16use core::{
17 num::NonZero,
18 ops::{Deref, DerefMut},
19};
20use wgpu::{
21 SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor,
22};
23
24pub mod screenshot;
25
26use screenshot::{ScreenshotPlugin, ScreenshotToScreenPipeline};
27
28pub struct WindowRenderPlugin;
29
30impl Plugin for WindowRenderPlugin {
31 fn build(&self, app: &mut App) {
32 app.add_plugins(ScreenshotPlugin);
33
34 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
35 render_app
36 .init_resource::<ExtractedWindows>()
37 .init_resource::<WindowSurfaces>()
38 .add_systems(ExtractSchedule, extract_windows)
39 .add_systems(
40 Render,
41 create_surfaces
42 .run_if(need_surface_configuration)
43 .before(prepare_windows),
44 )
45 .add_systems(Render, prepare_windows.in_set(RenderSet::ManageViews));
46 }
47 }
48
49 fn finish(&self, app: &mut App) {
50 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
51 render_app.init_resource::<ScreenshotToScreenPipeline>();
52 }
53 }
54}
55
56pub struct ExtractedWindow {
57 pub entity: Entity,
59 pub handle: RawHandleWrapper,
60 pub physical_width: u32,
61 pub physical_height: u32,
62 pub present_mode: PresentMode,
63 pub desired_maximum_frame_latency: Option<NonZero<u32>>,
64 pub swap_chain_texture_view: Option<TextureView>,
68 pub swap_chain_texture: Option<SurfaceTexture>,
69 pub swap_chain_texture_format: Option<TextureFormat>,
70 pub size_changed: bool,
71 pub present_mode_changed: bool,
72 pub alpha_mode: CompositeAlphaMode,
73}
74
75impl ExtractedWindow {
76 fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) {
77 let texture_view_descriptor = TextureViewDescriptor {
78 format: Some(frame.texture.format().add_srgb_suffix()),
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
88#[derive(Default, Resource)]
89pub struct ExtractedWindows {
90 pub primary: Option<Entity>,
91 pub windows: EntityHashMap<ExtractedWindow>,
92}
93
94impl Deref for ExtractedWindows {
95 type Target = EntityHashMap<ExtractedWindow>;
96
97 fn deref(&self) -> &Self::Target {
98 &self.windows
99 }
100}
101
102impl DerefMut for ExtractedWindows {
103 fn deref_mut(&mut self) -> &mut Self::Target {
104 &mut self.windows
105 }
106}
107
108fn extract_windows(
109 mut extracted_windows: ResMut<ExtractedWindows>,
110 mut closing: Extract<EventReader<WindowClosing>>,
111 windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
112 mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
113 mut window_surfaces: ResMut<WindowSurfaces>,
114) {
115 for (entity, window, handle, primary) in windows.iter() {
116 if primary.is_some() {
117 extracted_windows.primary = Some(entity);
118 }
119
120 let (new_width, new_height) = (
121 window.resolution.physical_width().max(1),
122 window.resolution.physical_height().max(1),
123 );
124
125 let extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
126 entity,
127 handle: handle.clone(),
128 physical_width: new_width,
129 physical_height: new_height,
130 present_mode: window.present_mode,
131 desired_maximum_frame_latency: window.desired_maximum_frame_latency,
132 swap_chain_texture: None,
133 swap_chain_texture_view: None,
134 size_changed: false,
135 swap_chain_texture_format: None,
136 present_mode_changed: false,
137 alpha_mode: window.composite_alpha_mode,
138 });
139
140 extracted_window.swap_chain_texture_view = None;
142 extracted_window.size_changed = new_width != extracted_window.physical_width
143 || new_height != extracted_window.physical_height;
144 extracted_window.present_mode_changed =
145 window.present_mode != extracted_window.present_mode;
146
147 if extracted_window.size_changed {
148 debug!(
149 "Window size changed from {}x{} to {}x{}",
150 extracted_window.physical_width,
151 extracted_window.physical_height,
152 new_width,
153 new_height
154 );
155 extracted_window.physical_width = new_width;
156 extracted_window.physical_height = new_height;
157 }
158
159 if extracted_window.present_mode_changed {
160 debug!(
161 "Window Present Mode changed from {:?} to {:?}",
162 extracted_window.present_mode, window.present_mode
163 );
164 extracted_window.present_mode = window.present_mode;
165 }
166 }
167
168 for closing_window in closing.read() {
169 extracted_windows.remove(&closing_window.window);
170 window_surfaces.remove(&closing_window.window);
171 }
172 for removed_window in removed.read() {
173 extracted_windows.remove(&removed_window);
174 window_surfaces.remove(&removed_window);
175 }
176}
177
178struct SurfaceData {
179 surface: WgpuWrapper<wgpu::Surface<'static>>,
181 configuration: SurfaceConfiguration,
182}
183
184#[derive(Resource, Default)]
185pub struct WindowSurfaces {
186 surfaces: EntityHashMap<SurfaceData>,
187 configured_windows: HashSet<Entity>,
189}
190
191impl WindowSurfaces {
192 fn remove(&mut self, window: &Entity) {
193 self.surfaces.remove(window);
194 self.configured_windows.remove(window);
195 }
196}
197
198#[allow(clippy::too_many_arguments)]
220pub fn prepare_windows(
221 mut windows: ResMut<ExtractedWindows>,
222 mut window_surfaces: ResMut<WindowSurfaces>,
223 render_device: Res<RenderDevice>,
224 #[cfg(target_os = "linux")] render_instance: Res<RenderInstance>,
225) {
226 for window in windows.windows.values_mut() {
227 let window_surfaces = window_surfaces.deref_mut();
228 let Some(surface_data) = window_surfaces.surfaces.get(&window.entity) else {
229 continue;
230 };
231
232 #[cfg(target_os = "linux")]
240 let may_erroneously_timeout = || {
241 render_instance
242 .enumerate_adapters(wgpu::Backends::VULKAN)
243 .iter()
244 .any(|adapter| {
245 let name = adapter.get_info().name;
246 name.starts_with("Radeon")
247 || name.starts_with("AMD")
248 || name.starts_with("Intel")
249 })
250 };
251
252 let surface = &surface_data.surface;
253 match surface.get_current_texture() {
254 Ok(frame) => {
255 window.set_swapchain_texture(frame);
256 }
257 Err(wgpu::SurfaceError::Outdated) => {
258 render_device.configure_surface(surface, &surface_data.configuration);
259 let frame = match surface.get_current_texture() {
260 Ok(frame) => frame,
261 Err(err) => {
262 warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'");
265 continue;
266 }
267 };
268 window.set_swapchain_texture(frame);
269 }
270 #[cfg(target_os = "linux")]
271 Err(wgpu::SurfaceError::Timeout) if may_erroneously_timeout() => {
272 bevy_utils::tracing::trace!(
273 "Couldn't get swap chain texture. This is probably a quirk \
274 of your Linux GPU driver, so it can be safely ignored."
275 );
276 }
277 Err(err) => {
278 panic!("Couldn't get swap chain texture, operation unrecoverable: {err}");
279 }
280 }
281 window.swap_chain_texture_format = Some(surface_data.configuration.format);
282 }
283}
284
285pub fn need_surface_configuration(
286 windows: Res<ExtractedWindows>,
287 window_surfaces: Res<WindowSurfaces>,
288) -> bool {
289 for window in windows.windows.values() {
290 if !window_surfaces.configured_windows.contains(&window.entity)
291 || window.size_changed
292 || window.present_mode_changed
293 {
294 return true;
295 }
296 }
297 false
298}
299
300const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2;
305
306pub fn create_surfaces(
308 #[cfg(any(target_os = "macos", target_os = "ios"))] _marker: Option<
311 NonSend<bevy_core::NonSendMarker>,
312 >,
313 windows: Res<ExtractedWindows>,
314 mut window_surfaces: ResMut<WindowSurfaces>,
315 render_instance: Res<RenderInstance>,
316 render_adapter: Res<RenderAdapter>,
317 render_device: Res<RenderDevice>,
318) {
319 for window in windows.windows.values() {
320 let data = window_surfaces
321 .surfaces
322 .entry(window.entity)
323 .or_insert_with(|| {
324 let surface_target = SurfaceTargetUnsafe::RawHandle {
325 raw_display_handle: window.handle.display_handle,
326 raw_window_handle: window.handle.window_handle,
327 };
328 let surface = unsafe {
330 render_instance
333 .create_surface_unsafe(surface_target)
334 .expect("Failed to create wgpu surface")
335 };
336 let caps = surface.get_capabilities(&render_adapter);
337 let formats = caps.formats;
338 let mut format = *formats.first().expect("No supported formats for surface");
342 for available_format in formats {
343 if available_format == TextureFormat::Rgba8UnormSrgb
345 || available_format == TextureFormat::Bgra8UnormSrgb
346 {
347 format = available_format;
348 break;
349 }
350 }
351
352 let configuration = SurfaceConfiguration {
353 format,
354 width: window.physical_width,
355 height: window.physical_height,
356 usage: TextureUsages::RENDER_ATTACHMENT,
357 present_mode: match window.present_mode {
358 PresentMode::Fifo => wgpu::PresentMode::Fifo,
359 PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
360 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
361 PresentMode::Immediate => wgpu::PresentMode::Immediate,
362 PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
363 PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
364 },
365 desired_maximum_frame_latency: window
366 .desired_maximum_frame_latency
367 .map(NonZero::<u32>::get)
368 .unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY),
369 alpha_mode: match window.alpha_mode {
370 CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto,
371 CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque,
372 CompositeAlphaMode::PreMultiplied => {
373 wgpu::CompositeAlphaMode::PreMultiplied
374 }
375 CompositeAlphaMode::PostMultiplied => {
376 wgpu::CompositeAlphaMode::PostMultiplied
377 }
378 CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,
379 },
380 view_formats: if !format.is_srgb() {
381 vec![format.add_srgb_suffix()]
382 } else {
383 vec![]
384 },
385 };
386
387 render_device.configure_surface(&surface, &configuration);
388
389 SurfaceData {
390 surface: WgpuWrapper::new(surface),
391 configuration,
392 }
393 });
394
395 if window.size_changed || window.present_mode_changed {
396 data.configuration.width = window.physical_width;
397 data.configuration.height = window.physical_height;
398 data.configuration.present_mode = match window.present_mode {
399 PresentMode::Fifo => wgpu::PresentMode::Fifo,
400 PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
401 PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
402 PresentMode::Immediate => wgpu::PresentMode::Immediate,
403 PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
404 PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
405 };
406 render_device.configure_surface(&data.surface, &data.configuration);
407 }
408
409 window_surfaces.configured_windows.insert(window.entity);
410 }
411}