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