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