wgpu/api/
surface.rs

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