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