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 present mode {requested:?} is not in the list of supported present modes: {available:?}")]
102 UnsupportedPresentMode {
103 requested: wgt::PresentMode,
104 available: Vec<wgt::PresentMode>,
105 },
106 #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
107 UnsupportedAlphaMode {
108 requested: wgt::CompositeAlphaMode,
109 available: Vec<wgt::CompositeAlphaMode>,
110 },
111 #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
112 UnsupportedUsage {
113 requested: wgt::TextureUses,
114 available: wgt::TextureUses,
115 },
116}
117
118impl From<WaitIdleError> for ConfigureSurfaceError {
119 fn from(e: WaitIdleError) -> Self {
120 match e {
121 WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
122 WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
123 WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
124 }
125 }
126}
127
128impl WebGpuError for ConfigureSurfaceError {
129 fn webgpu_error_type(&self) -> ErrorType {
130 match self {
131 Self::Device(e) => e.webgpu_error_type(),
132 Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
133 Self::InvalidSurface
134 | Self::InvalidViewFormat(..)
135 | Self::PreviousOutputExists
136 | Self::GpuWaitTimeout
137 | Self::ZeroArea
138 | Self::TooLarge { .. }
139 | Self::UnsupportedQueueFamily
140 | Self::UnsupportedFormat { .. }
141 | Self::UnsupportedPresentMode { .. }
142 | Self::UnsupportedAlphaMode { .. }
143 | Self::UnsupportedUsage { .. } => ErrorType::Validation,
144 }
145 }
146}
147
148pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
149
150#[repr(C)]
151#[derive(Debug)]
152pub struct SurfaceOutput<T = id::TextureId> {
153 pub status: Status,
154 pub texture: Option<T>,
155}
156
157impl Surface {
158 pub fn get_current_texture(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
159 profiling::scope!("Surface::get_current_texture");
160
161 let (device, config) = if let Some(ref present) = *self.presentation.lock() {
162 present.device.check_is_valid()?;
163 (present.device.clone(), present.config.clone())
164 } else {
165 return Err(SurfaceError::NotConfigured);
166 };
167
168 let fence = device.fence.read();
169
170 let suf = self.raw(device.backend()).unwrap();
171 let (texture, status) = match unsafe {
172 suf.acquire_texture(
173 Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
174 fence.as_ref(),
175 )
176 } {
177 Ok(ast) => {
178 drop(fence);
179
180 let texture_desc = wgt::TextureDescriptor {
181 label: hal_label(
182 Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
183 device.instance_flags,
184 ),
185 size: wgt::Extent3d {
186 width: config.width,
187 height: config.height,
188 depth_or_array_layers: 1,
189 },
190 sample_count: 1,
191 mip_level_count: 1,
192 format: config.format,
193 dimension: wgt::TextureDimension::D2,
194 usage: config.usage,
195 view_formats: config.view_formats,
196 };
197 let format_features = wgt::TextureFormatFeatures {
198 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
199 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
200 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
201 };
202 let hal_usage = conv::map_texture_usage(
203 config.usage,
204 config.format.into(),
205 format_features.flags,
206 );
207 let clear_view_desc = hal::TextureViewDescriptor {
208 label: hal_label(
209 Some("(wgpu internal) clear surface texture view"),
210 device.instance_flags,
211 ),
212 format: config.format,
213 dimension: wgt::TextureViewDimension::D2,
214 usage: wgt::TextureUses::COLOR_TARGET,
215 range: wgt::ImageSubresourceRange::default(),
216 };
217 let clear_view = unsafe {
218 device
219 .raw()
220 .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
221 }
222 .map_err(|e| device.handle_hal_error(e))?;
223
224 let mut presentation = self.presentation.lock();
225 let present = presentation.as_mut().unwrap();
226 let texture = resource::Texture::new(
227 &device,
228 resource::TextureInner::Surface { raw: ast.texture },
229 hal_usage,
230 &texture_desc,
231 format_features,
232 resource::TextureClearMode::Surface {
233 clear_view: ManuallyDrop::new(clear_view),
234 },
235 true,
236 );
237
238 let texture = Arc::new(texture);
239
240 device
241 .trackers
242 .lock()
243 .textures
244 .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
245
246 if present.acquired_texture.is_some() {
247 return Err(SurfaceError::AlreadyAcquired);
248 }
249 present.acquired_texture = Some(texture.clone());
250
251 let status = if ast.suboptimal {
252 Status::Suboptimal
253 } else {
254 Status::Good
255 };
256 (Some(texture), status)
257 }
258 Err(err) => (
259 None,
260 match err {
261 hal::SurfaceError::Timeout => Status::Timeout,
262 hal::SurfaceError::Occluded => Status::Occluded,
263 hal::SurfaceError::Lost => Status::Lost,
264 hal::SurfaceError::Device(err) => {
265 return Err(device.handle_hal_error(err).into());
266 }
267 hal::SurfaceError::Outdated => Status::Outdated,
268 hal::SurfaceError::Other(msg) => {
269 log::error!("acquire error: {msg}");
270 Status::Lost
271 }
272 },
273 ),
274 };
275
276 Ok(ResolvedSurfaceOutput { status, texture })
277 }
278
279 pub fn present(&self) -> Result<Status, SurfaceError> {
280 profiling::scope!("Surface::present");
281
282 let presentation = self.presentation.lock();
283 let present = match presentation.as_ref() {
284 Some(present) => present,
285 None => return Err(SurfaceError::NotConfigured),
286 };
287
288 present.device.check_is_valid()?;
289 let queue = present
290 .device
291 .get_queue()
292 .ok_or(SurfaceError::Device(DeviceError::Lost))?;
293 drop(presentation);
294
295 queue.present(self)
296 }
297}
298
299impl Queue {
300 pub fn present(&self, surface: &Surface) -> Result<Status, SurfaceError> {
301 profiling::scope!("Queue::present");
302
303 let texture = {
304 let mut presentation = surface.presentation.lock();
305 let present = match presentation.as_mut() {
306 Some(present) => present,
307 None => return Err(SurfaceError::NotConfigured),
308 };
309
310 let device = &self.device;
311
312 if !Arc::ptr_eq(&present.device, device) {
314 return Err(SurfaceError::Device(DeviceError::DeviceMismatch(Box::new(
315 crate::device::DeviceMismatch {
316 res: self.error_ident(),
317 res_device: device.error_ident(),
318 target: None,
319 target_device: present.device.error_ident(),
320 },
321 ))));
322 }
323
324 present
325 .acquired_texture
326 .take()
327 .ok_or(SurfaceError::NothingToPresent)?
328 };
329
330 self.prepare_surface_texture_for_present(&texture)?;
334
335 let device = &self.device;
336
337 let mut exclusive_snatch_guard = device.snatchable_lock.write();
338 let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
339 drop(exclusive_snatch_guard);
340
341 let result = match inner {
342 None => return Err(SurfaceError::TextureDestroyed),
343 Some(resource::TextureInner::Surface { raw }) => {
344 let raw_surface = surface.raw(device.backend()).unwrap();
345 let raw_queue = self.raw();
346 let _fence_lock = device.fence.write();
347 unsafe { raw_queue.present(raw_surface, raw) }
348 }
349 _ => unreachable!(),
350 };
351
352 match result {
353 Ok(()) => Ok(Status::Good),
354 Err(err) => match err {
355 hal::SurfaceError::Timeout => Ok(Status::Timeout),
356 hal::SurfaceError::Occluded => Ok(Status::Occluded),
357 hal::SurfaceError::Lost => Ok(Status::Lost),
358 hal::SurfaceError::Device(err) => {
359 Err(SurfaceError::from(device.handle_hal_error(err)))
360 }
361 hal::SurfaceError::Outdated => Ok(Status::Outdated),
362 hal::SurfaceError::Other(msg) => {
363 log::error!("present error: {msg}");
364 Err(SurfaceError::Invalid)
365 }
366 },
367 }
368 }
369}
370
371impl Surface {
372 pub fn discard(&self) -> Result<(), SurfaceError> {
373 profiling::scope!("Surface::discard");
374
375 let mut presentation = self.presentation.lock();
376 let present = match presentation.as_mut() {
377 Some(present) => present,
378 None => return Err(SurfaceError::NotConfigured),
379 };
380
381 let device = &present.device;
382
383 device.check_is_valid()?;
384
385 let texture = present
386 .acquired_texture
387 .take()
388 .ok_or(SurfaceError::NothingToPresent)?;
389
390 let mut exclusive_snatch_guard = device.snatchable_lock.write();
391 let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
392 drop(exclusive_snatch_guard);
393
394 match inner {
395 None => return Err(SurfaceError::TextureDestroyed),
396 Some(resource::TextureInner::Surface { raw }) => {
397 let raw_surface = self.raw(device.backend()).unwrap();
398 unsafe { raw_surface.discard_texture(raw) };
399 }
400 _ => unreachable!(),
401 }
402
403 Ok(())
404 }
405}
406
407impl Global {
408 pub fn surface_get_current_texture(
409 &self,
410 surface_id: id::SurfaceId,
411 texture_id_in: Option<id::TextureId>,
412 ) -> Result<SurfaceOutput, SurfaceError> {
413 let surface = self.surfaces.get(surface_id);
414
415 let fid = self.hub.textures.prepare(texture_id_in);
416
417 let output = surface.get_current_texture()?;
418
419 #[cfg(feature = "trace")]
420 if let Some(present) = surface.presentation.lock().as_ref() {
421 if let Some(ref mut trace) = *present.device.trace.lock() {
422 if let Some(texture) = present.acquired_texture.as_ref() {
423 trace.add(Action::GetSurfaceTexture {
424 id: texture.to_trace(),
425 parent: surface.to_trace(),
426 });
427 }
428 }
429 }
430
431 let status = output.status;
432 let texture_id = output
433 .texture
434 .map(|texture| fid.assign(resource::Fallible::Valid(texture)));
435
436 Ok(SurfaceOutput {
437 status,
438 texture: texture_id,
439 })
440 }
441
442 pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
443 let surface = self.surfaces.get(surface_id);
444
445 #[cfg(feature = "trace")]
446 if let Some(present) = surface.presentation.lock().as_ref() {
447 if let Some(ref mut trace) = *present.device.trace.lock() {
448 trace.add(Action::Present(surface.to_trace()));
449 }
450 }
451
452 surface.present()
453 }
454
455 pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
456 let surface = self.surfaces.get(surface_id);
457
458 #[cfg(feature = "trace")]
459 if let Some(present) = surface.presentation.lock().as_ref() {
460 if let Some(ref mut trace) = *present.device.trace.lock() {
461 trace.add(Action::DiscardSurfaceTexture(surface.to_trace()));
462 }
463 }
464
465 surface.discard()
466 }
467}