wgpu/api/surface.rs
1use alloc::{boxed::Box, string::String, vec, vec::Vec};
2#[cfg(wgpu_core)]
3use core::ops::Deref;
4use core::{error, fmt};
5
6use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
7
8use crate::util::Mutex;
9use crate::*;
10
11/// Describes a [`Surface`].
12///
13/// For use with [`Surface::configure`].
14///
15/// Corresponds to [WebGPU `GPUCanvasConfiguration`](
16/// https://gpuweb.github.io/gpuweb/#canvas-configuration).
17pub type SurfaceConfiguration = wgt::SurfaceConfiguration<Vec<TextureFormat>>;
18static_assertions::assert_impl_all!(SurfaceConfiguration: Send, Sync);
19
20/// Handle to a presentable surface.
21///
22/// A `Surface` represents a platform-specific surface (e.g. a window) onto which rendered images may
23/// be presented. A `Surface` may be created with the function [`Instance::create_surface`].
24///
25/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification,
26/// [`GPUCanvasContext`](https://gpuweb.github.io/gpuweb/#canvas-context)
27/// serves a similar role.
28pub struct Surface<'window> {
29 /// Additional surface data returned by [`DynContext::instance_create_surface`].
30 pub(crate) inner: dispatch::DispatchSurface,
31
32 // Stores the latest `SurfaceConfiguration` that was set using `Surface::configure`.
33 // It is required to set the attributes of the `SurfaceTexture` in the
34 // `Surface::get_current_texture` method.
35 // Because the `Surface::configure` method operates on an immutable reference this type has to
36 // be wrapped in a mutex and since the configuration is only supplied after the surface has
37 // been created is is additionally wrapped in an option.
38 pub(crate) config: Mutex<Option<SurfaceConfiguration>>,
39
40 /// Optionally, keep the source of the handle used for the surface alive.
41 ///
42 /// This is useful for platforms where the surface is created from a window and the surface
43 /// would become invalid when the window is dropped.
44 ///
45 /// SAFETY: This field must be dropped *after* all other fields to ensure proper cleanup.
46 pub(crate) _handle_source: Option<Box<dyn WindowHandle + 'window>>,
47}
48
49impl Surface<'_> {
50 /// Returns the capabilities of the surface when used with the given adapter.
51 ///
52 /// Returns specified values (see [`SurfaceCapabilities`]) if surface is incompatible with the adapter.
53 pub fn get_capabilities(&self, adapter: &Adapter) -> SurfaceCapabilities {
54 self.inner.get_capabilities(&adapter.inner)
55 }
56
57 /// Return a default `SurfaceConfiguration` from width and height to use for the [`Surface`] with this adapter.
58 ///
59 /// Returns None if the surface isn't supported by this adapter
60 pub fn get_default_config(
61 &self,
62 adapter: &Adapter,
63 width: u32,
64 height: u32,
65 ) -> Option<SurfaceConfiguration> {
66 let caps = self.get_capabilities(adapter);
67 Some(SurfaceConfiguration {
68 usage: wgt::TextureUsages::RENDER_ATTACHMENT,
69 format: *caps.formats.first()?,
70 width,
71 height,
72 desired_maximum_frame_latency: 2,
73 present_mode: *caps.present_modes.first()?,
74 alpha_mode: wgt::CompositeAlphaMode::Auto,
75 view_formats: vec![],
76 })
77 }
78
79 /// Initializes [`Surface`] for presentation.
80 ///
81 /// If the surface is already configured, this will wait for the GPU to come idle
82 /// before recreating the swapchain to prevent race conditions.
83 ///
84 /// # Validation Errors
85 /// - Submissions that happen _during_ the configure may cause the
86 /// internal wait-for-idle to fail, raising a validation error.
87 ///
88 /// # Panics
89 ///
90 /// - A old [`SurfaceTexture`] is still alive referencing an old surface.
91 /// - Texture format requested is unsupported on the surface.
92 /// - `config.width` or `config.height` is zero.
93 pub fn configure(&self, device: &Device, config: &SurfaceConfiguration) {
94 self.inner.configure(&device.inner, config);
95
96 let mut conf = self.config.lock();
97 *conf = Some(config.clone());
98 }
99
100 /// Returns the next texture to be presented by the swapchain for drawing.
101 ///
102 /// In order to present the [`SurfaceTexture`] returned by this method,
103 /// first a [`Queue::submit`] needs to be done with some work rendering to this texture.
104 /// Then [`SurfaceTexture::present`] needs to be called.
105 ///
106 /// If a SurfaceTexture referencing this surface is alive when the swapchain is recreated,
107 /// recreating the swapchain will panic.
108 pub fn get_current_texture(&self) -> Result<SurfaceTexture, SurfaceError> {
109 let (texture, status, detail) = self.inner.get_current_texture();
110
111 let suboptimal = match status {
112 SurfaceStatus::Good => false,
113 SurfaceStatus::Suboptimal => true,
114 SurfaceStatus::Timeout => return Err(SurfaceError::Timeout),
115 SurfaceStatus::Outdated => return Err(SurfaceError::Outdated),
116 SurfaceStatus::Lost => return Err(SurfaceError::Lost),
117 SurfaceStatus::Unknown => return Err(SurfaceError::Other),
118 };
119
120 let guard = self.config.lock();
121 let config = guard
122 .as_ref()
123 .expect("This surface has not been configured yet.");
124
125 let descriptor = TextureDescriptor {
126 label: None,
127 size: Extent3d {
128 width: config.width,
129 height: config.height,
130 depth_or_array_layers: 1,
131 },
132 format: config.format,
133 usage: config.usage,
134 mip_level_count: 1,
135 sample_count: 1,
136 dimension: TextureDimension::D2,
137 view_formats: &[],
138 };
139
140 texture
141 .map(|texture| SurfaceTexture {
142 texture: Texture {
143 inner: texture,
144 descriptor,
145 },
146 suboptimal,
147 presented: false,
148 detail,
149 })
150 .ok_or(SurfaceError::Lost)
151 }
152
153 /// Get the [`wgpu_hal`] surface from this `Surface`.
154 ///
155 /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`],
156 /// and pass that struct to the to the `A` type parameter.
157 ///
158 /// Returns a guard that dereferences to the type of the hal backend
159 /// which implements [`A::Surface`].
160 ///
161 /// # Types
162 ///
163 /// The returned type depends on the backend:
164 ///
165 #[doc = crate::hal_type_vulkan!("Surface")]
166 #[doc = crate::hal_type_metal!("Surface")]
167 #[doc = crate::hal_type_dx12!("Surface")]
168 #[doc = crate::hal_type_gles!("Surface")]
169 ///
170 /// # Errors
171 ///
172 /// This method will return None if:
173 /// - The surface is not from the backend specified by `A`.
174 /// - The surface is from the `webgpu` or `custom` backend.
175 ///
176 /// # Safety
177 ///
178 /// - The returned resource must not be destroyed unless the guard
179 /// is the last reference to it and it is not in use by the GPU.
180 /// The guard and handle may be dropped at any time however.
181 /// - All the safety requirements of wgpu-hal must be upheld.
182 ///
183 /// [`A::Surface`]: hal::Api::Surface
184 #[cfg(wgpu_core)]
185 pub unsafe fn as_hal<A: hal::Api>(
186 &self,
187 ) -> Option<impl Deref<Target = A::Surface> + WasmNotSendSync> {
188 let core_surface = self.inner.as_core_opt()?;
189
190 unsafe { core_surface.context.surface_as_hal::<A>(core_surface) }
191 }
192
193 #[cfg(custom)]
194 /// Returns custom implementation of Surface (if custom backend and is internally T)
195 pub fn as_custom<T: custom::SurfaceInterface>(&self) -> Option<&T> {
196 self.inner.as_custom()
197 }
198}
199
200// This custom implementation is required because [`Surface::_surface`] doesn't
201// require [`Debug`](fmt::Debug), which we should not require from the user.
202impl fmt::Debug for Surface<'_> {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 f.debug_struct("Surface")
205 .field(
206 "_handle_source",
207 &if self._handle_source.is_some() {
208 "Some"
209 } else {
210 "None"
211 },
212 )
213 .field("inner", &self.inner)
214 .field("config", &self.config)
215 .finish()
216 }
217}
218
219#[cfg(send_sync)]
220static_assertions::assert_impl_all!(Surface<'_>: Send, Sync);
221
222crate::cmp::impl_eq_ord_hash_proxy!(Surface<'_> => .inner);
223
224/// Super trait for window handles as used in [`SurfaceTarget`].
225pub trait WindowHandle: HasWindowHandle + HasDisplayHandle + WasmNotSendSync {}
226
227impl<T> WindowHandle for T where T: HasWindowHandle + HasDisplayHandle + WasmNotSendSync {}
228
229/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with safe surface creation.
230///
231/// This is either a window or an actual web canvas depending on the platform and
232/// enabled features.
233/// Refer to the individual variants for more information.
234///
235/// See also [`SurfaceTargetUnsafe`] for unsafe variants.
236#[non_exhaustive]
237pub enum SurfaceTarget<'window> {
238 /// Window handle producer.
239 ///
240 /// If the specified display and window handle are not supported by any of the backends, then the surface
241 /// will not be supported by any adapters.
242 ///
243 /// # Errors
244 ///
245 /// - On WebGL2: surface creation returns an error if the browser does not support WebGL2,
246 /// or declines to provide GPU access (such as due to a resource shortage).
247 ///
248 /// # Panics
249 ///
250 /// - On macOS/Metal: will panic if not called on the main thread.
251 /// - On web: will panic if the `raw_window_handle` does not properly refer to a
252 /// canvas element.
253 Window(Box<dyn WindowHandle + 'window>),
254
255 /// Surface from a `web_sys::HtmlCanvasElement`.
256 ///
257 /// The `canvas` argument must be a valid `<canvas>` element to
258 /// create a surface upon.
259 ///
260 /// # Errors
261 ///
262 /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
263 /// or declines to provide GPU access (such as due to a resource shortage).
264 #[cfg(web)]
265 Canvas(web_sys::HtmlCanvasElement),
266
267 /// Surface from a `web_sys::OffscreenCanvas`.
268 ///
269 /// The `canvas` argument must be a valid `OffscreenCanvas` object
270 /// to create a surface upon.
271 ///
272 /// # Errors
273 ///
274 /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
275 /// or declines to provide GPU access (such as due to a resource shortage).
276 #[cfg(web)]
277 OffscreenCanvas(web_sys::OffscreenCanvas),
278}
279
280impl<'a, T> From<T> for SurfaceTarget<'a>
281where
282 T: WindowHandle + 'a,
283{
284 fn from(window: T) -> Self {
285 Self::Window(Box::new(window))
286 }
287}
288
289/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with unsafe surface creation.
290///
291/// This is either a window or an actual web canvas depending on the platform and
292/// enabled features.
293/// Refer to the individual variants for more information.
294///
295/// See also [`SurfaceTarget`] for safe variants.
296#[non_exhaustive]
297pub enum SurfaceTargetUnsafe {
298 /// Raw window & display handle.
299 ///
300 /// If the specified display and window handle are not supported by any of the backends, then the surface
301 /// will not be supported by any adapters.
302 ///
303 /// # Safety
304 ///
305 /// - `raw_window_handle` & `raw_display_handle` must be valid objects to create a surface upon.
306 /// - `raw_window_handle` & `raw_display_handle` must remain valid until after the returned
307 /// [`Surface`] is dropped.
308 RawHandle {
309 /// Raw display handle, underlying display must outlive the surface created from this.
310 raw_display_handle: raw_window_handle::RawDisplayHandle,
311
312 /// Raw display handle, underlying window must outlive the surface created from this.
313 raw_window_handle: raw_window_handle::RawWindowHandle,
314 },
315
316 /// Surface from a DRM device.
317 ///
318 /// If the specified DRM configuration is not supported by any of the backends, then the surface
319 /// will not be supported by any adapters.
320 ///
321 /// # Safety
322 ///
323 /// - All parameters must point to valid DRM values and remain valid for as long as the resulting [`Surface`] exists.
324 /// - The file descriptor (`fd`), plane, connector, and mode configuration must be valid and compatible.
325 #[cfg(all(unix, not(target_vendor = "apple"), not(target_family = "wasm")))]
326 Drm {
327 /// The file descriptor of the DRM device.
328 fd: i32,
329 /// The plane index on which to create the surface.
330 plane: u32,
331 /// The ID of the connector associated with the selected mode.
332 connector_id: u32,
333 /// The display width of the selected mode.
334 width: u32,
335 /// The display height of the selected mode.
336 height: u32,
337 /// The display refresh rate of the selected mode multiplied by 1000 (e.g., 60Hz → 60000).
338 refresh_rate: u32,
339 },
340
341 /// Surface from `CoreAnimationLayer`.
342 ///
343 /// # Safety
344 ///
345 /// - layer must be a valid object to create a surface upon.
346 #[cfg(metal)]
347 CoreAnimationLayer(*mut core::ffi::c_void),
348
349 /// Surface from `IDCompositionVisual`.
350 ///
351 /// # Safety
352 ///
353 /// - visual must be a valid `IDCompositionVisual` to create a surface upon. Its refcount will be incremented internally and kept live as long as the resulting [`Surface`] is live.
354 #[cfg(dx12)]
355 CompositionVisual(*mut core::ffi::c_void),
356
357 /// Surface from DX12 `DirectComposition` handle.
358 ///
359 /// <https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgifactorymedia-createswapchainforcompositionsurfacehandle>
360 ///
361 /// # Safety
362 ///
363 /// - surface_handle must be a valid `DirectComposition` handle to create a surface upon. Its lifetime **will not** be internally managed: this handle **should not** be freed before
364 /// the resulting [`Surface`] is destroyed.
365 #[cfg(dx12)]
366 SurfaceHandle(*mut core::ffi::c_void),
367
368 /// Surface from DX12 `SwapChainPanel`.
369 ///
370 /// # Safety
371 ///
372 /// - visual must be a valid SwapChainPanel to create a surface upon. Its refcount will be incremented internally and kept live as long as the resulting [`Surface`] is live.
373 #[cfg(dx12)]
374 SwapChainPanel(*mut core::ffi::c_void),
375}
376
377impl SurfaceTargetUnsafe {
378 /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a window.
379 ///
380 /// # Safety
381 ///
382 /// - `window` must outlive the resulting surface target
383 /// (and subsequently the surface created for this target).
384 pub unsafe fn from_window<T>(window: &T) -> Result<Self, raw_window_handle::HandleError>
385 where
386 T: HasDisplayHandle + HasWindowHandle,
387 {
388 Ok(Self::RawHandle {
389 raw_display_handle: window.display_handle()?.as_raw(),
390 raw_window_handle: window.window_handle()?.as_raw(),
391 })
392 }
393}
394
395/// [`Instance::create_surface()`] or a related function failed.
396#[derive(Clone, Debug)]
397#[non_exhaustive]
398pub struct CreateSurfaceError {
399 pub(crate) inner: CreateSurfaceErrorKind,
400}
401#[derive(Clone, Debug)]
402pub(crate) enum CreateSurfaceErrorKind {
403 /// Error from [`wgpu_hal`].
404 #[cfg(wgpu_core)]
405 Hal(wgc::instance::CreateSurfaceError),
406
407 /// Error from WebGPU surface creation.
408 #[cfg_attr(not(webgpu), expect(dead_code))]
409 Web(String),
410
411 /// Error when trying to get a [`DisplayHandle`] or a [`WindowHandle`] from
412 /// `raw_window_handle`.
413 RawHandle(raw_window_handle::HandleError),
414}
415static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync);
416
417impl fmt::Display for CreateSurfaceError {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 match &self.inner {
420 #[cfg(wgpu_core)]
421 CreateSurfaceErrorKind::Hal(e) => e.fmt(f),
422 CreateSurfaceErrorKind::Web(e) => e.fmt(f),
423 CreateSurfaceErrorKind::RawHandle(e) => e.fmt(f),
424 }
425 }
426}
427
428impl error::Error for CreateSurfaceError {
429 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
430 match &self.inner {
431 #[cfg(wgpu_core)]
432 CreateSurfaceErrorKind::Hal(e) => e.source(),
433 CreateSurfaceErrorKind::Web(_) => None,
434 #[cfg(feature = "std")]
435 CreateSurfaceErrorKind::RawHandle(e) => e.source(),
436 #[cfg(not(feature = "std"))]
437 CreateSurfaceErrorKind::RawHandle(_) => None,
438 }
439 }
440}
441
442#[cfg(wgpu_core)]
443impl From<wgc::instance::CreateSurfaceError> for CreateSurfaceError {
444 fn from(e: wgc::instance::CreateSurfaceError) -> Self {
445 Self {
446 inner: CreateSurfaceErrorKind::Hal(e),
447 }
448 }
449}