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