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