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