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