1use alloc::{boxed::Box, sync::Arc, vec::Vec};
13use core::mem::ManuallyDrop;
14
15#[cfg(feature = "trace")]
16use crate::device::trace::{Action, IntoTrace};
17use crate::{
18 conv,
19 device::{queue::Queue, Device, DeviceError, MissingDownlevelFlags, WaitIdleError},
20 hal_label,
21 instance::Surface,
22 resource::{self, Labeled},
23};
24
25use thiserror::Error;
26use wgt::{
27 error::{ErrorType, WebGpuError},
28 SurfaceStatus as Status,
29};
30
31const FRAME_TIMEOUT_MS: u32 = 1000;
32
33#[derive(Debug)]
34pub(crate) struct Presentation {
35 pub(crate) device: Arc<Device>,
36 pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>,
37 pub(crate) acquired_texture: Option<Arc<resource::Texture>>,
38}
39
40#[derive(Clone, Debug, Error)]
41#[non_exhaustive]
42pub enum SurfaceError {
43 #[error("Surface is invalid")]
44 Invalid,
45 #[error("Surface is not configured for presentation")]
46 NotConfigured,
47 #[error(transparent)]
48 Device(#[from] DeviceError),
49 #[error("Surface image is already acquired")]
50 AlreadyAcquired,
51 #[error("No surface image is currently acquired to present")]
52 NothingToPresent,
53 #[error("Texture has been destroyed")]
54 TextureDestroyed,
55}
56
57impl WebGpuError for SurfaceError {
58 fn webgpu_error_type(&self) -> ErrorType {
59 match self {
60 Self::Device(e) => e.webgpu_error_type(),
61 Self::Invalid
62 | Self::NotConfigured
63 | Self::AlreadyAcquired
64 | Self::NothingToPresent
65 | Self::TextureDestroyed => ErrorType::Validation,
66 }
67 }
68}
69
70#[derive(Clone, Debug, Error)]
71#[non_exhaustive]
72pub enum ConfigureSurfaceError {
73 #[error(transparent)]
74 Device(#[from] DeviceError),
75 #[error("Invalid surface")]
76 InvalidSurface,
77 #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
78 InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
79 #[error(transparent)]
80 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
81 #[error("The `SurfaceOutput` returned by `get_current_texture` must be dropped before re-configuring via `configure` or retrieving a new texture via `get_current_texture`.")]
82 PreviousOutputExists,
83 #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")]
84 GpuWaitTimeout,
85 #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
86 ZeroArea,
87 #[error("`Surface` width and height must be within the maximum supported texture size. Requested was ({width}, {height}), maximum extent for either dimension is {max_texture_dimension_2d}.")]
88 TooLarge {
89 width: u32,
90 height: u32,
91 max_texture_dimension_2d: u32,
92 },
93 #[error("Surface does not support the adapter's queue family")]
94 UnsupportedQueueFamily,
95 #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
96 UnsupportedFormat {
97 requested: wgt::TextureFormat,
98 available: Vec<wgt::TextureFormat>,
99 },
100 #[error("Requested color space {requested:?} is not in the list of color spaces supported for format {format:?}: {available:?}")]
101 UnsupportedColorSpace {
102 requested: wgt::SurfaceColorSpace,
103 format: wgt::TextureFormat,
104 available: wgt::SurfaceColorSpaces,
105 },
106 #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
107 UnsupportedPresentMode {
108 requested: wgt::PresentMode,
109 available: Vec<wgt::PresentMode>,
110 },
111 #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
112 UnsupportedAlphaMode {
113 requested: wgt::CompositeAlphaMode,
114 available: Vec<wgt::CompositeAlphaMode>,
115 },
116 #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
117 UnsupportedUsage {
118 requested: wgt::TextureUses,
119 available: wgt::TextureUses,
120 },
121}
122
123impl From<WaitIdleError> for ConfigureSurfaceError {
124 fn from(e: WaitIdleError) -> Self {
125 match e {
126 WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
127 WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
128 WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
129 }
130 }
131}
132
133impl WebGpuError for ConfigureSurfaceError {
134 fn webgpu_error_type(&self) -> ErrorType {
135 match self {
136 Self::Device(e) => e.webgpu_error_type(),
137 Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
138 Self::InvalidSurface
139 | Self::InvalidViewFormat(..)
140 | Self::PreviousOutputExists
141 | Self::GpuWaitTimeout
142 | Self::ZeroArea
143 | Self::TooLarge { .. }
144 | Self::UnsupportedQueueFamily
145 | Self::UnsupportedFormat { .. }
146 | Self::UnsupportedColorSpace { .. }
147 | Self::UnsupportedPresentMode { .. }
148 | Self::UnsupportedAlphaMode { .. }
149 | Self::UnsupportedUsage { .. } => ErrorType::Validation,
150 }
151 }
152}
153
154#[repr(C)]
155#[derive(Debug)]
156pub struct SurfaceOutput<T = Arc<resource::Texture>> {
157 pub status: Status,
158 pub texture: Option<T>,
159}
160
161impl Surface {
162 pub fn get_current_texture(self: &Arc<Self>) -> Result<SurfaceOutput, SurfaceError> {
163 let output = self.get_current_texture_inner();
164 #[cfg(feature = "trace")]
165 if let Some(present) = self.presentation.lock().as_ref() {
166 if let Some(ref mut trace) = *present.device.trace.lock() {
167 if let Some(texture) = present.acquired_texture.as_ref() {
168 trace.add(Action::GetSurfaceTexture {
169 id: texture.to_trace(),
170 parent: self.to_trace(),
171 });
172 }
173 }
174 }
175 output
176 }
177
178 pub(crate) fn get_current_texture_inner(&self) -> Result<SurfaceOutput, SurfaceError> {
179 profiling::scope!("Surface::get_current_texture");
180
181 let (device, config) = if let Some(ref present) = *self.presentation.lock() {
182 present.device.check_is_valid()?;
183 (present.device.clone(), present.config.clone())
184 } else {
185 return Err(SurfaceError::NotConfigured);
186 };
187
188 let suf = self.raw(device.backend()).unwrap();
189 let (texture, status) = match unsafe {
190 suf.acquire_texture(
191 Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
192 device.fence.as_ref(),
193 )
194 } {
195 Ok(ast) => {
196 let texture_desc = wgt::TextureDescriptor {
197 label: hal_label(
198 Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
199 device.instance_flags,
200 ),
201 size: wgt::Extent3d {
202 width: config.width,
203 height: config.height,
204 depth_or_array_layers: 1,
205 },
206 sample_count: 1,
207 mip_level_count: 1,
208 format: config.format,
209 dimension: wgt::TextureDimension::D2,
210 usage: config.usage,
211 view_formats: config.view_formats,
212 };
213 let format_features = wgt::TextureFormatFeatures {
214 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
215 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
216 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
217 };
218 let hal_usage = conv::map_texture_usage(
219 config.usage,
220 config.format.into(),
221 format_features.flags,
222 );
223 let clear_view_desc = hal::TextureViewDescriptor {
224 label: hal_label(
225 Some("(wgpu internal) clear surface texture view"),
226 device.instance_flags,
227 ),
228 format: config.format,
229 dimension: wgt::TextureViewDimension::D2,
230 usage: wgt::TextureUses::COLOR_TARGET,
231 range: wgt::ImageSubresourceRange::default(),
232 swizzle: wgt::TextureComponentSwizzle::default(),
233 };
234 let clear_view = unsafe {
235 device
236 .raw()
237 .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
238 }
239 .map_err(|e| device.handle_hal_error(e))?;
240
241 let mut presentation = self.presentation.lock();
242 let present = presentation.as_mut().unwrap();
243 let texture = resource::Texture::new(
244 &device,
245 resource::TextureInner::Surface { raw: ast.texture },
246 hal_usage,
247 &texture_desc,
248 format_features,
249 resource::TextureClearMode::Surface {
250 clear_view: ManuallyDrop::new(clear_view),
251 },
252 true,
253 );
254
255 let texture = Arc::new(texture);
256
257 device
258 .trackers
259 .lock()
260 .textures
261 .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
262
263 if present.acquired_texture.is_some() {
264 return Err(SurfaceError::AlreadyAcquired);
265 }
266 present.acquired_texture = Some(texture.clone());
267
268 let status = if ast.suboptimal {
269 Status::Suboptimal
270 } else {
271 Status::Good
272 };
273 (Some(texture), status)
274 }
275 Err(err) => (
276 None,
277 match err {
278 hal::SurfaceError::Timeout => Status::Timeout,
279 hal::SurfaceError::Occluded => Status::Occluded,
280 hal::SurfaceError::Lost => Status::Lost,
281 hal::SurfaceError::Device(err) => {
282 return Err(device.handle_hal_error(err).into());
283 }
284 hal::SurfaceError::Outdated => Status::Outdated,
285 hal::SurfaceError::Other(msg) => {
286 log::error!("acquire error: {msg}");
287 Status::Lost
288 }
289 },
290 ),
291 };
292
293 Ok(SurfaceOutput { status, texture })
294 }
295
296 pub fn present(self: &Arc<Self>) -> Result<Status, SurfaceError> {
297 #[cfg(feature = "trace")]
298 if let Some(present) = self.presentation.lock().as_ref() {
299 if let Some(ref mut trace) = *present.device.trace.lock() {
300 trace.add(Action::Present(self.to_trace()));
301 }
302 }
303 self.present_inner()
304 }
305
306 pub(crate) fn present_inner(&self) -> Result<Status, SurfaceError> {
307 profiling::scope!("Surface::present");
308
309 let presentation = self.presentation.lock();
310 let present = match presentation.as_ref() {
311 Some(present) => present,
312 None => return Err(SurfaceError::NotConfigured),
313 };
314
315 present.device.check_is_valid()?;
316 let queue = present
317 .device
318 .get_queue()
319 .ok_or(SurfaceError::Device(DeviceError::Lost))?;
320 drop(presentation);
321
322 queue.present(self)
323 }
324}
325
326impl Queue {
327 pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
328 profiling::scope!("Queue::present");
329
330 let texture = {
331 let mut presentation = surface.presentation.lock();
332 let present = match presentation.as_mut() {
333 Some(present) => present,
334 None => return Err(SurfaceError::NotConfigured),
335 };
336
337 let device = &self.device;
338
339 if !Arc::ptr_eq(&present.device, device) {
341 return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
342 crate::device::DeviceMismatch {
343 res: self.error_ident(),
344 res_device: device.error_ident(),
345 target: None,
346 target_device: present.device.error_ident(),
347 },
348 ))));
349 }
350
351 present
352 .acquired_texture
353 .take()
354 .ok_or(SurfaceError::NothingToPresent)?
355 };
356
357 self.prepare_surface_texture_for_present(&texture)?;
361
362 let device = &self.device;
363
364 let mut exclusive_snatch_guard = device.snatchable_lock.write();
365 let inner = texture
366 .state()
367 .ok()
368 .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
369 drop(exclusive_snatch_guard);
370
371 let result = match inner {
372 None => return Err(SurfaceError::TextureDestroyed),
373 Some(resource::TextureInner::Surface { raw }) => {
374 let raw_surface = surface.raw(device.backend()).unwrap();
375 let raw_queue = self.raw();
376 let _command_indices = device.command_indices.write();
380 unsafe { raw_queue.present(raw_surface, raw) }
381 }
382 _ => unreachable!(),
383 };
384
385 match result {
386 Ok(()) => Ok(Status::Good),
387 Err(err) => match err {
388 hal::SurfaceError::Timeout => Ok(Status::Timeout),
389 hal::SurfaceError::Occluded => Ok(Status::Occluded),
390 hal::SurfaceError::Lost => Ok(Status::Lost),
391 hal::SurfaceError::Device(err) => {
392 Err(SurfaceError::from(device.handle_hal_error(err)))
393 }
394 hal::SurfaceError::Outdated => Ok(Status::Outdated),
395 hal::SurfaceError::Other(msg) => {
396 log::error!("present error: {msg}");
397 Err(SurfaceError::Invalid)
398 }
399 },
400 }
401 }
402}
403
404impl Surface {
405 pub fn discard(self: &Arc<Self>) -> Result<(), SurfaceError> {
406 #[cfg(feature = "trace")]
407 if let Some(present) = self.presentation.lock().as_ref() {
408 if let Some(ref mut trace) = *present.device.trace.lock() {
409 trace.add(Action::DiscardSurfaceTexture(self.to_trace()));
410 }
411 }
412 self.discard_inner()
413 }
414
415 pub(crate) fn discard_inner(&self) -> Result<(), SurfaceError> {
416 profiling::scope!("Surface::discard");
417
418 let mut presentation = self.presentation.lock();
419 let present = match presentation.as_mut() {
420 Some(present) => present,
421 None => return Err(SurfaceError::NotConfigured),
422 };
423
424 let device = &present.device;
425
426 device.check_is_valid()?;
427
428 let texture = present
429 .acquired_texture
430 .take()
431 .ok_or(SurfaceError::NothingToPresent)?;
432
433 let mut exclusive_snatch_guard = device.snatchable_lock.write();
434 let inner = texture
435 .state()
436 .ok()
437 .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
438 drop(exclusive_snatch_guard);
439
440 match inner {
441 None => return Err(SurfaceError::TextureDestroyed),
442 Some(resource::TextureInner::Surface { raw }) => {
443 let raw_surface = self.raw(device.backend()).unwrap();
444 unsafe { raw_surface.discard_texture(raw) };
445 }
446 _ => unreachable!(),
447 }
448
449 Ok(())
450 }
451
452 pub fn release(self: &Arc<Self>) -> Result<(), SurfaceError> {
453 #[cfg(feature = "trace")]
454 if let Some(present) = self.presentation.lock().as_ref() {
455 if let Some(ref mut trace) = *present.device.trace.lock() {
456 trace.add(Action::ReleaseSurfaceTexture(self.to_trace()));
457 }
458 }
459 self.release_inner()
460 }
461
462 pub(crate) fn release_inner(&self) -> Result<(), SurfaceError> {
465 profiling::scope!("Surface::release");
466
467 let mut presentation = self.presentation.lock();
468 let Some(present) = presentation.as_mut() else {
469 return Err(SurfaceError::NotConfigured);
470 };
471
472 _ = present
477 .acquired_texture
478 .take()
479 .ok_or(SurfaceError::NothingToPresent)?;
480
481 Ok(())
482 }
483}