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    /// - An 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 current configuration of [`Surface`], if configured.
103    ///
104    /// This is similar to [WebGPU `GPUcCanvasContext::getConfiguration`](https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-getconfiguration).
105    pub fn get_configuration(&self) -> Option<SurfaceConfiguration> {
106        self.config.lock().clone()
107    }
108
109    /// Returns the next texture to be presented by the surface for drawing.
110    ///
111    /// After rendering to the returned [`SurfaceTexture`], submit work via [`Queue::submit`]
112    /// and then call [`Queue::present`] to display it.
113    ///
114    /// If a [`SurfaceTexture`] referencing this surface is alive when [`Surface::configure()`]
115    /// is called, the configure call will panic.
116    ///
117    /// See the documentation of [`CurrentSurfaceTexture`] for how each possible result
118    /// should be handled.
119    pub fn get_current_texture(&self) -> CurrentSurfaceTexture {
120        let (texture, status, detail) = self.inner.get_current_texture();
121
122        let suboptimal = match status {
123            SurfaceStatus::Good => false,
124            SurfaceStatus::Suboptimal => true,
125            SurfaceStatus::Timeout => return CurrentSurfaceTexture::Timeout,
126            SurfaceStatus::Occluded => return CurrentSurfaceTexture::Occluded,
127            SurfaceStatus::Outdated => return CurrentSurfaceTexture::Outdated,
128            SurfaceStatus::Lost => return CurrentSurfaceTexture::Lost,
129            SurfaceStatus::Validation => return CurrentSurfaceTexture::Validation,
130        };
131
132        let guard = self.config.lock();
133        let config = guard
134            .as_ref()
135            .expect("This surface has not been configured yet.");
136
137        let descriptor = TextureDescriptor {
138            label: None,
139            size: Extent3d {
140                width: config.width,
141                height: config.height,
142                depth_or_array_layers: 1,
143            },
144            format: config.format,
145            usage: config.usage,
146            mip_level_count: 1,
147            sample_count: 1,
148            dimension: TextureDimension::D2,
149            view_formats: &[],
150        };
151
152        match texture {
153            Some(texture) => {
154                let surface_texture = SurfaceTexture {
155                    texture: Texture {
156                        inner: texture,
157                        descriptor,
158                    },
159                    presented: false,
160                    detail,
161                };
162                if suboptimal {
163                    CurrentSurfaceTexture::Suboptimal(surface_texture)
164                } else {
165                    CurrentSurfaceTexture::Success(surface_texture)
166                }
167            }
168            None => CurrentSurfaceTexture::Lost,
169        }
170    }
171
172    /// Get the [`wgpu_hal`] surface from this `Surface`.
173    ///
174    /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`],
175    /// and pass that struct to the to the `A` type parameter.
176    ///
177    /// Returns a guard that dereferences to the type of the hal backend
178    /// which implements [`A::Surface`].
179    ///
180    /// # Types
181    ///
182    /// The returned type depends on the backend:
183    ///
184    #[doc = crate::macros::hal_type_vulkan!("Surface")]
185    #[doc = crate::macros::hal_type_metal!("Surface")]
186    #[doc = crate::macros::hal_type_dx12!("Surface")]
187    #[doc = crate::macros::hal_type_gles!("Surface")]
188    ///
189    /// # Errors
190    ///
191    /// This method will return None if:
192    /// - The surface is not from the backend specified by `A`.
193    /// - The surface is from the `webgpu` or `custom` backend.
194    ///
195    /// # Safety
196    ///
197    /// - The returned resource must not be destroyed unless the guard
198    ///   is the last reference to it and it is not in use by the GPU.
199    ///   The guard and handle may be dropped at any time however.
200    /// - All the safety requirements of wgpu-hal must be upheld.
201    ///
202    /// [`A::Surface`]: hal::Api::Surface
203    #[cfg(wgpu_core)]
204    pub unsafe fn as_hal<A: hal::Api>(
205        &self,
206    ) -> Option<impl Deref<Target = A::Surface> + WasmNotSendSync> {
207        let core_surface = self.inner.as_core_opt()?;
208
209        unsafe { core_surface.context.surface_as_hal::<A>(core_surface) }
210    }
211
212    #[cfg(custom)]
213    /// Returns custom implementation of Surface (if custom backend and is internally T)
214    pub fn as_custom<T: custom::SurfaceInterface>(&self) -> Option<&T> {
215        self.inner.as_custom()
216    }
217}
218
219// This custom implementation is required because [`Surface::_surface`] doesn't
220// require [`Debug`](fmt::Debug), which we should not require from the user.
221impl fmt::Debug for Surface<'_> {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        f.debug_struct("Surface")
224            .field(
225                "_handle_source",
226                &if self._handle_source.is_some() {
227                    "Some"
228                } else {
229                    "None"
230                },
231            )
232            .field("inner", &self.inner)
233            .field("config", &self.config)
234            .finish()
235    }
236}
237
238#[cfg(send_sync)]
239static_assertions::assert_impl_all!(Surface<'_>: Send, Sync);
240
241crate::cmp::impl_eq_ord_hash_proxy!(Surface<'_> => .inner);
242
243/// [`Send`]/[`Sync`] blanket trait for [`HasWindowHandle`] used in [`SurfaceTarget`].
244pub trait WindowHandle: HasWindowHandle + WasmNotSendSync {}
245
246impl<T: HasWindowHandle + WasmNotSendSync> WindowHandle for T {}
247
248/// Super trait for a pair of display and window handles as used in [`SurfaceTarget`].
249pub trait DisplayAndWindowHandle: WindowHandle + HasDisplayHandle {}
250
251impl<T> DisplayAndWindowHandle for T where T: WindowHandle + HasDisplayHandle {}
252
253/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with safe surface creation.
254///
255/// This is either a window or an actual web canvas depending on the platform and
256/// enabled features.
257/// Refer to the individual variants for more information.
258///
259/// See also [`SurfaceTargetUnsafe`] for unsafe variants.
260#[non_exhaustive]
261pub enum SurfaceTarget<'window> {
262    /// Window and display handle producer.
263    ///
264    /// If the specified display and window handle are not supported by any of the backends, then the surface
265    /// will not be supported by any adapters.
266    ///
267    /// # Errors
268    ///
269    /// - On WebGL2: surface creation returns an error if the browser does not support WebGL2,
270    ///   or declines to provide GPU access (such as due to a resource shortage).
271    ///
272    /// # Panics
273    ///
274    /// - On macOS/Metal: will panic if not called on the main thread.
275    /// - On web: will panic if the [`HasWindowHandle`] does not properly refer to a
276    ///   canvas element.
277    /// - On all platforms: If [`crate::InstanceDescriptor::display`] was not [`None`]
278    ///   but its value is not identical to that returned by [`HasDisplayHandle::display_handle()`].
279    DisplayAndWindow(Box<dyn DisplayAndWindowHandle + 'window>),
280
281    /// Window handle producer.
282    ///
283    /// [`HasWindowHandle`]-only version of [`SurfaceTarget::DisplayAndWindow`].
284    ///
285    /// This requires that the display handle was already passed through
286    /// [`crate::InstanceDescriptor::display`].
287    Window(Box<dyn WindowHandle + 'window>),
288
289    /// Surface from a `web_sys::HtmlCanvasElement`.
290    ///
291    /// The `canvas` argument must be a valid `<canvas>` element to
292    /// create a surface upon.
293    ///
294    /// # Errors
295    ///
296    /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
297    ///   or declines to provide GPU access (such as due to a resource shortage).
298    #[cfg(web)]
299    Canvas(web_sys::HtmlCanvasElement),
300
301    /// Surface from a `web_sys::OffscreenCanvas`.
302    ///
303    /// The `canvas` argument must be a valid `OffscreenCanvas` object
304    /// to create a surface upon.
305    ///
306    /// # Errors
307    ///
308    /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
309    ///   or declines to provide GPU access (such as due to a resource shortage).
310    #[cfg(web)]
311    OffscreenCanvas(web_sys::OffscreenCanvas),
312}
313
314impl<'a> SurfaceTarget<'a> {
315    /// Constructor for [`Self::Window`] without consuming a display handle
316    pub fn from_window_without_display(window: impl WindowHandle + 'a) -> Self {
317        Self::Window(Box::new(window))
318    }
319}
320
321impl<'a, T> From<T> for SurfaceTarget<'a>
322where
323    T: DisplayAndWindowHandle + 'a,
324{
325    fn from(window: T) -> Self {
326        Self::DisplayAndWindow(Box::new(window))
327    }
328}
329
330/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with unsafe surface creation.
331///
332/// This is either a window or an actual web canvas depending on the platform and
333/// enabled features.
334/// Refer to the individual variants for more information.
335///
336/// See also [`SurfaceTarget`] for safe variants.
337#[non_exhaustive]
338pub enum SurfaceTargetUnsafe {
339    /// Raw window & display handle.
340    ///
341    /// If the specified display and window handle are not supported by any of the backends, then the surface
342    /// will not be supported by any adapters.
343    ///
344    /// If the `raw_display_handle` is not [`None`] here and was not [`None`] in
345    /// [`crate::InstanceDescriptor::display`], their values _must_ be identical.
346    ///
347    /// # Safety
348    ///
349    /// - `raw_window_handle` & `raw_display_handle` must be valid objects to create a surface upon.
350    /// - `raw_window_handle` & `raw_display_handle` must remain valid until after the returned
351    ///   [`Surface`] is  dropped.
352    RawHandle {
353        /// Raw display handle, underlying display must outlive the surface created from this.
354        raw_display_handle: Option<raw_window_handle::RawDisplayHandle>,
355
356        /// Raw window handle, underlying window must outlive the surface created from this.
357        raw_window_handle: raw_window_handle::RawWindowHandle,
358    },
359
360    /// Surface from a DRM device.
361    ///
362    /// If the specified DRM configuration is not supported by any of the backends, then the surface
363    /// will not be supported by any adapters.
364    ///
365    /// # Safety
366    ///
367    /// - All parameters must point to valid DRM values and remain valid for as long as the resulting [`Surface`] exists.
368    /// - The file descriptor (`fd`), plane, connector, and mode configuration must be valid and compatible.
369    #[cfg(drm)]
370    Drm {
371        /// The file descriptor of the DRM device.
372        fd: i32,
373        /// The plane index on which to create the surface.
374        plane: u32,
375        /// The ID of the connector associated with the selected mode.
376        connector_id: u32,
377        /// The display width of the selected mode.
378        width: u32,
379        /// The display height of the selected mode.
380        height: u32,
381        /// The display refresh rate of the selected mode multiplied by 1000 (e.g., 60Hz → 60000).
382        refresh_rate: u32,
383    },
384
385    /// Surface from `CoreAnimationLayer`.
386    ///
387    /// # Safety
388    ///
389    /// - layer must be a valid object to create a surface upon.
390    #[cfg(metal)]
391    CoreAnimationLayer(*mut core::ffi::c_void),
392
393    /// Surface from `IDCompositionVisual`.
394    ///
395    /// # Safety
396    ///
397    /// - 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.
398    #[cfg(dx12)]
399    CompositionVisual(*mut core::ffi::c_void),
400
401    /// Surface from DX12 `DirectComposition` handle.
402    ///
403    /// <https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgifactorymedia-createswapchainforcompositionsurfacehandle>
404    ///
405    /// # Safety
406    ///
407    /// - 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
408    ///   the resulting [`Surface`] is destroyed.
409    #[cfg(dx12)]
410    SurfaceHandle(*mut core::ffi::c_void),
411
412    /// Surface from DX12 `SwapChainPanel`.
413    ///
414    /// # Safety
415    ///
416    /// - 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.
417    #[cfg(dx12)]
418    SwapChainPanel(*mut core::ffi::c_void),
419}
420
421impl SurfaceTargetUnsafe {
422    /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a display and window.
423    ///
424    /// The `display` is optional and may be omitted if it was also passed to
425    /// [`crate::InstanceDescriptor::display`].  If passed to both it must (currently) be identical.
426    ///
427    /// # Safety
428    ///
429    /// - `display` must outlive the resulting surface target
430    ///   (and subsequently the surface created for this target).
431    /// - `window` must outlive the resulting surface target
432    ///   (and subsequently the surface created for this target).
433    pub unsafe fn from_display_and_window(
434        display: &impl HasDisplayHandle,
435        window: &impl HasWindowHandle,
436    ) -> Result<Self, raw_window_handle::HandleError> {
437        Ok(Self::RawHandle {
438            raw_display_handle: Some(display.display_handle()?.as_raw()),
439            raw_window_handle: window.window_handle()?.as_raw(),
440        })
441    }
442
443    /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a window.
444    ///
445    /// # Safety
446    ///
447    /// - `window` must outlive the resulting surface target
448    ///   (and subsequently the surface created for this target).
449    pub unsafe fn from_window(
450        window: &impl HasWindowHandle,
451    ) -> Result<Self, raw_window_handle::HandleError> {
452        Ok(Self::RawHandle {
453            raw_display_handle: None,
454            raw_window_handle: window.window_handle()?.as_raw(),
455        })
456    }
457}
458
459/// [`Instance::create_surface()`] or a related function failed.
460#[derive(Clone, Debug)]
461#[non_exhaustive]
462pub struct CreateSurfaceError {
463    pub(crate) inner: CreateSurfaceErrorKind,
464}
465#[derive(Clone, Debug)]
466pub(crate) enum CreateSurfaceErrorKind {
467    /// Error from [`wgpu_hal`].
468    #[cfg(wgpu_core)]
469    Hal(wgc::instance::CreateSurfaceError),
470
471    /// Error from WebGPU surface creation.
472    #[cfg_attr(not(webgpu), expect(dead_code))]
473    Web(String),
474
475    /// Error when trying to get a [`RawDisplayHandle`][rdh] or a
476    /// [`RawWindowHandle`][rwh] from a [`SurfaceTarget`].
477    ///
478    /// [rdh]: raw_window_handle::RawDisplayHandle
479    /// [rwh]: raw_window_handle::RawWindowHandle
480    RawHandle(raw_window_handle::HandleError),
481}
482static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync);
483
484impl fmt::Display for CreateSurfaceError {
485    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486        match &self.inner {
487            #[cfg(wgpu_core)]
488            CreateSurfaceErrorKind::Hal(e) => e.fmt(f),
489            CreateSurfaceErrorKind::Web(e) => e.fmt(f),
490            CreateSurfaceErrorKind::RawHandle(e) => e.fmt(f),
491        }
492    }
493}
494
495impl error::Error for CreateSurfaceError {
496    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
497        match &self.inner {
498            #[cfg(wgpu_core)]
499            CreateSurfaceErrorKind::Hal(e) => e.source(),
500            CreateSurfaceErrorKind::Web(_) => None,
501            #[cfg(feature = "std")]
502            CreateSurfaceErrorKind::RawHandle(e) => e.source(),
503            #[cfg(not(feature = "std"))]
504            CreateSurfaceErrorKind::RawHandle(_) => None,
505        }
506    }
507}
508
509#[cfg(wgpu_core)]
510impl From<wgc::instance::CreateSurfaceError> for CreateSurfaceError {
511    fn from(e: wgc::instance::CreateSurfaceError) -> Self {
512        Self {
513            inner: CreateSurfaceErrorKind::Hal(e),
514        }
515    }
516}