wgpu/api/
surface.rs

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