1use std::{borrow::Borrow, sync::Arc};
13
14#[cfg(feature = "trace")]
15use crate::device::trace::Action;
16use crate::{
17 conv,
18 device::any_device::AnyDevice,
19 device::{DeviceError, MissingDownlevelFlags, WaitIdleError},
20 global::Global,
21 hal_api::HalApi,
22 hal_label, id,
23 init_tracker::TextureInitTracker,
24 lock::{rank, Mutex, RwLock},
25 resource::{self, ResourceInfo},
26 snatch::Snatchable,
27 track,
28};
29
30use hal::{Queue as _, Surface as _};
31use thiserror::Error;
32use wgt::SurfaceStatus as Status;
33
34const FRAME_TIMEOUT_MS: u32 = 1000;
35
36#[derive(Debug)]
37pub(crate) struct Presentation {
38 pub(crate) device: AnyDevice,
39 pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>,
40 pub(crate) acquired_texture: Option<id::TextureId>,
41}
42
43#[derive(Clone, Debug, Error)]
44#[non_exhaustive]
45pub enum SurfaceError {
46 #[error("Surface is invalid")]
47 Invalid,
48 #[error("Surface is not configured for presentation")]
49 NotConfigured,
50 #[error(transparent)]
51 Device(#[from] DeviceError),
52 #[error("Surface image is already acquired")]
53 AlreadyAcquired,
54 #[error("Acquired frame is still referenced")]
55 StillReferenced,
56}
57
58#[derive(Clone, Debug, Error)]
59#[non_exhaustive]
60pub enum ConfigureSurfaceError {
61 #[error(transparent)]
62 Device(#[from] DeviceError),
63 #[error("Invalid surface")]
64 InvalidSurface,
65 #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
66 InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat),
67 #[error(transparent)]
68 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
69 #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")]
70 PreviousOutputExists,
71 #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")]
72 ZeroArea,
73 #[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}.")]
74 TooLarge {
75 width: u32,
76 height: u32,
77 max_texture_dimension_2d: u32,
78 },
79 #[error("Surface does not support the adapter's queue family")]
80 UnsupportedQueueFamily,
81 #[error("Requested format {requested:?} is not in list of supported formats: {available:?}")]
82 UnsupportedFormat {
83 requested: wgt::TextureFormat,
84 available: Vec<wgt::TextureFormat>,
85 },
86 #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")]
87 UnsupportedPresentMode {
88 requested: wgt::PresentMode,
89 available: Vec<wgt::PresentMode>,
90 },
91 #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")]
92 UnsupportedAlphaMode {
93 requested: wgt::CompositeAlphaMode,
94 available: Vec<wgt::CompositeAlphaMode>,
95 },
96 #[error("Requested usage is not supported")]
97 UnsupportedUsage,
98 #[error("Gpu got stuck :(")]
99 StuckGpu,
100}
101
102impl From<WaitIdleError> for ConfigureSurfaceError {
103 fn from(e: WaitIdleError) -> Self {
104 match e {
105 WaitIdleError::Device(d) => ConfigureSurfaceError::Device(d),
106 WaitIdleError::WrongSubmissionIndex(..) => unreachable!(),
107 WaitIdleError::StuckGpu => ConfigureSurfaceError::StuckGpu,
108 }
109 }
110}
111
112#[repr(C)]
113#[derive(Debug)]
114pub struct SurfaceOutput {
115 pub status: Status,
116 pub texture_id: Option<id::TextureId>,
117}
118
119impl Global {
120 pub fn surface_get_current_texture<A: HalApi>(
121 &self,
122 surface_id: id::SurfaceId,
123 texture_id_in: Option<id::TextureId>,
124 ) -> Result<SurfaceOutput, SurfaceError> {
125 profiling::scope!("SwapChain::get_next_texture");
126
127 let hub = A::hub(self);
128
129 let fid = hub.textures.prepare(texture_id_in);
130
131 let surface = self
132 .surfaces
133 .get(surface_id)
134 .map_err(|_| SurfaceError::Invalid)?;
135
136 let (device, config) = if let Some(ref present) = *surface.presentation.lock() {
137 match present.device.downcast_clone::<A>() {
138 Some(device) => {
139 if !device.is_valid() {
140 return Err(DeviceError::Lost.into());
141 }
142 (device, present.config.clone())
143 }
144 None => return Err(SurfaceError::NotConfigured),
145 }
146 } else {
147 return Err(SurfaceError::NotConfigured);
148 };
149
150 #[cfg(feature = "trace")]
151 if let Some(ref mut trace) = *device.trace.lock() {
152 trace.add(Action::GetSurfaceTexture {
153 id: fid.id(),
154 parent_id: surface_id,
155 });
156 }
157
158 let fence_guard = device.fence.read();
159 let fence = fence_guard.as_ref().unwrap();
160
161 let suf = A::surface_as_hal(surface.as_ref());
162 let (texture_id, status) = match unsafe {
163 suf.unwrap().acquire_texture(
164 Some(std::time::Duration::from_millis(FRAME_TIMEOUT_MS as u64)),
165 fence,
166 )
167 } {
168 Ok(Some(ast)) => {
169 drop(fence_guard);
170
171 let texture_desc = wgt::TextureDescriptor {
172 label: (),
173 size: wgt::Extent3d {
174 width: config.width,
175 height: config.height,
176 depth_or_array_layers: 1,
177 },
178 sample_count: 1,
179 mip_level_count: 1,
180 format: config.format,
181 dimension: wgt::TextureDimension::D2,
182 usage: config.usage,
183 view_formats: config.view_formats,
184 };
185 let hal_usage = conv::map_texture_usage(config.usage, config.format.into());
186 let format_features = wgt::TextureFormatFeatures {
187 allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT,
188 flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4
189 | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE,
190 };
191 let clear_view_desc = hal::TextureViewDescriptor {
192 label: hal_label(
193 Some("(wgpu internal) clear surface texture view"),
194 self.instance.flags,
195 ),
196 format: config.format,
197 dimension: wgt::TextureViewDimension::D2,
198 usage: hal::TextureUses::COLOR_TARGET,
199 range: wgt::ImageSubresourceRange::default(),
200 };
201 let clear_view = unsafe {
202 hal::Device::create_texture_view(
203 device.raw(),
204 ast.texture.borrow(),
205 &clear_view_desc,
206 )
207 }
208 .map_err(DeviceError::from)?;
209
210 let mut presentation = surface.presentation.lock();
211 let present = presentation.as_mut().unwrap();
212 let texture = resource::Texture {
213 inner: Snatchable::new(resource::TextureInner::Surface {
214 raw: Some(ast.texture),
215 parent_id: surface_id,
216 }),
217 device: device.clone(),
218 desc: texture_desc,
219 hal_usage,
220 format_features,
221 initialization_status: RwLock::new(
222 rank::TEXTURE_INITIALIZATION_STATUS,
223 TextureInitTracker::new(1, 1),
224 ),
225 full_range: track::TextureSelector {
226 layers: 0..1,
227 mips: 0..1,
228 },
229 info: ResourceInfo::new(
230 "<Surface Texture>",
231 Some(device.tracker_indices.textures.clone()),
232 ),
233 clear_mode: RwLock::new(
234 rank::TEXTURE_CLEAR_MODE,
235 resource::TextureClearMode::Surface {
236 clear_view: Some(clear_view),
237 },
238 ),
239 views: Mutex::new(rank::TEXTURE_VIEWS, Vec::new()),
240 bind_groups: Mutex::new(rank::TEXTURE_BIND_GROUPS, Vec::new()),
241 };
242
243 let (id, resource) = fid.assign(Arc::new(texture));
244 log::debug!("Created CURRENT Surface Texture {:?}", id);
245
246 {
247 let mut trackers = device.trackers.lock();
249 trackers
250 .textures
251 .insert_single(resource, hal::TextureUses::UNINITIALIZED);
252 }
253
254 if present.acquired_texture.is_some() {
255 return Err(SurfaceError::AlreadyAcquired);
256 }
257 present.acquired_texture = Some(id);
258
259 let status = if ast.suboptimal {
260 Status::Suboptimal
261 } else {
262 Status::Good
263 };
264 (Some(id), status)
265 }
266 Ok(None) => (None, Status::Timeout),
267 Err(err) => (
268 None,
269 match err {
270 hal::SurfaceError::Lost => Status::Lost,
271 hal::SurfaceError::Device(err) => {
272 return Err(DeviceError::from(err).into());
273 }
274 hal::SurfaceError::Outdated => Status::Outdated,
275 hal::SurfaceError::Other(msg) => {
276 log::error!("acquire error: {}", msg);
277 Status::Lost
278 }
279 },
280 ),
281 };
282
283 Ok(SurfaceOutput { status, texture_id })
284 }
285
286 pub fn surface_present<A: HalApi>(
287 &self,
288 surface_id: id::SurfaceId,
289 ) -> Result<Status, SurfaceError> {
290 profiling::scope!("SwapChain::present");
291
292 let hub = A::hub(self);
293
294 let surface = self
295 .surfaces
296 .get(surface_id)
297 .map_err(|_| SurfaceError::Invalid)?;
298
299 let mut presentation = surface.presentation.lock();
300 let present = match presentation.as_mut() {
301 Some(present) => present,
302 None => return Err(SurfaceError::NotConfigured),
303 };
304
305 let device = present.device.downcast_ref::<A>().unwrap();
306 if !device.is_valid() {
307 return Err(DeviceError::Lost.into());
308 }
309 let queue = device.get_queue().unwrap();
310
311 #[cfg(feature = "trace")]
312 if let Some(ref mut trace) = *device.trace.lock() {
313 trace.add(Action::Present(surface_id));
314 }
315
316 let result = {
317 let texture_id = present
318 .acquired_texture
319 .take()
320 .ok_or(SurfaceError::AlreadyAcquired)?;
321
322 log::debug!(
325 "Removing swapchain texture {:?} from the device tracker",
326 texture_id
327 );
328 let texture = hub.textures.unregister(texture_id);
329 if let Some(texture) = texture {
330 device
331 .trackers
332 .lock()
333 .textures
334 .remove(texture.info.tracker_index());
335 let mut exclusive_snatch_guard = device.snatchable_lock.write();
336 let suf = A::surface_as_hal(&surface);
337 let mut inner = texture.inner_mut(&mut exclusive_snatch_guard);
338 let inner = inner.as_mut().unwrap();
339
340 match *inner {
341 resource::TextureInner::Surface {
342 ref mut raw,
343 ref parent_id,
344 } => {
345 if surface_id != *parent_id {
346 log::error!("Presented frame is from a different surface");
347 Err(hal::SurfaceError::Lost)
348 } else {
349 unsafe {
350 queue
351 .raw
352 .as_ref()
353 .unwrap()
354 .present(suf.unwrap(), raw.take().unwrap())
355 }
356 }
357 }
358 _ => unreachable!(),
359 }
360 } else {
361 Err(hal::SurfaceError::Outdated) }
363 };
364
365 log::debug!("Presented. End of Frame");
366
367 match result {
368 Ok(()) => Ok(Status::Good),
369 Err(err) => match err {
370 hal::SurfaceError::Lost => Ok(Status::Lost),
371 hal::SurfaceError::Device(err) => Err(SurfaceError::from(DeviceError::from(err))),
372 hal::SurfaceError::Outdated => Ok(Status::Outdated),
373 hal::SurfaceError::Other(msg) => {
374 log::error!("acquire error: {}", msg);
375 Err(SurfaceError::Invalid)
376 }
377 },
378 }
379 }
380
381 pub fn surface_texture_discard<A: HalApi>(
382 &self,
383 surface_id: id::SurfaceId,
384 ) -> Result<(), SurfaceError> {
385 profiling::scope!("SwapChain::discard");
386
387 let hub = A::hub(self);
388
389 let surface = self
390 .surfaces
391 .get(surface_id)
392 .map_err(|_| SurfaceError::Invalid)?;
393 let mut presentation = surface.presentation.lock();
394 let present = match presentation.as_mut() {
395 Some(present) => present,
396 None => return Err(SurfaceError::NotConfigured),
397 };
398
399 let device = present.device.downcast_ref::<A>().unwrap();
400 if !device.is_valid() {
401 return Err(DeviceError::Lost.into());
402 }
403
404 #[cfg(feature = "trace")]
405 if let Some(ref mut trace) = *device.trace.lock() {
406 trace.add(Action::DiscardSurfaceTexture(surface_id));
407 }
408
409 {
410 let texture_id = present
411 .acquired_texture
412 .take()
413 .ok_or(SurfaceError::AlreadyAcquired)?;
414
415 log::debug!(
418 "Removing swapchain texture {:?} from the device tracker",
419 texture_id
420 );
421
422 let texture = hub.textures.unregister(texture_id);
423
424 if let Some(texture) = texture {
425 device
426 .trackers
427 .lock()
428 .textures
429 .remove(texture.info.tracker_index());
430 let suf = A::surface_as_hal(&surface);
431 let exclusive_snatch_guard = device.snatchable_lock.write();
432 match texture.inner.snatch(exclusive_snatch_guard).unwrap() {
433 resource::TextureInner::Surface { mut raw, parent_id } => {
434 if surface_id == parent_id {
435 unsafe { suf.unwrap().discard_texture(raw.take().unwrap()) };
436 } else {
437 log::warn!("Surface texture is outdated");
438 }
439 }
440 _ => unreachable!(),
441 }
442 }
443 }
444
445 Ok(())
446 }
447}