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 global::Global,
21 hal_label, id,
22 instance::Surface,
23 resource::{self, Labeled},
24};
25
26use thiserror::Error;
27use wgt::{
28 error::{ErrorType, WebGpuError},
29 SurfaceStatus as Status,
30};
31
32const FRAME_TIMEOUT_MS: u32 = 1000;
33
34#[derive(Debug)]
35pub(crate) struct Presentation {
36 pub(crate) device: Arc<Device>,
37 pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>,
38 pub(crate) acquired_texture: Option<Arc<resource::Texture>>,
39}
40
41#[derive(Clone, Debug, Error)]
42#[non_exhaustive]
43pub enum SurfaceError {
44 #[error("Surface is invalid")]
45 Invalid,
46 #[error("Surface is not configured for presentation")]
47 NotConfigured,
48 #[error(transparent)]
49 Device(#[from] DeviceError),
50 #[error("Surface image is already acquired")]
51 AlreadyAcquired,
52 #[error("No surface image is currently acquired to present")]
53 NothingToPresent,
54 #[error("Texture has been destroyed")]
55 TextureDestroyed,
56}
57
58impl WebGpuError for SurfaceError {
59 fn webgpu_error_type(&self) -> ErrorType {
60 match self {
61 Self::Device(e) => e.webgpu_error_type(),
62 Self::Invalid
63 | Self::NotConfigured
64 | Self::AlreadyAcquired
65 | Self::NothingToPresent
66 | Self::TextureDestroyed => ErrorType::Validation,
67 }
68 }
69}
70
71#[derive(Clone, Debug, Error)]
72#[non_exhaustive]
73pub enum ConfigureSurfaceError {
74 #[error(transparent)]
75 Device(#[from] DeviceError),
76 #[error("Invalid surface")]
77 InvalidSurface,
78 #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
79 InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
80 #[error(transparent)]
81 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
82 #[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`.")]
83 PreviousOutputExists,
84 #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")]
85 GpuWaitTimeout,
86 #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
87 ZeroArea,
88 #[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}.")]
89 TooLarge {
90 width: u32,
91 height: u32,
92 max_texture_dimension_2d: u32,
93 },
94 #[error("Surface does not support the adapter's queue family")]
95 UnsupportedQueueFamily,
96 #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
97 UnsupportedFormat {
98 requested: wgt::TextureFormat,
99 available: Vec<wgt::TextureFormat>,
100 },
101 #[error("Requested color space {requested:?} is not in the list of color spaces supported for format {format:?}: {available:?}")]
102 UnsupportedColorSpace {
103 requested: wgt::SurfaceColorSpace,
104 format: wgt::TextureFormat,
105 available: wgt::SurfaceColorSpaces,
106 },
107 #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
108 UnsupportedPresentMode {
109 requested: wgt::PresentMode,
110 available: Vec<wgt::PresentMode>,
111 },
112 #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
113 UnsupportedAlphaMode {
114 requested: wgt::CompositeAlphaMode,
115 available: Vec<wgt::CompositeAlphaMode>,
116 },
117 #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
118 UnsupportedUsage {
119 requested: wgt::TextureUses,
120 available: wgt::TextureUses,
121 },
122}
123
124impl From<WaitIdleError> for ConfigureSurfaceError {
125 fn from(e: WaitIdleError) -> Self {
126 match e {
127 WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
128 WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
129 WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
130 }
131 }
132}
133
134impl WebGpuError for ConfigureSurfaceError {
135 fn webgpu_error_type(&self) -> ErrorType {
136 match self {
137 Self::Device(e) => e.webgpu_error_type(),
138 Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
139 Self::InvalidSurface
140 | Self::InvalidViewFormat(..)
141 | Self::PreviousOutputExists
142 | Self::GpuWaitTimeout
143 | Self::ZeroArea
144 | Self::TooLarge { .. }
145 | Self::UnsupportedQueueFamily
146 | Self::UnsupportedFormat { .. }
147 | Self::UnsupportedColorSpace { .. }
148 | Self::UnsupportedPresentMode { .. }
149 | Self::UnsupportedAlphaMode { .. }
150 | Self::UnsupportedUsage { .. } => ErrorType::Validation,
151 }
152 }
153}
154
155#[repr(C)]
156#[derive(Debug)]
157pub struct SurfaceOutput<T = Arc<resource::Texture>> {
158 pub status: Status,
159 pub texture: Option<T>,
160}
161
162impl Surface {
163 pub fn get_current_texture(self: &Arc<Self>) -> Result<SurfaceOutput, SurfaceError> {
164 let output = self.get_current_texture_inner();
165 #[cfg(feature = "trace")]
166 if let Some(present) = self.presentation.lock().as_ref() {
167 if let Some(ref mut trace) = *present.device.trace.lock() {
168 if let Some(texture) = present.acquired_texture.as_ref() {
169 trace.add(Action::GetSurfaceTexture {
170 id: texture.to_trace(),
171 parent: self.to_trace(),
172 });
173 }
174 }
175 }
176 output
177 }
178
179 pub(crate) fn get_current_texture_inner(&self) -> Result<SurfaceOutput, SurfaceError> {
180 profiling::scope!("Surface::get_current_texture");
181
182 let (device, config) = if let Some(ref present) = *self.presentation.lock() {
183 present.device.check_is_valid()?;
184 (present.device.clone(), present.config.clone())
185 } else {
186 return Err(SurfaceError::NotConfigured);
187 };
188
189 let suf = self.raw(device.backend()).unwrap();
190 let (texture, status) = match unsafe {
191 suf.acquire_texture(
192 Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
193 device.fence.as_ref(),
194 )
195 } {
196 Ok(ast) => {
197 let texture_desc = wgt::TextureDescriptor {
198 label: hal_label(
199 Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
200 device.instance_flags,
201 ),
202 size: wgt::Extent3d {
203 width: config.width,
204 height: config.height,
205 depth_or_array_layers: 1,
206 },
207 sample_count: 1,
208 mip_level_count: 1,
209 format: config.format,
210 dimension: wgt::TextureDimension::D2,
211 usage: config.usage,
212 view_formats: config.view_formats,
213 };
214 let format_features = wgt::TextureFormatFeatures {
215 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
216 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
217 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
218 };
219 let hal_usage = conv::map_texture_usage(
220 config.usage,
221 config.format.into(),
222 format_features.flags,
223 );
224 let clear_view_desc = hal::TextureViewDescriptor {
225 label: hal_label(
226 Some("(wgpu internal) clear surface texture view"),
227 device.instance_flags,
228 ),
229 format: config.format,
230 dimension: wgt::TextureViewDimension::D2,
231 usage: wgt::TextureUses::COLOR_TARGET,
232 range: wgt::ImageSubresourceRange::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}
484
485impl Global {
486 pub fn surface_get_current_texture(
487 &self,
488 surface_id: id::SurfaceId,
489 texture_id_in: Option<id::TextureId>,
490 ) -> Result<SurfaceOutput<id::TextureId>, SurfaceError> {
491 let surface = self.surfaces.get(surface_id);
492
493 let fid = self.hub.textures.prepare(texture_id_in);
494
495 let output = surface.get_current_texture()?;
496
497 let status = output.status;
498 let texture_id = output.texture.map(|texture| fid.assign(texture));
499
500 Ok(SurfaceOutput {
501 status,
502 texture: texture_id,
503 })
504 }
505
506 pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
507 let surface = self.surfaces.get(surface_id);
508
509 surface.present()
510 }
511
512 pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
513 let surface = self.surfaces.get(surface_id);
514
515 surface.discard()
516 }
517
518 pub fn surface_texture_release(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
519 let surface = self.surfaces.get(surface_id);
520
521 surface.release()
522 }
523}