Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/view/window/mod.rs
9383 views
1
use crate::renderer::WgpuWrapper;
2
use crate::{
3
render_resource::{SurfaceTexture, TextureView},
4
renderer::{RenderAdapter, RenderDevice, RenderInstance},
5
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
6
};
7
use bevy_app::{App, Plugin};
8
use bevy_ecs::{entity::EntityHashMap, prelude::*};
9
use bevy_log::{debug, info, warn};
10
use bevy_platform::collections::HashSet;
11
use bevy_utils::default;
12
use bevy_window::{
13
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,
14
};
15
use core::{
16
num::NonZero,
17
ops::{Deref, DerefMut},
18
};
19
use wgpu::{
20
SurfaceConfiguration, SurfaceTargetUnsafe, TextureFormat, TextureUsages, TextureViewDescriptor,
21
};
22
23
pub mod screenshot;
24
25
use screenshot::ScreenshotPlugin;
26
27
pub struct WindowRenderPlugin;
28
29
impl 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
49
pub struct ExtractedWindow {
50
/// An entity that contains the components in [`Window`].
51
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
/// Note: this will not always be the swap chain texture view. When taking a screenshot,
58
/// this will point to an alternative texture instead to allow for copying the render result
59
/// to CPU memory.
60
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
/// Whether this window needs an initial buffer commit.
68
///
69
/// On Wayland, windows must present at least once before they are shown.
70
/// See <https://wayland.app/protocols/xdg-shell#xdg_surface>
71
pub needs_initial_present: bool,
72
}
73
74
impl 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
// TODO(clean): winit docs recommends calling pre_present_notify before this.
94
// though `present()` doesn't present the frame, it schedules it to be presented
95
// by wgpu.
96
// https://docs.rs/winit/0.29.9/wasm32-unknown-unknown/winit/window/struct.Window.html#method.pre_present_notify
97
surface_texture.present();
98
}
99
}
100
}
101
102
#[derive(Default, Resource)]
103
pub struct ExtractedWindows {
104
pub primary: Option<Entity>,
105
pub windows: EntityHashMap<ExtractedWindow>,
106
}
107
108
impl Deref for ExtractedWindows {
109
type Target = EntityHashMap<ExtractedWindow>;
110
111
fn deref(&self) -> &Self::Target {
112
&self.windows
113
}
114
}
115
116
impl DerefMut for ExtractedWindows {
117
fn deref_mut(&mut self) -> &mut Self::Target {
118
&mut self.windows
119
}
120
}
121
122
fn 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
// If we called present on the previous swap-chain texture last update,
158
// then drop the swap chain frame here, otherwise we can keep it for the
159
// next update as an optimization. `prepare_windows` will only acquire a new
160
// swap chain texture if needed.
161
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
199
struct SurfaceData {
200
// TODO: what lifetime should this be?
201
surface: WgpuWrapper<wgpu::Surface<'static>>,
202
configuration: SurfaceConfiguration,
203
texture_view_format: Option<TextureFormat>,
204
}
205
206
#[derive(Resource, Default)]
207
pub struct WindowSurfaces {
208
surfaces: EntityHashMap<SurfaceData>,
209
/// List of windows that we have already called the initial `configure_surface` for
210
configured_windows: HashSet<Entity>,
211
}
212
213
impl WindowSurfaces {
214
fn remove(&mut self, window: &Entity) {
215
self.surfaces.remove(window);
216
self.configured_windows.remove(window);
217
}
218
}
219
220
/// (re)configures window surfaces, and obtains a swapchain texture for rendering.
221
///
222
/// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is
223
/// the performance bottleneck. This can be seen in profiles as multiple prepare-set systems all
224
/// taking an unusually long time to complete, and all finishing at about the same time as the
225
/// `prepare_windows` system. Improvements in bevy are planned to avoid this happening when it
226
/// should not but it will still happen as it is easy for a user to create a large GPU workload
227
/// relative to the GPU performance and/or CPU workload.
228
/// This can be caused by many reasons, but several of them are:
229
/// - GPU workload is more than your current GPU can manage
230
/// - Error / performance bug in your custom shaders
231
/// - wgpu was unable to detect a proper GPU hardware-accelerated device given the chosen
232
/// [`Backends`](crate::settings::Backends), [`WgpuLimits`](crate::settings::WgpuLimits),
233
/// and/or [`WgpuFeatures`](crate::settings::WgpuFeatures). For example, on Windows currently
234
/// `DirectX 11` is not supported by wgpu 0.12 and so if your GPU/drivers do not support Vulkan,
235
/// it may be that a software renderer called "Microsoft Basic Render Driver" using `DirectX 12`
236
/// will be chosen and performance will be very poor. This is visible in a log message that is
237
/// output during renderer initialization.
238
/// Another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and
239
/// [`Backends::GL`](crate::settings::Backends::GL) with the `gles` feature enabled if your
240
/// GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or later.
241
pub 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
// We didn't present the previous frame, so we can keep using our existing swapchain texture.
254
if window.has_swapchain_texture() && !window.size_changed && !window.present_mode_changed {
255
continue;
256
}
257
258
// A recurring issue is hitting `wgpu::SurfaceError::Timeout` on certain Linux
259
// mesa driver implementations. This seems to be a quirk of some drivers.
260
// We'd rather keep panicking when not on Linux mesa, because in those case,
261
// the `Timeout` is still probably the symptom of a degraded unrecoverable
262
// application state.
263
// see https://github.com/bevyengine/bevy/pull/5957
264
// and https://github.com/gfx-rs/wgpu/issues/1218
265
#[cfg(target_os = "linux")]
266
let may_erroneously_timeout = || {
267
bevy_tasks::IoTaskPool::get().scope(|scope| {
268
scope.spawn(async {
269
render_instance
270
.enumerate_adapters(wgpu::Backends::VULKAN)
271
.await
272
.iter()
273
.any(|adapter| {
274
let name = adapter.get_info().name;
275
name.starts_with("Radeon")
276
|| name.starts_with("AMD")
277
|| name.starts_with("Intel")
278
})
279
});
280
})[0]
281
};
282
283
let surface = &surface_data.surface;
284
match surface.get_current_texture() {
285
Ok(frame) => {
286
window.set_swapchain_texture(frame);
287
}
288
Err(wgpu::SurfaceError::Outdated) => {
289
render_device.configure_surface(surface, &surface_data.configuration);
290
let frame = match surface.get_current_texture() {
291
Ok(frame) => frame,
292
Err(err) => {
293
// This is a common occurrence on X11 and Xwayland with NVIDIA drivers
294
// when opening and resizing the window.
295
warn!("Couldn't get swap chain texture after configuring. Cause: '{err}'");
296
continue;
297
}
298
};
299
window.set_swapchain_texture(frame);
300
}
301
#[cfg(target_os = "linux")]
302
Err(wgpu::SurfaceError::Timeout) if may_erroneously_timeout() => {
303
bevy_log::trace!(
304
"Couldn't get swap chain texture. This is probably a quirk \
305
of your Linux GPU driver, so it can be safely ignored."
306
);
307
}
308
Err(err) => {
309
panic!("Couldn't get swap chain texture, operation unrecoverable: {err}");
310
}
311
}
312
window.swap_chain_texture_format = Some(surface_data.configuration.format);
313
}
314
}
315
316
pub fn need_surface_configuration(
317
windows: Res<ExtractedWindows>,
318
window_surfaces: Res<WindowSurfaces>,
319
) -> bool {
320
for window in windows.windows.values() {
321
if !window_surfaces.configured_windows.contains(&window.entity)
322
|| window.size_changed
323
|| window.present_mode_changed
324
{
325
return true;
326
}
327
}
328
false
329
}
330
331
// 2 is wgpu's default/what we've been using so far.
332
// 1 is the minimum, but may cause lower framerates due to the cpu waiting for the gpu to finish
333
// all work for the previous frame before starting work on the next frame, which then means the gpu
334
// has to wait for the cpu to finish to start on the next frame.
335
const DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY: u32 = 2;
336
337
/// Creates window surfaces.
338
pub fn create_surfaces(
339
// By accessing a NonSend resource, we tell the scheduler to put this system on the main thread,
340
// which is necessary for some OS's
341
#[cfg(any(target_os = "macos", target_os = "ios"))] _marker: bevy_ecs::system::NonSendMarker,
342
mut windows: ResMut<ExtractedWindows>,
343
mut window_surfaces: ResMut<WindowSurfaces>,
344
render_instance: Res<RenderInstance>,
345
render_adapter: Res<RenderAdapter>,
346
render_device: Res<RenderDevice>,
347
) {
348
for window in windows.windows.values_mut() {
349
let data = window_surfaces
350
.surfaces
351
.entry(window.entity)
352
.or_insert_with(|| {
353
let surface_target = SurfaceTargetUnsafe::RawHandle {
354
raw_display_handle: window.handle.get_display_handle(),
355
raw_window_handle: window.handle.get_window_handle(),
356
};
357
// SAFETY: The window handles in ExtractedWindows will always be valid objects to create surfaces on
358
let surface = unsafe {
359
// NOTE: On some OSes this MUST be called from the main thread.
360
// As of wgpu 0.15, only fallible if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails.
361
render_instance
362
.create_surface_unsafe(surface_target)
363
.expect("Failed to create wgpu surface")
364
};
365
let caps = surface.get_capabilities(&render_adapter);
366
let present_mode = present_mode(window, &caps);
367
let formats = caps.formats;
368
// For future HDR output support, we'll need to request a format that supports HDR,
369
// but as of wgpu 0.15 that is not yet supported.
370
// Prefer sRGB formats for surfaces, but fall back to first available format if no sRGB formats are available.
371
let mut format = *formats.first().expect("No supported formats for surface");
372
for available_format in formats {
373
// Rgba8UnormSrgb and Bgra8UnormSrgb and the only sRGB formats wgpu exposes that we can use for surfaces.
374
if available_format == TextureFormat::Rgba8UnormSrgb
375
|| available_format == TextureFormat::Bgra8UnormSrgb
376
{
377
format = available_format;
378
break;
379
}
380
}
381
382
let texture_view_format = if !format.is_srgb() {
383
Some(format.add_srgb_suffix())
384
} else {
385
None
386
};
387
let configuration = SurfaceConfiguration {
388
format,
389
width: window.physical_width,
390
height: window.physical_height,
391
usage: TextureUsages::RENDER_ATTACHMENT,
392
present_mode,
393
desired_maximum_frame_latency: window
394
.desired_maximum_frame_latency
395
.map(NonZero::<u32>::get)
396
.unwrap_or(DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY),
397
alpha_mode: match window.alpha_mode {
398
CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto,
399
CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque,
400
CompositeAlphaMode::PreMultiplied => {
401
wgpu::CompositeAlphaMode::PreMultiplied
402
}
403
CompositeAlphaMode::PostMultiplied => {
404
wgpu::CompositeAlphaMode::PostMultiplied
405
}
406
CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,
407
},
408
view_formats: match texture_view_format {
409
Some(format) => vec![format],
410
None => vec![],
411
},
412
};
413
414
render_device.configure_surface(&surface, &configuration);
415
416
SurfaceData {
417
surface: WgpuWrapper::new(surface),
418
configuration,
419
texture_view_format,
420
}
421
});
422
423
if window.size_changed || window.present_mode_changed {
424
// normally this is dropped on present but we double check here to be safe as failure to
425
// drop it will cause validation errors in wgpu
426
drop(window.swap_chain_texture.take());
427
#[cfg_attr(
428
target_arch = "wasm32",
429
expect(clippy::drop_non_drop, reason = "texture views are not drop on wasm")
430
)]
431
drop(window.swap_chain_texture_view.take());
432
433
data.configuration.width = window.physical_width;
434
data.configuration.height = window.physical_height;
435
let caps = data.surface.get_capabilities(&render_adapter);
436
data.configuration.present_mode = present_mode(window, &caps);
437
render_device.configure_surface(&data.surface, &data.configuration);
438
}
439
440
window_surfaces.configured_windows.insert(window.entity);
441
}
442
}
443
444
fn present_mode(
445
window: &mut ExtractedWindow,
446
caps: &wgpu::SurfaceCapabilities,
447
) -> wgpu::PresentMode {
448
let present_mode = match window.present_mode {
449
PresentMode::Fifo => wgpu::PresentMode::Fifo,
450
PresentMode::FifoRelaxed => wgpu::PresentMode::FifoRelaxed,
451
PresentMode::Mailbox => wgpu::PresentMode::Mailbox,
452
PresentMode::Immediate => wgpu::PresentMode::Immediate,
453
PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync,
454
PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync,
455
};
456
let fallbacks = match present_mode {
457
wgpu::PresentMode::AutoVsync => {
458
&[wgpu::PresentMode::FifoRelaxed, wgpu::PresentMode::Fifo][..]
459
}
460
wgpu::PresentMode::AutoNoVsync => &[
461
wgpu::PresentMode::Immediate,
462
wgpu::PresentMode::Mailbox,
463
wgpu::PresentMode::Fifo,
464
][..],
465
wgpu::PresentMode::Mailbox => &[
466
wgpu::PresentMode::Mailbox,
467
wgpu::PresentMode::Immediate,
468
wgpu::PresentMode::Fifo,
469
][..],
470
// Always end in FIFO to make sure it's always supported
471
x => &[x, wgpu::PresentMode::Fifo][..],
472
};
473
let new_present_mode = fallbacks
474
.iter()
475
.copied()
476
.find(|fallback| caps.present_modes.contains(fallback))
477
.unwrap_or_else(|| {
478
unreachable!(
479
"Fallback system failed to choose present mode. \
480
This is a bug. Mode: {:?}, Options: {:?}",
481
window.present_mode, &caps.present_modes
482
);
483
});
484
if new_present_mode != present_mode && fallbacks.contains(&present_mode) {
485
info!("PresentMode {present_mode:?} requested but not available. Falling back to {new_present_mode:?}");
486
}
487
new_present_mode
488
}
489
490