/*! 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 `wgpu-hal` indicates that the * mapping is not coherent (that is, automatically synchronized * between the two devices). * * - 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 * group. 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 [`d3d12`] crate's bindings. * * [`ash`]: https://crates.io/crates/ash * [MoltenVK]: https://github.com/KhronosGroup/MoltenVK * [`metal`]: https://crates.io/crates/metal * [`d3d12`]: ahttps://crates.io/crates/d3d12 * * ## 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 */ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_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, // for `if_then_panic` until it reaches stable unknown_lints, // We use loops for getting early-out of scope without closures. clippy::never_loop, // 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, // "if panic" is a good uniform construct. clippy::if_then_panic, // We unsafe impl `Send` for a reason. clippy::non_send_fields_in_send_ty, // TODO! clippy::missing_safety_doc, // Clashes with clippy::pattern_type_mismatch clippy::needless_borrowed_reference, )] #![warn( trivial_casts, trivial_numeric_casts, unsafe_op_in_unsafe_fn, unused_extern_crates, unused_qualifications, // We don't match on a reference, unless required. clippy::pattern_type_mismatch, )] /// DirectX12 API internals. #[cfg(dx12)] pub mod dx12; /// A dummy API implementation. pub mod empty; /// GLES API internals. #[cfg(gles)] pub mod gles; /// Metal API internals. #[cfg(metal)] pub mod metal; /// Vulkan API internals. #[cfg(vulkan)] pub mod vulkan; pub mod auxil; pub mod api { #[cfg(dx12)] pub use super::dx12::Api as Dx12; pub use super::empty::Api as Empty; #[cfg(gles)] pub use super::gles::Api as Gles; #[cfg(metal)] pub use super::metal::Api as Metal; #[cfg(vulkan)] pub use super::vulkan::Api as Vulkan; } use std::{ borrow::{Borrow, Cow}, fmt, num::NonZeroU32, ops::{Range, RangeInclusive}, ptr::NonNull, sync::Arc, }; use bitflags::bitflags; use parking_lot::Mutex; use thiserror::Error; use wgt::WasmNotSendSync; // - Vertex + Fragment // - Compute pub const MAX_CONCURRENT_SHADER_STAGES: usize = 2; 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. pub const QUERY_SIZE: wgt::BufferAddress = 8; pub type Label<'a> = Option<&'a str>; pub type MemoryRange = Range; pub type FenceValue = u64; /// Drop guard to signal wgpu-hal is no longer using an externally created object. pub type DropGuard = Box; #[derive(Clone, Debug, PartialEq, Eq, Error)] pub enum DeviceError { #[error("Out of memory")] OutOfMemory, #[error("Device is lost")] Lost, #[error("Creation of a resource failed for a reason other than running out of memory.")] ResourceCreationFailed, } #[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), } #[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(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)] // may be unused on some platforms pub(crate) fn new(message: String) -> Self { Self { message, source: None, } } #[allow(dead_code)] // may be unused on some platforms pub(crate) fn with_source( message: String, source: impl std::error::Error + Send + Sync + 'static, ) -> Self { Self { message, source: Some(Arc::new(source)), } } } pub trait Api: Clone + fmt::Debug + Sized { type Instance: Instance; type Surface: Surface; type Adapter: Adapter; type Device: Device; type Queue: Queue; type CommandEncoder: 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: WasmNotSendSync + fmt::Debug; type Buffer: fmt::Debug + WasmNotSendSync + 'static; type Texture: fmt::Debug + WasmNotSendSync + 'static; type SurfaceTexture: fmt::Debug + WasmNotSendSync + Borrow; type TextureView: fmt::Debug + WasmNotSendSync; type Sampler: fmt::Debug + WasmNotSendSync; type QuerySet: fmt::Debug + WasmNotSendSync; /// 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: fmt::Debug + WasmNotSendSync; type BindGroupLayout: fmt::Debug + WasmNotSendSync; type BindGroup: fmt::Debug + WasmNotSendSync; type PipelineLayout: fmt::Debug + WasmNotSendSync; type ShaderModule: fmt::Debug + WasmNotSendSync; type RenderPipeline: fmt::Debug + WasmNotSendSync; type ComputePipeline: fmt::Debug + WasmNotSendSync; type AccelerationStructure: fmt::Debug + WasmNotSendSync + '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>; unsafe fn destroy_surface(&self, surface: ::Surface); unsafe fn enumerate_adapters(&self) -> Vec>; } pub trait Surface: WasmNotSendSync { type A: Api; /// Configures the surface to use the given device. /// /// # Safety /// /// - All gpu work that uses the 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. /// - All surfaces created using other devices must have been unconfigured before this call. unsafe fn configure( &self, device: &::Device, config: &SurfaceConfiguration, ) -> Result<(), SurfaceError>; /// Unconfigures the surface on the given device. /// /// # Safety /// /// - All gpu work that uses the 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 must have been configured on the given device. unsafe fn unconfigure(&self, device: &::Device); /// Returns the next texture to be presented by the swapchain for drawing /// /// A `timeout` of `None` means to wait indefinitely, with no timeout. /// /// # Portability /// /// Some backends can't support a timeout when acquiring a texture and /// the timeout will be ignored. /// /// Returns `None` on timing out. unsafe fn acquire_texture( &self, timeout: Option, ) -> Result>, SurfaceError>; unsafe fn discard_texture(&self, texture: ::SurfaceTexture); } pub trait Adapter: WasmNotSendSync { type A: Api; unsafe fn open( &self, features: wgt::Features, limits: &wgt::Limits, ) -> 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; } pub trait Device: WasmNotSendSync { type A: Api; /// Exit connection to this logical device. unsafe fn exit(self, queue: ::Queue); /// Creates a new buffer. /// /// The initial usage is `BufferUses::empty()`. unsafe fn create_buffer( &self, desc: &BufferDescriptor, ) -> Result<::Buffer, DeviceError>; unsafe fn destroy_buffer(&self, buffer: ::Buffer); //TODO: clarify if zero-sized mapping is allowed unsafe fn map_buffer( &self, buffer: &::Buffer, range: MemoryRange, ) -> Result; unsafe fn unmap_buffer(&self, buffer: &::Buffer) -> Result<(), DeviceError>; unsafe fn flush_mapped_ranges(&self, buffer: &::Buffer, ranges: I) where I: Iterator; unsafe fn invalidate_mapped_ranges(&self, buffer: &::Buffer, ranges: I) where I: Iterator; /// Creates a new texture. /// /// The initial usage for all subresources is `TextureUses::UNINITIALIZED`. unsafe fn create_texture( &self, desc: &TextureDescriptor, ) -> Result<::Texture, DeviceError>; unsafe fn destroy_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, ) -> Result<::CommandEncoder, DeviceError>; unsafe fn destroy_command_encoder(&self, pool: ::CommandEncoder); /// 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, ) -> Result<::PipelineLayout, DeviceError>; unsafe fn destroy_pipeline_layout(&self, pipeline_layout: ::PipelineLayout); unsafe fn create_bind_group( &self, desc: &BindGroupDescriptor, ) -> 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); unsafe fn create_render_pipeline( &self, desc: &RenderPipelineDescriptor, ) -> Result<::RenderPipeline, PipelineError>; unsafe fn destroy_render_pipeline(&self, pipeline: ::RenderPipeline); unsafe fn create_compute_pipeline( &self, desc: &ComputePipelineDescriptor, ) -> Result<::ComputePipeline, PipelineError>; unsafe fn destroy_compute_pipeline(&self, pipeline: ::ComputePipeline); unsafe fn create_query_set( &self, desc: &wgt::QuerySetDescriptor