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