1use alloc::{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::{Device, DeviceError, MissingDownlevelFlags, WaitIdleError},
20 global::Global,
21 hal_label, id,
22 instance::Surface,
23 resource,
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("Texture has been destroyed")]
53 TextureDestroyed,
54}
55
56impl WebGpuError for SurfaceError {
57 fn webgpu_error_type(&self) -> ErrorType {
58 match self {
59 Self::Device(e) => e.webgpu_error_type(),
60 Self::Invalid
61 | Self::NotConfigured
62 | Self::AlreadyAcquired
63 | Self::TextureDestroyed => ErrorType::Validation,
64 }
65 }
66}
67
68#[derive(Clone, Debug, Error)]
69#[non_exhaustive]
70pub enum ConfigureSurfaceError {
71 #[error(transparent)]
72 Device(#[from] DeviceError),
73 #[error("Invalid surface")]
74 InvalidSurface,
75 #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
76 InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
77 #[error(transparent)]
78 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
79 #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")]
80 PreviousOutputExists,
81 #[error("Failed to wait for GPU to come idle before reconfiguring the Surface")]
82 GpuWaitTimeout,
83 #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
84 ZeroArea,
85 #[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}.")]
86 TooLarge {
87 width: u32,
88 height: u32,
89 max_texture_dimension_2d: u32,
90 },
91 #[error("Surface does not support the adapter's queue family")]
92 UnsupportedQueueFamily,
93 #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
94 UnsupportedFormat {
95 requested: wgt::TextureFormat,
96 available: Vec<wgt::TextureFormat>,
97 },
98 #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
99 UnsupportedPresentMode {
100 requested: wgt::PresentMode,
101 available: Vec<wgt::PresentMode>,
102 },
103 #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
104 UnsupportedAlphaMode {
105 requested: wgt::CompositeAlphaMode,
106 available: Vec<wgt::CompositeAlphaMode>,
107 },
108 #[error("Requested usage {requested:?} is not in the list of supported usages: {available:?}")]
109 UnsupportedUsage {
110 requested: wgt::TextureUses,
111 available: wgt::TextureUses,
112 },
113}
114
115impl From<WaitIdleError> for ConfigureSurfaceError {
116 fn from(e: WaitIdleError) -> Self {
117 match e {
118 WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
119 WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
120 WaitIdleError::Timeout => ConfigureSurfaceError::GpuWaitTimeout,
121 }
122 }
123}
124
125impl WebGpuError for ConfigureSurfaceError {
126 fn webgpu_error_type(&self) -> ErrorType {
127 match self {
128 Self::Device(e) => e.webgpu_error_type(),
129 Self::MissingDownlevelFlags(e) => e.webgpu_error_type(),
130 Self::InvalidSurface
131 | Self::InvalidViewFormat(..)
132 | Self::PreviousOutputExists
133 | Self::GpuWaitTimeout
134 | Self::ZeroArea
135 | Self::TooLarge { .. }
136 | Self::UnsupportedQueueFamily
137 | Self::UnsupportedFormat { .. }
138 | Self::UnsupportedPresentMode { .. }
139 | Self::UnsupportedAlphaMode { .. }
140 | Self::UnsupportedUsage { .. } => ErrorType::Validation,
141 }
142 }
143}
144
145pub type ResolvedSurfaceOutput = SurfaceOutput<Arc<resource::Texture>>;
146
147#[repr(C)]
148#[derive(Debug)]
149pub struct SurfaceOutput<T = id::TextureId> {
150 pub status: Status,
151 pub texture: Option<T>,
152}
153
154impl Surface {
155 pub fn get_current_texture(&self) -> Result<ResolvedSurfaceOutput, SurfaceError> {
156 profiling::scope!("Surface::get_current_texture");
157
158 let (device, config) = if let Some(ref present) = *self.presentation.lock() {
159 present.device.check_is_valid()?;
160 (present.device.clone(), present.config.clone())
161 } else {
162 return Err(SurfaceError::NotConfigured);
163 };
164
165 let fence = device.fence.read();
166
167 let suf = self.raw(device.backend()).unwrap();
168 let (texture, status) = match unsafe {
169 suf.acquire_texture(
170 Some(core::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
171 fence.as_ref(),
172 )
173 } {
174 Ok(ast) => {
175 drop(fence);
176
177 let texture_desc = wgt::TextureDescriptor {
178 label: hal_label(
179 Some(alloc::borrow::Cow::Borrowed("<Surface Texture>")),
180 device.instance_flags,
181 ),
182 size: wgt::Extent3d {
183 width: config.width,
184 height: config.height,
185 depth_or_array_layers: 1,
186 },
187 sample_count: 1,
188 mip_level_count: 1,
189 format: config.format,
190 dimension: wgt::TextureDimension::D2,
191 usage: config.usage,
192 view_formats: config.view_formats,
193 };
194 let format_features = wgt::TextureFormatFeatures {
195 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
196 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
197 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
198 };
199 let hal_usage = conv::map_texture_usage(
200 config.usage,
201 config.format.into(),
202 format_features.flags,
203 );
204 let clear_view_desc = hal::TextureViewDescriptor {
205 label: hal_label(
206 Some("(wgpu internal) clear surface texture view"),
207 device.instance_flags,
208 ),
209 format: config.format,
210 dimension: wgt::TextureViewDimension::D2,
211 usage: wgt::TextureUses::COLOR_TARGET,
212 range: wgt::ImageSubresourceRange::default(),
213 };
214 let clear_view = unsafe {
215 device
216 .raw()
217 .create_texture_view(ast.texture.as_ref().borrow(), &clear_view_desc)
218 }
219 .map_err(|e| device.handle_hal_error(e))?;
220
221 let mut presentation = self.presentation.lock();
222 let present = presentation.as_mut().unwrap();
223 let texture = resource::Texture::new(
224 &device,
225 resource::TextureInner::Surface { raw: ast.texture },
226 hal_usage,
227 &texture_desc,
228 format_features,
229 resource::TextureClearMode::Surface {
230 clear_view: ManuallyDrop::new(clear_view),
231 },
232 true,
233 );
234
235 let texture = Arc::new(texture);
236
237 device
238 .trackers
239 .lock()
240 .textures
241 .insert_single(&texture, wgt::TextureUses::UNINITIALIZED);
242
243 if present.acquired_texture.is_some() {
244 return Err(SurfaceError::AlreadyAcquired);
245 }
246 present.acquired_texture = Some(texture.clone());
247
248 let status = if ast.suboptimal {
249 Status::Suboptimal
250 } else {
251 Status::Good
252 };
253 (Some(texture), status)
254 }
255 Err(err) => (
256 None,
257 match err {
258 hal::SurfaceError::Timeout => Status::Timeout,
259 hal::SurfaceError::Occluded => Status::Occluded,
260 hal::SurfaceError::Lost => Status::Lost,
261 hal::SurfaceError::Device(err) => {
262 return Err(device.handle_hal_error(err).into());
263 }
264 hal::SurfaceError::Outdated => Status::Outdated,
265 hal::SurfaceError::Other(msg) => {
266 log::error!("acquire error: {msg}");
267 Status::Lost
268 }
269 },
270 ),
271 };
272
273 Ok(ResolvedSurfaceOutput { status, texture })
274 }
275
276 pub fn present(&self) -> Result<Status, SurfaceError> {
277 profiling::scope!("Surface::present");
278
279 let mut presentation = self.presentation.lock();
280 let present = match presentation.as_mut() {
281 Some(present) => present,
282 None => return Err(SurfaceError::NotConfigured),
283 };
284
285 let device = &present.device;
286
287 device.check_is_valid()?;
288 let queue = device.get_queue().unwrap();
289
290 let texture = present
291 .acquired_texture
292 .take()
293 .ok_or(SurfaceError::AlreadyAcquired)?;
294
295 let mut exclusive_snatch_guard = device.snatchable_lock.write();
296 let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
297 drop(exclusive_snatch_guard);
298
299 let result = match inner {
300 None => return Err(SurfaceError::TextureDestroyed),
301 Some(resource::TextureInner::Surface { raw }) => {
302 let raw_surface = self.raw(device.backend()).unwrap();
303 let raw_queue = queue.raw();
304 let _fence_lock = device.fence.write();
305 unsafe { raw_queue.present(raw_surface, raw) }
306 }
307 _ => unreachable!(),
308 };
309
310 match result {
311 Ok(()) => Ok(Status::Good),
312 Err(err) => match err {
313 hal::SurfaceError::Timeout => Ok(Status::Timeout),
314 hal::SurfaceError::Occluded => Ok(Status::Occluded),
315 hal::SurfaceError::Lost => Ok(Status::Lost),
316 hal::SurfaceError::Device(err) => {
317 Err(SurfaceError::from(device.handle_hal_error(err)))
318 }
319 hal::SurfaceError::Outdated => Ok(Status::Outdated),
320 hal::SurfaceError::Other(msg) => {
321 log::error!("present error: {msg}");
322 Err(SurfaceError::Invalid)
323 }
324 },
325 }
326 }
327
328 pub fn discard(&self) -> Result<(), SurfaceError> {
329 profiling::scope!("Surface::discard");
330
331 let mut presentation = self.presentation.lock();
332 let present = match presentation.as_mut() {
333 Some(present) => present,
334 None => return Err(SurfaceError::NotConfigured),
335 };
336
337 let device = &present.device;
338
339 device.check_is_valid()?;
340
341 let texture = present
342 .acquired_texture
343 .take()
344 .ok_or(SurfaceError::AlreadyAcquired)?;
345
346 let mut exclusive_snatch_guard = device.snatchable_lock.write();
347 let inner = texture.inner.snatch(&mut exclusive_snatch_guard);
348 drop(exclusive_snatch_guard);
349
350 match inner {
351 None => return Err(SurfaceError::TextureDestroyed),
352 Some(resource::TextureInner::Surface { raw }) => {
353 let raw_surface = self.raw(device.backend()).unwrap();
354 unsafe { raw_surface.discard_texture(raw) };
355 }
356 _ => unreachable!(),
357 }
358
359 Ok(())
360 }
361}
362
363impl Global {
364 pub fn surface_get_current_texture(
365 &self,
366 surface_id: id::SurfaceId,
367 texture_id_in: Option<id::TextureId>,
368 ) -> Result<SurfaceOutput, SurfaceError> {
369 let surface = self.surfaces.get(surface_id);
370
371 let fid = self.hub.textures.prepare(texture_id_in);
372
373 let output = surface.get_current_texture()?;
374
375 #[cfg(feature = "trace")]
376 if let Some(present) = surface.presentation.lock().as_ref() {
377 if let Some(ref mut trace) = *present.device.trace.lock() {
378 if let Some(texture) = present.acquired_texture.as_ref() {
379 trace.add(Action::GetSurfaceTexture {
380 id: texture.to_trace(),
381 parent: surface.to_trace(),
382 });
383 }
384 }
385 }
386
387 let status = output.status;
388 let texture_id = output
389 .texture
390 .map(|texture| fid.assign(resource::Fallible::Valid(texture)));
391
392 Ok(SurfaceOutput {
393 status,
394 texture: texture_id,
395 })
396 }
397
398 pub fn surface_present(&self, surface_id: id::SurfaceId) -> Result<Status, SurfaceError> {
399 let surface = self.surfaces.get(surface_id);
400
401 #[cfg(feature = "trace")]
402 if let Some(present) = surface.presentation.lock().as_ref() {
403 if let Some(ref mut trace) = *present.device.trace.lock() {
404 trace.add(Action::Present(surface.to_trace()));
405 }
406 }
407
408 surface.present()
409 }
410
411 pub fn surface_texture_discard(&self, surface_id: id::SurfaceId) -> Result<(), SurfaceError> {
412 let surface = self.surfaces.get(surface_id);
413
414 #[cfg(feature = "trace")]
415 if let Some(present) = surface.presentation.lock().as_ref() {
416 if let Some(ref mut trace) = *present.device.trace.lock() {
417 trace.add(Action::DiscardSurfaceTexture(surface.to_trace()));
418 }
419 }
420
421 surface.discard()
422 }
423}