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