//! A cross-platform unsafe graphics abstraction. //! //! This crate defines a set of traits abstracting over modern graphics APIs, //! with implementations ("backends") for Vulkan, Metal, Direct3D, and GL. //! //! `wgpu-hal` is a spiritual successor to //! [gfx-hal](https://github.com/gfx-rs/gfx), but with reduced scope, and //! oriented towards WebGPU implementation goals. It has no overhead for //! validation or tracking, and the API translation overhead is kept to the bare //! minimum by the design of WebGPU. This API can be used for resource-demanding //! applications and engines. //! //! The `wgpu-hal` crate's main design choices: //! //! - Our traits are meant to be *portable*: proper use //! should get equivalent results regardless of the backend. //! //! - Our traits' contracts are *unsafe*: implementations perform minimal //! validation, if any, and incorrect use will often cause undefined behavior. //! This allows us to minimize the overhead we impose over the underlying //! graphics system. If you need safety, the [`wgpu-core`] crate provides a //! safe API for driving `wgpu-hal`, implementing all necessary validation, //! resource state tracking, and so on. (Note that `wgpu-core` is designed for //! use via FFI; the [`wgpu`] crate provides more idiomatic Rust bindings for //! `wgpu-core`.) Or, you can do your own validation. //! //! - In the same vein, returned errors *only cover cases the user can't //! anticipate*, like running out of memory or losing the device. Any errors //! that the user could reasonably anticipate are their responsibility to //! avoid. For example, `wgpu-hal` returns no error for mapping a buffer that's //! not mappable: as the buffer creator, the user should already know if they //! can map it. //! //! - We use *static dispatch*. The traits are not //! generally object-safe. You must select a specific backend type //! like [`vulkan::Api`] or [`metal::Api`], and then use that //! according to the main traits, or call backend-specific methods. //! //! - We use *idiomatic Rust parameter passing*, //! taking objects by reference, returning them by value, and so on, //! unlike `wgpu-core`, which refers to objects by ID. //! //! - We map buffer contents *persistently*. This means that the buffer can //! remain mapped on the CPU while the GPU reads or writes to it. You must //! explicitly indicate when data might need to be transferred between CPU and //! GPU, if [`Device::map_buffer`] indicates that this is necessary. //! //! - You must record *explicit barriers* between different usages of a //! resource. For example, if a buffer is written to by a compute //! shader, and then used as and index buffer to a draw call, you //! must use [`CommandEncoder::transition_buffers`] between those two //! operations. //! //! - Pipeline layouts are *explicitly specified* when setting bind groups. //! Incompatible layouts disturb groups bound at higher indices. //! //! - The API *accepts collections as iterators*, to avoid forcing the user to //! store data in particular containers. The implementation doesn't guarantee //! that any of the iterators are drained, unless stated otherwise by the //! function documentation. For this reason, we recommend that iterators don't //! do any mutating work. //! //! Unfortunately, `wgpu-hal`'s safety requirements are not fully documented. //! Ideally, all trait methods would have doc comments setting out the //! requirements users must meet to ensure correct and portable behavior. If you //! are aware of a specific requirement that a backend imposes that is not //! ensured by the traits' documented rules, please file an issue. Or, if you are //! a capable technical writer, please file a pull request! //! //! [`wgpu-core`]: https://crates.io/crates/wgpu-core //! [`wgpu`]: https://crates.io/crates/wgpu //! [`vulkan::Api`]: vulkan/struct.Api.html //! [`metal::Api`]: metal/struct.Api.html //! //! ## Primary backends //! //! The `wgpu-hal` crate has full-featured backends implemented on the following //! platform graphics APIs: //! //! - Vulkan, available on Linux, Android, and Windows, using the [`ash`] crate's //! Vulkan bindings. It's also available on macOS, if you install [MoltenVK]. //! //! - Metal on macOS, using the [`metal`] crate's bindings. //! //! - Direct3D 12 on Windows, using the [`windows`] crate's bindings. //! //! [`ash`]: https://crates.io/crates/ash //! [MoltenVK]: https://github.com/KhronosGroup/MoltenVK //! [`metal`]: https://crates.io/crates/metal //! [`windows`]: https://crates.io/crates/windows //! //! ## Secondary backends //! //! The `wgpu-hal` crate has a partial implementation based on the following //! platform graphics API: //! //! - The GL backend is available anywhere OpenGL, OpenGL ES, or WebGL are //! available. See the [`gles`] module documentation for details. //! //! [`gles`]: gles/index.html //! //! You can see what capabilities an adapter is missing by checking the //! [`DownlevelCapabilities`][tdc] in [`ExposedAdapter::capabilities`], available //! from [`Instance::enumerate_adapters`]. //! //! The API is generally designed to fit the primary backends better than the //! secondary backends, so the latter may impose more overhead. //! //! [tdc]: wgt::DownlevelCapabilities //! //! ## Traits //! //! The `wgpu-hal` crate defines a handful of traits that together //! represent a cross-platform abstraction for modern GPU APIs. //! //! - The [`Api`] trait represents a `wgpu-hal` backend. It has no methods of its //! own, only a collection of associated types. //! //! - [`Api::Instance`] implements the [`Instance`] trait. [`Instance::init`] //! creates an instance value, which you can use to enumerate the adapters //! available on the system. For example, [`vulkan::Api::Instance::init`][Ii] //! returns an instance that can enumerate the Vulkan physical devices on your //! system. //! //! - [`Api::Adapter`] implements the [`Adapter`] trait, representing a //! particular device from a particular backend. For example, a Vulkan instance //! might have a Lavapipe software adapter and a GPU-based adapter. //! //! - [`Api::Device`] implements the [`Device`] trait, representing an active //! link to a device. You get a device value by calling [`Adapter::open`], and //! then use it to create buffers, textures, shader modules, and so on. //! //! - [`Api::Queue`] implements the [`Queue`] trait, which you use to submit //! command buffers to a given device. //! //! - [`Api::CommandEncoder`] implements the [`CommandEncoder`] trait, which you //! use to build buffers of commands to submit to a queue. This has all the //! methods for drawing and running compute shaders, which is presumably what //! you're here for. //! //! - [`Api::Surface`] implements the [`Surface`] trait, which represents a //! swapchain for presenting images on the screen, via interaction with the //! system's window manager. //! //! The [`Api`] trait has various other associated types like [`Api::Buffer`] and //! [`Api::Texture`] that represent resources the rest of the interface can //! operate on, but these generally do not have their own traits. //! //! [Ii]: Instance::init //! //! ## Validation is the calling code's responsibility, not `wgpu-hal`'s //! //! As much as possible, `wgpu-hal` traits place the burden of validation, //! resource tracking, and state tracking on the caller, not on the trait //! implementations themselves. Anything which can reasonably be handled in //! backend-independent code should be. A `wgpu_hal` backend's sole obligation is //! to provide portable behavior, and report conditions that the calling code //! can't reasonably anticipate, like device loss or running out of memory. //! //! The `wgpu` crate collection is intended for use in security-sensitive //! applications, like web browsers, where the API is available to untrusted //! code. This means that `wgpu-core`'s validation is not simply a service to //! developers, to be provided opportunistically when the performance costs are //! acceptable and the necessary data is ready at hand. Rather, `wgpu-core`'s //! validation must be exhaustive, to ensure that even malicious content cannot //! provoke and exploit undefined behavior in the platform's graphics API. //! //! Because graphics APIs' requirements are complex, the only practical way for //! `wgpu` to provide exhaustive validation is to comprehensively track the //! lifetime and state of all the resources in the system. Implementing this //! separately for each backend is infeasible; effort would be better spent //! making the cross-platform validation in `wgpu-core` legible and trustworthy. //! Fortunately, the requirements are largely similar across the various //! platforms, so cross-platform validation is practical. //! //! Some backends have specific requirements that aren't practical to foist off //! on the `wgpu-hal` user. For example, properly managing macOS Objective-C or //! Microsoft COM reference counts is best handled by using appropriate pointer //! types within the backend. //! //! A desire for "defense in depth" may suggest performing additional validation //! in `wgpu-hal` when the opportunity arises, but this must be done with //! caution. Even experienced contributors infer the expectations their changes //! must meet by considering not just requirements made explicit in types, tests, //! assertions, and comments, but also those implicit in the surrounding code. //! When one sees validation or state-tracking code in `wgpu-hal`, it is tempting //! to conclude, "Oh, `wgpu-hal` checks for this, so `wgpu-core` needn't worry //! about it - that would be redundant!" The responsibility for exhaustive //! validation always rests with `wgpu-core`, regardless of what may or may not //! be checked in `wgpu-hal`. //! //! To this end, any "defense in depth" validation that does appear in `wgpu-hal` //! for requirements that `wgpu-core` should have enforced should report failure //! via the `unreachable!` macro, because problems detected at this stage always //! indicate a bug in `wgpu-core`. //! //! ## Debugging //! //! Most of the information on the wiki [Debugging wgpu Applications][wiki-debug] //! page still applies to this API, with the exception of API tracing/replay //! functionality, which is only available in `wgpu-core`. //! //! [wiki-debug]: https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![allow( // this happens on the GL backend, where it is both thread safe and non-thread safe in the same code. clippy::arc_with_non_send_sync, // We don't use syntax sugar where it's not necessary. clippy::match_like_matches_macro, // Redundant matching is more explicit. clippy::redundant_pattern_matching, // Explicit lifetimes are often easier to reason about. clippy::needless_lifetimes, // No need for defaults in the internal types. clippy::new_without_default, // Matches are good and extendable, no need to make an exception here. clippy::single_match, // Push commands are more regular than macros. clippy::vec_init_then_push, // We unsafe impl `Send` for a reason. clippy::non_send_fields_in_send_ty, // TODO! clippy::missing_safety_doc, // It gets in the way a lot and does not prevent bugs in practice. clippy::pattern_type_mismatch, // We should investigate these. clippy::large_enum_variant )] #![warn( clippy::alloc_instead_of_core, clippy::ptr_as_ptr, clippy::std_instead_of_alloc, clippy::std_instead_of_core, trivial_casts, trivial_numeric_casts, unsafe_op_in_unsafe_fn, unused_extern_crates, unused_qualifications )] extern crate alloc; extern crate wgpu_types as wgt; // Each of these backends needs `std` in some fashion; usually `std::thread` functions. #[cfg(any(dx12, gles_with_std, metal, vulkan))] #[macro_use] extern crate std; /// DirectX12 API internals. #[cfg(dx12)] pub mod dx12; /// GLES API internals. #[cfg(gles)] pub mod gles; /// Metal API internals. #[cfg(metal)] pub mod metal; /// A dummy API implementation. // TODO(https://github.com/gfx-rs/wgpu/issues/7120): this should have a cfg pub mod noop; /// Vulkan API internals. #[cfg(vulkan)] pub mod vulkan; pub mod auxil; pub mod api { #[cfg(dx12)] pub use super::dx12::Api as Dx12; #[cfg(gles)] pub use super::gles::Api as Gles; #[cfg(metal)] pub use super::metal::Api as Metal; pub use super::noop::Api as Noop; #[cfg(vulkan)] pub use super::vulkan::Api as Vulkan; } mod dynamic; #[cfg(feature = "validation_canary")] mod validation_canary; #[cfg(feature = "validation_canary")] pub use validation_canary::{ValidationCanary, VALIDATION_CANARY}; pub(crate) use dynamic::impl_dyn_resource; pub use dynamic::{ DynAccelerationStructure, DynAcquiredSurfaceTexture, DynAdapter, DynBindGroup, DynBindGroupLayout, DynBuffer, DynCommandBuffer, DynCommandEncoder, DynComputePipeline, DynDevice, DynExposedAdapter, DynFence, DynInstance, DynOpenDevice, DynPipelineCache, DynPipelineLayout, DynQuerySet, DynQueue, DynRenderPipeline, DynResource, DynSampler, DynShaderModule, DynSurface, DynSurfaceTexture, DynTexture, DynTextureView, }; #[allow(unused)] use alloc::boxed::Box; use alloc::{borrow::Cow, string::String, vec::Vec}; use core::{ borrow::Borrow, error::Error, fmt, num::{NonZeroU32, NonZeroU64}, ops::{Range, RangeInclusive}, ptr::NonNull, }; use bitflags::bitflags; use raw_window_handle::DisplayHandle; use thiserror::Error; use wgt::WasmNotSendSync; cfg_if::cfg_if! { if #[cfg(supports_ptr_atomics)] { use alloc::sync::Arc; } else if #[cfg(feature = "portable-atomic")] { use portable_atomic_util::Arc; } } // - Vertex + Fragment // - Compute // Task + Mesh + Fragment pub const MAX_CONCURRENT_SHADER_STAGES: usize = 3; pub const MAX_ANISOTROPY: u8 = 16; pub const MAX_BIND_GROUPS: usize = 8; pub const MAX_VERTEX_BUFFERS: usize = 16; pub const MAX_COLOR_ATTACHMENTS: usize = 8; pub const MAX_MIP_LEVELS: u32 = 16; /// Size of a single occlusion/timestamp query, when copied into a buffer, in bytes. /// cbindgen:ignore pub const QUERY_SIZE: wgt::BufferAddress = 8; pub type Label<'a> = Option<&'a str>; pub type MemoryRange = Range; pub type FenceValue = u64; #[cfg(supports_64bit_atomics)] pub type AtomicFenceValue = core::sync::atomic::AtomicU64; #[cfg(not(supports_64bit_atomics))] pub type AtomicFenceValue = portable_atomic::AtomicU64; /// A callback to signal that wgpu is no longer using a resource. #[cfg(any(gles, vulkan))] pub type DropCallback = Box; #[cfg(any(gles, vulkan))] pub struct DropGuard { callback: Option, } #[cfg(all(any(gles, vulkan), any(native, Emscripten)))] impl DropGuard { fn from_option(callback: Option) -> Option { callback.map(|callback| Self { callback: Some(callback), }) } } #[cfg(any(gles, vulkan))] impl Drop for DropGuard { fn drop(&mut self) { if let Some(cb) = self.callback.take() { (cb)(); } } } #[cfg(any(gles, vulkan))] impl fmt::Debug for DropGuard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DropGuard").finish() } } #[derive(Clone, Debug, PartialEq, Eq, Error)] pub enum DeviceError { #[error("Out of memory")] OutOfMemory, #[error("Device is lost")] Lost, #[error("Unexpected error variant (driver implementation is at fault)")] Unexpected, } #[cfg(any(dx12, vulkan))] impl From for DeviceError { fn from(result: gpu_allocator::AllocationError) -> Self { match result { gpu_allocator::AllocationError::OutOfMemory => Self::OutOfMemory, gpu_allocator::AllocationError::FailedToMap(e) => { log::error!("gpu-allocator: Failed to map: {e}"); Self::Lost } gpu_allocator::AllocationError::NoCompatibleMemoryTypeFound => { log::error!("gpu-allocator: No Compatible Memory Type Found"); Self::Lost } gpu_allocator::AllocationError::InvalidAllocationCreateDesc => { log::error!("gpu-allocator: Invalid Allocation Creation Description"); Self::Lost } gpu_allocator::AllocationError::InvalidAllocatorCreateDesc(e) => { log::error!("gpu-allocator: Invalid Allocator Creation Description: {e}"); Self::Lost } gpu_allocator::AllocationError::Internal(e) => { log::error!("gpu-allocator: Internal Error: {e}"); Self::Lost } gpu_allocator::AllocationError::BarrierLayoutNeedsDevice10 | gpu_allocator::AllocationError::CastableFormatsRequiresEnhancedBarriers | gpu_allocator::AllocationError::CastableFormatsRequiresAtLeastDevice12 => { unreachable!() } } } } // A copy of gpu_allocator::AllocationSizes, allowing to read the configured value for // the dx12 backend, we should instead add getters to gpu_allocator::AllocationSizes // and remove this type. // https://github.com/Traverse-Research/gpu-allocator/issues/295 #[cfg_attr(not(any(dx12, vulkan)), expect(dead_code))] pub(crate) struct AllocationSizes { pub(crate) min_device_memblock_size: u64, pub(crate) max_device_memblock_size: u64, pub(crate) min_host_memblock_size: u64, pub(crate) max_host_memblock_size: u64, } impl AllocationSizes { #[allow(dead_code, reason = "may be unused on some platforms")] pub(crate) fn from_memory_hints(memory_hints: &wgt::MemoryHints) -> Self { // TODO: the allocator's configuration should take hardware capability into // account. const MB: u64 = 1024 * 1024; match memory_hints { wgt::MemoryHints::Performance => Self { min_device_memblock_size: 128 * MB, max_device_memblock_size: 256 * MB, min_host_memblock_size: 64 * MB, max_host_memblock_size: 128 * MB, }, wgt::MemoryHints::MemoryUsage => Self { min_device_memblock_size: 8 * MB, max_device_memblock_size: 64 * MB, min_host_memblock_size: 4 * MB, max_host_memblock_size: 32 * MB, }, wgt::MemoryHints::Manual { suballocated_device_memory_block_size, } => { // TODO: https://github.com/gfx-rs/wgpu/issues/8625 // Would it be useful to expose the host size in memory hints // instead of always using half of the device size? let device_size = suballocated_device_memory_block_size; let host_size = device_size.start / 2..device_size.end / 2; // gpu_allocator clamps the sizes between 4MiB and 256MiB, but we clamp them ourselves since we use // the sizes when detecting high memory pressure and there is no way to query the values otherwise. Self { min_device_memblock_size: device_size.start.clamp(4 * MB, 256 * MB), max_device_memblock_size: device_size.end.clamp(4 * MB, 256 * MB), min_host_memblock_size: host_size.start.clamp(4 * MB, 256 * MB), max_host_memblock_size: host_size.end.clamp(4 * MB, 256 * MB), } } } } } #[cfg(any(dx12, vulkan))] impl From for gpu_allocator::AllocationSizes { fn from(value: AllocationSizes) -> gpu_allocator::AllocationSizes { gpu_allocator::AllocationSizes::new( value.min_device_memblock_size, value.min_host_memblock_size, ) .with_max_device_memblock_size(value.max_device_memblock_size) .with_max_host_memblock_size(value.max_host_memblock_size) } } #[allow(dead_code, reason = "may be unused on some platforms")] #[cold] fn hal_usage_error(txt: T) -> ! { panic!("wgpu-hal invariant was violated (usage error): {txt}") } #[allow(dead_code, reason = "may be unused on some platforms")] #[cold] fn hal_internal_error(txt: T) -> ! { panic!("wgpu-hal ran into a preventable internal error: {txt}") } #[derive(Clone, Debug, Eq, PartialEq, Error)] pub enum ShaderError { #[error("Compilation failed: {0:?}")] Compilation(String), #[error(transparent)] Device(#[from] DeviceError), } #[derive(Clone, Debug, Eq, PartialEq, Error)] pub enum PipelineError { #[error("Linkage failed for stage {0:?}: {1}")] Linkage(wgt::ShaderStages, String), #[error("Entry point for stage {0:?} is invalid")] EntryPoint(naga::ShaderStage), #[error(transparent)] Device(#[from] DeviceError), #[error("Pipeline constant error for stage {0:?}: {1}")] PipelineConstants(wgt::ShaderStages, String), } #[derive(Clone, Debug, Eq, PartialEq, Error)] pub enum PipelineCacheError { #[error(transparent)] Device(#[from] DeviceError), } #[derive(Clone, Debug, Eq, PartialEq, Error)] pub enum SurfaceError { #[error("Surface is lost")] Lost, #[error("Surface is outdated, needs to be re-created")] Outdated, #[error("Timed out waiting for a surface texture")] Timeout, #[error("The window is occluded (e.g. minimized or behind another window). Try again once the window is no longer occluded.")] Occluded, #[error(transparent)] Device(#[from] DeviceError), #[error("Other reason: {0}")] Other(&'static str), } /// Error occurring while trying to create an instance, or create a surface from an instance; /// typically relating to the state of the underlying graphics API or hardware. #[derive(Clone, Debug, Error)] #[error("{message}")] pub struct InstanceError { /// These errors are very platform specific, so do not attempt to encode them as an enum. /// /// This message should describe the problem in sufficient detail to be useful for a /// user-to-developer “why won't this work on my machine” bug report, and otherwise follow /// . message: String, /// Underlying error value, if any is available. #[source] source: Option>, } impl InstanceError { #[allow(dead_code, reason = "may be unused on some platforms")] pub(crate) fn new(message: String) -> Self { Self { message, source: None, } } #[allow(dead_code, reason = "may be unused on some platforms")] pub(crate) fn with_source(message: String, source: impl Error + Send + Sync + 'static) -> Self { cfg_if::cfg_if! { if #[cfg(supports_ptr_atomics)] { let source = Arc::new(source); } else { // TODO(https://github.com/rust-lang/rust/issues/18598): avoid indirection via Box once arbitrary types support unsized coercion let source: Box = Box::new(source); let source = Arc::from(source); } } Self { message, source: Some(source), } } } /// All the types and methods that make up a implementation on top of a backend. /// /// Only the types that have non-dyn trait bounds have methods on them. Most methods /// are either on [`CommandEncoder`] or [`Device`]. /// /// The api can either be used through generics (through use of this trait and associated /// types) or dynamically through using the `Dyn*` traits. pub trait Api: Clone + fmt::Debug + Sized + WasmNotSendSync + 'static { const VARIANT: wgt::Backend; type Instance: DynInstance + Instance; type Surface: DynSurface + Surface; type Adapter: DynAdapter + Adapter; type Device: DynDevice + Device; type Queue: DynQueue + Queue; type CommandEncoder: DynCommandEncoder + CommandEncoder; /// This API's command buffer type. /// /// The only thing you can do with `CommandBuffer`s is build them /// with a [`CommandEncoder`] and then pass them to /// [`Queue::submit`] for execution, or destroy them by passing /// them to [`CommandEncoder::reset_all`]. /// /// [`CommandEncoder`]: Api::CommandEncoder type CommandBuffer: DynCommandBuffer; type Buffer: DynBuffer; type Texture: DynTexture; type SurfaceTexture: DynSurfaceTexture + Borrow; type TextureView: DynTextureView; type Sampler: DynSampler; type QuerySet: DynQuerySet; /// A value you can block on to wait for something to finish. /// /// A `Fence` holds a monotonically increasing [`FenceValue`]. You can call /// [`Device::wait`] to block until a fence reaches or passes a value you /// choose. [`Queue::submit`] can take a `Fence` and a [`FenceValue`] to /// store in it when the submitted work is complete. /// /// Attempting to set a fence to a value less than its current value has no /// effect. /// /// Waiting on a fence returns as soon as the fence reaches *or passes* the /// requested value. This implies that, in order to reliably determine when /// an operation has completed, operations must finish in order of /// increasing fence values: if a higher-valued operation were to finish /// before a lower-valued operation, then waiting for the fence to reach the /// lower value could return before the lower-valued operation has actually /// finished. type Fence: DynFence; type BindGroupLayout: DynBindGroupLayout; type BindGroup: DynBindGroup; type PipelineLayout: DynPipelineLayout; type ShaderModule: DynShaderModule; type RenderPipeline: DynRenderPipeline; type ComputePipeline: DynComputePipeline; type PipelineCache: DynPipelineCache; type AccelerationStructure: DynAccelerationStructure + 'static; } pub trait Instance: Sized + WasmNotSendSync { type A: Api; unsafe fn init(desc: &InstanceDescriptor<'_>) -> Result; unsafe fn create_surface( &self, display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, ) -> Result<::Surface, InstanceError>; /// `surface_hint` is only used by the GLES backend targeting WebGL2 unsafe fn enumerate_adapters( &self, surface_hint: Option<&::Surface>, ) -> Vec>; } pub trait Surface: WasmNotSendSync { type A: Api; /// Configure `self` to use `device`. /// /// # Safety /// /// - All GPU work using `self` must have been completed. /// - All [`AcquiredSurfaceTexture`]s must have been destroyed. /// - All [`Api::TextureView`]s derived from the [`AcquiredSurfaceTexture`]s must have been destroyed. /// - The surface `self` must not currently be configured to use any other [`Device`]. unsafe fn configure( &self, device: &::Device, config: &SurfaceConfiguration, ) -> Result<(), SurfaceError>; /// Unconfigure `self` on `device`. /// /// # Safety /// /// - All GPU work that uses `surface` must have been completed. /// - All [`AcquiredSurfaceTexture`]s must have been destroyed. /// - All [`Api::TextureView`]s derived from the [`AcquiredSurfaceTexture`]s must have been destroyed. /// - The surface `self` must have been configured on `device`. unsafe fn unconfigure(&self, device: &::Device); /// Return the next texture to be presented by `self`, for the caller to draw on. /// /// On success, return an [`AcquiredSurfaceTexture`] representing the /// texture into which the caller should draw the image to be displayed on /// `self`. /// /// If `timeout` elapses before `self` has a texture ready to be acquired, /// return `Err(SurfaceError::Timeout)`. If `timeout` is `None`, wait /// indefinitely, with no timeout. /// /// # Using an [`AcquiredSurfaceTexture`] /// /// On success, this function returns an [`AcquiredSurfaceTexture`] whose /// [`texture`] field is a [`SurfaceTexture`] from which the caller can /// [`borrow`] a [`Texture`] to draw on. The [`AcquiredSurfaceTexture`] also /// carries some metadata about that [`SurfaceTexture`]. /// /// All calls to [`Queue::submit`] that draw on that [`Texture`] must also /// include the [`SurfaceTexture`] in the `surface_textures` argument. /// /// When you are done drawing on the texture, you can display it on `self` /// by passing the [`SurfaceTexture`] and `self` to [`Queue::present`]. /// /// If you do not wish to display the texture, you must pass the /// [`SurfaceTexture`] to [`self.discard_texture`], so that it can be reused /// by future acquisitions. /// /// # Portability /// /// Some backends can't support a timeout when acquiring a texture. On these /// backends, `timeout` is ignored. /// /// On macOS, this returns `Err(SurfaceError::Timeout)` when the window is /// not visible (minimized, fully occluded, or on another virtual desktop) /// to avoid blocking in `CAMetalLayer.nextDrawable()`. /// /// # Safety /// /// - The surface `self` must currently be configured on some [`Device`]. /// /// - The `fence` argument must be the same [`Fence`] passed to all calls to /// [`Queue::submit`] that used [`Texture`]s acquired from this surface. /// /// - You may only have one texture acquired from `self` at a time. When /// `acquire_texture` returns `Ok(ast)`, you must pass the returned /// [`SurfaceTexture`] `ast.texture` to either [`Queue::present`] or /// [`Surface::discard_texture`] before calling `acquire_texture` again. /// /// [`texture`]: AcquiredSurfaceTexture::texture /// [`SurfaceTexture`]: Api::SurfaceTexture /// [`borrow`]: alloc::borrow::Borrow::borrow /// [`Texture`]: Api::Texture /// [`Fence`]: Api::Fence /// [`self.discard_texture`]: Surface::discard_texture unsafe fn acquire_texture( &self, timeout: Option, fence: &::Fence, ) -> Result, SurfaceError>; /// Relinquish an acquired texture without presenting it. /// /// After this call, the texture underlying [`SurfaceTexture`] may be /// returned by subsequent calls to [`self.acquire_texture`]. /// /// # Safety /// /// - The surface `self` must currently be configured on some [`Device`]. /// /// - `texture` must be a [`SurfaceTexture`] returned by a call to /// [`self.acquire_texture`] that has not yet been passed to /// [`Queue::present`]. /// /// [`SurfaceTexture`]: Api::SurfaceTexture /// [`self.acquire_texture`]: Surface::acquire_texture unsafe fn discard_texture(&self, texture: ::SurfaceTexture); } pub trait Adapter: WasmNotSendSync { type A: Api; unsafe fn open( &self, features: wgt::Features, limits: &wgt::Limits, memory_hints: &wgt::MemoryHints, ) -> Result, DeviceError>; /// Return the set of supported capabilities for a texture format. unsafe fn texture_format_capabilities( &self, format: wgt::TextureFormat, ) -> TextureFormatCapabilities; /// Returns the capabilities of working with a specified surface. /// /// `None` means presentation is not supported for it. unsafe fn surface_capabilities( &self, surface: &::Surface, ) -> Option; /// Creates a [`PresentationTimestamp`] using the adapter's WSI. /// /// [`PresentationTimestamp`]: wgt::PresentationTimestamp unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp; /// The combination of all usages that the are guaranteed to be be ordered by the hardware. /// If a usage is ordered, then if the buffer state doesn't change between draw calls, /// there are no barriers needed for synchronization. fn get_ordered_buffer_usages(&self) -> wgt::BufferUses; /// The combination of all usages that the are guaranteed to be be ordered by the hardware. /// If a usage is ordered, then if the buffer state doesn't change between draw calls, /// there are no barriers needed for synchronization. fn get_ordered_texture_usages(&self) -> wgt::TextureUses; } /// A connection to a GPU and a pool of resources to use with it. /// /// A `wgpu-hal` `Device` represents an open connection to a specific graphics /// processor, controlled via the backend [`Device::A`]. A `Device` is mostly /// used for creating resources. Each `Device` has an associated [`Queue`] used /// for command submission. /// /// On Vulkan a `Device` corresponds to a logical device ([`VkDevice`]). Other /// backends don't have an exact analog: for example, [`ID3D12Device`]s and /// [`MTLDevice`]s are owned by the backends' [`wgpu_hal::Adapter`] /// implementations, and shared by all [`wgpu_hal::Device`]s created from that /// `Adapter`. /// /// A `Device`'s life cycle is generally: /// /// 1) Obtain a `Device` and its associated [`Queue`] by calling /// [`Adapter::open`]. /// /// Alternatively, the backend-specific types that implement [`Adapter`] often /// have methods for creating a `wgpu-hal` `Device` from a platform-specific /// handle. For example, [`vulkan::Adapter::device_from_raw`] can create a /// [`vulkan::Device`] from an [`ash::Device`]. /// /// 1) Create resources to use on the device by calling methods like /// [`Device::create_texture`] or [`Device::create_shader_module`]. /// /// 1) Call [`Device::create_command_encoder`] to obtain a [`CommandEncoder`], /// which you can use to build [`CommandBuffer`]s holding commands to be /// executed on the GPU. /// /// 1) Call [`Queue::submit`] on the `Device`'s associated [`Queue`] to submit /// [`CommandBuffer`]s for execution on the GPU. If needed, call /// [`Device::wait`] to wait for them to finish execution. /// /// 1) Free resources with methods like [`Device::destroy_texture`] or /// [`Device::destroy_shader_module`]. /// /// 1) Drop the device. /// /// [`vkDevice`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkDevice /// [`ID3D12Device`]: https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nn-d3d12-id3d12device /// [`MTLDevice`]: https://developer.apple.com/documentation/metal/mtldevice /// [`wgpu_hal::Adapter`]: Adapter /// [`wgpu_hal::Device`]: Device /// [`vulkan::Adapter::device_from_raw`]: vulkan/struct.Adapter.html#method.device_from_raw /// [`vulkan::Device`]: vulkan/struct.Device.html /// [`ash::Device`]: https://docs.rs/ash/latest/ash/struct.Device.html /// [`CommandBuffer`]: Api::CommandBuffer /// /// # Safety /// /// As with other `wgpu-hal` APIs, [validation] is the caller's /// responsibility. Here are the general requirements for all `Device` /// methods: /// /// - Any resource passed to a `Device` method must have been created by that /// `Device`. For example, a [`Texture`] passed to [`Device::destroy_texture`] must /// have been created with the `Device` passed as `self`. /// /// - Resources may not be destroyed if they are used by any submitted command /// buffers that have not yet finished execution. /// /// [validation]: index.html#validation-is-the-calling-codes-responsibility-not-wgpu-hals /// [`Texture`]: Api::Texture pub trait Device: WasmNotSendSync { type A: Api; /// Creates a new buffer. /// /// The initial usage is `wgt::BufferUses::empty()`. unsafe fn create_buffer( &self, desc: &BufferDescriptor, ) -> Result<::Buffer, DeviceError>; /// Free `buffer` and any GPU resources it owns. /// /// Note that backends are allowed to allocate GPU memory for buffers from /// allocation pools, and this call is permitted to simply return `buffer`'s /// storage to that pool, without making it available to other applications. /// /// # Safety /// /// - The given `buffer` must not currently be mapped. unsafe fn destroy_buffer(&self, buffer: ::Buffer); /// A hook for when a wgpu-core buffer is created from a raw wgpu-hal buffer. unsafe fn add_raw_buffer(&self, buffer: &::Buffer); /// Return a pointer to CPU memory mapping the contents of `buffer`. /// /// Buffer mappings are persistent: the buffer may remain mapped on the CPU /// while the GPU reads or writes to it. (Note that `wgpu_core` does not use /// this feature: when a `wgpu_core::Buffer` is unmapped, the underlying /// `wgpu_hal` buffer is also unmapped.) /// /// If this function returns `Ok(mapping)`, then: /// /// - `mapping.ptr` is the CPU address of the start of the mapped memory. /// /// - If `mapping.is_coherent` is `true`, then CPU writes to the mapped /// memory are immediately visible on the GPU, and vice versa. /// /// # Safety /// /// - The given `buffer` must have been created with the [`MAP_READ`] or /// [`MAP_WRITE`] flags set in [`BufferDescriptor::usage`]. /// /// - The given `range` must fall within the size of `buffer`. /// /// - The caller must avoid data races between the CPU and the GPU. A data /// race is any pair of accesses to a particular byte, one of which is a /// write, that are not ordered with respect to each other by some sort of /// synchronization operation. /// /// - If this function returns `Ok(mapping)` and `mapping.is_coherent` is /// `false`, then: /// /// - Every CPU write to a mapped byte followed by a GPU read of that byte /// must have at least one call to [`Device::flush_mapped_ranges`] /// covering that byte that occurs between those two accesses. /// /// - Every GPU write to a mapped byte followed by a CPU read of that byte /// must have at least one call to [`Device::invalidate_mapped_ranges`] /// covering that byte that occurs between those two accesses. /// /// Note that the data race rule above requires that all such access pairs /// be ordered, so it is meaningful to talk about what must occur /// "between" them. /// /// - Zero-sized mappings are not allowed. /// /// - The returned [`BufferMapping::ptr`] must not be used after a call to /// [`Device::unmap_buffer`]. /// /// [`MAP_READ`]: wgt::BufferUses::MAP_READ /// [`MAP_WRITE`]: wgt::BufferUses::MAP_WRITE unsafe fn map_buffer( &self, buffer: &::Buffer, range: MemoryRange, ) -> Result; /// Remove the mapping established by the last call to [`Device::map_buffer`]. /// /// # Safety /// /// - The given `buffer` must be currently mapped. unsafe fn unmap_buffer(&self, buffer: &::Buffer); /// Indicate that CPU writes to mapped buffer memory should be made visible to the GPU. /// /// # Safety /// /// - The given `buffer` must be currently mapped. /// /// - All ranges produced by `ranges` must fall within `buffer`'s size. unsafe fn flush_mapped_ranges(&self, buffer: &::Buffer, ranges: I) where I: Iterator; /// Indicate that GPU writes to mapped buffer memory should be made visible to the CPU. /// /// # Safety /// /// - The given `buffer` must be currently mapped. /// /// - All ranges produced by `ranges` must fall within `buffer`'s size. unsafe fn invalidate_mapped_ranges(&self, buffer: &::Buffer, ranges: I) where I: Iterator; /// Creates a new texture. /// /// The initial usage for all subresources is `wgt::TextureUses::UNINITIALIZED`. unsafe fn create_texture( &self, desc: &TextureDescriptor, ) -> Result<::Texture, DeviceError>; unsafe fn destroy_texture(&self, texture: ::Texture); /// A hook for when a wgpu-core texture is created from a raw wgpu-hal texture. unsafe fn add_raw_texture(&self, texture: &::Texture); unsafe fn create_texture_view( &self, texture: &::Texture, desc: &TextureViewDescriptor, ) -> Result<::TextureView, DeviceError>; unsafe fn destroy_texture_view(&self, view: ::TextureView); unsafe fn create_sampler( &self, desc: &SamplerDescriptor, ) -> Result<::Sampler, DeviceError>; unsafe fn destroy_sampler(&self, sampler: ::Sampler); /// Create a fresh [`CommandEncoder`]. /// /// The new `CommandEncoder` is in the "closed" state. unsafe fn create_command_encoder( &self, desc: &CommandEncoderDescriptor<::Queue>, ) -> Result<::CommandEncoder, DeviceError>; /// Creates a bind group layout. unsafe fn create_bind_group_layout( &self, desc: &BindGroupLayoutDescriptor, ) -> Result<::BindGroupLayout, DeviceError>; unsafe fn destroy_bind_group_layout(&self, bg_layout: ::BindGroupLayout); unsafe fn create_pipeline_layout( &self, desc: &PipelineLayoutDescriptor<::BindGroupLayout>, ) -> Result<::PipelineLayout, DeviceError>; unsafe fn destroy_pipeline_layout(&self, pipeline_layout: ::PipelineLayout); #[allow(clippy::type_complexity)] unsafe fn create_bind_group( &self, desc: &BindGroupDescriptor< ::BindGroupLayout, ::Buffer, ::Sampler, ::TextureView, ::AccelerationStructure, >, ) -> Result<::BindGroup, DeviceError>; unsafe fn destroy_bind_group(&self, group: ::BindGroup); unsafe fn create_shader_module( &self, desc: &ShaderModuleDescriptor, shader: ShaderInput, ) -> Result<::ShaderModule, ShaderError>; unsafe fn destroy_shader_module(&self, module: ::ShaderModule); #[allow(clippy::type_complexity)] unsafe fn create_render_pipeline( &self, desc: &RenderPipelineDescriptor< ::PipelineLayout, ::ShaderModule, ::PipelineCache, >, ) -> Result<::RenderPipeline, PipelineError>; unsafe fn destroy_render_pipeline(&self, pipeline: ::RenderPipeline); #[allow(clippy::type_complexity)] unsafe fn create_compute_pipeline( &self, desc: &ComputePipelineDescriptor< ::PipelineLayout, ::ShaderModule, ::PipelineCache, >, ) -> Result<::ComputePipeline, PipelineError>; unsafe fn destroy_compute_pipeline(&self, pipeline: ::ComputePipeline); unsafe fn create_pipeline_cache( &self, desc: &PipelineCacheDescriptor<'_>, ) -> Result<::PipelineCache, PipelineCacheError>; fn pipeline_cache_validation_key(&self) -> Option<[u8; 16]> { None } unsafe fn destroy_pipeline_cache(&self, cache: ::PipelineCache); unsafe fn create_query_set( &self, desc: &wgt::QuerySetDescriptor