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
155pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
156
157#[repr(C)]
158#[derive(Debug)]
159pub struct SurfaceOutput<T = id::TextureId> {
160 pub status: Status,
161 pub texture: Option<T>,
162}
163
164impl Surface {
165 pub fn get_current_texture(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
166 profiling::scope!("Surface::get_current_texture");
167
168 let (device, config) = if let Some(ref present) = *self.presentation.lock() {
169 present.device.check_is_valid()?;
170 (present.device.clone(), present.config.clone())
171 } else {
172 return Err(SurfaceError::NotConfigured);
173 };
174
175 let suf = self.raw(device.backend()).unwrap();
176 let (texture, status) = match unsafe {
177 suf.acquire_texture(
178 Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
179 device.fence.as_ref(),
180 )
181 } {
182 Ok(ast) => {
183 let texture_desc = wgt::TextureDescriptor {
184 label: hal_label(
185 Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
186 device.instance_flags,
187 ),
188 size: wgt::Extent3d {
189 width: config.width,
190 height: config.height,
191 depth_or_array_layers: 1,
192 },
193 sample_count: 1,
194 mip_level_count: 1,
195 format: config.format,
196 dimension: wgt::TextureDimension::D2,
197 usage: config.usage,
198 view_formats: config.view_formats,
199 };
200 let format_features = wgt::TextureFormatFeatures {
201 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
202 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
203 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
204 };
205 let hal_usage = conv::map_texture_usage(
206 config.usage,
207 config.format.into(),
208 format_features.flags,
209 );
210 let clear_view_desc = hal::TextureViewDescriptor {
211 label: hal_label(
212 Some("(wgpu internal) clear surface texture view"),
213 device.instance_flags,
214 ),
215 format: config.format,
216 dimension: wgt::TextureViewDimension::D2,
217 usage: wgt::TextureUses::COLOR_TARGET,
218 range: wgt::ImageSubresourceRange::default(),
219 };
220 let clear_view = unsafe {
221 device
222 .raw()
223 .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
224 }
225 .map_err(|e| device.handle_hal_error(e))?;
226
227 let mut presentation = self.presentation.lock();
228 let present = presentation.as_mut().unwrap();
229 let texture = resource::Texture::new(
230 &device,
231 resource::TextureInner::Surface { raw: ast.texture },
232 hal_usage,
233 &texture_desc,
234 format_features,
235 resource::TextureClearMode::Surface {
236 clear_view: ManuallyDrop::new(clear_view),
237 },
238 true,
239 );
240
241 let texture = Arc::new(texture);
242
243 device
244 .trackers
245 .lock()
246 .textures
247 .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
248
249 if present.acquired_texture.is_some() {
250 return Err(SurfaceError::AlreadyAcquired);
251 }
252 present.acquired_texture = Some(texture.clone());
253
254 let status = if ast.suboptimal {
255 Status::Suboptimal
256 } else {
257 Status::Good
258 };
259 (Some(texture), status)
260 }
261 Err(err) => (
262 None,
263 match err {
264 hal::SurfaceError::Timeout => Status::Timeout,
265 hal::SurfaceError::Occluded => Status::Occluded,
266 hal::SurfaceError::Lost => Status::Lost,
267 hal::SurfaceError::Device(err) => {
268 return Err(device.handle_hal_error(err).into());
269 }
270 hal::SurfaceError::Outdated => Status::Outdated,
271 hal::SurfaceError::Other(msg) => {
272 log::error!("acquire error: {msg}");
273 Status::Lost
274 }
275 },
276 ),
277 };
278
279 Ok(ResolvedSurfaceOutput { status, texture })
280 }
281
282 pub fn present(&self) -> Result<Status, SurfaceError> {
283 profiling::scope!("Surface::present");
284
285 let presentation = self.presentation.lock();
286 let present = match presentation.as_ref() {
287 Some(present) => present,
288 None => return Err(SurfaceError::NotConfigured),
289 };
290
291 present.device.check_is_valid()?;
292 let queue = present
293 .device
294 .get_queue()
295 .ok_or(SurfaceError::Device(DeviceError::Lost))?;
296 drop(presentation);
297
298 queue.present(self)
299 }
300}
301
302impl Queue {
303 pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
304 profiling::scope!("Queue::present");
305
306 let texture = {
307 let mut presentation = surface.presentation.lock();
308 let present = match presentation.as_mut() {
309 Some(present) => present,
310 None => return Err(SurfaceError::NotConfigured),
311 };
312
313 let device = &self.device;
314
315 if !Arc::ptr_eq(&present.device, device) {
317 return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
318 crate::device::DeviceMismatch {
319 res: self.error_ident(),
320 res_device: device.error_ident(),
321 target: None,
322 target_device: present.device.error_ident(),
323 },
324 ))));
325 }
326
327 present
328 .acquired_texture
329 .take()
330 .ok_or(SurfaceError::NothingToPresent)?
331 };
332
333 self.prepare_surface_texture_for_present(&texture)?;
337
338 let device = &self.device;
339
340 let mut exclusive_snatch_guard = device.snatchable_lock.write();
341 let inner = texture
342 .state()
343 .ok()
344 .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
345 drop(exclusive_snatch_guard);
346
347 let result = match inner {
348 None => return Err(SurfaceError::TextureDestroyed),
349 Some(resource::TextureInner::Surface { raw }) => {
350 let raw_surface = surface.raw(device.backend()).unwrap();
351 let raw_queue = self.raw();
352 let _command_indices = device.command_indices.write();
356 unsafe { raw_queue.present(raw_surface, raw) }
357 }
358 _ => unreachable!(),
359 };
360
361 match result {
362 Ok(()) => Ok(Status::Good),
363 Err(err) => match err {
364 hal::SurfaceError::Timeout => Ok(Status::Timeout),
365 hal::SurfaceError::Occluded => Ok(Status::Occluded),
366 hal::SurfaceError::Lost => Ok(Status::Lost),
367 hal::SurfaceError::Device(err) => {
368 Err(SurfaceError::from(device.handle_hal_error(err)))
369 }
370 hal::SurfaceError::Outdated => Ok(Status::Outdated),
371 hal::SurfaceError::Other(msg) => {
372 log::error!("present error: {msg}");
373 Err(SurfaceError::Invalid)
374 }
375 },
376 }
377 }
378}
379
380impl Surface {
381 pub fn discard(&self) -> Result<(), SurfaceError> {
382 profiling::scope!("Surface::discard");
383
384 let mut presentation = self.presentation.lock();
385 let present = match presentation.as_mut() {
386 Some(present) => present,
387 None => return Err(SurfaceError::NotConfigured),
388 };
389
390 let device = &present.device;
391
392 device.check_is_valid()?;
393
394 let texture = present
395 .acquired_texture
396 .take()
397 .ok_or(SurfaceError::NothingToPresent)?;
398
399 let mut exclusive_snatch_guard = device.snatchable_lock.write();
400 let inner = texture
401 .state()
402 .ok()
403 .and_then(|state| state.inner.snatch(&mut exclusive_snatch_guard));
404 drop(exclusive_snatch_guard);
405
406 match inner {
407 None => return Err(SurfaceError::TextureDestroyed),
408 Some(resource::TextureInner::Surface { raw }) => {
409 let raw_surface = self.raw(device.backend()).unwrap();
410 unsafe { raw_surface.discard_texture(raw) };
411 }
412 _ => unreachable!(),
413 }
414
415 Ok(())
416 }
417
418 pub fn release(&self) -> Result<(), SurfaceError> {
421 profiling::scope!("Surface::release");
422
423 let mut presentation = self.presentation.lock();
424 let Some(present) = presentation.as_mut() else {
425 return Err(SurfaceError::NotConfigured);
426 };
427
428 _ = present
433 .acquired_texture
434 .take()
435 .ok_or(SurfaceError::NothingToPresent)?;
436
437 Ok(())
438 }
439}
440
441impl Global {
442 pub fn surface_get_current_texture(
443 &self,
444 surface_id: id::SurfaceId,
445 texture_id_in: Option<id::TextureId>,
446 ) -> Result<SurfaceOutput, SurfaceError> {
447 let surface = self.surfaces.get(surface_id);
448
449 let fid = self.hub.textures.prepare(texture_id_in);
450
451 let output = surface.get_current_texture()?;
452
453 #[cfg(feature = "trace")]
454 if let Some(present) = surface.presentation.lock().as_ref() {
455 if let Some(ref mut trace) = *present.device.trace.lock() {
456 if let Some(texture) = present.acquired_texture.as_ref() {
457 trace.add(Action::GetSurfaceTexture {
458 id: texture.to_trace(),
459 parent: surface.to_trace(),
460 });
461 }
462 }
463 }
464
465 let status = output.status;
466 let texture_id = output.texture.map(|texture| fid.assign(texture));
467
468 Ok(SurfaceOutput {
469 status,
470 texture: texture_id,
471 })
472 }
473
474 pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
475 let surface = self.surfaces.get(surface_id);
476
477 #[cfg(feature = "trace")]
478 if let Some(present) = surface.presentation.lock().as_ref() {
479 if let Some(ref mut trace) = *present.device.trace.lock() {
480 trace.add(Action::Present(surface.to_trace()));
481 }
482 }
483
484 surface.present()
485 }
486
487 pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
488 let surface = self.surfaces.get(surface_id);
489
490 #[cfg(feature = "trace")]
491 if let Some(present) = surface.presentation.lock().as_ref() {
492 if let Some(ref mut trace) = *present.device.trace.lock() {
493 trace.add(Action::DiscardSurfaceTexture(surface.to_trace()));
494 }
495 }
496
497 surface.discard()
498 }
499
500 pub fn surface_texture_release(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
501 let surface = self.surfaces.get(surface_id);
502
503 #[cfg(feature = "trace")]
504 if let Some(present) = surface.presentation.lock().as_ref() {
505 if let Some(ref mut trace) = *present.device.trace.lock() {
506 trace.add(Action::ReleaseSurfaceTexture(surface.to_trace()));
507 }
508 }
509
510 surface.release()
511 }
512}