use crate::{Epoch, Index}; use std::{ cmp::Ordering, fmt::{self, Debug}, hash::Hash, marker::PhantomData, }; use wgt::{Backend, WasmNotSendSync}; type IdType = u64; type ZippedIndex = Index; type NonZeroId = std::num::NonZeroU64; const INDEX_BITS: usize = std::mem::size_of::() * 8; const EPOCH_BITS: usize = INDEX_BITS - BACKEND_BITS; const BACKEND_BITS: usize = 3; const BACKEND_SHIFT: usize = INDEX_BITS * 2 - BACKEND_BITS; pub const EPOCH_MASK: u32 = (1 << (EPOCH_BITS)) - 1; /// The raw underlying representation of an identifier. #[repr(transparent)] #[cfg_attr( any(feature = "serde", feature = "trace"), derive(serde::Serialize), serde(into = "SerialId") )] #[cfg_attr( any(feature = "serde", feature = "replay"), derive(serde::Deserialize), serde(from = "SerialId") )] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RawId(NonZeroId); impl RawId { #[doc(hidden)] #[inline] pub fn from_non_zero(non_zero: NonZeroId) -> Self { Self(non_zero) } #[doc(hidden)] #[inline] pub fn into_non_zero(self) -> NonZeroId { self.0 } /// Zip together an identifier and return its raw underlying representation. pub fn zip(index: Index, epoch: Epoch, backend: Backend) -> RawId { assert_eq!(0, epoch >> EPOCH_BITS); assert_eq!(0, (index as IdType) >> INDEX_BITS); let v = index as IdType | ((epoch as IdType) << INDEX_BITS) | ((backend as IdType) << BACKEND_SHIFT); Self(NonZeroId::new(v).unwrap()) } /// Unzip a raw identifier into its components. #[allow(trivial_numeric_casts)] pub fn unzip(self) -> (Index, Epoch, Backend) { ( (self.0.get() as ZippedIndex) as Index, (((self.0.get() >> INDEX_BITS) as ZippedIndex) & (EPOCH_MASK as ZippedIndex)) as Index, self.backend(), ) } pub fn backend(self) -> Backend { match self.0.get() >> (BACKEND_SHIFT) as u8 { 0 => Backend::Empty, 1 => Backend::Vulkan, 2 => Backend::Metal, 3 => Backend::Dx12, 4 => Backend::Gl, _ => unreachable!(), } } } /// Coerce a slice of identifiers into a slice of optional raw identifiers. /// /// There's two reasons why we know this is correct: /// * `Option` is guaranteed to be niche-filled to 0's. /// * The `T` in `Option` can inhabit any representation except 0's, since /// its underlying representation is `NonZero*`. pub fn as_option_slice(ids: &[Id]) -> &[Option>] { // SAFETY: Any Id is repr(transparent) over `Option`, since both // are backed by non-zero types. unsafe { std::slice::from_raw_parts(ids.as_ptr().cast(), ids.len()) } } /// An identifier for a wgpu object. /// /// An `Id` value identifies a value stored in a [`Global`]'s [`Hub`]'s [`Storage`]. /// `Storage` implements [`Index`] and [`IndexMut`], accepting `Id` values as indices. /// /// ## Note on `Id` typing /// /// You might assume that an `Id` can only be used to retrieve a resource of /// type `T`, but that is not quite the case. The id types in `wgpu-core`'s /// public API ([`TextureId`], for example) can refer to resources belonging to /// any backend, but the corresponding resource types ([`Texture`], for /// example) are always parameterized by a specific backend `A`. /// /// So the `T` in `Id` is usually a resource type like `Texture`, /// where [`Empty`] is the `wgpu_hal` dummy back end. These empty types are /// never actually used, beyond just making sure you access each `Storage` with /// the right kind of identifier. The members of [`Hub`] pair up each /// `X` type with the resource type `X`, for some specific backend /// `A`. /// /// [`Global`]: crate::global::Global /// [`Hub`]: crate::hub::Hub /// [`Hub`]: crate::hub::Hub /// [`Storage`]: crate::storage::Storage /// [`Texture`]: crate::resource::Texture /// [`Index`]: std::ops::Index /// [`IndexMut`]: std::ops::IndexMut /// [`Registry`]: crate::hub::Registry /// [`Empty`]: hal::api::Empty #[repr(transparent)] #[cfg_attr(any(feature = "serde", feature = "trace"), derive(serde::Serialize))] #[cfg_attr(any(feature = "serde", feature = "replay"), derive(serde::Deserialize))] #[cfg_attr( any(feature = "serde", feature = "trace", feature = "replay"), serde(transparent) )] pub struct Id(RawId, PhantomData); // This type represents Id in a more readable (and editable) way. #[allow(dead_code)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] enum SerialId { // The only variant forces RON to not ignore "Id" Id(Index, Epoch, Backend), } impl From for SerialId { fn from(id: RawId) -> Self { let (index, epoch, backend) = id.unzip(); Self::Id(index, epoch, backend) } } impl From for RawId { fn from(id: SerialId) -> Self { match id { SerialId::Id(index, epoch, backend) => RawId::zip(index, epoch, backend), } } } impl Id where T: Marker, { /// # Safety /// /// The raw id must be valid for the type. pub unsafe fn from_raw(raw: RawId) -> Self { Self(raw, PhantomData) } /// Coerce the identifiers into its raw underlying representation. pub fn into_raw(self) -> RawId { self.0 } #[allow(dead_code)] pub(crate) fn dummy(index: u32) -> Self { Id::zip(index, 1, Backend::Empty) } #[allow(dead_code)] pub(crate) fn is_valid(&self) -> bool { self.backend() != Backend::Empty } /// Get the backend this identifier corresponds to. #[inline] pub fn backend(self) -> Backend { self.0.backend() } /// Transmute this identifier to one with a different marker trait. /// /// Legal use is governed through a sealed trait, however it's correctness /// depends on the current implementation of `wgpu-core`. #[inline] pub const fn transmute>(self) -> Id { Id(self.0, PhantomData) } #[inline] pub fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self { Id(RawId::zip(index, epoch, backend), PhantomData) } #[inline] pub fn unzip(self) -> (Index, Epoch, Backend) { self.0.unzip() } } pub(crate) mod transmute { // This trait is effectively sealed to prevent illegal transmutes. pub trait Transmute: super::Marker {} // Self-transmute is always legal. impl Transmute for T where T: super::Marker {} // TODO: Remove these once queues have their own identifiers. impl Transmute for super::markers::Device {} impl Transmute for super::markers::Queue {} impl Transmute for super::markers::CommandEncoder {} impl Transmute for super::markers::CommandBuffer {} } impl Copy for Id where T: Marker {} impl Clone for Id where T: Marker, { #[inline] fn clone(&self) -> Self { *self } } impl Debug for Id where T: Marker, { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let (index, epoch, backend) = self.unzip(); let backend = match backend { Backend::Empty => "_", Backend::Vulkan => "vk", Backend::Metal => "mtl", Backend::Dx12 => "d3d12", Backend::Gl => "gl", Backend::BrowserWebGpu => "webgpu", }; write!(formatter, "Id({index},{epoch},{backend})")?; Ok(()) } } impl Hash for Id where T: Marker, { #[inline] fn hash(&self, state: &mut H) { self.0.hash(state); } } impl PartialEq for Id where T: Marker, { #[inline] fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for Id where T: Marker {} impl PartialOrd for Id where T: Marker, { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Id where T: Marker, { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } /// Marker trait used to determine which types uniquely identify a resource. /// /// For example, `Device` will have the same type of identifier as /// `Device` because `Device` for any `T` defines the same maker type. pub trait Marker: 'static + WasmNotSendSync {} // This allows `()` to be used as a marker type for tests. // // We don't want these in production code, since they essentially remove type // safety, like how identifiers across different types can be compared. #[cfg(test)] impl Marker for () {} /// Define identifiers for each resource. macro_rules! ids { ($( $(#[$($meta:meta)*])* pub type $name:ident $marker:ident; )*) => { /// Marker types for each resource. pub mod markers { $( #[derive(Debug)] pub enum $marker {} impl super::Marker for $marker {} )* } $( $(#[$($meta)*])* pub type $name = Id; )* } } ids! { pub type AdapterId Adapter; pub type SurfaceId Surface; pub type DeviceId Device; pub type QueueId Queue; pub type BufferId Buffer; pub type StagingBufferId StagingBuffer; pub type TextureViewId TextureView; pub type TextureId Texture; pub type SamplerId Sampler; pub type BindGroupLayoutId BindGroupLayout; pub type PipelineLayoutId PipelineLayout; pub type BindGroupId BindGroup; pub type ShaderModuleId ShaderModule; pub type RenderPipelineId RenderPipeline; pub type ComputePipelineId ComputePipeline; pub type CommandEncoderId CommandEncoder; pub type CommandBufferId CommandBuffer; pub type RenderPassEncoderId RenderPassEncoder; pub type ComputePassEncoderId ComputePassEncoder; pub type RenderBundleEncoderId RenderBundleEncoder; pub type RenderBundleId RenderBundle; pub type QuerySetId QuerySet; } #[test] fn test_id_backend() { for &b in &[ Backend::Empty, Backend::Vulkan, Backend::Metal, Backend::Dx12, Backend::Gl, ] { let id = crate::id::Id::<()>::zip(1, 0, b); let (_id, _epoch, backend) = id.unzip(); assert_eq!(id.backend(), b); assert_eq!(backend, b); } } #[test] fn test_id() { let last_index = ((1u64 << INDEX_BITS) - 1) as Index; let indexes = [1, last_index / 2 - 1, last_index / 2 + 1, last_index]; let epochs = [1, EPOCH_MASK / 2 - 1, EPOCH_MASK / 2 + 1, EPOCH_MASK]; let backends = [ Backend::Empty, Backend::Vulkan, Backend::Metal, Backend::Dx12, Backend::Gl, ]; for &i in &indexes { for &e in &epochs { for &b in &backends { let id = crate::id::Id::<()>::zip(i, e, b); let (index, epoch, backend) = id.unzip(); assert_eq!(index, i); assert_eq!(epoch, e); assert_eq!(backend, b); } } } }