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