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 (texture, status, detail) = self.inner.get_current_texture();
150
151 let suboptimal = match status {
152 SurfaceStatus::Good => false,
153 SurfaceStatus::Suboptimal => true,
154 SurfaceStatus::Timeout => return CurrentSurfaceTexture::Timeout,
155 SurfaceStatus::Occluded => return CurrentSurfaceTexture::Occluded,
156 SurfaceStatus::Outdated => return CurrentSurfaceTexture::Outdated,
157 SurfaceStatus::Lost => return CurrentSurfaceTexture::Lost,
158 SurfaceStatus::Validation => return CurrentSurfaceTexture::Validation,
159 };
160
161 let guard = self.config.lock();
162 let config = guard
163 .as_ref()
164 .expect("This surface has not been configured yet.");
165
166 let descriptor = TextureDescriptor {
167 label: None,
168 size: Extent3d {
169 width: config.width,
170 height: config.height,
171 depth_or_array_layers: 1,
172 },
173 format: config.format,
174 usage: config.usage,
175 mip_level_count: 1,
176 sample_count: 1,
177 dimension: TextureDimension::D2,
178 view_formats: &[],
179 };
180
181 match texture {
182 Some(texture) => {
183 let surface_texture = SurfaceTexture {
184 texture: Texture {
185 inner: texture,
186 descriptor,
187 },
188 presented: false,
189 detail,
190 };
191 if suboptimal {
192 CurrentSurfaceTexture::Suboptimal(surface_texture)
193 } else {
194 CurrentSurfaceTexture::Success(surface_texture)
195 }
196 }
197 None => CurrentSurfaceTexture::Lost,
198 }
199 }
200
201 /// Get the [`wgpu_hal`] surface from this `Surface`.
202 ///
203 /// Find the Api struct corresponding to the active backend in [`wgpu_hal::api`],
204 /// and pass that struct to the to the `A` type parameter.
205 ///
206 /// Returns a guard that dereferences to the type of the hal backend
207 /// which implements [`A::Surface`].
208 ///
209 /// # Types
210 ///
211 /// The returned type depends on the backend:
212 ///
213 #[doc = crate::macros::hal_type_vulkan!("Surface")]
214 #[doc = crate::macros::hal_type_metal!("Surface")]
215 #[doc = crate::macros::hal_type_dx12!("Surface")]
216 #[doc = crate::macros::hal_type_gles!("Surface")]
217 ///
218 /// # Errors
219 ///
220 /// This method will return None if:
221 /// - The surface is not from the backend specified by `A`.
222 /// - The surface is from the `webgpu` or `custom` backend.
223 ///
224 /// # Safety
225 ///
226 /// - The returned resource must not be destroyed unless the guard
227 /// is the last reference to it and it is not in use by the GPU.
228 /// The guard and handle may be dropped at any time however.
229 /// - All the safety requirements of wgpu-hal must be upheld.
230 ///
231 /// [`A::Surface`]: hal::Api::Surface
232 #[cfg(wgpu_core)]
233 pub unsafe fn as_hal<A: hal::Api>(
234 &self,
235 ) -> Option<impl Deref<Target = A::Surface> + WasmNotSendSync> {
236 let core_surface = self.inner.as_core_opt()?;
237
238 unsafe { core_surface.context.surface_as_hal::<A>(core_surface) }
239 }
240
241 #[cfg(custom)]
242 /// Returns custom implementation of Surface (if custom backend and is internally T)
243 pub fn as_custom<T: custom::SurfaceInterface>(&self) -> Option<&T> {
244 self.inner.as_custom()
245 }
246}
247
248// This custom implementation is required because [`Surface::_surface`] doesn't
249// require [`Debug`](fmt::Debug), which we should not require from the user.
250impl fmt::Debug for Surface<'_> {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 f.debug_struct("Surface")
253 .field(
254 "_handle_source",
255 &if self._handle_source.is_some() {
256 "Some"
257 } else {
258 "None"
259 },
260 )
261 .field("inner", &self.inner)
262 .field("config", &self.config)
263 .finish()
264 }
265}
266
267#[cfg(send_sync)]
268static_assertions::assert_impl_all!(Surface<'_>: Send, Sync);
269
270crate::cmp::impl_eq_ord_hash_proxy!(Surface<'_> => .inner);
271
272/// [`Send`]/[`Sync`] blanket trait for [`HasWindowHandle`] used in [`SurfaceTarget`].
273pub trait WindowHandle: HasWindowHandle + WasmNotSendSync {}
274
275impl<T: HasWindowHandle + WasmNotSendSync> WindowHandle for T {}
276
277/// Super trait for a pair of display and window handles as used in [`SurfaceTarget`].
278pub trait DisplayAndWindowHandle: WindowHandle + HasDisplayHandle {}
279
280impl<T> DisplayAndWindowHandle for T where T: WindowHandle + HasDisplayHandle {}
281
282/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with safe surface creation.
283///
284/// This is either a window or an actual web canvas depending on the platform and
285/// enabled features.
286/// Refer to the individual variants for more information.
287///
288/// See also [`SurfaceTargetUnsafe`] for unsafe variants.
289#[non_exhaustive]
290pub enum SurfaceTarget<'window> {
291 /// Window and display handle producer.
292 ///
293 /// If the specified display and window handle are not supported by any of the backends, then the surface
294 /// will not be supported by any adapters.
295 ///
296 /// # Errors
297 ///
298 /// - On WebGL2: surface creation returns an error if the browser does not support WebGL2,
299 /// or declines to provide GPU access (such as due to a resource shortage).
300 ///
301 /// # Panics
302 ///
303 /// - On macOS/Metal: will panic if not called on the main thread.
304 /// - On web: will panic if the [`HasWindowHandle`] does not properly refer to a
305 /// canvas element.
306 /// - On all platforms: If [`crate::InstanceDescriptor::display`] was not [`None`]
307 /// but its value is not identical to that returned by [`HasDisplayHandle::display_handle()`].
308 DisplayAndWindow(Box<dyn DisplayAndWindowHandle + 'window>),
309
310 /// Window handle producer.
311 ///
312 /// [`HasWindowHandle`]-only version of [`SurfaceTarget::DisplayAndWindow`].
313 ///
314 /// This requires that the display handle was already passed through
315 /// [`crate::InstanceDescriptor::display`].
316 Window(Box<dyn WindowHandle + 'window>),
317
318 /// Surface from a `web_sys::HtmlCanvasElement`.
319 ///
320 /// The `canvas` argument must be a valid `<canvas>` element to
321 /// create a surface upon.
322 ///
323 /// # Errors
324 ///
325 /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
326 /// or declines to provide GPU access (such as due to a resource shortage).
327 #[cfg(web)]
328 Canvas(web_sys::HtmlCanvasElement),
329
330 /// Surface from a `web_sys::OffscreenCanvas`.
331 ///
332 /// The `canvas` argument must be a valid `OffscreenCanvas` object
333 /// to create a surface upon.
334 ///
335 /// # Errors
336 ///
337 /// - On WebGL2: surface creation will return an error if the browser does not support WebGL2,
338 /// or declines to provide GPU access (such as due to a resource shortage).
339 #[cfg(web)]
340 OffscreenCanvas(web_sys::OffscreenCanvas),
341}
342
343impl fmt::Debug for SurfaceTarget<'_> {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 match self {
346 Self::DisplayAndWindow(_) => f.debug_tuple("DisplayAndWindow").finish_non_exhaustive(),
347 Self::Window(_) => f.debug_tuple("Window").finish_non_exhaustive(),
348 #[cfg(web)]
349 Self::Canvas(canvas) => f.debug_tuple("Canvas").field(canvas).finish(),
350 #[cfg(web)]
351 Self::OffscreenCanvas(canvas) => {
352 f.debug_tuple("OffscreenCanvas").field(canvas).finish()
353 }
354 }
355 }
356}
357
358impl<'a> SurfaceTarget<'a> {
359 /// Constructor for [`Self::Window`] without consuming a display handle
360 pub fn from_window_without_display(window: impl WindowHandle + 'a) -> Self {
361 Self::Window(Box::new(window))
362 }
363}
364
365impl<'a, T> From<T> for SurfaceTarget<'a>
366where
367 T: DisplayAndWindowHandle + 'a,
368{
369 fn from(window: T) -> Self {
370 Self::DisplayAndWindow(Box::new(window))
371 }
372}
373
374/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with unsafe surface creation.
375///
376/// This is either a window or an actual web canvas depending on the platform and
377/// enabled features.
378/// Refer to the individual variants for more information.
379///
380/// See also [`SurfaceTarget`] for safe variants.
381#[non_exhaustive]
382#[derive(Debug)]
383pub enum SurfaceTargetUnsafe {
384 /// Raw window & display handle.
385 ///
386 /// If the specified display and window handle are not supported by any of the backends, then the surface
387 /// will not be supported by any adapters.
388 ///
389 /// If the `raw_display_handle` is not [`None`] here and was not [`None`] in
390 /// [`crate::InstanceDescriptor::display`], their values _must_ be identical.
391 ///
392 /// # Safety
393 ///
394 /// - `raw_window_handle` & `raw_display_handle` must be valid objects to create a surface upon.
395 /// - `raw_window_handle` & `raw_display_handle` must remain valid until after the returned
396 /// [`Surface`] is dropped.
397 RawHandle {
398 /// Raw display handle, underlying display must outlive the surface created from this.
399 raw_display_handle: Option<raw_window_handle::RawDisplayHandle>,
400
401 /// Raw window handle, underlying window must outlive the surface created from this.
402 raw_window_handle: raw_window_handle::RawWindowHandle,
403 },
404
405 /// Surface from a DRM device.
406 ///
407 /// If the specified DRM configuration is not supported by any of the backends, then the surface
408 /// will not be supported by any adapters.
409 ///
410 /// # Safety
411 ///
412 /// - All parameters must point to valid DRM values and remain valid for as long as the resulting [`Surface`] exists.
413 /// - The file descriptor (`fd`), plane, connector, and mode configuration must be valid and compatible.
414 #[cfg(drm)]
415 Drm {
416 /// The file descriptor of the DRM device.
417 fd: i32,
418 /// The plane index on which to create the surface.
419 plane: u32,
420 /// The ID of the connector associated with the selected mode.
421 connector_id: u32,
422 /// The display width of the selected mode.
423 width: u32,
424 /// The display height of the selected mode.
425 height: u32,
426 /// The display refresh rate of the selected mode multiplied by 1000 (e.g., 60Hz → 60000).
427 refresh_rate: u32,
428 },
429
430 /// Surface from `CoreAnimationLayer`.
431 ///
432 /// # Safety
433 ///
434 /// - layer must be a valid object to create a surface upon.
435 #[cfg(metal)]
436 CoreAnimationLayer(*mut core::ffi::c_void),
437
438 /// Surface from `IDCompositionVisual`.
439 ///
440 /// # Safety
441 ///
442 /// - 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.
443 #[cfg(dx12)]
444 CompositionVisual(*mut core::ffi::c_void),
445
446 /// Surface from DX12 `DirectComposition` handle.
447 ///
448 /// <https://learn.microsoft.com/en-us/windows/win32/api/dxgi1_3/nf-dxgi1_3-idxgifactorymedia-createswapchainforcompositionsurfacehandle>
449 ///
450 /// # Safety
451 ///
452 /// - 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
453 /// the resulting [`Surface`] is destroyed.
454 #[cfg(dx12)]
455 SurfaceHandle(*mut core::ffi::c_void),
456
457 /// Surface from DX12 `SwapChainPanel`.
458 ///
459 /// # Safety
460 ///
461 /// - 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.
462 #[cfg(dx12)]
463 SwapChainPanel(*mut core::ffi::c_void),
464}
465
466impl SurfaceTargetUnsafe {
467 /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a display and window.
468 ///
469 /// The `display` is optional and may be omitted if it was also passed to
470 /// [`crate::InstanceDescriptor::display`]. If passed to both it must (currently) be identical.
471 ///
472 /// # Safety
473 ///
474 /// - `display` must outlive the resulting surface target
475 /// (and subsequently the surface created for this target).
476 /// - `window` must outlive the resulting surface target
477 /// (and subsequently the surface created for this target).
478 pub unsafe fn from_display_and_window(
479 display: &impl HasDisplayHandle,
480 window: &impl HasWindowHandle,
481 ) -> Result<Self, raw_window_handle::HandleError> {
482 Ok(Self::RawHandle {
483 raw_display_handle: Some(display.display_handle()?.as_raw()),
484 raw_window_handle: window.window_handle()?.as_raw(),
485 })
486 }
487
488 /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a window.
489 ///
490 /// # Safety
491 ///
492 /// - `window` must outlive the resulting surface target
493 /// (and subsequently the surface created for this target).
494 pub unsafe fn from_window(
495 window: &impl HasWindowHandle,
496 ) -> Result<Self, raw_window_handle::HandleError> {
497 Ok(Self::RawHandle {
498 raw_display_handle: None,
499 raw_window_handle: window.window_handle()?.as_raw(),
500 })
501 }
502}
503
504/// [`Instance::create_surface()`] or a related function failed.
505#[derive(Clone, Debug)]
506#[non_exhaustive]
507pub struct CreateSurfaceError {
508 pub(crate) inner: CreateSurfaceErrorKind,
509}
510#[derive(Clone, Debug)]
511pub(crate) enum CreateSurfaceErrorKind {
512 /// Error from [`wgpu_hal`].
513 #[cfg(wgpu_core)]
514 Hal(wgc::instance::CreateSurfaceError),
515
516 /// Error from WebGPU surface creation.
517 #[cfg_attr(not(webgpu), expect(dead_code))]
518 Web(String),
519
520 /// Error when trying to get a [`RawDisplayHandle`][rdh] or a
521 /// [`RawWindowHandle`][rwh] from a [`SurfaceTarget`].
522 ///
523 /// [rdh]: raw_window_handle::RawDisplayHandle
524 /// [rwh]: raw_window_handle::RawWindowHandle
525 RawHandle(raw_window_handle::HandleError),
526}
527static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync);
528
529impl fmt::Display for CreateSurfaceError {
530 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531 match &self.inner {
532 #[cfg(wgpu_core)]
533 CreateSurfaceErrorKind::Hal(e) => e.fmt(f),
534 CreateSurfaceErrorKind::Web(e) => e.fmt(f),
535 CreateSurfaceErrorKind::RawHandle(e) => e.fmt(f),
536 }
537 }
538}
539
540impl error::Error for CreateSurfaceError {
541 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
542 match &self.inner {
543 #[cfg(wgpu_core)]
544 CreateSurfaceErrorKind::Hal(e) => e.source(),
545 CreateSurfaceErrorKind::Web(_) => None,
546 #[cfg(feature = "std")]
547 CreateSurfaceErrorKind::RawHandle(e) => e.source(),
548 #[cfg(not(feature = "std"))]
549 CreateSurfaceErrorKind::RawHandle(_) => None,
550 }
551 }
552}
553
554#[cfg(wgpu_core)]
555impl From<wgc::instance::CreateSurfaceError> for CreateSurfaceError {
556 fn from(e: wgc::instance::CreateSurfaceError) -> Self {
557 Self {
558 inner: CreateSurfaceErrorKind::Hal(e),
559 }
560 }
561}