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