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