summaryrefslogtreecommitdiffstats
path: root/gfx/wgpu/wgpu-core
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wgpu/wgpu-core')
-rw-r--r--gfx/wgpu/wgpu-core/Cargo.toml68
-rw-r--r--gfx/wgpu/wgpu-core/build.rs20
-rw-r--r--gfx/wgpu/wgpu-core/src/binding_model.rs632
-rw-r--r--gfx/wgpu/wgpu-core/src/command/allocator.rs268
-rw-r--r--gfx/wgpu/wgpu-core/src/command/bind.rs295
-rw-r--r--gfx/wgpu/wgpu-core/src/command/bundle.rs1230
-rw-r--r--gfx/wgpu/wgpu-core/src/command/compute.rs657
-rw-r--r--gfx/wgpu/wgpu-core/src/command/draw.rs180
-rw-r--r--gfx/wgpu/wgpu-core/src/command/mod.rs362
-rw-r--r--gfx/wgpu/wgpu-core/src/command/render.rs2078
-rw-r--r--gfx/wgpu/wgpu-core/src/command/transfer.rs789
-rw-r--r--gfx/wgpu/wgpu-core/src/conv.rs833
-rw-r--r--gfx/wgpu/wgpu-core/src/device/alloc.rs294
-rw-r--r--gfx/wgpu/wgpu-core/src/device/descriptor.rs168
-rw-r--r--gfx/wgpu/wgpu-core/src/device/life.rs760
-rw-r--r--gfx/wgpu/wgpu-core/src/device/mod.rs4217
-rw-r--r--gfx/wgpu/wgpu-core/src/device/queue.rs696
-rw-r--r--gfx/wgpu/wgpu-core/src/device/trace.rs192
-rw-r--r--gfx/wgpu/wgpu-core/src/hub.rs866
-rw-r--r--gfx/wgpu/wgpu-core/src/id.rs196
-rw-r--r--gfx/wgpu/wgpu-core/src/instance.rs840
-rw-r--r--gfx/wgpu/wgpu-core/src/lib.rs271
-rw-r--r--gfx/wgpu/wgpu-core/src/macros.rs226
-rw-r--r--gfx/wgpu/wgpu-core/src/pipeline.rs254
-rw-r--r--gfx/wgpu/wgpu-core/src/resource.rs447
-rw-r--r--gfx/wgpu/wgpu-core/src/swap_chain.rs294
-rw-r--r--gfx/wgpu/wgpu-core/src/track/buffer.rs241
-rw-r--r--gfx/wgpu/wgpu-core/src/track/mod.rs593
-rw-r--r--gfx/wgpu/wgpu-core/src/track/range.rs399
-rw-r--r--gfx/wgpu/wgpu-core/src/track/texture.rs466
-rw-r--r--gfx/wgpu/wgpu-core/src/validation.rs966
31 files changed, 19798 insertions, 0 deletions
diff --git a/gfx/wgpu/wgpu-core/Cargo.toml b/gfx/wgpu/wgpu-core/Cargo.toml
new file mode 100644
index 0000000000..2868fc9343
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/Cargo.toml
@@ -0,0 +1,68 @@
+[package]
+name = "wgpu-core"
+version = "0.6.0"
+authors = ["wgpu developers"]
+edition = "2018"
+description = "WebGPU core logic on gfx-hal"
+homepage = "https://github.com/gfx-rs/wgpu"
+repository = "https://github.com/gfx-rs/wgpu"
+keywords = ["graphics"]
+license = "MPL-2.0"
+
+[lib]
+
+[features]
+default = []
+# Enable API tracing
+trace = ["ron", "serde", "wgt/trace"]
+# Enable API replaying
+replay = ["serde", "wgt/replay"]
+# Enable serializable compute/render passes, and bundle encoders.
+serial-pass = ["serde", "wgt/serde", "arrayvec/serde"]
+
+[dependencies]
+arrayvec = "0.5"
+bitflags = "1.0"
+copyless = "0.1"
+fxhash = "0.2"
+hal = { package = "gfx-hal", git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+gfx-backend-empty = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+parking_lot = "0.11"
+raw-window-handle = { version = "0.3", optional = true }
+ron = { version = "0.6", optional = true }
+serde = { version = "1.0", features = ["serde_derive"], optional = true }
+smallvec = "1"
+tracing = { version = "0.1", default-features = false, features = ["std"] }
+thiserror = "1"
+gpu-alloc = { git = "https://github.com/zakarumych/gpu-alloc", rev = "d07be73f9439a37c89f5b72f2500cbf0eb4ff613" }
+gpu-descriptor = { git = "https://github.com/zakarumych/gpu-descriptor", rev = "831460c4b5120d9a74744d542f39a95b9816b5ab"}
+
+[dependencies.naga]
+version = "0.2"
+git = "https://github.com/gfx-rs/naga"
+rev = "96c80738650822de35f77ab6a589f309460c8f39"
+features = ["spv-in", "spv-out", "wgsl-in"]
+
+[dependencies.wgt]
+path = "../wgpu-types"
+package = "wgpu-types"
+version = "0.6"
+
+[target.'cfg(all(unix, not(target_os = "ios"), not(target_os = "macos")))'.dependencies]
+gfx-backend-vulkan = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+#gfx-backend-gl = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+
+[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
+gfx-backend-metal = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+gfx-backend-vulkan = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354", optional = true }
+
+[target.'cfg(windows)'.dependencies]
+gfx-backend-dx12 = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+gfx-backend-dx11 = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+gfx-backend-vulkan = { git = "https://github.com/gfx-rs/gfx", rev = "1d14789011cb892f4c1a205d3f8a87d479c2e354" }
+
+[dev-dependencies]
+loom = "0.3"
+
+[build-dependencies]
+cfg_aliases = "0.1"
diff --git a/gfx/wgpu/wgpu-core/build.rs b/gfx/wgpu/wgpu-core/build.rs
new file mode 100644
index 0000000000..382e47db90
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/build.rs
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ // Setup cfg aliases
+ cfg_aliases::cfg_aliases! {
+ // Vendors/systems
+ ios: { target_os = "ios" },
+ macos: { target_os = "macos" },
+ apple: { any(ios, macos) },
+
+ // Backends
+ vulkan: { any(windows, all(unix, not(apple)), feature = "gfx-backend-vulkan") },
+ metal: { apple },
+ dx12: { windows },
+ dx11: { windows },
+ gl: { all(not(unix), not(apple), not(windows)) },
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/binding_model.rs b/gfx/wgpu/wgpu-core/src/binding_model.rs
new file mode 100644
index 0000000000..10126a9b97
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/binding_model.rs
@@ -0,0 +1,632 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ device::{
+ descriptor::{DescriptorSet, DescriptorTotalCount},
+ DeviceError, SHADER_STAGE_COUNT,
+ },
+ hub::Resource,
+ id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureViewId, Valid},
+ track::{TrackerSet, DUMMY_SELECTOR},
+ validation::{MissingBufferUsageError, MissingTextureUsageError},
+ FastHashMap, Label, LifeGuard, MultiRefCount, Stored, MAX_BIND_GROUPS,
+};
+
+use arrayvec::ArrayVec;
+
+#[cfg(feature = "replay")]
+use serde::Deserialize;
+#[cfg(feature = "trace")]
+use serde::Serialize;
+
+use std::{
+ borrow::{Borrow, Cow},
+ ops::Range,
+};
+
+use thiserror::Error;
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateBindGroupLayoutError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("arrays of bindings unsupported for this type of binding")]
+ ArrayUnsupported,
+ #[error("conflicting binding at index {0}")]
+ ConflictBinding(u32),
+ #[error("required device feature is missing: {0:?}")]
+ MissingFeature(wgt::Features),
+ #[error(transparent)]
+ TooManyBindings(BindingTypeMaxCountError),
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateBindGroupError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("bind group layout is invalid")]
+ InvalidLayout,
+ #[error("buffer {0:?} is invalid or destroyed")]
+ InvalidBuffer(BufferId),
+ #[error("texture view {0:?} is invalid")]
+ InvalidTextureView(TextureViewId),
+ #[error("sampler {0:?} is invalid")]
+ InvalidSampler(SamplerId),
+ #[error("binding count declared with {expected} items, but {actual} items were provided")]
+ BindingArrayLengthMismatch { actual: usize, expected: usize },
+ #[error("bound buffer range {range:?} does not fit in buffer of size {size}")]
+ BindingRangeTooLarge {
+ range: Range<wgt::BufferAddress>,
+ size: u64,
+ },
+ #[error("buffer binding size {actual} is less than minimum {min}")]
+ BindingSizeTooSmall { actual: u64, min: u64 },
+ #[error("number of bindings in bind group descriptor ({actual}) does not match the number of bindings defined in the bind group layout ({expected})")]
+ BindingsNumMismatch { actual: usize, expected: usize },
+ #[error("binding {0} is used at least twice in the descriptor")]
+ DuplicateBinding(u32),
+ #[error("unable to find a corresponding declaration for the given binding {0}")]
+ MissingBindingDeclaration(u32),
+ #[error(transparent)]
+ MissingBufferUsage(#[from] MissingBufferUsageError),
+ #[error(transparent)]
+ MissingTextureUsage(#[from] MissingTextureUsageError),
+ #[error("required device features not enabled: {0:?}")]
+ MissingFeatures(wgt::Features),
+ #[error("binding declared as a single item, but bind group is using it as an array")]
+ SingleBindingExpected,
+ #[error("unable to create a bind group with a swap chain image")]
+ SwapChainImage,
+ #[error("buffer offset {0} does not respect `BIND_BUFFER_ALIGNMENT`")]
+ UnalignedBufferOffset(wgt::BufferAddress),
+ #[error("uniform buffer binding range exceeds `max_uniform_buffer_binding_size` limit")]
+ UniformBufferRangeTooLarge,
+ #[error("binding {binding} has a different type ({actual:?}) than the one in the layout ({expected:?})")]
+ WrongBindingType {
+ // Index of the binding
+ binding: u32,
+ // The type given to the function
+ actual: wgt::BindingType,
+ // Human-readable description of expected types
+ expected: &'static str,
+ },
+ #[error("the given sampler is/is not a comparison sampler, while the layout type indicates otherwise")]
+ WrongSamplerComparison,
+ #[error("bound texture views can not have both depth and stencil aspects enabled")]
+ DepthStencilAspect,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum BindingZone {
+ #[error("stage {0:?}")]
+ Stage(wgt::ShaderStage),
+ #[error("whole pipeline")]
+ Pipeline,
+}
+
+#[derive(Clone, Debug, Error)]
+#[error("too many bindings of type {kind:?} in {zone}, limit is {count}")]
+pub struct BindingTypeMaxCountError {
+ pub kind: BindingTypeMaxCountErrorKind,
+ pub zone: BindingZone,
+ pub count: u32,
+}
+
+#[derive(Clone, Debug)]
+pub enum BindingTypeMaxCountErrorKind {
+ DynamicUniformBuffers,
+ DynamicStorageBuffers,
+ SampledTextures,
+ Samplers,
+ StorageBuffers,
+ StorageTextures,
+ UniformBuffers,
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct PerStageBindingTypeCounter {
+ vertex: u32,
+ fragment: u32,
+ compute: u32,
+}
+
+impl PerStageBindingTypeCounter {
+ pub(crate) fn add(&mut self, stage: wgt::ShaderStage, count: u32) {
+ if stage.contains(wgt::ShaderStage::VERTEX) {
+ self.vertex += count;
+ }
+ if stage.contains(wgt::ShaderStage::FRAGMENT) {
+ self.fragment += count;
+ }
+ if stage.contains(wgt::ShaderStage::COMPUTE) {
+ self.compute += count;
+ }
+ }
+
+ pub(crate) fn max(&self) -> (BindingZone, u32) {
+ let max_value = self.vertex.max(self.fragment.max(self.compute));
+ let mut stage = wgt::ShaderStage::NONE;
+ if max_value == self.vertex {
+ stage |= wgt::ShaderStage::VERTEX
+ }
+ if max_value == self.fragment {
+ stage |= wgt::ShaderStage::FRAGMENT
+ }
+ if max_value == self.compute {
+ stage |= wgt::ShaderStage::COMPUTE
+ }
+ (BindingZone::Stage(stage), max_value)
+ }
+
+ pub(crate) fn merge(&mut self, other: &Self) {
+ self.vertex = self.vertex.max(other.vertex);
+ self.fragment = self.fragment.max(other.fragment);
+ self.compute = self.compute.max(other.compute);
+ }
+
+ pub(crate) fn validate(
+ &self,
+ limit: u32,
+ kind: BindingTypeMaxCountErrorKind,
+ ) -> Result<(), BindingTypeMaxCountError> {
+ let (zone, count) = self.max();
+ if limit < count {
+ Err(BindingTypeMaxCountError { kind, zone, count })
+ } else {
+ Ok(())
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct BindingTypeMaxCountValidator {
+ dynamic_uniform_buffers: u32,
+ dynamic_storage_buffers: u32,
+ sampled_textures: PerStageBindingTypeCounter,
+ samplers: PerStageBindingTypeCounter,
+ storage_buffers: PerStageBindingTypeCounter,
+ storage_textures: PerStageBindingTypeCounter,
+ uniform_buffers: PerStageBindingTypeCounter,
+}
+
+impl BindingTypeMaxCountValidator {
+ pub(crate) fn add_binding(&mut self, binding: &wgt::BindGroupLayoutEntry) {
+ let count = binding.count.map_or(1, |count| count.get());
+ match binding.ty {
+ wgt::BindingType::Buffer {
+ ty: wgt::BufferBindingType::Uniform,
+ has_dynamic_offset,
+ ..
+ } => {
+ self.uniform_buffers.add(binding.visibility, count);
+ if has_dynamic_offset {
+ self.dynamic_uniform_buffers += count;
+ }
+ }
+ wgt::BindingType::Buffer {
+ ty: wgt::BufferBindingType::Storage { .. },
+ has_dynamic_offset,
+ ..
+ } => {
+ self.storage_buffers.add(binding.visibility, count);
+ if has_dynamic_offset {
+ self.dynamic_storage_buffers += count;
+ }
+ }
+ wgt::BindingType::Sampler { .. } => {
+ self.samplers.add(binding.visibility, count);
+ }
+ wgt::BindingType::Texture { .. } => {
+ self.sampled_textures.add(binding.visibility, count);
+ }
+ wgt::BindingType::StorageTexture { .. } => {
+ self.storage_textures.add(binding.visibility, count);
+ }
+ }
+ }
+
+ pub(crate) fn merge(&mut self, other: &Self) {
+ self.dynamic_uniform_buffers += other.dynamic_uniform_buffers;
+ self.dynamic_storage_buffers += other.dynamic_storage_buffers;
+ self.sampled_textures.merge(&other.sampled_textures);
+ self.samplers.merge(&other.samplers);
+ self.storage_buffers.merge(&other.storage_buffers);
+ self.storage_textures.merge(&other.storage_textures);
+ self.uniform_buffers.merge(&other.uniform_buffers);
+ }
+
+ pub(crate) fn validate(&self, limits: &wgt::Limits) -> Result<(), BindingTypeMaxCountError> {
+ if limits.max_dynamic_uniform_buffers_per_pipeline_layout < self.dynamic_uniform_buffers {
+ return Err(BindingTypeMaxCountError {
+ kind: BindingTypeMaxCountErrorKind::DynamicUniformBuffers,
+ zone: BindingZone::Pipeline,
+ count: self.dynamic_uniform_buffers,
+ });
+ }
+ if limits.max_dynamic_storage_buffers_per_pipeline_layout < self.dynamic_storage_buffers {
+ return Err(BindingTypeMaxCountError {
+ kind: BindingTypeMaxCountErrorKind::DynamicStorageBuffers,
+ zone: BindingZone::Pipeline,
+ count: self.dynamic_storage_buffers,
+ });
+ }
+ self.sampled_textures.validate(
+ limits.max_sampled_textures_per_shader_stage,
+ BindingTypeMaxCountErrorKind::SampledTextures,
+ )?;
+ self.storage_buffers.validate(
+ limits.max_storage_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageBuffers,
+ )?;
+ self.samplers.validate(
+ limits.max_samplers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::Samplers,
+ )?;
+ self.storage_buffers.validate(
+ limits.max_storage_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageBuffers,
+ )?;
+ self.storage_textures.validate(
+ limits.max_storage_textures_per_shader_stage,
+ BindingTypeMaxCountErrorKind::StorageTextures,
+ )?;
+ self.uniform_buffers.validate(
+ limits.max_uniform_buffers_per_shader_stage,
+ BindingTypeMaxCountErrorKind::UniformBuffers,
+ )?;
+ Ok(())
+ }
+}
+
+/// Bindable resource and the slot to bind it to.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BindGroupEntry<'a> {
+ /// Slot for which binding provides resource. Corresponds to an entry of the same
+ /// binding index in the [`BindGroupLayoutDescriptor`].
+ pub binding: u32,
+ /// Resource to attach to the binding
+ pub resource: BindingResource<'a>,
+}
+
+/// Describes a group of bindings and the resources to be bound.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BindGroupDescriptor<'a> {
+ /// Debug label of the bind group. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// The [`BindGroupLayout`] that corresponds to this bind group.
+ pub layout: BindGroupLayoutId,
+ /// The resources to bind to this bind group.
+ pub entries: Cow<'a, [BindGroupEntry<'a>]>,
+}
+
+/// Describes a [`BindGroupLayout`].
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct BindGroupLayoutDescriptor<'a> {
+ /// Debug label of the bind group layout. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// Array of entries in this BindGroupLayout
+ pub entries: Cow<'a, [wgt::BindGroupLayoutEntry]>,
+}
+
+pub(crate) type BindEntryMap = FastHashMap<u32, wgt::BindGroupLayoutEntry>;
+
+#[derive(Debug)]
+pub struct BindGroupLayout<B: hal::Backend> {
+ pub(crate) raw: B::DescriptorSetLayout,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) multi_ref_count: MultiRefCount,
+ pub(crate) entries: BindEntryMap,
+ pub(crate) desc_count: DescriptorTotalCount,
+ pub(crate) dynamic_count: usize,
+ pub(crate) count_validator: BindingTypeMaxCountValidator,
+ #[cfg(debug_assertions)]
+ pub(crate) label: String,
+}
+
+impl<B: hal::Backend> Resource for BindGroupLayout<B> {
+ const TYPE: &'static str = "BindGroupLayout";
+
+ fn life_guard(&self) -> &LifeGuard {
+ unreachable!()
+ }
+
+ fn label(&self) -> &str {
+ #[cfg(debug_assertions)]
+ return &self.label;
+ #[cfg(not(debug_assertions))]
+ return "";
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreatePipelineLayoutError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("bind group layout {0:?} is invalid")]
+ InvalidBindGroupLayout(BindGroupLayoutId),
+ #[error(
+ "push constant at index {index} has range bound {bound} not aligned to {}",
+ wgt::PUSH_CONSTANT_ALIGNMENT
+ )]
+ MisalignedPushConstantRange { index: usize, bound: u32 },
+ #[error("device does not have required feature: {0:?}")]
+ MissingFeature(wgt::Features),
+ #[error("push constant range (index {index}) provides for stage(s) {provided:?} but there exists another range that provides stage(s) {intersected:?}. Each stage may only be provided by one range")]
+ MoreThanOnePushConstantRangePerStage {
+ index: usize,
+ provided: wgt::ShaderStage,
+ intersected: wgt::ShaderStage,
+ },
+ #[error("push constant at index {index} has range {}..{} which exceeds device push constant size limit 0..{max}", range.start, range.end)]
+ PushConstantRangeTooLarge {
+ index: usize,
+ range: Range<u32>,
+ max: u32,
+ },
+ #[error(transparent)]
+ TooManyBindings(BindingTypeMaxCountError),
+ #[error("bind group layout count {actual} exceeds device bind group limit {max}")]
+ TooManyGroups { actual: usize, max: usize },
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum PushConstantUploadError {
+ #[error("provided push constant with indices {offset}..{end_offset} overruns matching push constant range at index {idx}, with stage(s) {:?} and indices {:?}", range.stages, range.range)]
+ TooLarge {
+ offset: u32,
+ end_offset: u32,
+ idx: usize,
+ range: wgt::PushConstantRange,
+ },
+ #[error("provided push constant is for stage(s) {actual:?}, stage with a partial match found at index {idx} with stage(s) {matched:?}, however push constants must be complete matches")]
+ PartialRangeMatch {
+ actual: wgt::ShaderStage,
+ idx: usize,
+ matched: wgt::ShaderStage,
+ },
+ #[error("provided push constant is for stage(s) {actual:?}, but intersects a push constant range (at index {idx}) with stage(s) {missing:?}. Push constants must provide the stages for all ranges they intersect")]
+ MissingStages {
+ actual: wgt::ShaderStage,
+ idx: usize,
+ missing: wgt::ShaderStage,
+ },
+ #[error("provided push constant is for stage(s) {actual:?}, however the pipeline layout has no push constant range for the stage(s) {unmatched:?}")]
+ UnmatchedStages {
+ actual: wgt::ShaderStage,
+ unmatched: wgt::ShaderStage,
+ },
+ #[error("provided push constant offset {0} does not respect `PUSH_CONSTANT_ALIGNMENT`")]
+ Unaligned(u32),
+}
+
+/// Describes a pipeline layout.
+///
+/// A `PipelineLayoutDescriptor` can be used to create a pipeline layout.
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "trace", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PipelineLayoutDescriptor<'a> {
+ /// Debug label of the pipeine layout. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// Bind groups that this pipeline uses. The first entry will provide all the bindings for
+ /// "set = 0", second entry will provide all the bindings for "set = 1" etc.
+ pub bind_group_layouts: Cow<'a, [BindGroupLayoutId]>,
+ /// Set of push constant ranges this pipeline uses. Each shader stage that uses push constants
+ /// must define the range in push constant memory that corresponds to its single `layout(push_constant)`
+ /// uniform block.
+ ///
+ /// If this array is non-empty, the [`Features::PUSH_CONSTANTS`] must be enabled.
+ pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>,
+}
+
+#[derive(Debug)]
+pub struct PipelineLayout<B: hal::Backend> {
+ pub(crate) raw: B::PipelineLayout,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) life_guard: LifeGuard,
+ pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; MAX_BIND_GROUPS]>,
+ pub(crate) push_constant_ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>,
+}
+
+impl<B: hal::Backend> PipelineLayout<B> {
+ /// Validate push constants match up with expected ranges.
+ pub(crate) fn validate_push_constant_ranges(
+ &self,
+ stages: wgt::ShaderStage,
+ offset: u32,
+ end_offset: u32,
+ ) -> Result<(), PushConstantUploadError> {
+ // Don't need to validate size against the push constant size limit here,
+ // as push constant ranges are already validated to be within bounds,
+ // and we validate that they are within the ranges.
+
+ if offset % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
+ return Err(PushConstantUploadError::Unaligned(offset));
+ }
+
+ // Push constant validation looks very complicated on the surface, but
+ // the problem can be range-reduced pretty well.
+ //
+ // Push constants require (summarized from the vulkan spec):
+ // 1. For each byte in the range and for each shader stage in stageFlags,
+ // there must be a push constant range in the layout that includes that
+ // byte and that stage.
+ // 2. For each byte in the range and for each push constant range that overlaps that byte,
+ // `stage` must include all stages in that push constant range’s `stage`.
+ //
+ // However there are some additional constraints that help us:
+ // 3. All push constant ranges are the only range that can access that stage.
+ // i.e. if one range has VERTEX, no other range has VERTEX
+ //
+ // Therefore we can simplify the checks in the following ways:
+ // - Because 3 guarantees that the push constant range has a unique stage,
+ // when we check for 1, we can simply check that our entire updated range
+ // is within a push constant range. i.e. our range for a specific stage cannot
+ // intersect more than one push constant range.
+ let mut used_stages = wgt::ShaderStage::NONE;
+ for (idx, range) in self.push_constant_ranges.iter().enumerate() {
+ // contains not intersects due to 2
+ if stages.contains(range.stages) {
+ if !(range.range.start <= offset && end_offset <= range.range.end) {
+ return Err(PushConstantUploadError::TooLarge {
+ offset,
+ end_offset,
+ idx,
+ range: range.clone(),
+ });
+ }
+ used_stages |= range.stages;
+ } else if stages.intersects(range.stages) {
+ // Will be caught by used stages check below, but we can do this because of 1
+ // and is more helpful to the user.
+ return Err(PushConstantUploadError::PartialRangeMatch {
+ actual: stages,
+ idx,
+ matched: range.stages,
+ });
+ }
+
+ // The push constant range intersects range we are uploading
+ if offset < range.range.end && range.range.start < end_offset {
+ // But requires stages we don't provide
+ if !stages.contains(range.stages) {
+ return Err(PushConstantUploadError::MissingStages {
+ actual: stages,
+ idx,
+ missing: stages,
+ });
+ }
+ }
+ }
+ if used_stages != stages {
+ return Err(PushConstantUploadError::UnmatchedStages {
+ actual: stages,
+ unmatched: stages - used_stages,
+ });
+ }
+ Ok(())
+ }
+}
+
+impl<B: hal::Backend> Resource for PipelineLayout<B> {
+ const TYPE: &'static str = "PipelineLayout";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Hash, PartialEq)]
+#[cfg_attr(feature = "trace", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BufferBinding {
+ pub buffer_id: BufferId,
+ pub offset: wgt::BufferAddress,
+ pub size: Option<wgt::BufferSize>,
+}
+
+// Note: Duplicated in `wgpu-rs` as `BindingResource`
+// They're different enough that it doesn't make sense to share a common type
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum BindingResource<'a> {
+ Buffer(BufferBinding),
+ Sampler(SamplerId),
+ TextureView(TextureViewId),
+ TextureViewArray(Cow<'a, [TextureViewId]>),
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum BindError {
+ #[error("number of dynamic offsets ({actual}) doesn't match the number of dynamic bindings in the bind group layout ({expected})")]
+ MismatchedDynamicOffsetCount { actual: usize, expected: usize },
+ #[error(
+ "dynamic binding at index {idx}: offset {offset} does not respect `BIND_BUFFER_ALIGNMENT`"
+ )]
+ UnalignedDynamicBinding { idx: usize, offset: u32 },
+ #[error("dynamic binding at index {idx} with offset {offset} would overrun the buffer (limit: {max})")]
+ DynamicBindingOutOfBounds { idx: usize, offset: u32, max: u64 },
+}
+
+#[derive(Debug)]
+pub struct BindGroupDynamicBindingData {
+ /// The maximum value the dynamic offset can have before running off the end of the buffer.
+ pub(crate) maximum_dynamic_offset: wgt::BufferAddress,
+}
+
+#[derive(Debug)]
+pub struct BindGroup<B: hal::Backend> {
+ pub(crate) raw: DescriptorSet<B>,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) layout_id: Valid<BindGroupLayoutId>,
+ pub(crate) life_guard: LifeGuard,
+ pub(crate) used: TrackerSet,
+ pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>,
+}
+
+impl<B: hal::Backend> BindGroup<B> {
+ pub(crate) fn validate_dynamic_bindings(
+ &self,
+ offsets: &[wgt::DynamicOffset],
+ ) -> Result<(), BindError> {
+ if self.dynamic_binding_info.len() != offsets.len() {
+ return Err(BindError::MismatchedDynamicOffsetCount {
+ expected: self.dynamic_binding_info.len(),
+ actual: offsets.len(),
+ });
+ }
+
+ for (idx, (info, &offset)) in self
+ .dynamic_binding_info
+ .iter()
+ .zip(offsets.iter())
+ .enumerate()
+ {
+ if offset as wgt::BufferAddress % wgt::BIND_BUFFER_ALIGNMENT != 0 {
+ return Err(BindError::UnalignedDynamicBinding { idx, offset });
+ }
+
+ if offset as wgt::BufferAddress > info.maximum_dynamic_offset {
+ return Err(BindError::DynamicBindingOutOfBounds {
+ idx,
+ offset,
+ max: info.maximum_dynamic_offset,
+ });
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl<B: hal::Backend> Borrow<()> for BindGroup<B> {
+ fn borrow(&self) -> &() {
+ &DUMMY_SELECTOR
+ }
+}
+
+impl<B: hal::Backend> Resource for BindGroup<B> {
+ const TYPE: &'static str = "BindGroup";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum GetBindGroupLayoutError {
+ #[error("pipeline is invalid")]
+ InvalidPipeline,
+ #[error("invalid group index {0}")]
+ InvalidGroupIndex(u32),
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/allocator.rs b/gfx/wgpu/wgpu-core/src/command/allocator.rs
new file mode 100644
index 0000000000..cfaa6258c2
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/allocator.rs
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CommandBuffer;
+use crate::{
+ device::DeviceError, hub::GfxBackend, id::DeviceId, track::TrackerSet, FastHashMap,
+ PrivateFeatures, Stored, SubmissionIndex,
+};
+
+#[cfg(debug_assertions)]
+use crate::LabelHelpers;
+
+use hal::{command::CommandBuffer as _, device::Device as _, pool::CommandPool as _};
+use parking_lot::Mutex;
+use thiserror::Error;
+
+use std::thread;
+
+const GROW_AMOUNT: usize = 20;
+
+#[derive(Debug)]
+struct CommandPool<B: hal::Backend> {
+ raw: B::CommandPool,
+ total: usize,
+ available: Vec<B::CommandBuffer>,
+ pending: Vec<(B::CommandBuffer, SubmissionIndex)>,
+}
+
+impl<B: hal::Backend> CommandPool<B> {
+ fn maintain(&mut self, last_done_index: SubmissionIndex) {
+ for i in (0..self.pending.len()).rev() {
+ if self.pending[i].1 <= last_done_index {
+ let (cmd_buf, index) = self.pending.swap_remove(i);
+ tracing::trace!(
+ "recycling cmdbuf submitted in {} when {} is last done",
+ index,
+ last_done_index,
+ );
+ self.recycle(cmd_buf);
+ }
+ }
+ }
+
+ fn recycle(&mut self, mut raw: B::CommandBuffer) {
+ unsafe {
+ raw.reset(false);
+ }
+ self.available.push(raw);
+ }
+
+ fn allocate(&mut self) -> B::CommandBuffer {
+ if self.available.is_empty() {
+ self.total += GROW_AMOUNT;
+ unsafe {
+ self.raw.allocate(
+ GROW_AMOUNT,
+ hal::command::Level::Primary,
+ &mut self.available,
+ )
+ };
+ }
+ self.available.pop().unwrap()
+ }
+}
+
+#[derive(Debug)]
+struct Inner<B: hal::Backend> {
+ pools: FastHashMap<thread::ThreadId, CommandPool<B>>,
+}
+
+#[derive(Debug)]
+pub struct CommandAllocator<B: hal::Backend> {
+ queue_family: hal::queue::QueueFamilyId,
+ internal_thread_id: thread::ThreadId,
+ inner: Mutex<Inner<B>>,
+}
+
+impl<B: GfxBackend> CommandAllocator<B> {
+ pub(crate) fn allocate(
+ &self,
+ device_id: Stored<DeviceId>,
+ device: &B::Device,
+ limits: wgt::Limits,
+ private_features: PrivateFeatures,
+ label: &crate::Label,
+ #[cfg(feature = "trace")] enable_tracing: bool,
+ ) -> Result<CommandBuffer<B>, CommandAllocatorError> {
+ //debug_assert_eq!(device_id.backend(), B::VARIANT);
+ let _ = label; // silence warning on release
+ let thread_id = thread::current().id();
+ let mut inner = self.inner.lock();
+
+ use std::collections::hash_map::Entry;
+ let pool = match inner.pools.entry(thread_id) {
+ Entry::Occupied(e) => e.into_mut(),
+ Entry::Vacant(e) => {
+ tracing::info!("Starting on thread {:?}", thread_id);
+ let raw = unsafe {
+ device
+ .create_command_pool(
+ self.queue_family,
+ hal::pool::CommandPoolCreateFlags::RESET_INDIVIDUAL,
+ )
+ .or(Err(DeviceError::OutOfMemory))?
+ };
+ let pool = CommandPool {
+ raw,
+ total: 0,
+ available: Vec::new(),
+ pending: Vec::new(),
+ };
+ e.insert(pool)
+ }
+ };
+
+ let init = pool.allocate();
+
+ Ok(CommandBuffer {
+ raw: vec![init],
+ is_recording: true,
+ recorded_thread_id: thread_id,
+ device_id,
+ trackers: TrackerSet::new(B::VARIANT),
+ used_swap_chain: None,
+ limits,
+ private_features,
+ #[cfg(feature = "trace")]
+ commands: if enable_tracing {
+ Some(Vec::new())
+ } else {
+ None
+ },
+ #[cfg(debug_assertions)]
+ label: label.to_string_or_default(),
+ })
+ }
+}
+
+impl<B: hal::Backend> CommandAllocator<B> {
+ pub fn new(
+ queue_family: hal::queue::QueueFamilyId,
+ device: &B::Device,
+ ) -> Result<Self, CommandAllocatorError> {
+ let internal_thread_id = thread::current().id();
+ tracing::info!("Starting on (internal) thread {:?}", internal_thread_id);
+ let mut pools = FastHashMap::default();
+ pools.insert(
+ internal_thread_id,
+ CommandPool {
+ raw: unsafe {
+ device
+ .create_command_pool(
+ queue_family,
+ hal::pool::CommandPoolCreateFlags::RESET_INDIVIDUAL,
+ )
+ .or(Err(DeviceError::OutOfMemory))?
+ },
+ total: 0,
+ available: Vec::new(),
+ pending: Vec::new(),
+ },
+ );
+ Ok(Self {
+ queue_family,
+ internal_thread_id,
+ inner: Mutex::new(Inner { pools }),
+ })
+ }
+
+ fn allocate_for_thread_id(&self, thread_id: thread::ThreadId) -> B::CommandBuffer {
+ let mut inner = self.inner.lock();
+ inner.pools.get_mut(&thread_id).unwrap().allocate()
+ }
+
+ pub fn allocate_internal(&self) -> B::CommandBuffer {
+ self.allocate_for_thread_id(self.internal_thread_id)
+ }
+
+ pub fn extend(&self, cmd_buf: &CommandBuffer<B>) -> B::CommandBuffer {
+ self.allocate_for_thread_id(cmd_buf.recorded_thread_id)
+ }
+
+ pub fn discard_internal(&self, raw: B::CommandBuffer) {
+ let mut inner = self.inner.lock();
+ inner
+ .pools
+ .get_mut(&self.internal_thread_id)
+ .unwrap()
+ .recycle(raw);
+ }
+
+ pub fn discard(&self, mut cmd_buf: CommandBuffer<B>) {
+ cmd_buf.trackers.clear();
+ let mut inner = self.inner.lock();
+ let pool = inner.pools.get_mut(&cmd_buf.recorded_thread_id).unwrap();
+ for raw in cmd_buf.raw {
+ pool.recycle(raw);
+ }
+ }
+
+ pub fn after_submit_internal(&self, raw: B::CommandBuffer, submit_index: SubmissionIndex) {
+ let mut inner = self.inner.lock();
+ inner
+ .pools
+ .get_mut(&self.internal_thread_id)
+ .unwrap()
+ .pending
+ .push((raw, submit_index));
+ }
+
+ pub fn after_submit(&self, cmd_buf: CommandBuffer<B>, submit_index: SubmissionIndex) {
+ // Record this command buffer as pending
+ let mut inner = self.inner.lock();
+ inner
+ .pools
+ .get_mut(&cmd_buf.recorded_thread_id)
+ .unwrap()
+ .pending
+ .extend(cmd_buf.raw.into_iter().map(|raw| (raw, submit_index)));
+ }
+
+ pub fn maintain(&self, device: &B::Device, last_done_index: SubmissionIndex) {
+ let mut inner = self.inner.lock();
+ let mut remove_threads = Vec::new();
+ for (&thread_id, pool) in inner.pools.iter_mut() {
+ pool.maintain(last_done_index);
+ if pool.total == pool.available.len() && thread_id != self.internal_thread_id {
+ assert!(pool.pending.is_empty());
+ remove_threads.push(thread_id);
+ }
+ }
+ for thread_id in remove_threads {
+ tracing::info!("Removing from thread {:?}", thread_id);
+ let mut pool = inner.pools.remove(&thread_id).unwrap();
+ unsafe {
+ pool.raw.free(pool.available);
+ device.destroy_command_pool(pool.raw);
+ }
+ }
+ }
+
+ pub fn destroy(self, device: &B::Device) {
+ let mut inner = self.inner.lock();
+ for (_, mut pool) in inner.pools.drain() {
+ while let Some((raw, _)) = pool.pending.pop() {
+ pool.recycle(raw);
+ }
+ if pool.total != pool.available.len() {
+ tracing::error!(
+ "Some command buffers are still recorded, only tracking {} / {}",
+ pool.available.len(),
+ pool.total
+ );
+ }
+ unsafe {
+ pool.raw.free(pool.available);
+ device.destroy_command_pool(pool.raw);
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CommandAllocatorError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/bind.rs b/gfx/wgpu/wgpu-core/src/command/bind.rs
new file mode 100644
index 0000000000..a62b38d5b7
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/bind.rs
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ binding_model::{BindGroup, PipelineLayout},
+ device::SHADER_STAGE_COUNT,
+ hub::{GfxBackend, Storage},
+ id::{BindGroupId, BindGroupLayoutId, PipelineLayoutId, Valid},
+ Stored, MAX_BIND_GROUPS,
+};
+
+use arrayvec::ArrayVec;
+use std::slice;
+use wgt::DynamicOffset;
+
+type BindGroupMask = u8;
+
+#[derive(Clone, Debug)]
+pub(super) struct BindGroupPair {
+ layout_id: Valid<BindGroupLayoutId>,
+ group_id: Stored<BindGroupId>,
+}
+
+#[derive(Debug)]
+pub(super) enum LayoutChange<'a> {
+ Unchanged,
+ Match(Valid<BindGroupId>, &'a [DynamicOffset]),
+ Mismatch,
+}
+
+#[derive(Debug)]
+pub enum Provision {
+ Unchanged,
+ Changed { was_compatible: bool },
+}
+
+#[derive(Clone)]
+pub(super) struct FollowUpIter<'a> {
+ iter: slice::Iter<'a, BindGroupEntry>,
+}
+impl<'a> Iterator for FollowUpIter<'a> {
+ type Item = (Valid<BindGroupId>, &'a [DynamicOffset]);
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter
+ .next()
+ .and_then(|entry| Some((entry.actual_value()?, entry.dynamic_offsets.as_slice())))
+ }
+}
+
+#[derive(Clone, Default, Debug)]
+pub(super) struct BindGroupEntry {
+ expected_layout_id: Option<Valid<BindGroupLayoutId>>,
+ provided: Option<BindGroupPair>,
+ dynamic_offsets: Vec<DynamicOffset>,
+}
+
+impl BindGroupEntry {
+ fn provide<B: GfxBackend>(
+ &mut self,
+ bind_group_id: Valid<BindGroupId>,
+ bind_group: &BindGroup<B>,
+ offsets: &[DynamicOffset],
+ ) -> Provision {
+ debug_assert_eq!(B::VARIANT, bind_group_id.0.backend());
+
+ let was_compatible = match self.provided {
+ Some(BindGroupPair {
+ layout_id,
+ ref group_id,
+ }) => {
+ if group_id.value == bind_group_id && offsets == self.dynamic_offsets.as_slice() {
+ assert_eq!(layout_id, bind_group.layout_id);
+ return Provision::Unchanged;
+ }
+ self.expected_layout_id == Some(layout_id)
+ }
+ None => false,
+ };
+
+ self.provided = Some(BindGroupPair {
+ layout_id: bind_group.layout_id,
+ group_id: Stored {
+ value: bind_group_id,
+ ref_count: bind_group.life_guard.add_ref(),
+ },
+ });
+ self.dynamic_offsets.clear();
+ self.dynamic_offsets.extend_from_slice(offsets);
+
+ Provision::Changed { was_compatible }
+ }
+
+ pub fn expect_layout(
+ &mut self,
+ bind_group_layout_id: Valid<BindGroupLayoutId>,
+ ) -> LayoutChange {
+ let some = Some(bind_group_layout_id);
+ if self.expected_layout_id != some {
+ self.expected_layout_id = some;
+ match self.provided {
+ Some(BindGroupPair {
+ layout_id,
+ ref group_id,
+ }) if layout_id == bind_group_layout_id => {
+ LayoutChange::Match(group_id.value, &self.dynamic_offsets)
+ }
+ Some(_) | None => LayoutChange::Mismatch,
+ }
+ } else {
+ LayoutChange::Unchanged
+ }
+ }
+
+ fn is_valid(&self) -> Option<bool> {
+ match (self.expected_layout_id, self.provided.as_ref()) {
+ (None, None) => Some(true),
+ (None, Some(_)) => None,
+ (Some(_), None) => Some(false),
+ (Some(layout), Some(pair)) => Some(layout == pair.layout_id),
+ }
+ }
+
+ fn actual_value(&self) -> Option<Valid<BindGroupId>> {
+ self.expected_layout_id.and_then(|layout_id| {
+ self.provided.as_ref().and_then(|pair| {
+ if pair.layout_id == layout_id {
+ Some(pair.group_id.value)
+ } else {
+ None
+ }
+ })
+ })
+ }
+}
+
+#[derive(Debug)]
+pub struct Binder {
+ pub(super) pipeline_layout_id: Option<Valid<PipelineLayoutId>>, //TODO: strongly `Stored`
+ pub(super) entries: ArrayVec<[BindGroupEntry; MAX_BIND_GROUPS]>,
+}
+
+impl Binder {
+ pub(super) fn new(max_bind_groups: u32) -> Self {
+ Self {
+ pipeline_layout_id: None,
+ entries: (0..max_bind_groups)
+ .map(|_| BindGroupEntry::default())
+ .collect(),
+ }
+ }
+
+ pub(super) fn reset(&mut self) {
+ self.pipeline_layout_id = None;
+ self.entries.clear();
+ }
+
+ pub(super) fn change_pipeline_layout<B: GfxBackend>(
+ &mut self,
+ guard: &Storage<PipelineLayout<B>, PipelineLayoutId>,
+ new_id: Valid<PipelineLayoutId>,
+ ) {
+ let old_id_opt = self.pipeline_layout_id.replace(new_id);
+ let new = &guard[new_id];
+
+ let length = if let Some(old_id) = old_id_opt {
+ let old = &guard[old_id];
+ if old.push_constant_ranges == new.push_constant_ranges {
+ new.bind_group_layout_ids.len()
+ } else {
+ 0
+ }
+ } else {
+ 0
+ };
+
+ for entry in self.entries[length..].iter_mut() {
+ entry.expected_layout_id = None;
+ }
+ }
+
+ /// Attempt to set the value of the specified bind group index.
+ /// Returns Some() when the new bind group is ready to be actually bound
+ /// (i.e. compatible with current expectations). Also returns an iterator
+ /// of bind group IDs to be bound with it: those are compatible bind groups
+ /// that were previously blocked because the current one was incompatible.
+ pub(super) fn provide_entry<'a, B: GfxBackend>(
+ &'a mut self,
+ index: usize,
+ bind_group_id: Valid<BindGroupId>,
+ bind_group: &BindGroup<B>,
+ offsets: &[DynamicOffset],
+ ) -> Option<(Valid<PipelineLayoutId>, FollowUpIter<'a>)> {
+ tracing::trace!("\tBinding [{}] = group {:?}", index, bind_group_id);
+ debug_assert_eq!(B::VARIANT, bind_group_id.0.backend());
+
+ match self.entries[index].provide(bind_group_id, bind_group, offsets) {
+ Provision::Unchanged => None,
+ Provision::Changed { was_compatible, .. } => {
+ let compatible_count = self.compatible_count();
+ if index < compatible_count {
+ let end = compatible_count.min(if was_compatible {
+ index + 1
+ } else {
+ self.entries.len()
+ });
+ tracing::trace!("\t\tbinding up to {}", end);
+ Some((
+ self.pipeline_layout_id?,
+ FollowUpIter {
+ iter: self.entries[index + 1..end].iter(),
+ },
+ ))
+ } else {
+ tracing::trace!("\t\tskipping above compatible {}", compatible_count);
+ None
+ }
+ }
+ }
+ }
+
+ pub(super) fn list_active(&self) -> impl Iterator<Item = Valid<BindGroupId>> + '_ {
+ self.entries.iter().filter_map(|e| match e.provided {
+ Some(ref pair) if e.expected_layout_id.is_some() => Some(pair.group_id.value),
+ _ => None,
+ })
+ }
+
+ pub(super) fn invalid_mask(&self) -> BindGroupMask {
+ self.entries.iter().enumerate().fold(0, |mask, (i, entry)| {
+ if entry.is_valid().unwrap_or(true) {
+ mask
+ } else {
+ mask | 1u8 << i
+ }
+ })
+ }
+
+ fn compatible_count(&self) -> usize {
+ self.entries
+ .iter()
+ .position(|entry| !entry.is_valid().unwrap_or(false))
+ .unwrap_or_else(|| self.entries.len())
+ }
+}
+
+struct PushConstantChange {
+ stages: wgt::ShaderStage,
+ offset: u32,
+ enable: bool,
+}
+
+/// Break up possibly overlapping push constant ranges into a set of non-overlapping ranges
+/// which contain all the stage flags of the original ranges. This allows us to zero out (or write any value)
+/// to every possible value.
+pub fn compute_nonoverlapping_ranges(
+ ranges: &[wgt::PushConstantRange],
+) -> ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT * 2]> {
+ if ranges.is_empty() {
+ return ArrayVec::new();
+ }
+ debug_assert!(ranges.len() <= SHADER_STAGE_COUNT);
+
+ let mut breaks: ArrayVec<[PushConstantChange; SHADER_STAGE_COUNT * 2]> = ArrayVec::new();
+ for range in ranges {
+ breaks.push(PushConstantChange {
+ stages: range.stages,
+ offset: range.range.start,
+ enable: true,
+ });
+ breaks.push(PushConstantChange {
+ stages: range.stages,
+ offset: range.range.end,
+ enable: false,
+ });
+ }
+ breaks.sort_unstable_by_key(|change| change.offset);
+
+ let mut output_ranges = ArrayVec::new();
+ let mut position = 0_u32;
+ let mut stages = wgt::ShaderStage::NONE;
+
+ for bk in breaks {
+ if bk.offset - position > 0 && !stages.is_empty() {
+ output_ranges.push(wgt::PushConstantRange {
+ stages,
+ range: position..bk.offset,
+ })
+ }
+ position = bk.offset;
+ stages.set(bk.stages, bk.enable);
+ }
+
+ output_ranges
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/bundle.rs b/gfx/wgpu/wgpu-core/src/command/bundle.rs
new file mode 100644
index 0000000000..19c11c7136
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/bundle.rs
@@ -0,0 +1,1230 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*! Render Bundles
+
+ ## Software implementation
+
+ The path from nothing to using a render bundle consists of 3 phases.
+
+ ### Initial command encoding
+
+ User creates a `RenderBundleEncoder` and populates it by issuing commands
+ from `bundle_ffi` module, just like with `RenderPass`, except that the
+ set of available commands is reduced. Everything is written into a `RawPass`.
+
+ ### Bundle baking
+
+ Once the commands are encoded, user calls `render_bundle_encoder_finish`.
+ This is perhaps the most complex part of the logic. It consumes the
+ commands stored in `RawPass`, while validating everything, tracking the state,
+ and re-recording the commands into a separate `Vec<RenderCommand>`. It
+ doesn't actually execute any commands.
+
+ What's more important, is that the produced vector of commands is "normalized",
+ which means it can be executed verbatim without any state tracking. More
+ formally, "normalized" command stream guarantees that any state required by
+ a draw call is set explicitly by one of the commands between the draw call
+ and the last changing of the pipeline.
+
+ ### Execution
+
+ When the bundle is used in an actual render pass, `RenderBundle::execute` is
+ called. It goes through the commands and issues them into the native command
+ buffer. Thanks to the "normalized" property, it doesn't track any bind group
+ invalidations or index format changes.
+!*/
+#![allow(clippy::reversed_empty_ranges)]
+
+use crate::{
+ command::{
+ BasePass, DrawError, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError,
+ StateChange,
+ },
+ conv,
+ device::{
+ AttachmentData, Device, DeviceError, RenderPassContext, MAX_VERTEX_BUFFERS,
+ SHADER_STAGE_COUNT,
+ },
+ hub::{GfxBackend, GlobalIdentityHandlerFactory, Hub, Resource, Storage, Token},
+ id,
+ resource::BufferUse,
+ span,
+ track::{TrackerSet, UsageConflict},
+ validation::check_buffer_usage,
+ Label, LabelHelpers, LifeGuard, Stored, MAX_BIND_GROUPS,
+};
+use arrayvec::ArrayVec;
+use std::{borrow::Cow, iter, ops::Range};
+use thiserror::Error;
+
+/// Describes a [`RenderBundleEncoder`].
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct RenderBundleEncoderDescriptor<'a> {
+ /// Debug label of the render bundle encoder. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// The formats of the color attachments that this render bundle is capable to rendering to. This
+ /// must match the formats of the color attachments in the renderpass this render bundle is executed in.
+ pub color_formats: Cow<'a, [wgt::TextureFormat]>,
+ /// The formats of the depth attachment that this render bundle is capable to rendering to. This
+ /// must match the formats of the depth attachments in the renderpass this render bundle is executed in.
+ pub depth_stencil_format: Option<wgt::TextureFormat>,
+ /// Sample count this render bundle is capable of rendering to. This must match the pipelines and
+ /// the renderpasses it is used in.
+ pub sample_count: u32,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
+pub struct RenderBundleEncoder {
+ base: BasePass<RenderCommand>,
+ parent_id: id::DeviceId,
+ pub(crate) context: RenderPassContext,
+}
+
+impl RenderBundleEncoder {
+ pub fn new(
+ desc: &RenderBundleEncoderDescriptor,
+ parent_id: id::DeviceId,
+ base: Option<BasePass<RenderCommand>>,
+ ) -> Result<Self, CreateRenderBundleError> {
+ span!(_guard, INFO, "RenderBundleEncoder::new");
+ Ok(Self {
+ base: base.unwrap_or_else(BasePass::new),
+ parent_id,
+ context: RenderPassContext {
+ attachments: AttachmentData {
+ colors: desc.color_formats.iter().cloned().collect(),
+ resolves: ArrayVec::new(),
+ depth_stencil: desc.depth_stencil_format,
+ },
+ sample_count: {
+ let sc = desc.sample_count;
+ if sc == 0 || sc > 32 || !conv::is_power_of_two(sc) {
+ return Err(CreateRenderBundleError::InvalidSampleCount(sc));
+ }
+ sc as u8
+ },
+ },
+ })
+ }
+
+ pub fn dummy(parent_id: id::DeviceId) -> Self {
+ Self {
+ base: BasePass::new(),
+ parent_id,
+ context: RenderPassContext {
+ attachments: AttachmentData {
+ colors: ArrayVec::new(),
+ resolves: ArrayVec::new(),
+ depth_stencil: None,
+ },
+ sample_count: 0,
+ },
+ }
+ }
+
+ pub fn parent(&self) -> id::DeviceId {
+ self.parent_id
+ }
+
+ pub(crate) fn finish<B: hal::Backend, G: GlobalIdentityHandlerFactory>(
+ self,
+ desc: &RenderBundleDescriptor,
+ device: &Device<B>,
+ hub: &Hub<B, G>,
+ token: &mut Token<Device<B>>,
+ ) -> Result<RenderBundle, RenderBundleError> {
+ let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(token);
+ let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
+ let (pipeline_guard, mut token) = hub.render_pipelines.read(&mut token);
+ let (buffer_guard, _) = hub.buffers.read(&mut token);
+
+ let mut state = State {
+ trackers: TrackerSet::new(self.parent_id.backend()),
+ index: IndexState::new(),
+ vertex: (0..MAX_VERTEX_BUFFERS)
+ .map(|_| VertexState::new())
+ .collect(),
+ bind: (0..MAX_BIND_GROUPS).map(|_| BindState::new()).collect(),
+ push_constant_ranges: PushConstantState::new(),
+ raw_dynamic_offsets: Vec::new(),
+ flat_dynamic_offsets: Vec::new(),
+ used_bind_groups: 0,
+ pipeline: StateChange::new(),
+ };
+ let mut commands = Vec::new();
+ let mut base = self.base.as_ref();
+ let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
+
+ for &command in base.commands {
+ match command {
+ RenderCommand::SetBindGroup {
+ index,
+ num_dynamic_offsets,
+ bind_group_id,
+ } => {
+ let scope = PassErrorScope::SetBindGroup(bind_group_id);
+
+ let max_bind_groups = device.limits.max_bind_groups;
+ if (index as u32) >= max_bind_groups {
+ return Err(RenderCommandError::BindGroupIndexOutOfRange {
+ index,
+ max: max_bind_groups,
+ })
+ .map_pass_err(scope);
+ }
+
+ let offsets = &base.dynamic_offsets[..num_dynamic_offsets as usize];
+ base.dynamic_offsets = &base.dynamic_offsets[num_dynamic_offsets as usize..];
+ // Check for misaligned offsets.
+ if let Some(offset) = offsets
+ .iter()
+ .map(|offset| *offset as wgt::BufferAddress)
+ .find(|offset| offset % wgt::BIND_BUFFER_ALIGNMENT != 0)
+ {
+ return Err(RenderCommandError::UnalignedBufferOffset(offset))
+ .map_pass_err(scope);
+ }
+
+ let bind_group = state
+ .trackers
+ .bind_groups
+ .use_extend(&*bind_group_guard, bind_group_id, (), ())
+ .map_err(|_| RenderCommandError::InvalidBindGroup(bind_group_id))
+ .map_pass_err(scope)?;
+ if bind_group.dynamic_binding_info.len() != offsets.len() {
+ return Err(RenderCommandError::InvalidDynamicOffsetCount {
+ actual: offsets.len(),
+ expected: bind_group.dynamic_binding_info.len(),
+ })
+ .map_pass_err(scope);
+ }
+
+ state.set_bind_group(index, bind_group_id, bind_group.layout_id, offsets);
+ state
+ .trackers
+ .merge_extend(&bind_group.used)
+ .map_pass_err(scope)?;
+ }
+ RenderCommand::SetPipeline(pipeline_id) => {
+ let scope = PassErrorScope::SetPipelineRender(pipeline_id);
+ if state.pipeline.set_and_check_redundant(pipeline_id) {
+ continue;
+ }
+
+ let pipeline = state
+ .trackers
+ .render_pipes
+ .use_extend(&*pipeline_guard, pipeline_id, (), ())
+ .unwrap();
+
+ self.context
+ .check_compatible(&pipeline.pass_context)
+ .map_err(RenderCommandError::IncompatiblePipeline)
+ .map_pass_err(scope)?;
+
+ //TODO: check read-only depth
+
+ let layout = &pipeline_layout_guard[pipeline.layout_id.value];
+ pipeline_layout_id = Some(pipeline.layout_id.value);
+
+ state.set_pipeline(
+ pipeline.index_format,
+ &pipeline.vertex_strides,
+ &layout.bind_group_layout_ids,
+ &layout.push_constant_ranges,
+ );
+ commands.push(command);
+ if let Some(iter) = state.flush_push_constants() {
+ commands.extend(iter)
+ }
+ }
+ RenderCommand::SetIndexBuffer {
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let scope = PassErrorScope::SetIndexBuffer(buffer_id);
+ let buffer = state
+ .trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDEX)
+ .unwrap();
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::INDEX)
+ .map_pass_err(scope)?;
+
+ let end = match size {
+ Some(s) => offset + s.get(),
+ None => buffer.size,
+ };
+ state.index.set_buffer(buffer_id, offset..end);
+ }
+ RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let scope = PassErrorScope::SetVertexBuffer(buffer_id);
+ let buffer = state
+ .trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::VERTEX)
+ .unwrap();
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::VERTEX)
+ .map_pass_err(scope)?;
+
+ let end = match size {
+ Some(s) => offset + s.get(),
+ None => buffer.size,
+ };
+ state.vertex[slot as usize].set_buffer(buffer_id, offset..end);
+ }
+ RenderCommand::SetPushConstant {
+ stages,
+ offset,
+ size_bytes,
+ values_offset: _,
+ } => {
+ let scope = PassErrorScope::SetPushConstant;
+ let end_offset = offset + size_bytes;
+
+ let pipeline_layout_id = pipeline_layout_id
+ .ok_or(DrawError::MissingPipeline)
+ .map_pass_err(scope)?;
+ let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
+
+ pipeline_layout
+ .validate_push_constant_ranges(stages, offset, end_offset)
+ .map_pass_err(scope)?;
+
+ commands.push(command);
+ }
+ RenderCommand::Draw {
+ vertex_count,
+ instance_count,
+ first_vertex,
+ first_instance,
+ } => {
+ let scope = PassErrorScope::Draw;
+ let (vertex_limit, instance_limit) = state.vertex_limits();
+ let last_vertex = first_vertex + vertex_count;
+ if last_vertex > vertex_limit {
+ return Err(DrawError::VertexBeyondLimit {
+ last_vertex,
+ vertex_limit,
+ })
+ .map_pass_err(scope);
+ }
+ let last_instance = first_instance + instance_count;
+ if last_instance > instance_limit {
+ return Err(DrawError::InstanceBeyondLimit {
+ last_instance,
+ instance_limit,
+ })
+ .map_pass_err(scope);
+ }
+ commands.extend(state.flush_vertices());
+ commands.extend(state.flush_binds());
+ commands.push(command);
+ }
+ RenderCommand::DrawIndexed {
+ index_count,
+ instance_count,
+ first_index,
+ base_vertex: _,
+ first_instance,
+ } => {
+ let scope = PassErrorScope::DrawIndexed;
+ //TODO: validate that base_vertex + max_index() is within the provided range
+ let (_, instance_limit) = state.vertex_limits();
+ let index_limit = state.index.limit();
+ let last_index = first_index + index_count;
+ if last_index > index_limit {
+ return Err(DrawError::IndexBeyondLimit {
+ last_index,
+ index_limit,
+ })
+ .map_pass_err(scope);
+ }
+ let last_instance = first_instance + instance_count;
+ if last_instance > instance_limit {
+ return Err(DrawError::InstanceBeyondLimit {
+ last_instance,
+ instance_limit,
+ })
+ .map_pass_err(scope);
+ }
+ commands.extend(state.index.flush());
+ commands.extend(state.flush_vertices());
+ commands.extend(state.flush_binds());
+ commands.push(command);
+ }
+ RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset: _,
+ count: None,
+ indexed: false,
+ } => {
+ let scope = PassErrorScope::DrawIndirect;
+ let buffer = state
+ .trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
+ .unwrap();
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+
+ commands.extend(state.flush_vertices());
+ commands.extend(state.flush_binds());
+ commands.push(command);
+ }
+ RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset: _,
+ count: None,
+ indexed: true,
+ } => {
+ let scope = PassErrorScope::DrawIndexedIndirect;
+ let buffer = state
+ .trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
+ .map_err(|err| RenderCommandError::Buffer(buffer_id, err))
+ .map_pass_err(scope)?;
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+
+ commands.extend(state.index.flush());
+ commands.extend(state.flush_vertices());
+ commands.extend(state.flush_binds());
+ commands.push(command);
+ }
+ RenderCommand::MultiDrawIndirect { .. }
+ | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
+ RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
+ RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
+ RenderCommand::PopDebugGroup => unimplemented!(),
+ RenderCommand::ExecuteBundle(_)
+ | RenderCommand::SetBlendColor(_)
+ | RenderCommand::SetStencilReference(_)
+ | RenderCommand::SetViewport { .. }
+ | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
+ }
+ }
+
+ let _ = desc.label; //TODO: actually use
+ Ok(RenderBundle {
+ base: BasePass {
+ commands,
+ dynamic_offsets: state.flat_dynamic_offsets,
+ string_data: Vec::new(),
+ push_constant_data: Vec::new(),
+ },
+ device_id: Stored {
+ value: id::Valid(self.parent_id),
+ ref_count: device.life_guard.add_ref(),
+ },
+ used: state.trackers,
+ context: self.context,
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ })
+ }
+}
+
+/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
+#[derive(Clone, Debug, Error)]
+pub enum CreateRenderBundleError {
+ #[error("invalid number of samples {0}")]
+ InvalidSampleCount(u32),
+}
+
+/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
+#[derive(Clone, Debug, Error)]
+pub enum ExecutionError {
+ #[error("buffer {0:?} is destroyed")]
+ DestroyedBuffer(id::BufferId),
+}
+
+pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
+
+//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
+// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
+// or Metal indirect command buffer.
+#[derive(Debug)]
+pub struct RenderBundle {
+ // Normalized command stream. It can be executed verbatim,
+ // without re-binding anything on the pipeline change.
+ base: BasePass<RenderCommand>,
+ pub(crate) device_id: Stored<id::DeviceId>,
+ pub(crate) used: TrackerSet,
+ pub(crate) context: RenderPassContext,
+ pub(crate) life_guard: LifeGuard,
+}
+
+unsafe impl Send for RenderBundle {}
+unsafe impl Sync for RenderBundle {}
+
+impl RenderBundle {
+ #[cfg(feature = "trace")]
+ pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand> {
+ BasePass::from_ref(self.base.as_ref())
+ }
+
+ /// Actually encode the contents into a native command buffer.
+ ///
+ /// This is partially duplicating the logic of `command_encoder_run_render_pass`.
+ /// However the point of this function is to be lighter, since we already had
+ /// a chance to go through the commands in `render_bundle_encoder_finish`.
+ ///
+ /// Note that the function isn't expected to fail, generally.
+ /// All the validation has already been done by this point.
+ /// The only failure condition is if some of the used buffers are destroyed.
+ pub(crate) unsafe fn execute<B: GfxBackend>(
+ &self,
+ cmd_buf: &mut B::CommandBuffer,
+ pipeline_layout_guard: &Storage<
+ crate::binding_model::PipelineLayout<B>,
+ id::PipelineLayoutId,
+ >,
+ bind_group_guard: &Storage<crate::binding_model::BindGroup<B>, id::BindGroupId>,
+ pipeline_guard: &Storage<crate::pipeline::RenderPipeline<B>, id::RenderPipelineId>,
+ buffer_guard: &Storage<crate::resource::Buffer<B>, id::BufferId>,
+ ) -> Result<(), ExecutionError> {
+ use hal::command::CommandBuffer as _;
+
+ let mut offsets = self.base.dynamic_offsets.as_slice();
+ let mut index_type = hal::IndexType::U16;
+ let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
+
+ for command in self.base.commands.iter() {
+ match *command {
+ RenderCommand::SetBindGroup {
+ index,
+ num_dynamic_offsets,
+ bind_group_id,
+ } => {
+ let bind_group = bind_group_guard.get(bind_group_id).unwrap();
+ cmd_buf.bind_graphics_descriptor_sets(
+ &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw,
+ index as usize,
+ iter::once(bind_group.raw.raw()),
+ &offsets[..num_dynamic_offsets as usize],
+ );
+ offsets = &offsets[num_dynamic_offsets as usize..];
+ }
+ RenderCommand::SetPipeline(pipeline_id) => {
+ let pipeline = pipeline_guard.get(pipeline_id).unwrap();
+ cmd_buf.bind_graphics_pipeline(&pipeline.raw);
+ index_type = conv::map_index_format(pipeline.index_format);
+ pipeline_layout_id = Some(pipeline.layout_id.value);
+ }
+ RenderCommand::SetIndexBuffer {
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let &(ref buffer, _) = buffer_guard
+ .get(buffer_id)
+ .unwrap()
+ .raw
+ .as_ref()
+ .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
+ let range = hal::buffer::SubRange {
+ offset,
+ size: size.map(|s| s.get()),
+ };
+ cmd_buf.bind_index_buffer(buffer, range, index_type);
+ }
+ RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let &(ref buffer, _) = buffer_guard
+ .get(buffer_id)
+ .unwrap()
+ .raw
+ .as_ref()
+ .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
+ let range = hal::buffer::SubRange {
+ offset,
+ size: size.map(|s| s.get()),
+ };
+ cmd_buf.bind_vertex_buffers(slot, iter::once((buffer, range)));
+ }
+ RenderCommand::SetPushConstant {
+ stages,
+ offset,
+ size_bytes,
+ values_offset,
+ } => {
+ let pipeline_layout_id = pipeline_layout_id.unwrap();
+ let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
+
+ if let Some(values_offset) = values_offset {
+ let values_end_offset =
+ (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
+ let data_slice = &self.base.push_constant_data
+ [(values_offset as usize)..values_end_offset];
+
+ cmd_buf.push_graphics_constants(
+ &pipeline_layout.raw,
+ conv::map_shader_stage_flags(stages),
+ offset,
+ &data_slice,
+ )
+ } else {
+ super::push_constant_clear(
+ offset,
+ size_bytes,
+ |clear_offset, clear_data| {
+ cmd_buf.push_graphics_constants(
+ &pipeline_layout.raw,
+ conv::map_shader_stage_flags(stages),
+ clear_offset,
+ clear_data,
+ );
+ },
+ );
+ }
+ }
+ RenderCommand::Draw {
+ vertex_count,
+ instance_count,
+ first_vertex,
+ first_instance,
+ } => {
+ cmd_buf.draw(
+ first_vertex..first_vertex + vertex_count,
+ first_instance..first_instance + instance_count,
+ );
+ }
+ RenderCommand::DrawIndexed {
+ index_count,
+ instance_count,
+ first_index,
+ base_vertex,
+ first_instance,
+ } => {
+ cmd_buf.draw_indexed(
+ first_index..first_index + index_count,
+ base_vertex,
+ first_instance..first_instance + instance_count,
+ );
+ }
+ RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: false,
+ } => {
+ let &(ref buffer, _) = buffer_guard
+ .get(buffer_id)
+ .unwrap()
+ .raw
+ .as_ref()
+ .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
+ cmd_buf.draw_indirect(buffer, offset, 1, 0);
+ }
+ RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: true,
+ } => {
+ let &(ref buffer, _) = buffer_guard
+ .get(buffer_id)
+ .unwrap()
+ .raw
+ .as_ref()
+ .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
+ cmd_buf.draw_indexed_indirect(buffer, offset, 1, 0);
+ }
+ RenderCommand::MultiDrawIndirect { .. }
+ | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
+ RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
+ RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
+ RenderCommand::PopDebugGroup => unimplemented!(),
+ RenderCommand::ExecuteBundle(_)
+ | RenderCommand::SetBlendColor(_)
+ | RenderCommand::SetStencilReference(_)
+ | RenderCommand::SetViewport { .. }
+ | RenderCommand::SetScissor(_) => unreachable!(),
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Resource for RenderBundle {
+ const TYPE: &'static str = "RenderBundle";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+#[derive(Debug)]
+struct IndexState {
+ buffer: Option<id::BufferId>,
+ format: wgt::IndexFormat,
+ range: Range<wgt::BufferAddress>,
+ is_dirty: bool,
+}
+
+impl IndexState {
+ fn new() -> Self {
+ Self {
+ buffer: None,
+ format: wgt::IndexFormat::default(),
+ range: 0..0,
+ is_dirty: false,
+ }
+ }
+
+ fn limit(&self) -> u32 {
+ assert!(self.buffer.is_some());
+ let bytes_per_index = match self.format {
+ wgt::IndexFormat::Uint16 => 2,
+ wgt::IndexFormat::Uint32 => 4,
+ };
+ ((self.range.end - self.range.start) / bytes_per_index) as u32
+ }
+
+ fn flush(&mut self) -> Option<RenderCommand> {
+ if self.is_dirty {
+ self.is_dirty = false;
+ Some(RenderCommand::SetIndexBuffer {
+ buffer_id: self.buffer.unwrap(),
+ offset: self.range.start,
+ size: wgt::BufferSize::new(self.range.end - self.range.start),
+ })
+ } else {
+ None
+ }
+ }
+
+ fn set_format(&mut self, format: wgt::IndexFormat) {
+ if self.format != format {
+ self.format = format;
+ self.is_dirty = true;
+ }
+ }
+
+ fn set_buffer(&mut self, id: id::BufferId, range: Range<wgt::BufferAddress>) {
+ self.buffer = Some(id);
+ self.range = range;
+ self.is_dirty = true;
+ }
+}
+
+#[derive(Debug)]
+struct VertexState {
+ buffer: Option<id::BufferId>,
+ range: Range<wgt::BufferAddress>,
+ stride: wgt::BufferAddress,
+ rate: wgt::InputStepMode,
+ is_dirty: bool,
+}
+
+impl VertexState {
+ fn new() -> Self {
+ Self {
+ buffer: None,
+ range: 0..0,
+ stride: 0,
+ rate: wgt::InputStepMode::Vertex,
+ is_dirty: false,
+ }
+ }
+
+ fn set_buffer(&mut self, buffer_id: id::BufferId, range: Range<wgt::BufferAddress>) {
+ self.buffer = Some(buffer_id);
+ self.range = range;
+ self.is_dirty = true;
+ }
+
+ fn flush(&mut self, slot: u32) -> Option<RenderCommand> {
+ if self.is_dirty {
+ self.is_dirty = false;
+ Some(RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id: self.buffer.unwrap(),
+ offset: self.range.start,
+ size: wgt::BufferSize::new(self.range.end - self.range.start),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug)]
+struct BindState {
+ bind_group: Option<(id::BindGroupId, id::BindGroupLayoutId)>,
+ dynamic_offsets: Range<usize>,
+ is_dirty: bool,
+}
+
+impl BindState {
+ fn new() -> Self {
+ Self {
+ bind_group: None,
+ dynamic_offsets: 0..0,
+ is_dirty: false,
+ }
+ }
+
+ fn set_group(
+ &mut self,
+ bind_group_id: id::BindGroupId,
+ layout_id: id::BindGroupLayoutId,
+ dyn_offset: usize,
+ dyn_count: usize,
+ ) -> bool {
+ match self.bind_group {
+ Some((bg_id, _)) if bg_id == bind_group_id && dyn_count == 0 => false,
+ _ => {
+ self.bind_group = Some((bind_group_id, layout_id));
+ self.dynamic_offsets = dyn_offset..dyn_offset + dyn_count;
+ self.is_dirty = true;
+ true
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+struct PushConstantState {
+ ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>,
+ is_dirty: bool,
+}
+impl PushConstantState {
+ fn new() -> Self {
+ Self {
+ ranges: ArrayVec::new(),
+ is_dirty: false,
+ }
+ }
+
+ fn set_push_constants(&mut self, new_ranges: &[wgt::PushConstantRange]) -> bool {
+ if &*self.ranges != new_ranges {
+ self.ranges = new_ranges.iter().cloned().collect();
+ self.is_dirty = true;
+ true
+ } else {
+ false
+ }
+ }
+}
+
+#[derive(Debug)]
+struct State {
+ trackers: TrackerSet,
+ index: IndexState,
+ vertex: ArrayVec<[VertexState; MAX_VERTEX_BUFFERS]>,
+ bind: ArrayVec<[BindState; MAX_BIND_GROUPS]>,
+ push_constant_ranges: PushConstantState,
+ raw_dynamic_offsets: Vec<wgt::DynamicOffset>,
+ flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
+ used_bind_groups: usize,
+ pipeline: StateChange<id::RenderPipelineId>,
+}
+
+impl State {
+ fn vertex_limits(&self) -> (u32, u32) {
+ let mut vertex_limit = !0;
+ let mut instance_limit = !0;
+ for vbs in &self.vertex {
+ if vbs.stride == 0 {
+ continue;
+ }
+ let limit = ((vbs.range.end - vbs.range.start) / vbs.stride) as u32;
+ match vbs.rate {
+ wgt::InputStepMode::Vertex => vertex_limit = vertex_limit.min(limit),
+ wgt::InputStepMode::Instance => instance_limit = instance_limit.min(limit),
+ }
+ }
+ (vertex_limit, instance_limit)
+ }
+
+ fn invalidate_group_from(&mut self, slot: usize) {
+ for bind in self.bind[slot..].iter_mut() {
+ if bind.bind_group.is_some() {
+ bind.is_dirty = true;
+ }
+ }
+ }
+
+ fn set_bind_group(
+ &mut self,
+ slot: u8,
+ bind_group_id: id::BindGroupId,
+ layout_id: id::Valid<id::BindGroupLayoutId>,
+ offsets: &[wgt::DynamicOffset],
+ ) {
+ if self.bind[slot as usize].set_group(
+ bind_group_id,
+ layout_id.0,
+ self.raw_dynamic_offsets.len(),
+ offsets.len(),
+ ) {
+ self.invalidate_group_from(slot as usize + 1);
+ }
+ self.raw_dynamic_offsets.extend(offsets);
+ }
+
+ fn set_pipeline(
+ &mut self,
+ index_format: wgt::IndexFormat,
+ vertex_strides: &[(wgt::BufferAddress, wgt::InputStepMode)],
+ layout_ids: &[id::Valid<id::BindGroupLayoutId>],
+ push_constant_layouts: &[wgt::PushConstantRange],
+ ) {
+ self.index.set_format(index_format);
+ for (vs, &(stride, step_mode)) in self.vertex.iter_mut().zip(vertex_strides) {
+ if vs.stride != stride || vs.rate != step_mode {
+ vs.stride = stride;
+ vs.rate = step_mode;
+ vs.is_dirty = true;
+ }
+ }
+
+ let push_constants_changed = self
+ .push_constant_ranges
+ .set_push_constants(push_constant_layouts);
+
+ self.used_bind_groups = layout_ids.len();
+ let invalid_from = if push_constants_changed {
+ Some(0)
+ } else {
+ self.bind
+ .iter()
+ .zip(layout_ids)
+ .position(|(bs, layout_id)| match bs.bind_group {
+ Some((_, bgl_id)) => bgl_id != layout_id.0,
+ None => false,
+ })
+ };
+ if let Some(slot) = invalid_from {
+ self.invalidate_group_from(slot);
+ }
+ }
+
+ fn flush_push_constants(&mut self) -> Option<impl Iterator<Item = RenderCommand>> {
+ let is_dirty = self.push_constant_ranges.is_dirty;
+
+ if is_dirty {
+ let nonoverlapping_ranges =
+ super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges.ranges);
+
+ Some(
+ nonoverlapping_ranges
+ .into_iter()
+ .map(|range| RenderCommand::SetPushConstant {
+ stages: range.stages,
+ offset: range.range.start,
+ size_bytes: range.range.end - range.range.start,
+ values_offset: None,
+ }),
+ )
+ } else {
+ None
+ }
+ }
+
+ fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
+ self.vertex
+ .iter_mut()
+ .enumerate()
+ .flat_map(|(i, vs)| vs.flush(i as u32))
+ }
+
+ fn flush_binds(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
+ for bs in self.bind[..self.used_bind_groups].iter() {
+ if bs.is_dirty {
+ self.flat_dynamic_offsets
+ .extend_from_slice(&self.raw_dynamic_offsets[bs.dynamic_offsets.clone()]);
+ }
+ }
+ self.bind
+ .iter_mut()
+ .take(self.used_bind_groups)
+ .enumerate()
+ .flat_map(|(i, bs)| {
+ if bs.is_dirty {
+ bs.is_dirty = false;
+ Some(RenderCommand::SetBindGroup {
+ index: i as u8,
+ bind_group_id: bs.bind_group.unwrap().0,
+ num_dynamic_offsets: (bs.dynamic_offsets.end - bs.dynamic_offsets.start)
+ as u8,
+ })
+ } else {
+ None
+ }
+ })
+ }
+}
+
+/// Error encountered when finishing recording a render bundle.
+#[derive(Clone, Debug, Error)]
+pub(super) enum RenderBundleErrorInner {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error(transparent)]
+ RenderCommand(RenderCommandError),
+ #[error(transparent)]
+ ResourceUsageConflict(#[from] UsageConflict),
+ #[error(transparent)]
+ Draw(#[from] DrawError),
+}
+
+impl<T> From<T> for RenderBundleErrorInner
+where
+ T: Into<RenderCommandError>,
+{
+ fn from(t: T) -> Self {
+ Self::RenderCommand(t.into())
+ }
+}
+
+/// Error encountered when finishing recording a render bundle.
+#[derive(Clone, Debug, Error)]
+#[error("{scope}")]
+pub struct RenderBundleError {
+ pub scope: PassErrorScope,
+ #[source]
+ inner: RenderBundleErrorInner,
+}
+
+impl RenderBundleError {
+ pub(crate) const INVALID_DEVICE: Self = RenderBundleError {
+ scope: PassErrorScope::Bundle,
+ inner: RenderBundleErrorInner::Device(DeviceError::Invalid),
+ };
+}
+
+impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
+where
+ E: Into<RenderBundleErrorInner>,
+{
+ fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
+ self.map_err(|inner| RenderBundleError {
+ scope,
+ inner: inner.into(),
+ })
+ }
+}
+
+pub mod bundle_ffi {
+ use super::{RenderBundleEncoder, RenderCommand};
+ use crate::{id, span, RawString};
+ use std::{convert::TryInto, slice};
+ use wgt::{BufferAddress, BufferSize, DynamicOffset};
+
+ /// # Safety
+ ///
+ /// This function is unsafe as there is no guarantee that the given pointer is
+ /// valid for `offset_length` elements.
+ // TODO: There might be other safety issues, such as using the unsafe
+ // `RawPass::encode` and `RawPass::encode_slice`.
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_bundle_set_bind_group(
+ bundle: &mut RenderBundleEncoder,
+ index: u32,
+ bind_group_id: id::BindGroupId,
+ offsets: *const DynamicOffset,
+ offset_length: usize,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::set_bind_group");
+ bundle.base.commands.push(RenderCommand::SetBindGroup {
+ index: index.try_into().unwrap(),
+ num_dynamic_offsets: offset_length.try_into().unwrap(),
+ bind_group_id,
+ });
+ bundle
+ .base
+ .dynamic_offsets
+ .extend_from_slice(slice::from_raw_parts(offsets, offset_length));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_set_pipeline(
+ bundle: &mut RenderBundleEncoder,
+ pipeline_id: id::RenderPipelineId,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::set_pipeline");
+ bundle
+ .base
+ .commands
+ .push(RenderCommand::SetPipeline(pipeline_id));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_set_index_buffer(
+ bundle: &mut RenderBundleEncoder,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::set_index_buffer");
+ bundle.base.commands.push(RenderCommand::SetIndexBuffer {
+ buffer_id,
+ offset,
+ size,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_set_vertex_buffer(
+ bundle: &mut RenderBundleEncoder,
+ slot: u32,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::set_vertex_buffer");
+ bundle.base.commands.push(RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id,
+ offset,
+ size,
+ });
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
+ pass: &mut RenderBundleEncoder,
+ stages: wgt::ShaderStage,
+ offset: u32,
+ size_bytes: u32,
+ data: *const u8,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::set_push_constants");
+ assert_eq!(
+ offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant offset must be aligned to 4 bytes."
+ );
+ assert_eq!(
+ size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant size must be aligned to 4 bytes."
+ );
+ let data_slice = slice::from_raw_parts(data, size_bytes as usize);
+ let value_offset = pass.base.push_constant_data.len().try_into().expect(
+ "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
+ );
+
+ pass.base.push_constant_data.extend(
+ data_slice
+ .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
+ .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
+ );
+
+ pass.base.commands.push(RenderCommand::SetPushConstant {
+ stages,
+ offset,
+ size_bytes,
+ values_offset: Some(value_offset),
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_draw(
+ bundle: &mut RenderBundleEncoder,
+ vertex_count: u32,
+ instance_count: u32,
+ first_vertex: u32,
+ first_instance: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::draw");
+ bundle.base.commands.push(RenderCommand::Draw {
+ vertex_count,
+ instance_count,
+ first_vertex,
+ first_instance,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_draw_indexed(
+ bundle: &mut RenderBundleEncoder,
+ index_count: u32,
+ instance_count: u32,
+ first_index: u32,
+ base_vertex: i32,
+ first_instance: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::draw_indexed");
+ bundle.base.commands.push(RenderCommand::DrawIndexed {
+ index_count,
+ instance_count,
+ first_index,
+ base_vertex,
+ first_instance,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_bundle_draw_indirect(
+ bundle: &mut RenderBundleEncoder,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::draw_indirect");
+ bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: false,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_bundle_indexed_indirect(
+ bundle: &mut RenderBundleEncoder,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::draw_indexed_indirect");
+ bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: true,
+ });
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group(
+ _bundle: &mut RenderBundleEncoder,
+ _label: RawString,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::push_debug_group");
+ //TODO
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
+ span!(_guard, DEBUG, "RenderBundle::pop_debug_group");
+ //TODO
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
+ _bundle: &mut RenderBundleEncoder,
+ _label: RawString,
+ ) {
+ span!(_guard, DEBUG, "RenderBundle::insert_debug_marker");
+ //TODO
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/compute.rs b/gfx/wgpu/wgpu-core/src/command/compute.rs
new file mode 100644
index 0000000000..1ab9df2516
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/compute.rs
@@ -0,0 +1,657 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ binding_model::{BindError, BindGroup, PushConstantUploadError},
+ command::{
+ bind::{Binder, LayoutChange},
+ BasePass, BasePassRef, CommandBuffer, CommandEncoderError, MapPassErr, PassErrorScope,
+ StateChange,
+ },
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Storage, Token},
+ id,
+ resource::{Buffer, BufferUse, Texture},
+ span,
+ track::{TrackerSet, UsageConflict},
+ validation::{check_buffer_usage, MissingBufferUsageError},
+ MAX_BIND_GROUPS,
+};
+
+use arrayvec::ArrayVec;
+use hal::command::CommandBuffer as _;
+use thiserror::Error;
+use wgt::{BufferAddress, BufferUsage, ShaderStage};
+
+use std::{fmt, iter, str};
+
+#[doc(hidden)]
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "trace"),
+ derive(serde::Serialize)
+)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "replay"),
+ derive(serde::Deserialize)
+)]
+pub enum ComputeCommand {
+ SetBindGroup {
+ index: u8,
+ num_dynamic_offsets: u8,
+ bind_group_id: id::BindGroupId,
+ },
+ SetPipeline(id::ComputePipelineId),
+ SetPushConstant {
+ offset: u32,
+ size_bytes: u32,
+ values_offset: u32,
+ },
+ Dispatch([u32; 3]),
+ DispatchIndirect {
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ },
+ PushDebugGroup {
+ color: u32,
+ len: usize,
+ },
+ PopDebugGroup,
+ InsertDebugMarker {
+ color: u32,
+ len: usize,
+ },
+}
+
+#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
+pub struct ComputePass {
+ base: BasePass<ComputeCommand>,
+ parent_id: id::CommandEncoderId,
+}
+
+impl ComputePass {
+ pub fn new(parent_id: id::CommandEncoderId) -> Self {
+ Self {
+ base: BasePass::new(),
+ parent_id,
+ }
+ }
+
+ pub fn parent_id(&self) -> id::CommandEncoderId {
+ self.parent_id
+ }
+
+ #[cfg(feature = "trace")]
+ pub fn into_command(self) -> crate::device::trace::Command {
+ crate::device::trace::Command::RunComputePass { base: self.base }
+ }
+}
+
+impl fmt::Debug for ComputePass {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "ComputePass {{ encoder_id: {:?}, data: {:?} commands and {:?} dynamic offsets }}",
+ self.parent_id,
+ self.base.commands.len(),
+ self.base.dynamic_offsets.len()
+ )
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Debug, Default)]
+pub struct ComputePassDescriptor {
+ pub todo: u32,
+}
+
+#[derive(Clone, Debug, Error, PartialEq)]
+pub enum DispatchError {
+ #[error("compute pipeline must be set")]
+ MissingPipeline,
+ #[error("current compute pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index {index}")]
+ IncompatibleBindGroup {
+ index: u32,
+ //expected: BindGroupLayoutId,
+ //provided: Option<(BindGroupLayoutId, BindGroupId)>,
+ },
+}
+
+/// Error encountered when performing a compute pass.
+#[derive(Clone, Debug, Error)]
+pub enum ComputePassErrorInner {
+ #[error(transparent)]
+ Encoder(#[from] CommandEncoderError),
+ #[error("bind group {0:?} is invalid")]
+ InvalidBindGroup(id::BindGroupId),
+ #[error("bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")]
+ BindGroupIndexOutOfRange { index: u8, max: u32 },
+ #[error("compute pipeline {0:?} is invalid")]
+ InvalidPipeline(id::ComputePipelineId),
+ #[error("indirect buffer {0:?} is invalid or destroyed")]
+ InvalidIndirectBuffer(id::BufferId),
+ #[error(transparent)]
+ ResourceUsageConflict(#[from] UsageConflict),
+ #[error(transparent)]
+ MissingBufferUsage(#[from] MissingBufferUsageError),
+ #[error("cannot pop debug group, because number of pushed debug groups is zero")]
+ InvalidPopDebugGroup,
+ #[error(transparent)]
+ Dispatch(#[from] DispatchError),
+ #[error(transparent)]
+ Bind(#[from] BindError),
+ #[error(transparent)]
+ PushConstants(#[from] PushConstantUploadError),
+}
+
+/// Error encountered when performing a compute pass.
+#[derive(Clone, Debug, Error)]
+#[error("{scope}")]
+pub struct ComputePassError {
+ pub scope: PassErrorScope,
+ #[source]
+ inner: ComputePassErrorInner,
+}
+
+impl<T, E> MapPassErr<T, ComputePassError> for Result<T, E>
+where
+ E: Into<ComputePassErrorInner>,
+{
+ fn map_pass_err(self, scope: PassErrorScope) -> Result<T, ComputePassError> {
+ self.map_err(|inner| ComputePassError {
+ scope,
+ inner: inner.into(),
+ })
+ }
+}
+
+#[derive(Debug)]
+struct State {
+ binder: Binder,
+ pipeline: StateChange<id::ComputePipelineId>,
+ trackers: TrackerSet,
+ debug_scope_depth: u32,
+}
+
+impl State {
+ fn is_ready(&self) -> Result<(), DispatchError> {
+ //TODO: vertex buffers
+ let bind_mask = self.binder.invalid_mask();
+ if bind_mask != 0 {
+ //let (expected, provided) = self.binder.entries[index as usize].info();
+ return Err(DispatchError::IncompatibleBindGroup {
+ index: bind_mask.trailing_zeros(),
+ });
+ }
+ if self.pipeline.is_unset() {
+ return Err(DispatchError::MissingPipeline);
+ }
+ Ok(())
+ }
+
+ fn flush_states<B: GfxBackend>(
+ &mut self,
+ raw_cmd_buf: &mut B::CommandBuffer,
+ base_trackers: &mut TrackerSet,
+ bind_group_guard: &Storage<BindGroup<B>, id::BindGroupId>,
+ buffer_guard: &Storage<Buffer<B>, id::BufferId>,
+ texture_guard: &Storage<Texture<B>, id::TextureId>,
+ ) -> Result<(), UsageConflict> {
+ for id in self.binder.list_active() {
+ self.trackers.merge_extend(&bind_group_guard[id].used)?;
+ }
+
+ tracing::trace!("Encoding dispatch barriers");
+
+ CommandBuffer::insert_barriers(
+ raw_cmd_buf,
+ base_trackers,
+ &self.trackers,
+ buffer_guard,
+ texture_guard,
+ );
+
+ self.trackers.clear();
+ Ok(())
+ }
+}
+
+// Common routines between render/compute
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn command_encoder_run_compute_pass<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ pass: &ComputePass,
+ ) -> Result<(), ComputePassError> {
+ self.command_encoder_run_compute_pass_impl::<B>(encoder_id, pass.base.as_ref())
+ }
+
+ #[doc(hidden)]
+ pub fn command_encoder_run_compute_pass_impl<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ mut base: BasePassRef<ComputeCommand>,
+ ) -> Result<(), ComputePassError> {
+ span!(_guard, INFO, "CommandEncoder::run_compute_pass");
+ let scope = PassErrorScope::Pass(encoder_id);
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
+ let cmd_buf =
+ CommandBuffer::get_encoder(&mut *cmd_buf_guard, encoder_id).map_pass_err(scope)?;
+ let raw = cmd_buf.raw.last_mut().unwrap();
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(crate::device::trace::Command::RunComputePass {
+ base: BasePass::from_ref(base),
+ });
+ }
+
+ let (_, mut token) = hub.render_bundles.read(&mut token);
+ let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token);
+ let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
+ let (pipeline_guard, mut token) = hub.compute_pipelines.read(&mut token);
+ let (buffer_guard, mut token) = hub.buffers.read(&mut token);
+ let (texture_guard, _) = hub.textures.read(&mut token);
+
+ let mut state = State {
+ binder: Binder::new(cmd_buf.limits.max_bind_groups),
+ pipeline: StateChange::new(),
+ trackers: TrackerSet::new(B::VARIANT),
+ debug_scope_depth: 0,
+ };
+ let mut temp_offsets = Vec::new();
+
+ for command in base.commands {
+ match *command {
+ ComputeCommand::SetBindGroup {
+ index,
+ num_dynamic_offsets,
+ bind_group_id,
+ } => {
+ let scope = PassErrorScope::SetBindGroup(bind_group_id);
+
+ let max_bind_groups = cmd_buf.limits.max_bind_groups;
+ if (index as u32) >= max_bind_groups {
+ return Err(ComputePassErrorInner::BindGroupIndexOutOfRange {
+ index,
+ max: max_bind_groups,
+ })
+ .map_pass_err(scope);
+ }
+
+ temp_offsets.clear();
+ temp_offsets
+ .extend_from_slice(&base.dynamic_offsets[..num_dynamic_offsets as usize]);
+ base.dynamic_offsets = &base.dynamic_offsets[num_dynamic_offsets as usize..];
+
+ let bind_group = cmd_buf
+ .trackers
+ .bind_groups
+ .use_extend(&*bind_group_guard, bind_group_id, (), ())
+ .map_err(|_| ComputePassErrorInner::InvalidBindGroup(bind_group_id))
+ .map_pass_err(scope)?;
+ bind_group
+ .validate_dynamic_bindings(&temp_offsets)
+ .map_pass_err(scope)?;
+
+ if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry(
+ index as usize,
+ id::Valid(bind_group_id),
+ bind_group,
+ &temp_offsets,
+ ) {
+ let bind_groups = iter::once(bind_group.raw.raw())
+ .chain(
+ follow_ups
+ .clone()
+ .map(|(bg_id, _)| bind_group_guard[bg_id].raw.raw()),
+ )
+ .collect::<ArrayVec<[_; MAX_BIND_GROUPS]>>();
+ temp_offsets.extend(follow_ups.flat_map(|(_, offsets)| offsets));
+ unsafe {
+ raw.bind_compute_descriptor_sets(
+ &pipeline_layout_guard[pipeline_layout_id].raw,
+ index as usize,
+ bind_groups,
+ &temp_offsets,
+ );
+ }
+ }
+ }
+ ComputeCommand::SetPipeline(pipeline_id) => {
+ let scope = PassErrorScope::SetPipelineCompute(pipeline_id);
+
+ if state.pipeline.set_and_check_redundant(pipeline_id) {
+ continue;
+ }
+
+ let pipeline = cmd_buf
+ .trackers
+ .compute_pipes
+ .use_extend(&*pipeline_guard, pipeline_id, (), ())
+ .map_err(|_| ComputePassErrorInner::InvalidPipeline(pipeline_id))
+ .map_pass_err(scope)?;
+
+ unsafe {
+ raw.bind_compute_pipeline(&pipeline.raw);
+ }
+
+ // Rebind resources
+ if state.binder.pipeline_layout_id != Some(pipeline.layout_id.value) {
+ let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id.value];
+
+ state.binder.change_pipeline_layout(
+ &*pipeline_layout_guard,
+ pipeline.layout_id.value,
+ );
+
+ let mut is_compatible = true;
+
+ for (index, (entry, &bgl_id)) in state
+ .binder
+ .entries
+ .iter_mut()
+ .zip(&pipeline_layout.bind_group_layout_ids)
+ .enumerate()
+ {
+ match entry.expect_layout(bgl_id) {
+ LayoutChange::Match(bg_id, offsets) if is_compatible => {
+ let desc_set = bind_group_guard[bg_id].raw.raw();
+ unsafe {
+ raw.bind_compute_descriptor_sets(
+ &pipeline_layout.raw,
+ index,
+ iter::once(desc_set),
+ offsets.iter().cloned(),
+ );
+ }
+ }
+ LayoutChange::Match(..) | LayoutChange::Unchanged => {}
+ LayoutChange::Mismatch => {
+ is_compatible = false;
+ }
+ }
+ }
+
+ // Clear push constant ranges
+ let non_overlapping = super::bind::compute_nonoverlapping_ranges(
+ &pipeline_layout.push_constant_ranges,
+ );
+ for range in non_overlapping {
+ let offset = range.range.start;
+ let size_bytes = range.range.end - offset;
+ super::push_constant_clear(
+ offset,
+ size_bytes,
+ |clear_offset, clear_data| unsafe {
+ raw.push_compute_constants(
+ &pipeline_layout.raw,
+ clear_offset,
+ clear_data,
+ );
+ },
+ );
+ }
+ }
+ }
+ ComputeCommand::SetPushConstant {
+ offset,
+ size_bytes,
+ values_offset,
+ } => {
+ let scope = PassErrorScope::SetPushConstant;
+
+ let end_offset_bytes = offset + size_bytes;
+ let values_end_offset =
+ (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
+ let data_slice =
+ &base.push_constant_data[(values_offset as usize)..values_end_offset];
+
+ let pipeline_layout_id = state
+ .binder
+ .pipeline_layout_id
+ //TODO: don't error here, lazily update the push constants
+ .ok_or(ComputePassErrorInner::Dispatch(
+ DispatchError::MissingPipeline,
+ ))
+ .map_pass_err(scope)?;
+ let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
+
+ pipeline_layout
+ .validate_push_constant_ranges(
+ ShaderStage::COMPUTE,
+ offset,
+ end_offset_bytes,
+ )
+ .map_pass_err(scope)?;
+
+ unsafe { raw.push_compute_constants(&pipeline_layout.raw, offset, data_slice) }
+ }
+ ComputeCommand::Dispatch(groups) => {
+ let scope = PassErrorScope::Dispatch;
+
+ state.is_ready().map_pass_err(scope)?;
+ state
+ .flush_states(
+ raw,
+ &mut cmd_buf.trackers,
+ &*bind_group_guard,
+ &*buffer_guard,
+ &*texture_guard,
+ )
+ .map_pass_err(scope)?;
+ unsafe {
+ raw.dispatch(groups);
+ }
+ }
+ ComputeCommand::DispatchIndirect { buffer_id, offset } => {
+ let scope = PassErrorScope::DispatchIndirect;
+
+ state.is_ready().map_pass_err(scope)?;
+
+ let indirect_buffer = state
+ .trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
+ .map_err(|_| ComputePassErrorInner::InvalidIndirectBuffer(buffer_id))
+ .map_pass_err(scope)?;
+ check_buffer_usage(indirect_buffer.usage, BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+ let &(ref buf_raw, _) = indirect_buffer
+ .raw
+ .as_ref()
+ .ok_or(ComputePassErrorInner::InvalidIndirectBuffer(buffer_id))
+ .map_pass_err(scope)?;
+
+ state
+ .flush_states(
+ raw,
+ &mut cmd_buf.trackers,
+ &*bind_group_guard,
+ &*buffer_guard,
+ &*texture_guard,
+ )
+ .map_pass_err(scope)?;
+ unsafe {
+ raw.dispatch_indirect(buf_raw, offset);
+ }
+ }
+ ComputeCommand::PushDebugGroup { color, len } => {
+ state.debug_scope_depth += 1;
+
+ let label = str::from_utf8(&base.string_data[..len]).unwrap();
+ unsafe {
+ raw.begin_debug_marker(label, color);
+ }
+ base.string_data = &base.string_data[len..];
+ }
+ ComputeCommand::PopDebugGroup => {
+ let scope = PassErrorScope::PopDebugGroup;
+
+ if state.debug_scope_depth == 0 {
+ return Err(ComputePassErrorInner::InvalidPopDebugGroup)
+ .map_pass_err(scope);
+ }
+ state.debug_scope_depth -= 1;
+ unsafe {
+ raw.end_debug_marker();
+ }
+ }
+ ComputeCommand::InsertDebugMarker { color, len } => {
+ let label = str::from_utf8(&base.string_data[..len]).unwrap();
+ unsafe { raw.insert_debug_marker(label, color) }
+ base.string_data = &base.string_data[len..];
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+pub mod compute_ffi {
+ use super::{ComputeCommand, ComputePass};
+ use crate::{id, span, RawString};
+ use std::{convert::TryInto, ffi, slice};
+ use wgt::{BufferAddress, DynamicOffset};
+
+ /// # Safety
+ ///
+ /// This function is unsafe as there is no guarantee that the given pointer is
+ /// valid for `offset_length` elements.
+ // TODO: There might be other safety issues, such as using the unsafe
+ // `RawPass::encode` and `RawPass::encode_slice`.
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_compute_pass_set_bind_group(
+ pass: &mut ComputePass,
+ index: u32,
+ bind_group_id: id::BindGroupId,
+ offsets: *const DynamicOffset,
+ offset_length: usize,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::set_bind_group");
+ pass.base.commands.push(ComputeCommand::SetBindGroup {
+ index: index.try_into().unwrap(),
+ num_dynamic_offsets: offset_length.try_into().unwrap(),
+ bind_group_id,
+ });
+ pass.base
+ .dynamic_offsets
+ .extend_from_slice(slice::from_raw_parts(offsets, offset_length));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_compute_pass_set_pipeline(
+ pass: &mut ComputePass,
+ pipeline_id: id::ComputePipelineId,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::set_pipeline");
+ pass.base
+ .commands
+ .push(ComputeCommand::SetPipeline(pipeline_id));
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_compute_pass_set_push_constant(
+ pass: &mut ComputePass,
+ offset: u32,
+ size_bytes: u32,
+ data: *const u8,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::set_push_constant");
+ assert_eq!(
+ offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant offset must be aligned to 4 bytes."
+ );
+ assert_eq!(
+ size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant size must be aligned to 4 bytes."
+ );
+ let data_slice = slice::from_raw_parts(data, size_bytes as usize);
+ let value_offset = pass.base.push_constant_data.len().try_into().expect(
+ "Ran out of push constant space. Don't set 4gb of push constants per ComputePass.",
+ );
+
+ pass.base.push_constant_data.extend(
+ data_slice
+ .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
+ .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
+ );
+
+ pass.base.commands.push(ComputeCommand::SetPushConstant {
+ offset,
+ size_bytes,
+ values_offset: value_offset,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_compute_pass_dispatch(
+ pass: &mut ComputePass,
+ groups_x: u32,
+ groups_y: u32,
+ groups_z: u32,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::dispatch");
+ pass.base
+ .commands
+ .push(ComputeCommand::Dispatch([groups_x, groups_y, groups_z]));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_compute_pass_dispatch_indirect(
+ pass: &mut ComputePass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::dispatch_indirect");
+ pass.base
+ .commands
+ .push(ComputeCommand::DispatchIndirect { buffer_id, offset });
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_compute_pass_push_debug_group(
+ pass: &mut ComputePass,
+ label: RawString,
+ color: u32,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::push_debug_group");
+ let bytes = ffi::CStr::from_ptr(label).to_bytes();
+ pass.base.string_data.extend_from_slice(bytes);
+
+ pass.base.commands.push(ComputeCommand::PushDebugGroup {
+ color,
+ len: bytes.len(),
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_compute_pass_pop_debug_group(pass: &mut ComputePass) {
+ span!(_guard, DEBUG, "ComputePass::pop_debug_group");
+ pass.base.commands.push(ComputeCommand::PopDebugGroup);
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_compute_pass_insert_debug_marker(
+ pass: &mut ComputePass,
+ label: RawString,
+ color: u32,
+ ) {
+ span!(_guard, DEBUG, "ComputePass::insert_debug_marker");
+ let bytes = ffi::CStr::from_ptr(label).to_bytes();
+ pass.base.string_data.extend_from_slice(bytes);
+
+ pass.base.commands.push(ComputeCommand::InsertDebugMarker {
+ color,
+ len: bytes.len(),
+ });
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/draw.rs b/gfx/wgpu/wgpu-core/src/command/draw.rs
new file mode 100644
index 0000000000..30c19fef7f
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/draw.rs
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*! Draw structures - shared between render passes and bundles.
+!*/
+
+use crate::{
+ binding_model::PushConstantUploadError,
+ id,
+ resource::BufferUse,
+ track::UseExtendError,
+ validation::{MissingBufferUsageError, MissingTextureUsageError},
+};
+use wgt::{BufferAddress, BufferSize, Color};
+
+use std::num::NonZeroU32;
+use thiserror::Error;
+
+pub type BufferError = UseExtendError<BufferUse>;
+
+/// Error validating a draw call.
+#[derive(Clone, Debug, Error, PartialEq)]
+pub enum DrawError {
+ #[error("blend color needs to be set")]
+ MissingBlendColor,
+ #[error("render pipeline must be set")]
+ MissingPipeline,
+ #[error("current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index {index}")]
+ IncompatibleBindGroup {
+ index: u32,
+ //expected: BindGroupLayoutId,
+ //provided: Option<(BindGroupLayoutId, BindGroupId)>,
+ },
+ #[error("vertex {last_vertex} extends beyond limit {vertex_limit}")]
+ VertexBeyondLimit { last_vertex: u32, vertex_limit: u32 },
+ #[error("instance {last_instance} extends beyond limit {instance_limit}")]
+ InstanceBeyondLimit {
+ last_instance: u32,
+ instance_limit: u32,
+ },
+ #[error("index {last_index} extends beyond limit {index_limit}")]
+ IndexBeyondLimit { last_index: u32, index_limit: u32 },
+}
+
+/// Error encountered when encoding a render command.
+/// This is the shared error set between render bundles and passes.
+#[derive(Clone, Debug, Error)]
+pub enum RenderCommandError {
+ #[error("bind group {0:?} is invalid")]
+ InvalidBindGroup(id::BindGroupId),
+ #[error("bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")]
+ BindGroupIndexOutOfRange { index: u8, max: u32 },
+ #[error("dynamic buffer offset {0} does not respect `BIND_BUFFER_ALIGNMENT`")]
+ UnalignedBufferOffset(u64),
+ #[error("number of buffer offsets ({actual}) does not match the number of dynamic bindings ({expected})")]
+ InvalidDynamicOffsetCount { actual: usize, expected: usize },
+ #[error("render pipeline {0:?} is invalid")]
+ InvalidPipeline(id::RenderPipelineId),
+ #[error("Render pipeline is incompatible with render pass")]
+ IncompatiblePipeline(#[from] crate::device::RenderPassCompatibilityError),
+ #[error("pipeline is not compatible with the depth-stencil read-only render pass")]
+ IncompatibleReadOnlyDepthStencil,
+ #[error("buffer {0:?} is in error {1:?}")]
+ Buffer(id::BufferId, BufferError),
+ #[error("buffer {0:?} is destroyed")]
+ DestroyedBuffer(id::BufferId),
+ #[error(transparent)]
+ MissingBufferUsage(#[from] MissingBufferUsageError),
+ #[error(transparent)]
+ MissingTextureUsage(#[from] MissingTextureUsageError),
+ #[error(transparent)]
+ PushConstants(#[from] PushConstantUploadError),
+ #[error("Invalid Viewport parameters")]
+ InvalidViewport,
+ #[error("Invalid ScissorRect parameters")]
+ InvalidScissorRect,
+}
+
+#[derive(Clone, Copy, Debug, Default)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "trace"),
+ derive(serde::Serialize)
+)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "replay"),
+ derive(serde::Deserialize)
+)]
+pub struct Rect<T> {
+ pub x: T,
+ pub y: T,
+ pub w: T,
+ pub h: T,
+}
+
+#[doc(hidden)]
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "trace"),
+ derive(serde::Serialize)
+)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "replay"),
+ derive(serde::Deserialize)
+)]
+pub enum RenderCommand {
+ SetBindGroup {
+ index: u8,
+ num_dynamic_offsets: u8,
+ bind_group_id: id::BindGroupId,
+ },
+ SetPipeline(id::RenderPipelineId),
+ SetIndexBuffer {
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ },
+ SetVertexBuffer {
+ slot: u32,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ },
+ SetBlendColor(Color),
+ SetStencilReference(u32),
+ SetViewport {
+ rect: Rect<f32>,
+ //TODO: use half-float to reduce the size?
+ depth_min: f32,
+ depth_max: f32,
+ },
+ SetScissor(Rect<u32>),
+ SetPushConstant {
+ stages: wgt::ShaderStage,
+ offset: u32,
+ size_bytes: u32,
+ /// None means there is no data and the data should be an array of zeros.
+ ///
+ /// Facilitates clears in renderbundles which explicitly do their clears.
+ values_offset: Option<u32>,
+ },
+ Draw {
+ vertex_count: u32,
+ instance_count: u32,
+ first_vertex: u32,
+ first_instance: u32,
+ },
+ DrawIndexed {
+ index_count: u32,
+ instance_count: u32,
+ first_index: u32,
+ base_vertex: i32,
+ first_instance: u32,
+ },
+ MultiDrawIndirect {
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ /// Count of `None` represents a non-multi call.
+ count: Option<NonZeroU32>,
+ indexed: bool,
+ },
+ MultiDrawIndirectCount {
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ count_buffer_id: id::BufferId,
+ count_buffer_offset: BufferAddress,
+ max_count: u32,
+ indexed: bool,
+ },
+ PushDebugGroup {
+ color: u32,
+ len: usize,
+ },
+ PopDebugGroup,
+ InsertDebugMarker {
+ color: u32,
+ len: usize,
+ },
+ ExecuteBundle(id::RenderBundleId),
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/mod.rs b/gfx/wgpu/wgpu-core/src/command/mod.rs
new file mode 100644
index 0000000000..1093bde155
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/mod.rs
@@ -0,0 +1,362 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod allocator;
+mod bind;
+mod bundle;
+mod compute;
+mod draw;
+mod render;
+mod transfer;
+
+pub(crate) use self::allocator::CommandAllocator;
+pub use self::allocator::CommandAllocatorError;
+pub use self::bundle::*;
+pub use self::compute::*;
+pub use self::draw::*;
+pub use self::render::*;
+pub use self::transfer::*;
+
+use crate::{
+ device::{all_buffer_stages, all_image_stages},
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Storage, Token},
+ id,
+ resource::{Buffer, Texture},
+ span,
+ track::TrackerSet,
+ Label, PrivateFeatures, Stored,
+};
+
+use hal::command::CommandBuffer as _;
+use thiserror::Error;
+
+use std::thread::ThreadId;
+
+const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64];
+
+#[derive(Debug)]
+pub struct CommandBuffer<B: hal::Backend> {
+ pub(crate) raw: Vec<B::CommandBuffer>,
+ is_recording: bool,
+ recorded_thread_id: ThreadId,
+ pub(crate) device_id: Stored<id::DeviceId>,
+ pub(crate) trackers: TrackerSet,
+ pub(crate) used_swap_chain: Option<(Stored<id::SwapChainId>, B::Framebuffer)>,
+ limits: wgt::Limits,
+ private_features: PrivateFeatures,
+ #[cfg(feature = "trace")]
+ pub(crate) commands: Option<Vec<crate::device::trace::Command>>,
+ #[cfg(debug_assertions)]
+ pub(crate) label: String,
+}
+
+impl<B: GfxBackend> CommandBuffer<B> {
+ fn get_encoder(
+ storage: &mut Storage<Self, id::CommandEncoderId>,
+ id: id::CommandEncoderId,
+ ) -> Result<&mut Self, CommandEncoderError> {
+ match storage.get_mut(id) {
+ Ok(cmd_buf) if cmd_buf.is_recording => Ok(cmd_buf),
+ Ok(_) => Err(CommandEncoderError::NotRecording),
+ Err(_) => Err(CommandEncoderError::Invalid),
+ }
+ }
+
+ pub(crate) fn insert_barriers(
+ raw: &mut B::CommandBuffer,
+ base: &mut TrackerSet,
+ head: &TrackerSet,
+ buffer_guard: &Storage<Buffer<B>, id::BufferId>,
+ texture_guard: &Storage<Texture<B>, id::TextureId>,
+ ) {
+ use hal::command::CommandBuffer as _;
+
+ debug_assert_eq!(B::VARIANT, base.backend());
+ debug_assert_eq!(B::VARIANT, head.backend());
+
+ let buffer_barriers = base.buffers.merge_replace(&head.buffers).map(|pending| {
+ let buf = &buffer_guard[pending.id];
+ pending.into_hal(buf)
+ });
+ let texture_barriers = base.textures.merge_replace(&head.textures).map(|pending| {
+ let tex = &texture_guard[pending.id];
+ pending.into_hal(tex)
+ });
+ base.views.merge_extend(&head.views).unwrap();
+ base.bind_groups.merge_extend(&head.bind_groups).unwrap();
+ base.samplers.merge_extend(&head.samplers).unwrap();
+ base.compute_pipes
+ .merge_extend(&head.compute_pipes)
+ .unwrap();
+ base.render_pipes.merge_extend(&head.render_pipes).unwrap();
+ base.bundles.merge_extend(&head.bundles).unwrap();
+
+ let stages = all_buffer_stages() | all_image_stages();
+ unsafe {
+ raw.pipeline_barrier(
+ stages..stages,
+ hal::memory::Dependencies::empty(),
+ buffer_barriers.chain(texture_barriers),
+ );
+ }
+ }
+}
+
+impl<B: hal::Backend> crate::hub::Resource for CommandBuffer<B> {
+ const TYPE: &'static str = "CommandBuffer";
+
+ fn life_guard(&self) -> &crate::LifeGuard {
+ unreachable!()
+ }
+
+ fn label(&self) -> &str {
+ #[cfg(debug_assertions)]
+ return &self.label;
+ #[cfg(not(debug_assertions))]
+ return "";
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct BasePassRef<'a, C> {
+ pub commands: &'a [C],
+ pub dynamic_offsets: &'a [wgt::DynamicOffset],
+ pub string_data: &'a [u8],
+ pub push_constant_data: &'a [u32],
+}
+
+#[doc(hidden)]
+#[derive(Debug)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "trace"),
+ derive(serde::Serialize)
+)]
+#[cfg_attr(
+ any(feature = "serial-pass", feature = "replay"),
+ derive(serde::Deserialize)
+)]
+pub struct BasePass<C> {
+ pub commands: Vec<C>,
+ pub dynamic_offsets: Vec<wgt::DynamicOffset>,
+ pub string_data: Vec<u8>,
+ pub push_constant_data: Vec<u32>,
+}
+
+impl<C: Clone> BasePass<C> {
+ fn new() -> Self {
+ Self {
+ commands: Vec::new(),
+ dynamic_offsets: Vec::new(),
+ string_data: Vec::new(),
+ push_constant_data: Vec::new(),
+ }
+ }
+
+ #[cfg(feature = "trace")]
+ fn from_ref(base: BasePassRef<C>) -> Self {
+ Self {
+ commands: base.commands.to_vec(),
+ dynamic_offsets: base.dynamic_offsets.to_vec(),
+ string_data: base.string_data.to_vec(),
+ push_constant_data: base.push_constant_data.to_vec(),
+ }
+ }
+
+ pub fn as_ref(&self) -> BasePassRef<C> {
+ BasePassRef {
+ commands: &self.commands,
+ dynamic_offsets: &self.dynamic_offsets,
+ string_data: &self.string_data,
+ push_constant_data: &self.push_constant_data,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CommandEncoderError {
+ #[error("command encoder is invalid")]
+ Invalid,
+ #[error("command encoder must be active")]
+ NotRecording,
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn command_encoder_finish<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ _desc: &wgt::CommandBufferDescriptor<Label>,
+ ) -> (id::CommandBufferId, Option<CommandEncoderError>) {
+ span!(_guard, INFO, "CommandEncoder::finish");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (swap_chain_guard, mut token) = hub.swap_chains.read(&mut token);
+ //TODO: actually close the last recorded command buffer
+ let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
+
+ let error = match CommandBuffer::get_encoder(&mut *cmd_buf_guard, encoder_id) {
+ Ok(cmd_buf) => {
+ cmd_buf.is_recording = false;
+ // stop tracking the swapchain image, if used
+ if let Some((ref sc_id, _)) = cmd_buf.used_swap_chain {
+ let view_id = swap_chain_guard[sc_id.value]
+ .acquired_view_id
+ .as_ref()
+ .expect("Used swap chain frame has already presented");
+ cmd_buf.trackers.views.remove(view_id.value);
+ }
+ tracing::trace!("Command buffer {:?} {:#?}", encoder_id, cmd_buf.trackers);
+ None
+ }
+ Err(e) => Some(e),
+ };
+
+ (encoder_id, error)
+ }
+
+ pub fn command_encoder_push_debug_group<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ label: &str,
+ ) -> Result<(), CommandEncoderError> {
+ span!(_guard, DEBUG, "CommandEncoder::push_debug_group");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, encoder_id)?;
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+
+ unsafe {
+ cmb_raw.begin_debug_marker(label, 0);
+ }
+ Ok(())
+ }
+
+ pub fn command_encoder_insert_debug_marker<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ label: &str,
+ ) -> Result<(), CommandEncoderError> {
+ span!(_guard, DEBUG, "CommandEncoder::insert_debug_marker");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, encoder_id)?;
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+
+ unsafe {
+ cmb_raw.insert_debug_marker(label, 0);
+ }
+ Ok(())
+ }
+
+ pub fn command_encoder_pop_debug_group<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ ) -> Result<(), CommandEncoderError> {
+ span!(_guard, DEBUG, "CommandEncoder::pop_debug_marker");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, encoder_id)?;
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+
+ unsafe {
+ cmb_raw.end_debug_marker();
+ }
+ Ok(())
+ }
+}
+
+fn push_constant_clear<PushFn>(offset: u32, size_bytes: u32, mut push_fn: PushFn)
+where
+ PushFn: FnMut(u32, &[u32]),
+{
+ let mut count_words = 0_u32;
+ let size_words = size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT;
+ while count_words < size_words {
+ let count_bytes = count_words * wgt::PUSH_CONSTANT_ALIGNMENT;
+ let size_to_write_words =
+ (size_words - count_words).min(PUSH_CONSTANT_CLEAR_ARRAY.len() as u32);
+
+ push_fn(
+ offset + count_bytes,
+ &PUSH_CONSTANT_CLEAR_ARRAY[0..size_to_write_words as usize],
+ );
+
+ count_words += size_to_write_words;
+ }
+}
+
+#[derive(Debug)]
+struct StateChange<T> {
+ last_state: Option<T>,
+}
+
+impl<T: Copy + PartialEq> StateChange<T> {
+ fn new() -> Self {
+ Self { last_state: None }
+ }
+ fn set_and_check_redundant(&mut self, new_state: T) -> bool {
+ let already_set = self.last_state == Some(new_state);
+ self.last_state = Some(new_state);
+ already_set
+ }
+ fn is_unset(&self) -> bool {
+ self.last_state.is_none()
+ }
+ fn reset(&mut self) {
+ self.last_state = None;
+ }
+}
+
+trait MapPassErr<T, O> {
+ fn map_pass_err(self, scope: PassErrorScope) -> Result<T, O>;
+}
+
+#[derive(Clone, Copy, Debug, Error)]
+pub enum PassErrorScope {
+ #[error("In a bundle parameter")]
+ Bundle,
+ #[error("In a pass parameter")]
+ Pass(id::CommandEncoderId),
+ #[error("In a set_bind_group command")]
+ SetBindGroup(id::BindGroupId),
+ #[error("In a set_pipeline command")]
+ SetPipelineRender(id::RenderPipelineId),
+ #[error("In a set_pipeline command")]
+ SetPipelineCompute(id::ComputePipelineId),
+ #[error("In a set_push_constant command")]
+ SetPushConstant,
+ #[error("In a set_vertex_buffer command")]
+ SetVertexBuffer(id::BufferId),
+ #[error("In a set_index_buffer command")]
+ SetIndexBuffer(id::BufferId),
+ #[error("In a set_viewport command")]
+ SetViewport,
+ #[error("In a set_scissor_rect command")]
+ SetScissorRect,
+ #[error("In a draw command")]
+ Draw,
+ #[error("In a draw_indexed command")]
+ DrawIndexed,
+ #[error("In a draw_indirect command")]
+ DrawIndirect,
+ #[error("In a draw_indexed_indirect command")]
+ DrawIndexedIndirect,
+ #[error("In a execute_bundle command")]
+ ExecuteBundle,
+ #[error("In a dispatch command")]
+ Dispatch,
+ #[error("In a dispatch_indirect command")]
+ DispatchIndirect,
+ #[error("In a pop_debug_group command")]
+ PopDebugGroup,
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/render.rs b/gfx/wgpu/wgpu-core/src/command/render.rs
new file mode 100644
index 0000000000..531107956a
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/render.rs
@@ -0,0 +1,2078 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ binding_model::BindError,
+ command::{
+ bind::{Binder, LayoutChange},
+ BasePass, BasePassRef, CommandBuffer, CommandEncoderError, DrawError, ExecutionError,
+ MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange,
+ },
+ conv,
+ device::{
+ AttachmentData, AttachmentDataVec, FramebufferKey, RenderPassCompatibilityError,
+ RenderPassContext, RenderPassKey, MAX_COLOR_TARGETS, MAX_VERTEX_BUFFERS,
+ },
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Token},
+ id,
+ pipeline::PipelineFlags,
+ resource::{BufferUse, TextureUse, TextureView, TextureViewInner},
+ span,
+ track::{TextureSelector, TrackerSet, UsageConflict},
+ validation::{
+ check_buffer_usage, check_texture_usage, MissingBufferUsageError, MissingTextureUsageError,
+ },
+ Stored, MAX_BIND_GROUPS,
+};
+
+use arrayvec::ArrayVec;
+use hal::command::CommandBuffer as _;
+use thiserror::Error;
+use wgt::{BufferAddress, BufferUsage, Color, IndexFormat, InputStepMode, TextureUsage};
+
+#[cfg(any(feature = "serial-pass", feature = "replay"))]
+use serde::Deserialize;
+#[cfg(any(feature = "serial-pass", feature = "trace"))]
+use serde::Serialize;
+
+use std::{
+ borrow::{Borrow, Cow},
+ collections::hash_map::Entry,
+ fmt, iter,
+ num::NonZeroU32,
+ ops::Range,
+ str,
+};
+
+/// Operation to perform to the output attachment at the start of a renderpass.
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+#[cfg_attr(any(feature = "serial-pass", feature = "trace"), derive(Serialize))]
+#[cfg_attr(any(feature = "serial-pass", feature = "replay"), derive(Deserialize))]
+pub enum LoadOp {
+ /// Clear the output attachment with the clear color. Clearing is faster than loading.
+ Clear = 0,
+ /// Do not clear output attachment.
+ Load = 1,
+}
+
+/// Operation to perform to the output attachment at the end of a renderpass.
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+#[cfg_attr(any(feature = "serial-pass", feature = "trace"), derive(Serialize))]
+#[cfg_attr(any(feature = "serial-pass", feature = "replay"), derive(Deserialize))]
+pub enum StoreOp {
+ /// Clear the render target. If you don't care about the contents of the target, this can be faster.
+ Clear = 0,
+ /// Store the result of the renderpass.
+ Store = 1,
+}
+
+/// Describes an individual channel within a render pass, such as color, depth, or stencil.
+#[repr(C)]
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(any(feature = "serial-pass", feature = "trace"), derive(Serialize))]
+#[cfg_attr(any(feature = "serial-pass", feature = "replay"), derive(Deserialize))]
+pub struct PassChannel<V> {
+ /// Operation to perform to the output attachment at the start of a renderpass. This must be clear if it
+ /// is the first renderpass rendering to a swap chain image.
+ pub load_op: LoadOp,
+ /// Operation to perform to the output attachment at the end of a renderpass.
+ pub store_op: StoreOp,
+ /// If load_op is [`LoadOp::Clear`], the attachement will be cleared to this color.
+ pub clear_value: V,
+ /// If true, the relevant channel is not changed by a renderpass, and the corresponding attachment
+ /// can be used inside the pass by other read-only usages.
+ pub read_only: bool,
+}
+
+/// Describes a color attachment to a render pass.
+#[repr(C)]
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(any(feature = "serial-pass", feature = "trace"), derive(Serialize))]
+#[cfg_attr(any(feature = "serial-pass", feature = "replay"), derive(Deserialize))]
+pub struct ColorAttachmentDescriptor {
+ /// The view to use as an attachment.
+ pub attachment: id::TextureViewId,
+ /// The view that will receive the resolved output if multisampling is used.
+ pub resolve_target: Option<id::TextureViewId>,
+ /// What operations will be performed on this color attachment.
+ pub channel: PassChannel<Color>,
+}
+
+/// Describes a depth/stencil attachment to a render pass.
+#[repr(C)]
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(any(feature = "serial-pass", feature = "trace"), derive(Serialize))]
+#[cfg_attr(any(feature = "serial-pass", feature = "replay"), derive(Deserialize))]
+pub struct DepthStencilAttachmentDescriptor {
+ /// The view to use as an attachment.
+ pub attachment: id::TextureViewId,
+ /// What operations will be performed on the depth part of the attachment.
+ pub depth: PassChannel<f32>,
+ /// What operations will be performed on the stencil part of the attachment.
+ pub stencil: PassChannel<u32>,
+}
+
+impl DepthStencilAttachmentDescriptor {
+ fn is_read_only(&self, aspects: hal::format::Aspects) -> Result<bool, RenderPassErrorInner> {
+ if aspects.contains(hal::format::Aspects::DEPTH) && !self.depth.read_only {
+ return Ok(false);
+ }
+ if (self.depth.load_op, self.depth.store_op) != (LoadOp::Load, StoreOp::Store) {
+ return Err(RenderPassErrorInner::InvalidDepthOps);
+ }
+ if aspects.contains(hal::format::Aspects::STENCIL) && !self.stencil.read_only {
+ return Ok(false);
+ }
+ if (self.stencil.load_op, self.stencil.store_op) != (LoadOp::Load, StoreOp::Store) {
+ return Err(RenderPassErrorInner::InvalidStencilOps);
+ }
+ Ok(true)
+ }
+}
+
+/// Describes the attachments of a render pass.
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct RenderPassDescriptor<'a> {
+ /// The color attachments of the render pass.
+ pub color_attachments: Cow<'a, [ColorAttachmentDescriptor]>,
+ /// The depth and stencil attachment of the render pass, if any.
+ pub depth_stencil_attachment: Option<&'a DepthStencilAttachmentDescriptor>,
+}
+
+#[cfg_attr(feature = "serial-pass", derive(Deserialize, Serialize))]
+pub struct RenderPass {
+ base: BasePass<RenderCommand>,
+ parent_id: id::CommandEncoderId,
+ color_targets: ArrayVec<[ColorAttachmentDescriptor; MAX_COLOR_TARGETS]>,
+ depth_stencil_target: Option<DepthStencilAttachmentDescriptor>,
+}
+
+impl RenderPass {
+ pub fn new(parent_id: id::CommandEncoderId, desc: RenderPassDescriptor) -> Self {
+ Self {
+ base: BasePass::new(),
+ parent_id,
+ color_targets: desc.color_attachments.iter().cloned().collect(),
+ depth_stencil_target: desc.depth_stencil_attachment.cloned(),
+ }
+ }
+
+ pub fn parent_id(&self) -> id::CommandEncoderId {
+ self.parent_id
+ }
+
+ #[cfg(feature = "trace")]
+ pub fn into_command(self) -> crate::device::trace::Command {
+ crate::device::trace::Command::RunRenderPass {
+ base: self.base,
+ target_colors: self.color_targets.into_iter().collect(),
+ target_depth_stencil: self.depth_stencil_target,
+ }
+ }
+}
+
+impl fmt::Debug for RenderPass {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "RenderPass {{ encoder_id: {:?}, color_targets: {:?}, depth_stencil_target: {:?}, data: {:?} commands, {:?} dynamic offsets, and {:?} push constant u32s }}",
+ self.parent_id,
+ self.color_targets,
+ self.depth_stencil_target,
+ self.base.commands.len(),
+ self.base.dynamic_offsets.len(),
+ self.base.push_constant_data.len(),
+ )
+ }
+}
+
+#[derive(Debug, PartialEq)]
+enum OptionalState {
+ Unused,
+ Required,
+ Set,
+}
+
+impl OptionalState {
+ fn require(&mut self, require: bool) {
+ if require && *self == OptionalState::Unused {
+ *self = OptionalState::Required;
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+struct IndexState {
+ bound_buffer_view: Option<(id::Valid<id::BufferId>, Range<BufferAddress>)>,
+ format: IndexFormat,
+ limit: u32,
+}
+
+impl IndexState {
+ fn update_limit(&mut self) {
+ self.limit = match self.bound_buffer_view {
+ Some((_, ref range)) => {
+ let shift = match self.format {
+ IndexFormat::Uint16 => 1,
+ IndexFormat::Uint32 => 2,
+ };
+ ((range.end - range.start) >> shift) as u32
+ }
+ None => 0,
+ }
+ }
+
+ fn reset(&mut self) {
+ self.bound_buffer_view = None;
+ self.limit = 0;
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+struct VertexBufferState {
+ total_size: BufferAddress,
+ stride: BufferAddress,
+ rate: InputStepMode,
+}
+
+impl VertexBufferState {
+ const EMPTY: Self = VertexBufferState {
+ total_size: 0,
+ stride: 0,
+ rate: InputStepMode::Vertex,
+ };
+}
+
+#[derive(Debug, Default)]
+struct VertexState {
+ inputs: ArrayVec<[VertexBufferState; MAX_VERTEX_BUFFERS]>,
+ vertex_limit: u32,
+ instance_limit: u32,
+}
+
+impl VertexState {
+ fn update_limits(&mut self) {
+ self.vertex_limit = !0;
+ self.instance_limit = !0;
+ for vbs in &self.inputs {
+ if vbs.stride == 0 {
+ continue;
+ }
+ let limit = (vbs.total_size / vbs.stride) as u32;
+ match vbs.rate {
+ InputStepMode::Vertex => self.vertex_limit = self.vertex_limit.min(limit),
+ InputStepMode::Instance => self.instance_limit = self.instance_limit.min(limit),
+ }
+ }
+ }
+
+ fn reset(&mut self) {
+ self.inputs.clear();
+ self.vertex_limit = 0;
+ self.instance_limit = 0;
+ }
+}
+
+#[derive(Debug)]
+struct State {
+ pipeline_flags: PipelineFlags,
+ binder: Binder,
+ blend_color: OptionalState,
+ stencil_reference: u32,
+ pipeline: StateChange<id::RenderPipelineId>,
+ index: IndexState,
+ vertex: VertexState,
+ debug_scope_depth: u32,
+}
+
+impl State {
+ fn is_ready(&self) -> Result<(), DrawError> {
+ //TODO: vertex buffers
+ let bind_mask = self.binder.invalid_mask();
+ if bind_mask != 0 {
+ //let (expected, provided) = self.binder.entries[index as usize].info();
+ return Err(DrawError::IncompatibleBindGroup {
+ index: bind_mask.trailing_zeros(),
+ });
+ }
+ if self.pipeline.is_unset() {
+ return Err(DrawError::MissingPipeline);
+ }
+ if self.blend_color == OptionalState::Required {
+ return Err(DrawError::MissingBlendColor);
+ }
+ Ok(())
+ }
+
+ /// Reset the `RenderBundle`-related states.
+ fn reset_bundle(&mut self) {
+ self.binder.reset();
+ self.pipeline.reset();
+ self.index.reset();
+ self.vertex.reset();
+ }
+}
+
+/// Error encountered when performing a render pass.
+#[derive(Clone, Debug, Error)]
+pub enum RenderPassErrorInner {
+ #[error(transparent)]
+ Encoder(#[from] CommandEncoderError),
+ #[error("attachment texture view {0:?} is invalid")]
+ InvalidAttachment(id::TextureViewId),
+ #[error("attachments have different sizes")]
+ MismatchAttachments,
+ #[error("attachment's sample count {0} is invalid")]
+ InvalidSampleCount(u8),
+ #[error("attachment with resolve target must be multi-sampled")]
+ InvalidResolveSourceSampleCount,
+ #[error("resolve target must have a sample count of 1")]
+ InvalidResolveTargetSampleCount,
+ #[error("not enough memory left")]
+ OutOfMemory,
+ #[error("extent state {state_extent:?} must match extent from view {view_extent:?}")]
+ ExtentStateMismatch {
+ state_extent: hal::image::Extent,
+ view_extent: hal::image::Extent,
+ },
+ #[error("attempted to use a swap chain image as a depth/stencil attachment")]
+ SwapChainImageAsDepthStencil,
+ #[error("unable to clear non-present/read-only depth")]
+ InvalidDepthOps,
+ #[error("unable to clear non-present/read-only stencil")]
+ InvalidStencilOps,
+ #[error("all attachments must have the same sample count, found {actual} != {expected}")]
+ SampleCountMismatch { actual: u8, expected: u8 },
+ #[error("texture view's swap chain must match swap chain in use")]
+ SwapChainMismatch,
+ #[error("setting `values_offset` to be `None` is only for internal use in render bundles")]
+ InvalidValuesOffset,
+ #[error("required device features not enabled: {0:?}")]
+ MissingDeviceFeatures(wgt::Features),
+ #[error("indirect draw with offset {offset}{} uses bytes {begin_offset}..{end_offset} which overruns indirect buffer of size {buffer_size}", count.map_or_else(String::new, |v| format!(" and count {}", v)))]
+ IndirectBufferOverrun {
+ offset: u64,
+ count: Option<NonZeroU32>,
+ begin_offset: u64,
+ end_offset: u64,
+ buffer_size: u64,
+ },
+ #[error("indirect draw uses bytes {begin_count_offset}..{end_count_offset} which overruns indirect buffer of size {count_buffer_size}")]
+ IndirectCountBufferOverrun {
+ begin_count_offset: u64,
+ end_count_offset: u64,
+ count_buffer_size: u64,
+ },
+ #[error("cannot pop debug group, because number of pushed debug groups is zero")]
+ InvalidPopDebugGroup,
+ #[error(transparent)]
+ ResourceUsageConflict(#[from] UsageConflict),
+ #[error("render bundle is incompatible, {0}")]
+ IncompatibleRenderBundle(#[from] RenderPassCompatibilityError),
+ #[error(transparent)]
+ RenderCommand(#[from] RenderCommandError),
+ #[error(transparent)]
+ Draw(#[from] DrawError),
+ #[error(transparent)]
+ Bind(#[from] BindError),
+}
+
+impl From<MissingBufferUsageError> for RenderPassErrorInner {
+ fn from(error: MissingBufferUsageError) -> Self {
+ Self::RenderCommand(error.into())
+ }
+}
+
+impl From<MissingTextureUsageError> for RenderPassErrorInner {
+ fn from(error: MissingTextureUsageError) -> Self {
+ Self::RenderCommand(error.into())
+ }
+}
+
+/// Error encountered when performing a render pass.
+#[derive(Clone, Debug, Error)]
+#[error("Render pass error {scope}: {inner}")]
+pub struct RenderPassError {
+ pub scope: PassErrorScope,
+ #[source]
+ inner: RenderPassErrorInner,
+}
+
+impl<T, E> MapPassErr<T, RenderPassError> for Result<T, E>
+where
+ E: Into<RenderPassErrorInner>,
+{
+ fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderPassError> {
+ self.map_err(|inner| RenderPassError {
+ scope,
+ inner: inner.into(),
+ })
+ }
+}
+
+fn check_device_features(
+ actual: wgt::Features,
+ expected: wgt::Features,
+) -> Result<(), RenderPassErrorInner> {
+ if !actual.contains(expected) {
+ Err(RenderPassErrorInner::MissingDeviceFeatures(expected))
+ } else {
+ Ok(())
+ }
+}
+
+// Common routines between render/compute
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn command_encoder_run_render_pass<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ pass: &RenderPass,
+ ) -> Result<(), RenderPassError> {
+ self.command_encoder_run_render_pass_impl::<B>(
+ encoder_id,
+ pass.base.as_ref(),
+ &pass.color_targets,
+ pass.depth_stencil_target.as_ref(),
+ )
+ }
+
+ #[doc(hidden)]
+ pub fn command_encoder_run_render_pass_impl<B: GfxBackend>(
+ &self,
+ encoder_id: id::CommandEncoderId,
+ mut base: BasePassRef<RenderCommand>,
+ color_attachments: &[ColorAttachmentDescriptor],
+ depth_stencil_attachment: Option<&DepthStencilAttachmentDescriptor>,
+ ) -> Result<(), RenderPassError> {
+ span!(_guard, INFO, "CommandEncoder::run_render_pass");
+ let scope = PassErrorScope::Pass(encoder_id);
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (mut cmb_guard, mut token) = hub.command_buffers.write(&mut token);
+
+ let mut trackers = TrackerSet::new(B::VARIANT);
+ let cmd_buf =
+ CommandBuffer::get_encoder(&mut *cmb_guard, encoder_id).map_pass_err(scope)?;
+ let device = &device_guard[cmd_buf.device_id.value];
+ let mut raw = device.cmd_allocator.extend(cmd_buf);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(crate::device::trace::Command::RunRenderPass {
+ base: BasePass::from_ref(base),
+ target_colors: color_attachments.iter().cloned().collect(),
+ target_depth_stencil: depth_stencil_attachment.cloned(),
+ });
+ }
+
+ unsafe {
+ raw.begin_primary(hal::command::CommandBufferFlags::ONE_TIME_SUBMIT);
+ }
+
+ let (bundle_guard, mut token) = hub.render_bundles.read(&mut token);
+ let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token);
+ let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
+ let (pipeline_guard, mut token) = hub.render_pipelines.read(&mut token);
+ let (buffer_guard, mut token) = hub.buffers.read(&mut token);
+ let (texture_guard, mut token) = hub.textures.read(&mut token);
+ let (view_guard, _) = hub.texture_views.read(&mut token);
+
+ // We default to false intentionally, even if depth-stencil isn't used at all.
+ // This allows us to use the primary raw pipeline in `RenderPipeline`,
+ // instead of the special read-only one, which would be `None`.
+ let mut is_ds_read_only = false;
+
+ struct RenderAttachment<'a> {
+ texture_id: &'a Stored<id::TextureId>,
+ selector: &'a TextureSelector,
+ previous_use: Option<TextureUse>,
+ new_use: TextureUse,
+ }
+ let mut render_attachments = AttachmentDataVec::<RenderAttachment>::new();
+
+ let mut attachment_width = None;
+ let mut attachment_height = None;
+ let mut valid_attachment = true;
+
+ let context = {
+ use hal::device::Device as _;
+
+ let sample_count_limit = device.hal_limits.framebuffer_color_sample_counts;
+ let base_trackers = &cmd_buf.trackers;
+
+ let mut extent = None;
+ let mut sample_count = 0;
+ let mut depth_stencil_aspects = hal::format::Aspects::empty();
+ let mut used_swap_chain = None::<Stored<id::SwapChainId>>;
+
+ let mut add_view = |view: &TextureView<B>| {
+ if let Some(ex) = extent {
+ if ex != view.extent {
+ return Err(RenderPassErrorInner::ExtentStateMismatch {
+ state_extent: ex,
+ view_extent: view.extent,
+ });
+ }
+ } else {
+ extent = Some(view.extent);
+ }
+ if sample_count == 0 {
+ sample_count = view.samples;
+ } else if sample_count != view.samples {
+ return Err(RenderPassErrorInner::SampleCountMismatch {
+ actual: view.samples,
+ expected: sample_count,
+ });
+ }
+ Ok(())
+ };
+
+ tracing::trace!(
+ "Encoding render pass begin in command buffer {:?}",
+ encoder_id
+ );
+ let rp_key = {
+ let depth_stencil = match depth_stencil_attachment {
+ Some(at) => {
+ let view = trackers
+ .views
+ .use_extend(&*view_guard, at.attachment, (), ())
+ .map_err(|_| RenderPassErrorInner::InvalidAttachment(at.attachment))
+ .map_pass_err(scope)?;
+ add_view(view).map_pass_err(scope)?;
+ depth_stencil_aspects = view.aspects;
+
+ let source_id = match view.inner {
+ TextureViewInner::Native { ref source_id, .. } => source_id,
+ TextureViewInner::SwapChain { .. } => {
+ return Err(RenderPassErrorInner::SwapChainImageAsDepthStencil)
+ .map_pass_err(scope)
+ }
+ };
+
+ // Using render pass for transition.
+ let previous_use = base_trackers
+ .textures
+ .query(source_id.value, view.selector.clone());
+ let new_use = if at.is_read_only(view.aspects).map_pass_err(scope)? {
+ is_ds_read_only = true;
+ TextureUse::ATTACHMENT_READ
+ } else {
+ TextureUse::ATTACHMENT_WRITE
+ };
+ render_attachments.push(RenderAttachment {
+ texture_id: source_id,
+ selector: &view.selector,
+ previous_use,
+ new_use,
+ });
+
+ let new_layout = conv::map_texture_state(new_use, view.aspects).1;
+ let old_layout = match previous_use {
+ Some(usage) => conv::map_texture_state(usage, view.aspects).1,
+ None => new_layout,
+ };
+
+ let ds_at = hal::pass::Attachment {
+ format: Some(conv::map_texture_format(
+ view.format,
+ device.private_features,
+ )),
+ samples: view.samples,
+ ops: conv::map_load_store_ops(&at.depth),
+ stencil_ops: conv::map_load_store_ops(&at.stencil),
+ layouts: old_layout..new_layout,
+ };
+ Some((ds_at, new_layout))
+ }
+ None => None,
+ };
+
+ let mut colors = ArrayVec::new();
+ let mut resolves = ArrayVec::new();
+
+ for at in color_attachments {
+ let view = trackers
+ .views
+ .use_extend(&*view_guard, at.attachment, (), ())
+ .map_err(|_| RenderPassErrorInner::InvalidAttachment(at.attachment))
+ .map_pass_err(scope)?;
+ add_view(view).map_pass_err(scope)?;
+
+ valid_attachment &= *attachment_width.get_or_insert(view.extent.width)
+ == view.extent.width
+ && *attachment_height.get_or_insert(view.extent.height)
+ == view.extent.height;
+
+ let layouts = match view.inner {
+ TextureViewInner::Native { ref source_id, .. } => {
+ let previous_use = base_trackers
+ .textures
+ .query(source_id.value, view.selector.clone());
+ let new_use = TextureUse::ATTACHMENT_WRITE;
+ render_attachments.push(RenderAttachment {
+ texture_id: source_id,
+ selector: &view.selector,
+ previous_use,
+ new_use,
+ });
+
+ let new_layout =
+ conv::map_texture_state(new_use, hal::format::Aspects::COLOR).1;
+ let old_layout = match previous_use {
+ Some(usage) => {
+ conv::map_texture_state(usage, hal::format::Aspects::COLOR).1
+ }
+ None => new_layout,
+ };
+ old_layout..new_layout
+ }
+ TextureViewInner::SwapChain { ref source_id, .. } => {
+ if let Some((ref sc_id, _)) = cmd_buf.used_swap_chain {
+ if source_id.value != sc_id.value {
+ return Err(RenderPassErrorInner::SwapChainMismatch)
+ .map_pass_err(scope);
+ }
+ } else {
+ assert!(used_swap_chain.is_none());
+ used_swap_chain = Some(source_id.clone());
+ }
+
+ let end = hal::image::Layout::Present;
+ let start = match at.channel.load_op {
+ LoadOp::Clear => hal::image::Layout::Undefined,
+ LoadOp::Load => end,
+ };
+ start..end
+ }
+ };
+
+ let color_at = hal::pass::Attachment {
+ format: Some(conv::map_texture_format(
+ view.format,
+ device.private_features,
+ )),
+ samples: view.samples,
+ ops: conv::map_load_store_ops(&at.channel),
+ stencil_ops: hal::pass::AttachmentOps::DONT_CARE,
+ layouts,
+ };
+ colors.push((color_at, hal::image::Layout::ColorAttachmentOptimal));
+ }
+
+ if !valid_attachment {
+ return Err(RenderPassErrorInner::MismatchAttachments).map_pass_err(scope);
+ }
+
+ for resolve_target in color_attachments.iter().flat_map(|at| at.resolve_target) {
+ let view = trackers
+ .views
+ .use_extend(&*view_guard, resolve_target, (), ())
+ .map_err(|_| RenderPassErrorInner::InvalidAttachment(resolve_target))
+ .map_pass_err(scope)?;
+ if extent != Some(view.extent) {
+ return Err(RenderPassErrorInner::ExtentStateMismatch {
+ state_extent: extent.unwrap_or_default(),
+ view_extent: view.extent,
+ })
+ .map_pass_err(scope);
+ }
+ if view.samples != 1 {
+ return Err(RenderPassErrorInner::InvalidResolveTargetSampleCount)
+ .map_pass_err(scope);
+ }
+ if sample_count == 1 {
+ return Err(RenderPassErrorInner::InvalidResolveSourceSampleCount)
+ .map_pass_err(scope);
+ }
+
+ let layouts = match view.inner {
+ TextureViewInner::Native { ref source_id, .. } => {
+ let previous_use = base_trackers
+ .textures
+ .query(source_id.value, view.selector.clone());
+ let new_use = TextureUse::ATTACHMENT_WRITE;
+ render_attachments.push(RenderAttachment {
+ texture_id: source_id,
+ selector: &view.selector,
+ previous_use,
+ new_use,
+ });
+
+ let new_layout =
+ conv::map_texture_state(new_use, hal::format::Aspects::COLOR).1;
+ let old_layout = match previous_use {
+ Some(usage) => {
+ conv::map_texture_state(usage, hal::format::Aspects::COLOR).1
+ }
+ None => new_layout,
+ };
+ old_layout..new_layout
+ }
+ TextureViewInner::SwapChain { ref source_id, .. } => {
+ if let Some((ref sc_id, _)) = cmd_buf.used_swap_chain {
+ if source_id.value != sc_id.value {
+ return Err(RenderPassErrorInner::SwapChainMismatch)
+ .map_pass_err(scope);
+ }
+ } else {
+ assert!(used_swap_chain.is_none());
+ used_swap_chain = Some(source_id.clone());
+ }
+ hal::image::Layout::Undefined..hal::image::Layout::Present
+ }
+ };
+
+ let resolve_at = hal::pass::Attachment {
+ format: Some(conv::map_texture_format(
+ view.format,
+ device.private_features,
+ )),
+ samples: view.samples,
+ ops: hal::pass::AttachmentOps::new(
+ hal::pass::AttachmentLoadOp::DontCare,
+ hal::pass::AttachmentStoreOp::Store,
+ ),
+ stencil_ops: hal::pass::AttachmentOps::DONT_CARE,
+ layouts,
+ };
+ resolves.push((resolve_at, hal::image::Layout::ColorAttachmentOptimal));
+ }
+
+ RenderPassKey {
+ colors,
+ resolves,
+ depth_stencil,
+ }
+ };
+
+ if sample_count & sample_count_limit == 0 {
+ return Err(RenderPassErrorInner::InvalidSampleCount(sample_count))
+ .map_pass_err(scope);
+ }
+
+ let mut render_pass_cache = device.render_passes.lock();
+ let render_pass = match render_pass_cache.entry(rp_key.clone()) {
+ Entry::Occupied(e) => e.into_mut(),
+ Entry::Vacant(entry) => {
+ let color_ids: [hal::pass::AttachmentRef; MAX_COLOR_TARGETS] = [
+ (0, hal::image::Layout::ColorAttachmentOptimal),
+ (1, hal::image::Layout::ColorAttachmentOptimal),
+ (2, hal::image::Layout::ColorAttachmentOptimal),
+ (3, hal::image::Layout::ColorAttachmentOptimal),
+ ];
+
+ let mut resolve_ids = ArrayVec::<[_; MAX_COLOR_TARGETS]>::new();
+ let mut attachment_index = color_attachments.len();
+ if color_attachments
+ .iter()
+ .any(|at| at.resolve_target.is_some())
+ {
+ for ((i, at), &(_, layout)) in color_attachments
+ .iter()
+ .enumerate()
+ .zip(entry.key().resolves.iter())
+ {
+ let real_attachment_index = match at.resolve_target {
+ Some(_) => attachment_index + i,
+ None => hal::pass::ATTACHMENT_UNUSED,
+ };
+ resolve_ids.push((real_attachment_index, layout));
+ }
+ attachment_index += color_attachments.len();
+ }
+
+ let depth_id = depth_stencil_attachment.map(|_| {
+ let usage = if is_ds_read_only {
+ TextureUse::ATTACHMENT_READ
+ } else {
+ TextureUse::ATTACHMENT_WRITE
+ };
+ (
+ attachment_index,
+ conv::map_texture_state(usage, depth_stencil_aspects).1,
+ )
+ });
+
+ let subpass = hal::pass::SubpassDesc {
+ colors: &color_ids[..color_attachments.len()],
+ resolves: &resolve_ids,
+ depth_stencil: depth_id.as_ref(),
+ inputs: &[],
+ preserves: &[],
+ };
+ let all = entry
+ .key()
+ .all()
+ .map(|(at, _)| at)
+ .collect::<AttachmentDataVec<_>>();
+
+ let pass =
+ unsafe { device.raw.create_render_pass(all, iter::once(subpass), &[]) }
+ .unwrap();
+ entry.insert(pass)
+ }
+ };
+
+ let mut framebuffer_cache;
+ let fb_key = FramebufferKey {
+ colors: color_attachments
+ .iter()
+ .map(|at| id::Valid(at.attachment))
+ .collect(),
+ resolves: color_attachments
+ .iter()
+ .filter_map(|at| at.resolve_target)
+ .map(id::Valid)
+ .collect(),
+ depth_stencil: depth_stencil_attachment.map(|at| id::Valid(at.attachment)),
+ };
+ let context = RenderPassContext {
+ attachments: AttachmentData {
+ colors: fb_key
+ .colors
+ .iter()
+ .map(|&at| view_guard[at].format)
+ .collect(),
+ resolves: fb_key
+ .resolves
+ .iter()
+ .map(|&at| view_guard[at].format)
+ .collect(),
+ depth_stencil: fb_key.depth_stencil.map(|at| view_guard[at].format),
+ },
+ sample_count,
+ };
+
+ let framebuffer = match used_swap_chain.take() {
+ Some(sc_id) => {
+ assert!(cmd_buf.used_swap_chain.is_none());
+ // Always create a new framebuffer and delete it after presentation.
+ let attachments = fb_key
+ .all()
+ .map(|&id| match view_guard[id].inner {
+ TextureViewInner::Native { ref raw, .. } => raw,
+ TextureViewInner::SwapChain { ref image, .. } => Borrow::borrow(image),
+ })
+ .collect::<AttachmentDataVec<_>>();
+ let framebuffer = unsafe {
+ device
+ .raw
+ .create_framebuffer(&render_pass, attachments, extent.unwrap())
+ .or(Err(RenderPassErrorInner::OutOfMemory))
+ .map_pass_err(scope)?
+ };
+ cmd_buf.used_swap_chain = Some((sc_id, framebuffer));
+ &mut cmd_buf.used_swap_chain.as_mut().unwrap().1
+ }
+ None => {
+ // Cache framebuffers by the device.
+ framebuffer_cache = device.framebuffers.lock();
+ match framebuffer_cache.entry(fb_key) {
+ Entry::Occupied(e) => e.into_mut(),
+ Entry::Vacant(e) => {
+ let fb = {
+ let attachments = e
+ .key()
+ .all()
+ .map(|&id| match view_guard[id].inner {
+ TextureViewInner::Native { ref raw, .. } => raw,
+ TextureViewInner::SwapChain { ref image, .. } => {
+ Borrow::borrow(image)
+ }
+ })
+ .collect::<AttachmentDataVec<_>>();
+ unsafe {
+ device
+ .raw
+ .create_framebuffer(
+ &render_pass,
+ attachments,
+ extent.unwrap(),
+ )
+ .or(Err(RenderPassErrorInner::OutOfMemory))
+ .map_pass_err(scope)?
+ }
+ };
+ e.insert(fb)
+ }
+ }
+ }
+ };
+
+ let rect = {
+ let ex = extent.unwrap();
+ hal::pso::Rect {
+ x: 0,
+ y: 0,
+ w: ex.width as _,
+ h: ex.height as _,
+ }
+ };
+
+ let clear_values = color_attachments
+ .iter()
+ .zip(&rp_key.colors)
+ .flat_map(|(at, (rat, _layout))| {
+ match at.channel.load_op {
+ LoadOp::Load => None,
+ LoadOp::Clear => {
+ use hal::format::ChannelType;
+ //TODO: validate sign/unsign and normalized ranges of the color values
+ let value = match rat.format.unwrap().base_format().1 {
+ ChannelType::Unorm
+ | ChannelType::Snorm
+ | ChannelType::Ufloat
+ | ChannelType::Sfloat
+ | ChannelType::Uscaled
+ | ChannelType::Sscaled
+ | ChannelType::Srgb => hal::command::ClearColor {
+ float32: conv::map_color_f32(&at.channel.clear_value),
+ },
+ ChannelType::Sint => hal::command::ClearColor {
+ sint32: conv::map_color_i32(&at.channel.clear_value),
+ },
+ ChannelType::Uint => hal::command::ClearColor {
+ uint32: conv::map_color_u32(&at.channel.clear_value),
+ },
+ };
+ Some(hal::command::ClearValue { color: value })
+ }
+ }
+ })
+ .chain(depth_stencil_attachment.and_then(|at| {
+ match (at.depth.load_op, at.stencil.load_op) {
+ (LoadOp::Load, LoadOp::Load) => None,
+ (LoadOp::Clear, _) | (_, LoadOp::Clear) => {
+ let value = hal::command::ClearDepthStencil {
+ depth: at.depth.clear_value,
+ stencil: at.stencil.clear_value,
+ };
+ Some(hal::command::ClearValue {
+ depth_stencil: value,
+ })
+ }
+ }
+ }))
+ .collect::<ArrayVec<[_; MAX_COLOR_TARGETS + 1]>>();
+
+ unsafe {
+ raw.begin_render_pass(
+ render_pass,
+ framebuffer,
+ rect,
+ clear_values,
+ hal::command::SubpassContents::Inline,
+ );
+ raw.set_scissors(0, iter::once(&rect));
+ raw.set_viewports(
+ 0,
+ iter::once(hal::pso::Viewport {
+ rect,
+ depth: 0.0..1.0,
+ }),
+ );
+ }
+
+ context
+ };
+
+ let mut state = State {
+ pipeline_flags: PipelineFlags::empty(),
+ binder: Binder::new(cmd_buf.limits.max_bind_groups),
+ blend_color: OptionalState::Unused,
+ stencil_reference: 0,
+ pipeline: StateChange::new(),
+ index: IndexState::default(),
+ vertex: VertexState::default(),
+ debug_scope_depth: 0,
+ };
+ let mut temp_offsets = Vec::new();
+
+ for command in base.commands {
+ match *command {
+ RenderCommand::SetBindGroup {
+ index,
+ num_dynamic_offsets,
+ bind_group_id,
+ } => {
+ let scope = PassErrorScope::SetBindGroup(bind_group_id);
+ let max_bind_groups = device.limits.max_bind_groups;
+ if (index as u32) >= max_bind_groups {
+ return Err(RenderCommandError::BindGroupIndexOutOfRange {
+ index,
+ max: max_bind_groups,
+ })
+ .map_pass_err(scope);
+ }
+
+ temp_offsets.clear();
+ temp_offsets
+ .extend_from_slice(&base.dynamic_offsets[..num_dynamic_offsets as usize]);
+ base.dynamic_offsets = &base.dynamic_offsets[num_dynamic_offsets as usize..];
+
+ let bind_group = trackers
+ .bind_groups
+ .use_extend(&*bind_group_guard, bind_group_id, (), ())
+ .unwrap();
+ bind_group
+ .validate_dynamic_bindings(&temp_offsets)
+ .map_pass_err(scope)?;
+
+ trackers
+ .merge_extend(&bind_group.used)
+ .map_pass_err(scope)?;
+
+ if let Some((pipeline_layout_id, follow_ups)) = state.binder.provide_entry(
+ index as usize,
+ id::Valid(bind_group_id),
+ bind_group,
+ &temp_offsets,
+ ) {
+ let bind_groups = iter::once(bind_group.raw.raw())
+ .chain(
+ follow_ups
+ .clone()
+ .map(|(bg_id, _)| bind_group_guard[bg_id].raw.raw()),
+ )
+ .collect::<ArrayVec<[_; MAX_BIND_GROUPS]>>();
+ temp_offsets.extend(follow_ups.flat_map(|(_, offsets)| offsets));
+ unsafe {
+ raw.bind_graphics_descriptor_sets(
+ &pipeline_layout_guard[pipeline_layout_id].raw,
+ index as usize,
+ bind_groups,
+ &temp_offsets,
+ );
+ }
+ };
+ }
+ RenderCommand::SetPipeline(pipeline_id) => {
+ let scope = PassErrorScope::SetPipelineRender(pipeline_id);
+ if state.pipeline.set_and_check_redundant(pipeline_id) {
+ continue;
+ }
+
+ let pipeline = trackers
+ .render_pipes
+ .use_extend(&*pipeline_guard, pipeline_id, (), ())
+ .map_err(|_| RenderCommandError::InvalidPipeline(pipeline_id))
+ .map_pass_err(scope)?;
+
+ context
+ .check_compatible(&pipeline.pass_context)
+ .map_err(RenderCommandError::IncompatiblePipeline)
+ .map_pass_err(scope)?;
+
+ state.pipeline_flags = pipeline.flags;
+
+ if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH_STENCIL)
+ && is_ds_read_only
+ {
+ return Err(RenderCommandError::IncompatibleReadOnlyDepthStencil)
+ .map_pass_err(scope);
+ }
+
+ state
+ .blend_color
+ .require(pipeline.flags.contains(PipelineFlags::BLEND_COLOR));
+
+ unsafe {
+ raw.bind_graphics_pipeline(&pipeline.raw);
+ }
+
+ if pipeline.flags.contains(PipelineFlags::STENCIL_REFERENCE) {
+ unsafe {
+ raw.set_stencil_reference(
+ hal::pso::Face::all(),
+ state.stencil_reference,
+ );
+ }
+ }
+
+ // Rebind resource
+ if state.binder.pipeline_layout_id != Some(pipeline.layout_id.value) {
+ let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id.value];
+
+ state.binder.change_pipeline_layout(
+ &*pipeline_layout_guard,
+ pipeline.layout_id.value,
+ );
+
+ let mut is_compatible = true;
+
+ for (index, (entry, &bgl_id)) in state
+ .binder
+ .entries
+ .iter_mut()
+ .zip(&pipeline_layout.bind_group_layout_ids)
+ .enumerate()
+ {
+ match entry.expect_layout(bgl_id) {
+ LayoutChange::Match(bg_id, offsets) if is_compatible => {
+ let desc_set = bind_group_guard[bg_id].raw.raw();
+ unsafe {
+ raw.bind_graphics_descriptor_sets(
+ &pipeline_layout.raw,
+ index,
+ iter::once(desc_set),
+ offsets.iter().cloned(),
+ );
+ }
+ }
+ LayoutChange::Match(..) | LayoutChange::Unchanged => {}
+ LayoutChange::Mismatch => {
+ is_compatible = false;
+ }
+ }
+ }
+
+ // Clear push constant ranges
+ let non_overlapping = super::bind::compute_nonoverlapping_ranges(
+ &pipeline_layout.push_constant_ranges,
+ );
+ for range in non_overlapping {
+ let offset = range.range.start;
+ let size_bytes = range.range.end - offset;
+ super::push_constant_clear(
+ offset,
+ size_bytes,
+ |clear_offset, clear_data| unsafe {
+ raw.push_graphics_constants(
+ &pipeline_layout.raw,
+ conv::map_shader_stage_flags(range.stages),
+ clear_offset,
+ clear_data,
+ );
+ },
+ );
+ }
+ }
+
+ // Rebind index buffer if the index format has changed with the pipeline switch
+ if state.index.format != pipeline.index_format {
+ state.index.format = pipeline.index_format;
+ state.index.update_limit();
+
+ if let Some((buffer_id, ref range)) = state.index.bound_buffer_view {
+ let &(ref buffer, _) = buffer_guard[buffer_id].raw.as_ref().unwrap();
+
+ let range = hal::buffer::SubRange {
+ offset: range.start,
+ size: Some(range.end - range.start),
+ };
+ let index_type = conv::map_index_format(state.index.format);
+ unsafe {
+ raw.bind_index_buffer(buffer, range, index_type);
+ }
+ }
+ }
+ // Update vertex buffer limits
+ for (vbs, &(stride, rate)) in
+ state.vertex.inputs.iter_mut().zip(&pipeline.vertex_strides)
+ {
+ vbs.stride = stride;
+ vbs.rate = rate;
+ }
+ let vertex_strides_len = pipeline.vertex_strides.len();
+ for vbs in state.vertex.inputs.iter_mut().skip(vertex_strides_len) {
+ vbs.stride = 0;
+ vbs.rate = InputStepMode::Vertex;
+ }
+ state.vertex.update_limits();
+ }
+ RenderCommand::SetIndexBuffer {
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let scope = PassErrorScope::SetIndexBuffer(buffer_id);
+ let buffer = trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDEX)
+ .map_err(|e| RenderCommandError::Buffer(buffer_id, e))
+ .map_pass_err(scope)?;
+ check_buffer_usage(buffer.usage, BufferUsage::INDEX).map_pass_err(scope)?;
+ let &(ref buf_raw, _) = buffer
+ .raw
+ .as_ref()
+ .ok_or(RenderCommandError::DestroyedBuffer(buffer_id))
+ .map_pass_err(scope)?;
+
+ let end = match size {
+ Some(s) => offset + s.get(),
+ None => buffer.size,
+ };
+ state.index.bound_buffer_view = Some((id::Valid(buffer_id), offset..end));
+ state.index.update_limit();
+
+ let range = hal::buffer::SubRange {
+ offset,
+ size: Some(end - offset),
+ };
+ let index_type = conv::map_index_format(state.index.format);
+ unsafe {
+ raw.bind_index_buffer(buf_raw, range, index_type);
+ }
+ }
+ RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id,
+ offset,
+ size,
+ } => {
+ let scope = PassErrorScope::SetVertexBuffer(buffer_id);
+ let buffer = trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::VERTEX)
+ .map_err(|e| RenderCommandError::Buffer(buffer_id, e))
+ .map_pass_err(scope)?;
+ check_buffer_usage(buffer.usage, BufferUsage::VERTEX).map_pass_err(scope)?;
+ let &(ref buf_raw, _) = buffer
+ .raw
+ .as_ref()
+ .ok_or(RenderCommandError::DestroyedBuffer(buffer_id))
+ .map_pass_err(scope)?;
+
+ let empty_slots = (1 + slot as usize).saturating_sub(state.vertex.inputs.len());
+ state
+ .vertex
+ .inputs
+ .extend(iter::repeat(VertexBufferState::EMPTY).take(empty_slots));
+ state.vertex.inputs[slot as usize].total_size = match size {
+ Some(s) => s.get(),
+ None => buffer.size - offset,
+ };
+
+ let range = hal::buffer::SubRange {
+ offset,
+ size: size.map(|s| s.get()),
+ };
+ unsafe {
+ raw.bind_vertex_buffers(slot, iter::once((buf_raw, range)));
+ }
+ state.vertex.update_limits();
+ }
+ RenderCommand::SetBlendColor(ref color) => {
+ state.blend_color = OptionalState::Set;
+ unsafe {
+ raw.set_blend_constants(conv::map_color_f32(color));
+ }
+ }
+ RenderCommand::SetStencilReference(value) => {
+ state.stencil_reference = value;
+ if state
+ .pipeline_flags
+ .contains(PipelineFlags::STENCIL_REFERENCE)
+ {
+ unsafe {
+ raw.set_stencil_reference(hal::pso::Face::all(), value);
+ }
+ }
+ }
+ RenderCommand::SetViewport {
+ ref rect,
+ depth_min,
+ depth_max,
+ } => {
+ let scope = PassErrorScope::SetViewport;
+ use std::{convert::TryFrom, i16};
+ if rect.w <= 0.0
+ || rect.h <= 0.0
+ || depth_min < 0.0
+ || depth_min > 1.0
+ || depth_max < 0.0
+ || depth_max > 1.0
+ {
+ return Err(RenderCommandError::InvalidViewport).map_pass_err(scope);
+ }
+ let r = hal::pso::Rect {
+ x: i16::try_from(rect.x.round() as i64).unwrap_or(0),
+ y: i16::try_from(rect.y.round() as i64).unwrap_or(0),
+ w: i16::try_from(rect.w.round() as i64).unwrap_or(i16::MAX),
+ h: i16::try_from(rect.h.round() as i64).unwrap_or(i16::MAX),
+ };
+ unsafe {
+ raw.set_viewports(
+ 0,
+ iter::once(hal::pso::Viewport {
+ rect: r,
+ depth: depth_min..depth_max,
+ }),
+ );
+ }
+ }
+ RenderCommand::SetPushConstant {
+ stages,
+ offset,
+ size_bytes,
+ values_offset,
+ } => {
+ let scope = PassErrorScope::SetPushConstant;
+ let values_offset = values_offset
+ .ok_or(RenderPassErrorInner::InvalidValuesOffset)
+ .map_pass_err(scope)?;
+
+ let end_offset_bytes = offset + size_bytes;
+ let values_end_offset =
+ (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
+ let data_slice =
+ &base.push_constant_data[(values_offset as usize)..values_end_offset];
+
+ let pipeline_layout_id = state
+ .binder
+ .pipeline_layout_id
+ .ok_or(DrawError::MissingPipeline)
+ .map_pass_err(scope)?;
+ let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
+
+ pipeline_layout
+ .validate_push_constant_ranges(stages, offset, end_offset_bytes)
+ .map_err(RenderCommandError::from)
+ .map_pass_err(scope)?;
+
+ unsafe {
+ raw.push_graphics_constants(
+ &pipeline_layout.raw,
+ conv::map_shader_stage_flags(stages),
+ offset,
+ data_slice,
+ )
+ }
+ }
+ RenderCommand::SetScissor(ref rect) => {
+ let scope = PassErrorScope::SetScissorRect;
+ use std::{convert::TryFrom, i16};
+ if rect.w == 0
+ || rect.h == 0
+ || rect.x + rect.w > attachment_width.unwrap()
+ || rect.y + rect.h > attachment_height.unwrap()
+ {
+ return Err(RenderCommandError::InvalidScissorRect).map_pass_err(scope);
+ }
+ let r = hal::pso::Rect {
+ x: i16::try_from(rect.x).unwrap_or(0),
+ y: i16::try_from(rect.y).unwrap_or(0),
+ w: i16::try_from(rect.w).unwrap_or(i16::MAX),
+ h: i16::try_from(rect.h).unwrap_or(i16::MAX),
+ };
+ unsafe {
+ raw.set_scissors(0, iter::once(r));
+ }
+ }
+ RenderCommand::Draw {
+ vertex_count,
+ instance_count,
+ first_vertex,
+ first_instance,
+ } => {
+ let scope = PassErrorScope::Draw;
+ state.is_ready().map_pass_err(scope)?;
+ let last_vertex = first_vertex + vertex_count;
+ let vertex_limit = state.vertex.vertex_limit;
+ if last_vertex > vertex_limit {
+ return Err(DrawError::VertexBeyondLimit {
+ last_vertex,
+ vertex_limit,
+ })
+ .map_pass_err(scope);
+ }
+ let last_instance = first_instance + instance_count;
+ let instance_limit = state.vertex.instance_limit;
+ if last_instance > instance_limit {
+ return Err(DrawError::InstanceBeyondLimit {
+ last_instance,
+ instance_limit,
+ })
+ .map_pass_err(scope);
+ }
+
+ unsafe {
+ raw.draw(
+ first_vertex..first_vertex + vertex_count,
+ first_instance..first_instance + instance_count,
+ );
+ }
+ }
+ RenderCommand::DrawIndexed {
+ index_count,
+ instance_count,
+ first_index,
+ base_vertex,
+ first_instance,
+ } => {
+ let scope = PassErrorScope::DrawIndexed;
+ state.is_ready().map_pass_err(scope)?;
+
+ //TODO: validate that base_vertex + max_index() is within the provided range
+ let last_index = first_index + index_count;
+ let index_limit = state.index.limit;
+ if last_index > index_limit {
+ return Err(DrawError::IndexBeyondLimit {
+ last_index,
+ index_limit,
+ })
+ .map_pass_err(scope);
+ }
+ let last_instance = first_instance + instance_count;
+ let instance_limit = state.vertex.instance_limit;
+ if last_instance > instance_limit {
+ return Err(DrawError::InstanceBeyondLimit {
+ last_instance,
+ instance_limit,
+ })
+ .map_pass_err(scope);
+ }
+
+ unsafe {
+ raw.draw_indexed(
+ first_index..first_index + index_count,
+ base_vertex,
+ first_instance..first_instance + instance_count,
+ );
+ }
+ }
+ RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count,
+ indexed,
+ } => {
+ let scope = if indexed {
+ PassErrorScope::DrawIndexedIndirect
+ } else {
+ PassErrorScope::DrawIndirect
+ };
+ state.is_ready().map_pass_err(scope)?;
+
+ let stride = match indexed {
+ false => 16,
+ true => 20,
+ };
+
+ if count.is_some() {
+ check_device_features(device.features, wgt::Features::MULTI_DRAW_INDIRECT)
+ .map_pass_err(scope)?;
+ }
+
+ let indirect_buffer = trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
+ .map_err(|e| RenderCommandError::Buffer(buffer_id, e))
+ .map_pass_err(scope)?;
+ check_buffer_usage(indirect_buffer.usage, BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+ let &(ref indirect_raw, _) = indirect_buffer
+ .raw
+ .as_ref()
+ .ok_or(RenderCommandError::DestroyedBuffer(buffer_id))
+ .map_pass_err(scope)?;
+
+ let actual_count = count.map_or(1, |c| c.get());
+
+ let begin_offset = offset;
+ let end_offset = offset + stride * actual_count as u64;
+ if end_offset > indirect_buffer.size {
+ return Err(RenderPassErrorInner::IndirectBufferOverrun {
+ offset,
+ count,
+ begin_offset,
+ end_offset,
+ buffer_size: indirect_buffer.size,
+ })
+ .map_pass_err(scope);
+ }
+
+ match indexed {
+ false => unsafe {
+ raw.draw_indirect(indirect_raw, offset, actual_count, stride as u32);
+ },
+ true => unsafe {
+ raw.draw_indexed_indirect(
+ indirect_raw,
+ offset,
+ actual_count,
+ stride as u32,
+ );
+ },
+ }
+ }
+ RenderCommand::MultiDrawIndirectCount {
+ buffer_id,
+ offset,
+ count_buffer_id,
+ count_buffer_offset,
+ max_count,
+ indexed,
+ } => {
+ let scope = if indexed {
+ PassErrorScope::DrawIndexedIndirect
+ } else {
+ PassErrorScope::DrawIndirect
+ };
+ state.is_ready().map_pass_err(scope)?;
+
+ let stride = match indexed {
+ false => 16,
+ true => 20,
+ };
+
+ check_device_features(
+ device.features,
+ wgt::Features::MULTI_DRAW_INDIRECT_COUNT,
+ )
+ .map_pass_err(scope)?;
+
+ let indirect_buffer = trackers
+ .buffers
+ .use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
+ .map_err(|e| RenderCommandError::Buffer(buffer_id, e))
+ .map_pass_err(scope)?;
+ check_buffer_usage(indirect_buffer.usage, BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+ let &(ref indirect_raw, _) = indirect_buffer
+ .raw
+ .as_ref()
+ .ok_or(RenderCommandError::DestroyedBuffer(buffer_id))
+ .map_pass_err(scope)?;
+
+ let count_buffer = trackers
+ .buffers
+ .use_extend(&*buffer_guard, count_buffer_id, (), BufferUse::INDIRECT)
+ .map_err(|e| RenderCommandError::Buffer(count_buffer_id, e))
+ .map_pass_err(scope)?;
+ check_buffer_usage(count_buffer.usage, BufferUsage::INDIRECT)
+ .map_pass_err(scope)?;
+ let &(ref count_raw, _) = count_buffer
+ .raw
+ .as_ref()
+ .ok_or(RenderCommandError::DestroyedBuffer(count_buffer_id))
+ .map_pass_err(scope)?;
+
+ let begin_offset = offset;
+ let end_offset = offset + stride * max_count as u64;
+ if end_offset > indirect_buffer.size {
+ return Err(RenderPassErrorInner::IndirectBufferOverrun {
+ offset,
+ count: None,
+ begin_offset,
+ end_offset,
+ buffer_size: indirect_buffer.size,
+ })
+ .map_pass_err(scope);
+ }
+
+ let begin_count_offset = count_buffer_offset;
+ let end_count_offset = count_buffer_offset + 4;
+ if end_count_offset > count_buffer.size {
+ return Err(RenderPassErrorInner::IndirectCountBufferOverrun {
+ begin_count_offset,
+ end_count_offset,
+ count_buffer_size: count_buffer.size,
+ })
+ .map_pass_err(scope);
+ }
+
+ match indexed {
+ false => unsafe {
+ raw.draw_indirect_count(
+ indirect_raw,
+ offset,
+ count_raw,
+ count_buffer_offset,
+ max_count,
+ stride as u32,
+ );
+ },
+ true => unsafe {
+ raw.draw_indexed_indirect_count(
+ indirect_raw,
+ offset,
+ count_raw,
+ count_buffer_offset,
+ max_count,
+ stride as u32,
+ );
+ },
+ }
+ }
+ RenderCommand::PushDebugGroup { color, len } => {
+ state.debug_scope_depth += 1;
+ let label = str::from_utf8(&base.string_data[..len]).unwrap();
+ unsafe {
+ raw.begin_debug_marker(label, color);
+ }
+ base.string_data = &base.string_data[len..];
+ }
+ RenderCommand::PopDebugGroup => {
+ let scope = PassErrorScope::PopDebugGroup;
+ if state.debug_scope_depth == 0 {
+ return Err(RenderPassErrorInner::InvalidPopDebugGroup).map_pass_err(scope);
+ }
+ state.debug_scope_depth -= 1;
+ unsafe {
+ raw.end_debug_marker();
+ }
+ }
+ RenderCommand::InsertDebugMarker { color, len } => {
+ let label = str::from_utf8(&base.string_data[..len]).unwrap();
+ unsafe {
+ raw.insert_debug_marker(label, color);
+ }
+ base.string_data = &base.string_data[len..];
+ }
+ RenderCommand::ExecuteBundle(bundle_id) => {
+ let scope = PassErrorScope::ExecuteBundle;
+ let bundle = trackers
+ .bundles
+ .use_extend(&*bundle_guard, bundle_id, (), ())
+ .unwrap();
+
+ context
+ .check_compatible(&bundle.context)
+ .map_err(RenderPassErrorInner::IncompatibleRenderBundle)
+ .map_pass_err(scope)?;
+
+ unsafe {
+ bundle.execute(
+ &mut raw,
+ &*pipeline_layout_guard,
+ &*bind_group_guard,
+ &*pipeline_guard,
+ &*buffer_guard,
+ )
+ }
+ .map_err(|e| match e {
+ ExecutionError::DestroyedBuffer(id) => {
+ RenderCommandError::DestroyedBuffer(id)
+ }
+ })
+ .map_pass_err(scope)?;
+
+ trackers.merge_extend(&bundle.used).map_pass_err(scope)?;
+ state.reset_bundle();
+ }
+ }
+ }
+
+ tracing::trace!("Merging {:?} with the render pass", encoder_id);
+ unsafe {
+ raw.end_render_pass();
+ }
+
+ for ra in render_attachments {
+ let texture = &texture_guard[ra.texture_id.value];
+ check_texture_usage(texture.usage, TextureUsage::RENDER_ATTACHMENT)
+ .map_pass_err(scope)?;
+
+ // the tracker set of the pass is always in "extend" mode
+ trackers
+ .textures
+ .change_extend(
+ ra.texture_id.value,
+ &ra.texture_id.ref_count,
+ ra.selector.clone(),
+ ra.new_use,
+ )
+ .unwrap();
+
+ if let Some(usage) = ra.previous_use {
+ // Make the attachment tracks to be aware of the internal
+ // transition done by the render pass, by registering the
+ // previous usage as the initial state.
+ trackers
+ .textures
+ .prepend(
+ ra.texture_id.value,
+ &ra.texture_id.ref_count,
+ ra.selector.clone(),
+ usage,
+ )
+ .unwrap();
+ }
+ }
+
+ super::CommandBuffer::insert_barriers(
+ cmd_buf.raw.last_mut().unwrap(),
+ &mut cmd_buf.trackers,
+ &trackers,
+ &*buffer_guard,
+ &*texture_guard,
+ );
+ unsafe {
+ cmd_buf.raw.last_mut().unwrap().finish();
+ }
+ cmd_buf.raw.push(raw);
+
+ Ok(())
+ }
+}
+
+pub mod render_ffi {
+ use super::{
+ super::{Rect, RenderCommand},
+ RenderPass,
+ };
+ use crate::{id, span, RawString};
+ use std::{convert::TryInto, ffi, num::NonZeroU32, slice};
+ use wgt::{BufferAddress, BufferSize, Color, DynamicOffset};
+
+ /// # Safety
+ ///
+ /// This function is unsafe as there is no guarantee that the given pointer is
+ /// valid for `offset_length` elements.
+ // TODO: There might be other safety issues, such as using the unsafe
+ // `RawPass::encode` and `RawPass::encode_slice`.
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_pass_set_bind_group(
+ pass: &mut RenderPass,
+ index: u32,
+ bind_group_id: id::BindGroupId,
+ offsets: *const DynamicOffset,
+ offset_length: usize,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_bind_group");
+ pass.base.commands.push(RenderCommand::SetBindGroup {
+ index: index.try_into().unwrap(),
+ num_dynamic_offsets: offset_length.try_into().unwrap(),
+ bind_group_id,
+ });
+ pass.base
+ .dynamic_offsets
+ .extend_from_slice(slice::from_raw_parts(offsets, offset_length));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_pipeline(
+ pass: &mut RenderPass,
+ pipeline_id: id::RenderPipelineId,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_pipeline");
+ pass.base
+ .commands
+ .push(RenderCommand::SetPipeline(pipeline_id));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_index_buffer(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_index_buffer");
+ pass.base.commands.push(RenderCommand::SetIndexBuffer {
+ buffer_id,
+ offset,
+ size,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_vertex_buffer(
+ pass: &mut RenderPass,
+ slot: u32,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ size: Option<BufferSize>,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_vertex_buffer");
+ pass.base.commands.push(RenderCommand::SetVertexBuffer {
+ slot,
+ buffer_id,
+ offset,
+ size,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_blend_color(pass: &mut RenderPass, color: &Color) {
+ span!(_guard, DEBUG, "RenderPass::set_blend_color");
+ pass.base
+ .commands
+ .push(RenderCommand::SetBlendColor(*color));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_stencil_reference(pass: &mut RenderPass, value: u32) {
+ span!(_guard, DEBUG, "RenderPass::set_stencil_buffer");
+ pass.base
+ .commands
+ .push(RenderCommand::SetStencilReference(value));
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_viewport(
+ pass: &mut RenderPass,
+ x: f32,
+ y: f32,
+ w: f32,
+ h: f32,
+ depth_min: f32,
+ depth_max: f32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_viewport");
+ pass.base.commands.push(RenderCommand::SetViewport {
+ rect: Rect { x, y, w, h },
+ depth_min,
+ depth_max,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_set_scissor_rect(
+ pass: &mut RenderPass,
+ x: u32,
+ y: u32,
+ w: u32,
+ h: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_scissor_rect");
+ pass.base
+ .commands
+ .push(RenderCommand::SetScissor(Rect { x, y, w, h }));
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_pass_set_push_constants(
+ pass: &mut RenderPass,
+ stages: wgt::ShaderStage,
+ offset: u32,
+ size_bytes: u32,
+ data: *const u8,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::set_push_constants");
+ assert_eq!(
+ offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant offset must be aligned to 4 bytes."
+ );
+ assert_eq!(
+ size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
+ 0,
+ "Push constant size must be aligned to 4 bytes."
+ );
+ let data_slice = slice::from_raw_parts(data, size_bytes as usize);
+ let value_offset = pass.base.push_constant_data.len().try_into().expect(
+ "Ran out of push constant space. Don't set 4gb of push constants per RenderPass.",
+ );
+
+ pass.base.push_constant_data.extend(
+ data_slice
+ .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
+ .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
+ );
+
+ pass.base.commands.push(RenderCommand::SetPushConstant {
+ stages,
+ offset,
+ size_bytes,
+ values_offset: Some(value_offset),
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_draw(
+ pass: &mut RenderPass,
+ vertex_count: u32,
+ instance_count: u32,
+ first_vertex: u32,
+ first_instance: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::draw");
+ pass.base.commands.push(RenderCommand::Draw {
+ vertex_count,
+ instance_count,
+ first_vertex,
+ first_instance,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_draw_indexed(
+ pass: &mut RenderPass,
+ index_count: u32,
+ instance_count: u32,
+ first_index: u32,
+ base_vertex: i32,
+ first_instance: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::draw_indexed");
+ pass.base.commands.push(RenderCommand::DrawIndexed {
+ index_count,
+ instance_count,
+ first_index,
+ base_vertex,
+ first_instance,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_draw_indirect(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::draw_indirect");
+ pass.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: false,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_draw_indexed_indirect(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::draw_indexed_indirect");
+ pass.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: None,
+ indexed: true,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_multi_draw_indirect(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ count: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::multi_draw_indirect");
+ pass.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: NonZeroU32::new(count),
+ indexed: false,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_multi_draw_indexed_indirect(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ count: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::multi_draw_indexed_indirect");
+ pass.base.commands.push(RenderCommand::MultiDrawIndirect {
+ buffer_id,
+ offset,
+ count: NonZeroU32::new(count),
+ indexed: true,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_multi_draw_indirect_count(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ count_buffer_id: id::BufferId,
+ count_buffer_offset: BufferAddress,
+ max_count: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::multi_draw_indirect_count");
+ pass.base
+ .commands
+ .push(RenderCommand::MultiDrawIndirectCount {
+ buffer_id,
+ offset,
+ count_buffer_id,
+ count_buffer_offset,
+ max_count,
+ indexed: false,
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_multi_draw_indexed_indirect_count(
+ pass: &mut RenderPass,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ count_buffer_id: id::BufferId,
+ count_buffer_offset: BufferAddress,
+ max_count: u32,
+ ) {
+ span!(
+ _guard,
+ DEBUG,
+ "RenderPass::multi_draw_indexed_indirect_count"
+ );
+ pass.base
+ .commands
+ .push(RenderCommand::MultiDrawIndirectCount {
+ buffer_id,
+ offset,
+ count_buffer_id,
+ count_buffer_offset,
+ max_count,
+ indexed: true,
+ });
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_pass_push_debug_group(
+ pass: &mut RenderPass,
+ label: RawString,
+ color: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::push_debug_group");
+ let bytes = ffi::CStr::from_ptr(label).to_bytes();
+ pass.base.string_data.extend_from_slice(bytes);
+
+ pass.base.commands.push(RenderCommand::PushDebugGroup {
+ color,
+ len: bytes.len(),
+ });
+ }
+
+ #[no_mangle]
+ pub extern "C" fn wgpu_render_pass_pop_debug_group(pass: &mut RenderPass) {
+ span!(_guard, DEBUG, "RenderPass::pop_debug_group");
+ pass.base.commands.push(RenderCommand::PopDebugGroup);
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn wgpu_render_pass_insert_debug_marker(
+ pass: &mut RenderPass,
+ label: RawString,
+ color: u32,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::insert_debug_marker");
+ let bytes = ffi::CStr::from_ptr(label).to_bytes();
+ pass.base.string_data.extend_from_slice(bytes);
+
+ pass.base.commands.push(RenderCommand::InsertDebugMarker {
+ color,
+ len: bytes.len(),
+ });
+ }
+
+ #[no_mangle]
+ pub unsafe fn wgpu_render_pass_execute_bundles(
+ pass: &mut RenderPass,
+ render_bundle_ids: *const id::RenderBundleId,
+ render_bundle_ids_length: usize,
+ ) {
+ span!(_guard, DEBUG, "RenderPass::execute_bundles");
+ for &bundle_id in slice::from_raw_parts(render_bundle_ids, render_bundle_ids_length) {
+ pass.base
+ .commands
+ .push(RenderCommand::ExecuteBundle(bundle_id));
+ }
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/command/transfer.rs b/gfx/wgpu/wgpu-core/src/command/transfer.rs
new file mode 100644
index 0000000000..98ab294cfc
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/command/transfer.rs
@@ -0,0 +1,789 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[cfg(feature = "trace")]
+use crate::device::trace::Command as TraceCommand;
+use crate::{
+ command::{CommandBuffer, CommandEncoderError},
+ conv,
+ device::{all_buffer_stages, all_image_stages},
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Storage, Token},
+ id::{BufferId, CommandEncoderId, TextureId},
+ resource::{BufferUse, Texture, TextureErrorDimension, TextureUse},
+ span,
+ track::TextureSelector,
+};
+
+use hal::command::CommandBuffer as _;
+use thiserror::Error;
+use wgt::{BufferAddress, BufferUsage, Extent3d, TextureUsage};
+
+use std::iter;
+
+pub(crate) const BITS_PER_BYTE: u32 = 8;
+
+pub type BufferCopyView = wgt::BufferCopyView<BufferId>;
+pub type TextureCopyView = wgt::TextureCopyView<TextureId>;
+
+#[derive(Clone, Debug)]
+pub enum CopySide {
+ Source,
+ Destination,
+}
+
+/// Error encountered while attempting a data transfer.
+#[derive(Clone, Debug, Error)]
+pub enum TransferError {
+ #[error("buffer {0:?} is invalid or destroyed")]
+ InvalidBuffer(BufferId),
+ #[error("texture {0:?} is invalid or destroyed")]
+ InvalidTexture(TextureId),
+ #[error("Source and destination cannot be the same buffer")]
+ SameSourceDestinationBuffer,
+ #[error("source buffer/texture is missing the `COPY_SRC` usage flag")]
+ MissingCopySrcUsageFlag,
+ #[error("destination buffer/texture is missing the `COPY_DST` usage flag")]
+ MissingCopyDstUsageFlag(Option<BufferId>, Option<TextureId>),
+ #[error("copy of {start_offset}..{end_offset} would end up overruning the bounds of the {side:?} buffer of size {buffer_size}")]
+ BufferOverrun {
+ start_offset: BufferAddress,
+ end_offset: BufferAddress,
+ buffer_size: BufferAddress,
+ side: CopySide,
+ },
+ #[error("copy of {dimension:?} {start_offset}..{end_offset} would end up overruning the bounds of the {side:?} texture of {dimension:?} size {texture_size}")]
+ TextureOverrun {
+ start_offset: u32,
+ end_offset: u32,
+ texture_size: u32,
+ dimension: TextureErrorDimension,
+ side: CopySide,
+ },
+ #[error("buffer offset {0} is not aligned to block size or `COPY_BUFFER_ALIGNMENT`")]
+ UnalignedBufferOffset(BufferAddress),
+ #[error("copy size {0} does not respect `COPY_BUFFER_ALIGNMENT`")]
+ UnalignedCopySize(BufferAddress),
+ #[error("copy width is not a multiple of block width")]
+ UnalignedCopyWidth,
+ #[error("copy height is not a multiple of block height")]
+ UnalignedCopyHeight,
+ #[error("copy origin's x component is not a multiple of block width")]
+ UnalignedCopyOriginX,
+ #[error("copy origin's y component is not a multiple of block height")]
+ UnalignedCopyOriginY,
+ #[error("bytes per row does not respect `COPY_BYTES_PER_ROW_ALIGNMENT`")]
+ UnalignedBytesPerRow,
+ #[error("number of rows per image is not a multiple of block height")]
+ UnalignedRowsPerImage,
+ #[error("number of bytes per row is less than the number of bytes in a complete row")]
+ InvalidBytesPerRow,
+ #[error("image is 1D and the copy height and depth are not both set to 1")]
+ InvalidCopySize,
+ #[error("number of rows per image is invalid")]
+ InvalidRowsPerImage,
+ #[error("source and destination layers have different aspects")]
+ MismatchedAspects,
+ #[error("copying from textures with format {0:?} is forbidden")]
+ CopyFromForbiddenTextureFormat(wgt::TextureFormat),
+ #[error("copying to textures with format {0:?} is forbidden")]
+ CopyToForbiddenTextureFormat(wgt::TextureFormat),
+}
+
+/// Error encountered while attempting to do a copy on a command encoder.
+#[derive(Clone, Debug, Error)]
+pub enum CopyError {
+ #[error(transparent)]
+ Encoder(#[from] CommandEncoderError),
+ #[error("Copy error")]
+ Transfer(#[from] TransferError),
+}
+
+//TODO: we currently access each texture twice for a transfer,
+// once only to get the aspect flags, which is unfortunate.
+pub(crate) fn texture_copy_view_to_hal<B: hal::Backend>(
+ view: &TextureCopyView,
+ size: &Extent3d,
+ texture_guard: &Storage<Texture<B>, TextureId>,
+) -> Result<
+ (
+ hal::image::SubresourceLayers,
+ TextureSelector,
+ hal::image::Offset,
+ ),
+ TransferError,
+> {
+ let texture = texture_guard
+ .get(view.texture)
+ .map_err(|_| TransferError::InvalidTexture(view.texture))?;
+
+ let level = view.mip_level as hal::image::Level;
+ let (layer, layer_count, z) = match texture.dimension {
+ wgt::TextureDimension::D1 | wgt::TextureDimension::D2 => (
+ view.origin.z as hal::image::Layer,
+ size.depth as hal::image::Layer,
+ 0,
+ ),
+ wgt::TextureDimension::D3 => (0, 1, view.origin.z as i32),
+ };
+
+ // TODO: Can't satisfy clippy here unless we modify
+ // `TextureSelector` to use `std::ops::RangeBounds`.
+ #[allow(clippy::range_plus_one)]
+ Ok((
+ hal::image::SubresourceLayers {
+ aspects: texture.aspects,
+ level,
+ layers: layer..layer + layer_count,
+ },
+ TextureSelector {
+ levels: level..level + 1,
+ layers: layer..layer + layer_count,
+ },
+ hal::image::Offset {
+ x: view.origin.x as i32,
+ y: view.origin.y as i32,
+ z,
+ },
+ ))
+}
+
+/// Function copied with minor modifications from webgpu standard https://gpuweb.github.io/gpuweb/#valid-texture-copy-range
+pub(crate) fn validate_linear_texture_data(
+ layout: &wgt::TextureDataLayout,
+ format: wgt::TextureFormat,
+ buffer_size: BufferAddress,
+ buffer_side: CopySide,
+ bytes_per_block: BufferAddress,
+ copy_size: &Extent3d,
+) -> Result<(), TransferError> {
+ // Convert all inputs to BufferAddress (u64) to prevent overflow issues
+ let copy_width = copy_size.width as BufferAddress;
+ let copy_height = copy_size.height as BufferAddress;
+ let copy_depth = copy_size.depth as BufferAddress;
+
+ let offset = layout.offset;
+ let rows_per_image = layout.rows_per_image as BufferAddress;
+ let bytes_per_row = layout.bytes_per_row as BufferAddress;
+
+ let (block_width, block_height) = conv::texture_block_size(format);
+ let block_width = block_width as BufferAddress;
+ let block_height = block_height as BufferAddress;
+ let block_size = bytes_per_block;
+
+ if copy_width % block_width != 0 {
+ return Err(TransferError::UnalignedCopyWidth);
+ }
+ if copy_height % block_height != 0 {
+ return Err(TransferError::UnalignedCopyHeight);
+ }
+ if rows_per_image % block_height != 0 {
+ return Err(TransferError::UnalignedRowsPerImage);
+ }
+
+ let bytes_in_a_complete_row = block_size * copy_width / block_width;
+ let required_bytes_in_copy = if copy_width == 0 || copy_height == 0 || copy_depth == 0 {
+ 0
+ } else {
+ let actual_rows_per_image = if rows_per_image == 0 {
+ copy_height
+ } else {
+ rows_per_image
+ };
+ let texel_block_rows_per_image = actual_rows_per_image / block_height;
+ let bytes_per_image = bytes_per_row * texel_block_rows_per_image;
+ let bytes_in_last_slice =
+ bytes_per_row * (copy_height / block_height - 1) + bytes_in_a_complete_row;
+ bytes_per_image * (copy_depth - 1) + bytes_in_last_slice
+ };
+
+ if rows_per_image != 0 && rows_per_image < copy_height {
+ return Err(TransferError::InvalidRowsPerImage);
+ }
+ if offset + required_bytes_in_copy > buffer_size {
+ return Err(TransferError::BufferOverrun {
+ start_offset: offset,
+ end_offset: offset + required_bytes_in_copy,
+ buffer_size,
+ side: buffer_side,
+ });
+ }
+ if offset % block_size != 0 {
+ return Err(TransferError::UnalignedBufferOffset(offset));
+ }
+ if copy_height > 1 && bytes_per_row < bytes_in_a_complete_row {
+ return Err(TransferError::InvalidBytesPerRow);
+ }
+ if copy_depth > 1 && rows_per_image == 0 {
+ return Err(TransferError::InvalidRowsPerImage);
+ }
+ Ok(())
+}
+
+/// Function copied with minor modifications from webgpu standard https://gpuweb.github.io/gpuweb/#valid-texture-copy-range
+pub(crate) fn validate_texture_copy_range(
+ texture_copy_view: &TextureCopyView,
+ texture_format: wgt::TextureFormat,
+ texture_dimension: hal::image::Kind,
+ texture_side: CopySide,
+ copy_size: &Extent3d,
+) -> Result<(), TransferError> {
+ let (block_width, block_height) = conv::texture_block_size(texture_format);
+
+ let mut extent = texture_dimension.level_extent(texture_copy_view.mip_level as u8);
+ match texture_dimension {
+ hal::image::Kind::D1(..) => {
+ if (copy_size.height, copy_size.depth) != (1, 1) {
+ return Err(TransferError::InvalidCopySize);
+ }
+ }
+ hal::image::Kind::D2(_, _, array_layers, _) => {
+ extent.depth = array_layers as u32;
+ }
+ hal::image::Kind::D3(..) => {}
+ };
+
+ let x_copy_max = texture_copy_view.origin.x + copy_size.width;
+ if x_copy_max > extent.width {
+ return Err(TransferError::TextureOverrun {
+ start_offset: texture_copy_view.origin.x,
+ end_offset: x_copy_max,
+ texture_size: extent.width,
+ dimension: TextureErrorDimension::X,
+ side: texture_side,
+ });
+ }
+ let y_copy_max = texture_copy_view.origin.y + copy_size.height;
+ if y_copy_max > extent.height {
+ return Err(TransferError::TextureOverrun {
+ start_offset: texture_copy_view.origin.y,
+ end_offset: y_copy_max,
+ texture_size: extent.height,
+ dimension: TextureErrorDimension::Y,
+ side: texture_side,
+ });
+ }
+ let z_copy_max = texture_copy_view.origin.z + copy_size.depth;
+ if z_copy_max > extent.depth {
+ return Err(TransferError::TextureOverrun {
+ start_offset: texture_copy_view.origin.z,
+ end_offset: z_copy_max,
+ texture_size: extent.depth,
+ dimension: TextureErrorDimension::Z,
+ side: texture_side,
+ });
+ }
+
+ if texture_copy_view.origin.x % block_width != 0 {
+ return Err(TransferError::UnalignedCopyOriginX);
+ }
+ if texture_copy_view.origin.y % block_height != 0 {
+ return Err(TransferError::UnalignedCopyOriginY);
+ }
+ if copy_size.width % block_width != 0 {
+ return Err(TransferError::UnalignedCopyWidth);
+ }
+ if copy_size.height % block_height != 0 {
+ return Err(TransferError::UnalignedCopyHeight);
+ }
+ Ok(())
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn command_encoder_copy_buffer_to_buffer<B: GfxBackend>(
+ &self,
+ command_encoder_id: CommandEncoderId,
+ source: BufferId,
+ source_offset: BufferAddress,
+ destination: BufferId,
+ destination_offset: BufferAddress,
+ size: BufferAddress,
+ ) -> Result<(), CopyError> {
+ span!(_guard, INFO, "CommandEncoder::copy_buffer_to_buffer");
+
+ if source == destination {
+ Err(TransferError::SameSourceDestinationBuffer)?
+ }
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, command_encoder_id)?;
+ let (buffer_guard, _) = hub.buffers.read(&mut token);
+ // we can't hold both src_pending and dst_pending in scope because they
+ // borrow the buffer tracker mutably...
+ let mut barriers = Vec::new();
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(TraceCommand::CopyBufferToBuffer {
+ src: source,
+ src_offset: source_offset,
+ dst: destination,
+ dst_offset: destination_offset,
+ size,
+ });
+ }
+
+ let (src_buffer, src_pending) = cmd_buf
+ .trackers
+ .buffers
+ .use_replace(&*buffer_guard, source, (), BufferUse::COPY_SRC)
+ .map_err(TransferError::InvalidBuffer)?;
+ let &(ref src_raw, _) = src_buffer
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidBuffer(source))?;
+ if !src_buffer.usage.contains(BufferUsage::COPY_SRC) {
+ Err(TransferError::MissingCopySrcUsageFlag)?
+ }
+ barriers.extend(src_pending.map(|pending| pending.into_hal(src_buffer)));
+
+ let (dst_buffer, dst_pending) = cmd_buf
+ .trackers
+ .buffers
+ .use_replace(&*buffer_guard, destination, (), BufferUse::COPY_DST)
+ .map_err(TransferError::InvalidBuffer)?;
+ let &(ref dst_raw, _) = dst_buffer
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidBuffer(destination))?;
+ if !dst_buffer.usage.contains(BufferUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ Some(destination),
+ None,
+ ))?
+ }
+ barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_buffer)));
+
+ if size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ Err(TransferError::UnalignedCopySize(size))?
+ }
+ if source_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ Err(TransferError::UnalignedBufferOffset(source_offset))?
+ }
+ if destination_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ Err(TransferError::UnalignedBufferOffset(destination_offset))?
+ }
+
+ let source_end_offset = source_offset + size;
+ let destination_end_offset = destination_offset + size;
+ if source_end_offset > src_buffer.size {
+ Err(TransferError::BufferOverrun {
+ start_offset: source_offset,
+ end_offset: source_end_offset,
+ buffer_size: src_buffer.size,
+ side: CopySide::Source,
+ })?
+ }
+ if destination_end_offset > dst_buffer.size {
+ Err(TransferError::BufferOverrun {
+ start_offset: destination_offset,
+ end_offset: destination_end_offset,
+ buffer_size: dst_buffer.size,
+ side: CopySide::Destination,
+ })?
+ }
+
+ if size == 0 {
+ tracing::trace!("Ignoring copy_buffer_to_buffer of size 0");
+ return Ok(());
+ }
+
+ let region = hal::command::BufferCopy {
+ src: source_offset,
+ dst: destination_offset,
+ size,
+ };
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+ unsafe {
+ cmb_raw.pipeline_barrier(
+ all_buffer_stages()..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ barriers,
+ );
+ cmb_raw.copy_buffer(src_raw, dst_raw, iter::once(region));
+ }
+ Ok(())
+ }
+
+ pub fn command_encoder_copy_buffer_to_texture<B: GfxBackend>(
+ &self,
+ command_encoder_id: CommandEncoderId,
+ source: &BufferCopyView,
+ destination: &TextureCopyView,
+ copy_size: &Extent3d,
+ ) -> Result<(), CopyError> {
+ span!(_guard, INFO, "CommandEncoder::copy_buffer_to_texture");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, command_encoder_id)?;
+ let (buffer_guard, mut token) = hub.buffers.read(&mut token);
+ let (texture_guard, _) = hub.textures.read(&mut token);
+ let (dst_layers, dst_selector, dst_offset) =
+ texture_copy_view_to_hal(destination, copy_size, &*texture_guard)?;
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(TraceCommand::CopyBufferToTexture {
+ src: source.clone(),
+ dst: destination.clone(),
+ size: *copy_size,
+ });
+ }
+
+ if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
+ tracing::trace!("Ignoring copy_buffer_to_texture of size 0");
+ return Ok(());
+ }
+
+ let (src_buffer, src_pending) = cmd_buf
+ .trackers
+ .buffers
+ .use_replace(&*buffer_guard, source.buffer, (), BufferUse::COPY_SRC)
+ .map_err(TransferError::InvalidBuffer)?;
+ let &(ref src_raw, _) = src_buffer
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidBuffer(source.buffer))?;
+ if !src_buffer.usage.contains(BufferUsage::COPY_SRC) {
+ Err(TransferError::MissingCopySrcUsageFlag)?
+ }
+ let src_barriers = src_pending.map(|pending| pending.into_hal(src_buffer));
+
+ let (dst_texture, dst_pending) = cmd_buf
+ .trackers
+ .textures
+ .use_replace(
+ &*texture_guard,
+ destination.texture,
+ dst_selector,
+ TextureUse::COPY_DST,
+ )
+ .unwrap();
+ let &(ref dst_raw, _) = dst_texture
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidTexture(destination.texture))?;
+ if !dst_texture.usage.contains(TextureUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ None,
+ Some(destination.texture),
+ ))?
+ }
+ let dst_barriers = dst_pending.map(|pending| pending.into_hal(dst_texture));
+
+ let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT;
+ let bytes_per_block = conv::map_texture_format(dst_texture.format, cmd_buf.private_features)
+ .surface_desc()
+ .bits as u32
+ / BITS_PER_BYTE;
+ let src_bytes_per_row = source.layout.bytes_per_row;
+ if bytes_per_row_alignment % bytes_per_block != 0 {
+ Err(TransferError::UnalignedBytesPerRow)?
+ }
+ if src_bytes_per_row % bytes_per_row_alignment != 0 {
+ Err(TransferError::UnalignedBytesPerRow)?
+ }
+ validate_texture_copy_range(
+ destination,
+ dst_texture.format,
+ dst_texture.kind,
+ CopySide::Destination,
+ copy_size,
+ )?;
+ validate_linear_texture_data(
+ &source.layout,
+ dst_texture.format,
+ src_buffer.size,
+ CopySide::Source,
+ bytes_per_block as BufferAddress,
+ copy_size,
+ )?;
+
+ let (block_width, _) = conv::texture_block_size(dst_texture.format);
+ if !conv::is_valid_copy_dst_texture_format(dst_texture.format) {
+ Err(TransferError::CopyToForbiddenTextureFormat(
+ dst_texture.format,
+ ))?
+ }
+
+ let buffer_width = (source.layout.bytes_per_row / bytes_per_block) * block_width;
+ let region = hal::command::BufferImageCopy {
+ buffer_offset: source.layout.offset,
+ buffer_width,
+ buffer_height: source.layout.rows_per_image,
+ image_layers: dst_layers,
+ image_offset: dst_offset,
+ image_extent: conv::map_extent(copy_size, dst_texture.dimension),
+ };
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+ unsafe {
+ cmb_raw.pipeline_barrier(
+ all_buffer_stages() | all_image_stages()..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ src_barriers.chain(dst_barriers),
+ );
+ cmb_raw.copy_buffer_to_image(
+ src_raw,
+ dst_raw,
+ hal::image::Layout::TransferDstOptimal,
+ iter::once(region),
+ );
+ }
+ Ok(())
+ }
+
+ pub fn command_encoder_copy_texture_to_buffer<B: GfxBackend>(
+ &self,
+ command_encoder_id: CommandEncoderId,
+ source: &TextureCopyView,
+ destination: &BufferCopyView,
+ copy_size: &Extent3d,
+ ) -> Result<(), CopyError> {
+ span!(_guard, INFO, "CommandEncoder::copy_texture_to_buffer");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, command_encoder_id)?;
+ let (buffer_guard, mut token) = hub.buffers.read(&mut token);
+ let (texture_guard, _) = hub.textures.read(&mut token);
+ let (src_layers, src_selector, src_offset) =
+ texture_copy_view_to_hal(source, copy_size, &*texture_guard)?;
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(TraceCommand::CopyTextureToBuffer {
+ src: source.clone(),
+ dst: destination.clone(),
+ size: *copy_size,
+ });
+ }
+
+ if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
+ tracing::trace!("Ignoring copy_texture_to_buffer of size 0");
+ return Ok(());
+ }
+
+ let (src_texture, src_pending) = cmd_buf
+ .trackers
+ .textures
+ .use_replace(
+ &*texture_guard,
+ source.texture,
+ src_selector,
+ TextureUse::COPY_SRC,
+ )
+ .unwrap();
+ let &(ref src_raw, _) = src_texture
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidTexture(source.texture))?;
+ if !src_texture.usage.contains(TextureUsage::COPY_SRC) {
+ Err(TransferError::MissingCopySrcUsageFlag)?
+ }
+ let src_barriers = src_pending.map(|pending| pending.into_hal(src_texture));
+
+ let (dst_buffer, dst_barriers) = cmd_buf
+ .trackers
+ .buffers
+ .use_replace(&*buffer_guard, destination.buffer, (), BufferUse::COPY_DST)
+ .map_err(TransferError::InvalidBuffer)?;
+ let &(ref dst_raw, _) = dst_buffer
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidBuffer(destination.buffer))?;
+ if !dst_buffer.usage.contains(BufferUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ Some(destination.buffer),
+ None,
+ ))?
+ }
+ let dst_barrier = dst_barriers.map(|pending| pending.into_hal(dst_buffer));
+
+ let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT;
+ let bytes_per_block = conv::map_texture_format(src_texture.format, cmd_buf.private_features)
+ .surface_desc()
+ .bits as u32
+ / BITS_PER_BYTE;
+ let dst_bytes_per_row = destination.layout.bytes_per_row;
+ if bytes_per_row_alignment % bytes_per_block != 0 {
+ Err(TransferError::UnalignedBytesPerRow)?
+ }
+ if dst_bytes_per_row % bytes_per_row_alignment != 0 {
+ Err(TransferError::UnalignedBytesPerRow)?
+ }
+ validate_texture_copy_range(
+ source,
+ src_texture.format,
+ src_texture.kind,
+ CopySide::Source,
+ copy_size,
+ )?;
+ validate_linear_texture_data(
+ &destination.layout,
+ src_texture.format,
+ dst_buffer.size,
+ CopySide::Destination,
+ bytes_per_block as BufferAddress,
+ copy_size,
+ )?;
+
+ let (block_width, _) = conv::texture_block_size(src_texture.format);
+ if !conv::is_valid_copy_src_texture_format(src_texture.format) {
+ Err(TransferError::CopyFromForbiddenTextureFormat(
+ src_texture.format,
+ ))?
+ }
+
+ let buffer_width = (destination.layout.bytes_per_row / bytes_per_block) * block_width;
+ let region = hal::command::BufferImageCopy {
+ buffer_offset: destination.layout.offset,
+ buffer_width,
+ buffer_height: destination.layout.rows_per_image,
+ image_layers: src_layers,
+ image_offset: src_offset,
+ image_extent: conv::map_extent(copy_size, src_texture.dimension),
+ };
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+ unsafe {
+ cmb_raw.pipeline_barrier(
+ all_buffer_stages() | all_image_stages()..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ src_barriers.chain(dst_barrier),
+ );
+ cmb_raw.copy_image_to_buffer(
+ src_raw,
+ hal::image::Layout::TransferSrcOptimal,
+ dst_raw,
+ iter::once(region),
+ );
+ }
+ Ok(())
+ }
+
+ pub fn command_encoder_copy_texture_to_texture<B: GfxBackend>(
+ &self,
+ command_encoder_id: CommandEncoderId,
+ source: &TextureCopyView,
+ destination: &TextureCopyView,
+ copy_size: &Extent3d,
+ ) -> Result<(), CopyError> {
+ span!(_guard, INFO, "CommandEncoder::copy_texture_to_texture");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token);
+ let cmd_buf = CommandBuffer::get_encoder(&mut *cmd_buf_guard, command_encoder_id)?;
+ let (_, mut token) = hub.buffers.read(&mut token); // skip token
+ let (texture_guard, _) = hub.textures.read(&mut token);
+ // we can't hold both src_pending and dst_pending in scope because they
+ // borrow the buffer tracker mutably...
+ let mut barriers = Vec::new();
+ let (src_layers, src_selector, src_offset) =
+ texture_copy_view_to_hal(source, copy_size, &*texture_guard)?;
+ let (dst_layers, dst_selector, dst_offset) =
+ texture_copy_view_to_hal(destination, copy_size, &*texture_guard)?;
+ if src_layers.aspects != dst_layers.aspects {
+ Err(TransferError::MismatchedAspects)?
+ }
+
+ #[cfg(feature = "trace")]
+ if let Some(ref mut list) = cmd_buf.commands {
+ list.push(TraceCommand::CopyTextureToTexture {
+ src: source.clone(),
+ dst: destination.clone(),
+ size: *copy_size,
+ });
+ }
+
+ if copy_size.width == 0 || copy_size.height == 0 || copy_size.width == 0 {
+ tracing::trace!("Ignoring copy_texture_to_texture of size 0");
+ return Ok(());
+ }
+
+ let (src_texture, src_pending) = cmd_buf
+ .trackers
+ .textures
+ .use_replace(
+ &*texture_guard,
+ source.texture,
+ src_selector,
+ TextureUse::COPY_SRC,
+ )
+ .unwrap();
+ let &(ref src_raw, _) = src_texture
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidTexture(source.texture))?;
+ if !src_texture.usage.contains(TextureUsage::COPY_SRC) {
+ Err(TransferError::MissingCopySrcUsageFlag)?
+ }
+ barriers.extend(src_pending.map(|pending| pending.into_hal(src_texture)));
+
+ let (dst_texture, dst_pending) = cmd_buf
+ .trackers
+ .textures
+ .use_replace(
+ &*texture_guard,
+ destination.texture,
+ dst_selector,
+ TextureUse::COPY_DST,
+ )
+ .unwrap();
+ let &(ref dst_raw, _) = dst_texture
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidTexture(destination.texture))?;
+ if !dst_texture.usage.contains(TextureUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ None,
+ Some(destination.texture),
+ ))?
+ }
+ barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_texture)));
+
+ validate_texture_copy_range(
+ source,
+ src_texture.format,
+ src_texture.kind,
+ CopySide::Source,
+ copy_size,
+ )?;
+ validate_texture_copy_range(
+ destination,
+ dst_texture.format,
+ dst_texture.kind,
+ CopySide::Destination,
+ copy_size,
+ )?;
+
+ let region = hal::command::ImageCopy {
+ src_subresource: src_layers,
+ src_offset,
+ dst_subresource: dst_layers,
+ dst_offset,
+ extent: conv::map_extent(copy_size, src_texture.dimension),
+ };
+ let cmb_raw = cmd_buf.raw.last_mut().unwrap();
+ unsafe {
+ cmb_raw.pipeline_barrier(
+ all_image_stages()..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ barriers,
+ );
+ cmb_raw.copy_image(
+ src_raw,
+ hal::image::Layout::TransferSrcOptimal,
+ dst_raw,
+ hal::image::Layout::TransferDstOptimal,
+ iter::once(region),
+ );
+ }
+ Ok(())
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/conv.rs b/gfx/wgpu/wgpu-core/src/conv.rs
new file mode 100644
index 0000000000..58093b37e6
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/conv.rs
@@ -0,0 +1,833 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ command::{LoadOp, PassChannel, StoreOp},
+ resource, PrivateFeatures,
+};
+
+use std::convert::TryInto;
+
+pub fn map_buffer_usage(usage: wgt::BufferUsage) -> (hal::buffer::Usage, hal::memory::Properties) {
+ use hal::buffer::Usage as U;
+ use hal::memory::Properties as P;
+ use wgt::BufferUsage as W;
+
+ let mut hal_memory = P::empty();
+ if usage.contains(W::MAP_READ) {
+ hal_memory |= P::CPU_VISIBLE | P::CPU_CACHED;
+ }
+ if usage.contains(W::MAP_WRITE) {
+ hal_memory |= P::CPU_VISIBLE;
+ }
+
+ let mut hal_usage = U::empty();
+ if usage.contains(W::COPY_SRC) {
+ hal_usage |= U::TRANSFER_SRC;
+ }
+ if usage.contains(W::COPY_DST) {
+ hal_usage |= U::TRANSFER_DST;
+ }
+ if usage.contains(W::INDEX) {
+ hal_usage |= U::INDEX;
+ }
+ if usage.contains(W::VERTEX) {
+ hal_usage |= U::VERTEX;
+ }
+ if usage.contains(W::UNIFORM) {
+ hal_usage |= U::UNIFORM;
+ }
+ if usage.contains(W::STORAGE) {
+ hal_usage |= U::STORAGE;
+ }
+ if usage.contains(W::INDIRECT) {
+ hal_usage |= U::INDIRECT;
+ }
+
+ (hal_usage, hal_memory)
+}
+
+pub fn map_texture_usage(
+ usage: wgt::TextureUsage,
+ aspects: hal::format::Aspects,
+) -> hal::image::Usage {
+ use hal::image::Usage as U;
+ use wgt::TextureUsage as W;
+
+ let mut value = U::empty();
+ if usage.contains(W::COPY_SRC) {
+ value |= U::TRANSFER_SRC;
+ }
+ if usage.contains(W::COPY_DST) {
+ value |= U::TRANSFER_DST;
+ }
+ if usage.contains(W::SAMPLED) {
+ value |= U::SAMPLED;
+ }
+ if usage.contains(W::STORAGE) {
+ value |= U::STORAGE;
+ }
+ if usage.contains(W::RENDER_ATTACHMENT) {
+ if aspects.intersects(hal::format::Aspects::DEPTH | hal::format::Aspects::STENCIL) {
+ value |= U::DEPTH_STENCIL_ATTACHMENT;
+ } else {
+ value |= U::COLOR_ATTACHMENT;
+ }
+ }
+ // Note: TextureUsage::Present does not need to be handled explicitly
+ // TODO: HAL Transient Attachment, HAL Input Attachment
+ value
+}
+
+pub fn map_binding_type(binding: &wgt::BindGroupLayoutEntry) -> hal::pso::DescriptorType {
+ use hal::pso;
+ use wgt::BindingType as Bt;
+ match binding.ty {
+ Bt::Buffer {
+ ty,
+ has_dynamic_offset,
+ min_binding_size: _,
+ } => pso::DescriptorType::Buffer {
+ ty: match ty {
+ wgt::BufferBindingType::Uniform => pso::BufferDescriptorType::Uniform,
+ wgt::BufferBindingType::Storage { read_only } => {
+ pso::BufferDescriptorType::Storage { read_only }
+ }
+ },
+ format: pso::BufferDescriptorFormat::Structured {
+ dynamic_offset: has_dynamic_offset,
+ },
+ },
+ Bt::Sampler { .. } => pso::DescriptorType::Sampler,
+ Bt::Texture { .. } => pso::DescriptorType::Image {
+ ty: pso::ImageDescriptorType::Sampled {
+ with_sampler: false,
+ },
+ },
+ Bt::StorageTexture { access, .. } => pso::DescriptorType::Image {
+ ty: pso::ImageDescriptorType::Storage {
+ read_only: match access {
+ wgt::StorageTextureAccess::ReadOnly => true,
+ _ => false,
+ },
+ },
+ },
+ }
+}
+
+pub fn map_shader_stage_flags(shader_stage_flags: wgt::ShaderStage) -> hal::pso::ShaderStageFlags {
+ use hal::pso::ShaderStageFlags as H;
+ use wgt::ShaderStage as Ss;
+
+ let mut value = H::empty();
+ if shader_stage_flags.contains(Ss::VERTEX) {
+ value |= H::VERTEX;
+ }
+ if shader_stage_flags.contains(Ss::FRAGMENT) {
+ value |= H::FRAGMENT;
+ }
+ if shader_stage_flags.contains(Ss::COMPUTE) {
+ value |= H::COMPUTE;
+ }
+ value
+}
+
+pub fn map_extent(extent: &wgt::Extent3d, dim: wgt::TextureDimension) -> hal::image::Extent {
+ hal::image::Extent {
+ width: extent.width,
+ height: extent.height,
+ depth: match dim {
+ wgt::TextureDimension::D1 | wgt::TextureDimension::D2 => 1,
+ wgt::TextureDimension::D3 => extent.depth,
+ },
+ }
+}
+
+pub fn map_primitive_topology(primitive_topology: wgt::PrimitiveTopology) -> hal::pso::Primitive {
+ use hal::pso::Primitive as H;
+ use wgt::PrimitiveTopology as Pt;
+ match primitive_topology {
+ Pt::PointList => H::PointList,
+ Pt::LineList => H::LineList,
+ Pt::LineStrip => H::LineStrip,
+ Pt::TriangleList => H::TriangleList,
+ Pt::TriangleStrip => H::TriangleStrip,
+ }
+}
+
+pub fn map_color_state_descriptor(desc: &wgt::ColorStateDescriptor) -> hal::pso::ColorBlendDesc {
+ let color_mask = desc.write_mask;
+ let blend_state = if desc.color_blend != wgt::BlendDescriptor::REPLACE
+ || desc.alpha_blend != wgt::BlendDescriptor::REPLACE
+ {
+ Some(hal::pso::BlendState {
+ color: map_blend_descriptor(&desc.color_blend),
+ alpha: map_blend_descriptor(&desc.alpha_blend),
+ })
+ } else {
+ None
+ };
+ hal::pso::ColorBlendDesc {
+ mask: map_color_write_flags(color_mask),
+ blend: blend_state,
+ }
+}
+
+fn map_color_write_flags(flags: wgt::ColorWrite) -> hal::pso::ColorMask {
+ use hal::pso::ColorMask as H;
+ use wgt::ColorWrite as Cw;
+
+ let mut value = H::empty();
+ if flags.contains(Cw::RED) {
+ value |= H::RED;
+ }
+ if flags.contains(Cw::GREEN) {
+ value |= H::GREEN;
+ }
+ if flags.contains(Cw::BLUE) {
+ value |= H::BLUE;
+ }
+ if flags.contains(Cw::ALPHA) {
+ value |= H::ALPHA;
+ }
+ value
+}
+
+fn map_blend_descriptor(blend_desc: &wgt::BlendDescriptor) -> hal::pso::BlendOp {
+ use hal::pso::BlendOp as H;
+ use wgt::BlendOperation as Bo;
+ match blend_desc.operation {
+ Bo::Add => H::Add {
+ src: map_blend_factor(blend_desc.src_factor),
+ dst: map_blend_factor(blend_desc.dst_factor),
+ },
+ Bo::Subtract => H::Sub {
+ src: map_blend_factor(blend_desc.src_factor),
+ dst: map_blend_factor(blend_desc.dst_factor),
+ },
+ Bo::ReverseSubtract => H::RevSub {
+ src: map_blend_factor(blend_desc.src_factor),
+ dst: map_blend_factor(blend_desc.dst_factor),
+ },
+ Bo::Min => H::Min,
+ Bo::Max => H::Max,
+ }
+}
+
+fn map_blend_factor(blend_factor: wgt::BlendFactor) -> hal::pso::Factor {
+ use hal::pso::Factor as H;
+ use wgt::BlendFactor as Bf;
+ match blend_factor {
+ Bf::Zero => H::Zero,
+ Bf::One => H::One,
+ Bf::SrcColor => H::SrcColor,
+ Bf::OneMinusSrcColor => H::OneMinusSrcColor,
+ Bf::SrcAlpha => H::SrcAlpha,
+ Bf::OneMinusSrcAlpha => H::OneMinusSrcAlpha,
+ Bf::DstColor => H::DstColor,
+ Bf::OneMinusDstColor => H::OneMinusDstColor,
+ Bf::DstAlpha => H::DstAlpha,
+ Bf::OneMinusDstAlpha => H::OneMinusDstAlpha,
+ Bf::SrcAlphaSaturated => H::SrcAlphaSaturate,
+ Bf::BlendColor => H::ConstColor,
+ Bf::OneMinusBlendColor => H::OneMinusConstColor,
+ }
+}
+
+pub fn map_depth_stencil_state_descriptor(
+ desc: &wgt::DepthStencilStateDescriptor,
+) -> hal::pso::DepthStencilDesc {
+ hal::pso::DepthStencilDesc {
+ depth: if desc.is_depth_enabled() {
+ Some(hal::pso::DepthTest {
+ fun: map_compare_function(desc.depth_compare),
+ write: desc.depth_write_enabled,
+ })
+ } else {
+ None
+ },
+ depth_bounds: false, // TODO
+ stencil: if desc.stencil.is_enabled() {
+ let s = &desc.stencil;
+ Some(hal::pso::StencilTest {
+ faces: hal::pso::Sided {
+ front: map_stencil_face(&s.front),
+ back: map_stencil_face(&s.back),
+ },
+ read_masks: hal::pso::State::Static(hal::pso::Sided::new(s.read_mask)),
+ write_masks: hal::pso::State::Static(hal::pso::Sided::new(s.write_mask)),
+ reference_values: if s.needs_ref_value() {
+ hal::pso::State::Dynamic
+ } else {
+ hal::pso::State::Static(hal::pso::Sided::new(0))
+ },
+ })
+ } else {
+ None
+ },
+ }
+}
+
+fn map_stencil_face(
+ stencil_state_face_desc: &wgt::StencilStateFaceDescriptor,
+) -> hal::pso::StencilFace {
+ hal::pso::StencilFace {
+ fun: map_compare_function(stencil_state_face_desc.compare),
+ op_fail: map_stencil_operation(stencil_state_face_desc.fail_op),
+ op_depth_fail: map_stencil_operation(stencil_state_face_desc.depth_fail_op),
+ op_pass: map_stencil_operation(stencil_state_face_desc.pass_op),
+ }
+}
+
+pub fn map_compare_function(compare_function: wgt::CompareFunction) -> hal::pso::Comparison {
+ use hal::pso::Comparison as H;
+ use wgt::CompareFunction as Cf;
+ match compare_function {
+ Cf::Never => H::Never,
+ Cf::Less => H::Less,
+ Cf::Equal => H::Equal,
+ Cf::LessEqual => H::LessEqual,
+ Cf::Greater => H::Greater,
+ Cf::NotEqual => H::NotEqual,
+ Cf::GreaterEqual => H::GreaterEqual,
+ Cf::Always => H::Always,
+ }
+}
+
+fn map_stencil_operation(stencil_operation: wgt::StencilOperation) -> hal::pso::StencilOp {
+ use hal::pso::StencilOp as H;
+ use wgt::StencilOperation as So;
+ match stencil_operation {
+ So::Keep => H::Keep,
+ So::Zero => H::Zero,
+ So::Replace => H::Replace,
+ So::Invert => H::Invert,
+ So::IncrementClamp => H::IncrementClamp,
+ So::DecrementClamp => H::DecrementClamp,
+ So::IncrementWrap => H::IncrementWrap,
+ So::DecrementWrap => H::DecrementWrap,
+ }
+}
+
+pub(crate) fn map_texture_format(
+ texture_format: wgt::TextureFormat,
+ private_features: PrivateFeatures,
+) -> hal::format::Format {
+ use hal::format::Format as H;
+ use wgt::TextureFormat as Tf;
+ match texture_format {
+ // Normal 8 bit formats
+ Tf::R8Unorm => H::R8Unorm,
+ Tf::R8Snorm => H::R8Snorm,
+ Tf::R8Uint => H::R8Uint,
+ Tf::R8Sint => H::R8Sint,
+
+ // Normal 16 bit formats
+ Tf::R16Uint => H::R16Uint,
+ Tf::R16Sint => H::R16Sint,
+ Tf::R16Float => H::R16Sfloat,
+ Tf::Rg8Unorm => H::Rg8Unorm,
+ Tf::Rg8Snorm => H::Rg8Snorm,
+ Tf::Rg8Uint => H::Rg8Uint,
+ Tf::Rg8Sint => H::Rg8Sint,
+
+ // Normal 32 bit formats
+ Tf::R32Uint => H::R32Uint,
+ Tf::R32Sint => H::R32Sint,
+ Tf::R32Float => H::R32Sfloat,
+ Tf::Rg16Uint => H::Rg16Uint,
+ Tf::Rg16Sint => H::Rg16Sint,
+ Tf::Rg16Float => H::Rg16Sfloat,
+ Tf::Rgba8Unorm => H::Rgba8Unorm,
+ Tf::Rgba8UnormSrgb => H::Rgba8Srgb,
+ Tf::Rgba8Snorm => H::Rgba8Snorm,
+ Tf::Rgba8Uint => H::Rgba8Uint,
+ Tf::Rgba8Sint => H::Rgba8Sint,
+ Tf::Bgra8Unorm => H::Bgra8Unorm,
+ Tf::Bgra8UnormSrgb => H::Bgra8Srgb,
+
+ // Packed 32 bit formats
+ Tf::Rgb10a2Unorm => H::A2r10g10b10Unorm,
+ Tf::Rg11b10Float => H::B10g11r11Ufloat,
+
+ // Normal 64 bit formats
+ Tf::Rg32Uint => H::Rg32Uint,
+ Tf::Rg32Sint => H::Rg32Sint,
+ Tf::Rg32Float => H::Rg32Sfloat,
+ Tf::Rgba16Uint => H::Rgba16Uint,
+ Tf::Rgba16Sint => H::Rgba16Sint,
+ Tf::Rgba16Float => H::Rgba16Sfloat,
+
+ // Normal 128 bit formats
+ Tf::Rgba32Uint => H::Rgba32Uint,
+ Tf::Rgba32Sint => H::Rgba32Sint,
+ Tf::Rgba32Float => H::Rgba32Sfloat,
+
+ // Depth and stencil formats
+ Tf::Depth32Float => H::D32Sfloat,
+ Tf::Depth24Plus => {
+ if private_features.texture_d24 {
+ H::X8D24Unorm
+ } else {
+ H::D32Sfloat
+ }
+ }
+ Tf::Depth24PlusStencil8 => {
+ if private_features.texture_d24_s8 {
+ H::D24UnormS8Uint
+ } else {
+ H::D32SfloatS8Uint
+ }
+ }
+
+ // BCn compressed formats
+ Tf::Bc1RgbaUnorm => H::Bc1RgbaUnorm,
+ Tf::Bc1RgbaUnormSrgb => H::Bc1RgbaSrgb,
+ Tf::Bc2RgbaUnorm => H::Bc2Unorm,
+ Tf::Bc2RgbaUnormSrgb => H::Bc2Srgb,
+ Tf::Bc3RgbaUnorm => H::Bc3Unorm,
+ Tf::Bc3RgbaUnormSrgb => H::Bc3Srgb,
+ Tf::Bc4RUnorm => H::Bc4Unorm,
+ Tf::Bc4RSnorm => H::Bc4Snorm,
+ Tf::Bc5RgUnorm => H::Bc5Unorm,
+ Tf::Bc5RgSnorm => H::Bc5Snorm,
+ Tf::Bc6hRgbSfloat => H::Bc6hSfloat,
+ Tf::Bc6hRgbUfloat => H::Bc6hUfloat,
+ Tf::Bc7RgbaUnorm => H::Bc7Unorm,
+ Tf::Bc7RgbaUnormSrgb => H::Bc7Srgb,
+ }
+}
+
+pub fn texture_block_size(format: wgt::TextureFormat) -> (u32, u32) {
+ use wgt::TextureFormat as Tf;
+ match format {
+ Tf::R8Unorm
+ | Tf::R8Snorm
+ | Tf::R8Uint
+ | Tf::R8Sint
+ | Tf::R16Uint
+ | Tf::R16Sint
+ | Tf::R16Float
+ | Tf::Rg8Unorm
+ | Tf::Rg8Snorm
+ | Tf::Rg8Uint
+ | Tf::Rg8Sint
+ | Tf::R32Uint
+ | Tf::R32Sint
+ | Tf::R32Float
+ | Tf::Rg16Uint
+ | Tf::Rg16Sint
+ | Tf::Rg16Float
+ | Tf::Rgba8Unorm
+ | Tf::Rgba8UnormSrgb
+ | Tf::Rgba8Snorm
+ | Tf::Rgba8Uint
+ | Tf::Rgba8Sint
+ | Tf::Bgra8Unorm
+ | Tf::Bgra8UnormSrgb
+ | Tf::Rgb10a2Unorm
+ | Tf::Rg11b10Float
+ | Tf::Rg32Uint
+ | Tf::Rg32Sint
+ | Tf::Rg32Float
+ | Tf::Rgba16Uint
+ | Tf::Rgba16Sint
+ | Tf::Rgba16Float
+ | Tf::Rgba32Uint
+ | Tf::Rgba32Sint
+ | Tf::Rgba32Float
+ | Tf::Depth32Float
+ | Tf::Depth24Plus
+ | Tf::Depth24PlusStencil8 => (1, 1),
+
+ Tf::Bc1RgbaUnorm
+ | Tf::Bc1RgbaUnormSrgb
+ | Tf::Bc2RgbaUnorm
+ | Tf::Bc2RgbaUnormSrgb
+ | Tf::Bc3RgbaUnorm
+ | Tf::Bc3RgbaUnormSrgb
+ | Tf::Bc4RUnorm
+ | Tf::Bc4RSnorm
+ | Tf::Bc5RgUnorm
+ | Tf::Bc5RgSnorm
+ | Tf::Bc6hRgbUfloat
+ | Tf::Bc6hRgbSfloat
+ | Tf::Bc7RgbaUnorm
+ | Tf::Bc7RgbaUnormSrgb => (4, 4),
+ }
+}
+
+pub fn texture_features(format: wgt::TextureFormat) -> wgt::Features {
+ use wgt::TextureFormat as Tf;
+ match format {
+ Tf::R8Unorm
+ | Tf::R8Snorm
+ | Tf::R8Uint
+ | Tf::R8Sint
+ | Tf::R16Uint
+ | Tf::R16Sint
+ | Tf::R16Float
+ | Tf::Rg8Unorm
+ | Tf::Rg8Snorm
+ | Tf::Rg8Uint
+ | Tf::Rg8Sint
+ | Tf::R32Uint
+ | Tf::R32Sint
+ | Tf::R32Float
+ | Tf::Rg16Uint
+ | Tf::Rg16Sint
+ | Tf::Rg16Float
+ | Tf::Rgba8Unorm
+ | Tf::Rgba8UnormSrgb
+ | Tf::Rgba8Snorm
+ | Tf::Rgba8Uint
+ | Tf::Rgba8Sint
+ | Tf::Bgra8Unorm
+ | Tf::Bgra8UnormSrgb
+ | Tf::Rgb10a2Unorm
+ | Tf::Rg11b10Float
+ | Tf::Rg32Uint
+ | Tf::Rg32Sint
+ | Tf::Rg32Float
+ | Tf::Rgba16Uint
+ | Tf::Rgba16Sint
+ | Tf::Rgba16Float
+ | Tf::Rgba32Uint
+ | Tf::Rgba32Sint
+ | Tf::Rgba32Float
+ | Tf::Depth32Float
+ | Tf::Depth24Plus
+ | Tf::Depth24PlusStencil8 => wgt::Features::empty(),
+
+ Tf::Bc1RgbaUnorm
+ | Tf::Bc1RgbaUnormSrgb
+ | Tf::Bc2RgbaUnorm
+ | Tf::Bc2RgbaUnormSrgb
+ | Tf::Bc3RgbaUnorm
+ | Tf::Bc3RgbaUnormSrgb
+ | Tf::Bc4RUnorm
+ | Tf::Bc4RSnorm
+ | Tf::Bc5RgUnorm
+ | Tf::Bc5RgSnorm
+ | Tf::Bc6hRgbUfloat
+ | Tf::Bc6hRgbSfloat
+ | Tf::Bc7RgbaUnorm
+ | Tf::Bc7RgbaUnormSrgb => wgt::Features::TEXTURE_COMPRESSION_BC,
+ }
+}
+
+pub fn map_vertex_format(vertex_format: wgt::VertexFormat) -> hal::format::Format {
+ use hal::format::Format as H;
+ use wgt::VertexFormat as Vf;
+ match vertex_format {
+ Vf::Uchar2 => H::Rg8Uint,
+ Vf::Uchar4 => H::Rgba8Uint,
+ Vf::Char2 => H::Rg8Sint,
+ Vf::Char4 => H::Rgba8Sint,
+ Vf::Uchar2Norm => H::Rg8Unorm,
+ Vf::Uchar4Norm => H::Rgba8Unorm,
+ Vf::Char2Norm => H::Rg8Snorm,
+ Vf::Char4Norm => H::Rgba8Snorm,
+ Vf::Ushort2 => H::Rg16Uint,
+ Vf::Ushort4 => H::Rgba16Uint,
+ Vf::Short2 => H::Rg16Sint,
+ Vf::Short4 => H::Rgba16Sint,
+ Vf::Ushort2Norm => H::Rg16Unorm,
+ Vf::Ushort4Norm => H::Rgba16Unorm,
+ Vf::Short2Norm => H::Rg16Snorm,
+ Vf::Short4Norm => H::Rgba16Snorm,
+ Vf::Half2 => H::Rg16Sfloat,
+ Vf::Half4 => H::Rgba16Sfloat,
+ Vf::Float => H::R32Sfloat,
+ Vf::Float2 => H::Rg32Sfloat,
+ Vf::Float3 => H::Rgb32Sfloat,
+ Vf::Float4 => H::Rgba32Sfloat,
+ Vf::Uint => H::R32Uint,
+ Vf::Uint2 => H::Rg32Uint,
+ Vf::Uint3 => H::Rgb32Uint,
+ Vf::Uint4 => H::Rgba32Uint,
+ Vf::Int => H::R32Sint,
+ Vf::Int2 => H::Rg32Sint,
+ Vf::Int3 => H::Rgb32Sint,
+ Vf::Int4 => H::Rgba32Sint,
+ }
+}
+
+pub fn is_power_of_two(val: u32) -> bool {
+ val != 0 && (val & (val - 1)) == 0
+}
+
+pub fn is_valid_copy_src_texture_format(format: wgt::TextureFormat) -> bool {
+ use wgt::TextureFormat as Tf;
+ match format {
+ Tf::Depth24Plus | Tf::Depth24PlusStencil8 => false,
+ _ => true,
+ }
+}
+
+pub fn is_valid_copy_dst_texture_format(format: wgt::TextureFormat) -> bool {
+ use wgt::TextureFormat as Tf;
+ match format {
+ Tf::Depth32Float | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => false,
+ _ => true,
+ }
+}
+
+pub fn map_texture_dimension_size(
+ dimension: wgt::TextureDimension,
+ wgt::Extent3d {
+ width,
+ height,
+ depth,
+ }: wgt::Extent3d,
+ sample_size: u32,
+) -> Result<hal::image::Kind, resource::TextureDimensionError> {
+ use hal::image::Kind as H;
+ use resource::TextureDimensionError as Tde;
+ use wgt::TextureDimension::*;
+
+ let zero_dim = if width == 0 {
+ Some(resource::TextureErrorDimension::X)
+ } else if height == 0 {
+ Some(resource::TextureErrorDimension::Y)
+ } else if depth == 0 {
+ Some(resource::TextureErrorDimension::Z)
+ } else {
+ None
+ };
+ if let Some(dim) = zero_dim {
+ return Err(resource::TextureDimensionError::Zero(dim));
+ }
+
+ Ok(match dimension {
+ D1 => {
+ if height != 1 {
+ return Err(Tde::InvalidHeight);
+ }
+ if sample_size != 1 {
+ return Err(Tde::InvalidSampleCount(sample_size));
+ }
+ let layers = depth.try_into().unwrap_or(!0);
+ H::D1(width, layers)
+ }
+ D2 => {
+ if sample_size > 32 || !is_power_of_two(sample_size) {
+ return Err(Tde::InvalidSampleCount(sample_size));
+ }
+ let layers = depth.try_into().unwrap_or(!0);
+ H::D2(width, height, layers, sample_size as u8)
+ }
+ D3 => {
+ if sample_size != 1 {
+ return Err(Tde::InvalidSampleCount(sample_size));
+ }
+ H::D3(width, height, depth)
+ }
+ })
+}
+
+pub fn map_texture_view_dimension(dimension: wgt::TextureViewDimension) -> hal::image::ViewKind {
+ use hal::image::ViewKind as H;
+ use wgt::TextureViewDimension::*;
+ match dimension {
+ D1 => H::D1,
+ D2 => H::D2,
+ D2Array => H::D2Array,
+ Cube => H::Cube,
+ CubeArray => H::CubeArray,
+ D3 => H::D3,
+ }
+}
+
+pub(crate) fn map_buffer_state(usage: resource::BufferUse) -> hal::buffer::State {
+ use crate::resource::BufferUse as W;
+ use hal::buffer::Access as A;
+
+ let mut access = A::empty();
+ if usage.contains(W::MAP_READ) {
+ access |= A::HOST_READ;
+ }
+ if usage.contains(W::MAP_WRITE) {
+ access |= A::HOST_WRITE;
+ }
+ if usage.contains(W::COPY_SRC) {
+ access |= A::TRANSFER_READ;
+ }
+ if usage.contains(W::COPY_DST) {
+ access |= A::TRANSFER_WRITE;
+ }
+ if usage.contains(W::INDEX) {
+ access |= A::INDEX_BUFFER_READ;
+ }
+ if usage.contains(W::VERTEX) {
+ access |= A::VERTEX_BUFFER_READ;
+ }
+ if usage.contains(W::UNIFORM) {
+ access |= A::UNIFORM_READ | A::SHADER_READ;
+ }
+ if usage.contains(W::STORAGE_LOAD) {
+ access |= A::SHADER_READ;
+ }
+ if usage.contains(W::STORAGE_STORE) {
+ access |= A::SHADER_WRITE;
+ }
+ if usage.contains(W::INDIRECT) {
+ access |= A::INDIRECT_COMMAND_READ;
+ }
+
+ access
+}
+
+pub(crate) fn map_texture_state(
+ usage: resource::TextureUse,
+ aspects: hal::format::Aspects,
+) -> hal::image::State {
+ use crate::resource::TextureUse as W;
+ use hal::image::{Access as A, Layout as L};
+
+ let is_color = aspects.contains(hal::format::Aspects::COLOR);
+ let layout = match usage {
+ W::UNINITIALIZED => return (A::empty(), L::Undefined),
+ W::COPY_SRC => L::TransferSrcOptimal,
+ W::COPY_DST => L::TransferDstOptimal,
+ W::SAMPLED if is_color => L::ShaderReadOnlyOptimal,
+ W::ATTACHMENT_READ | W::ATTACHMENT_WRITE if is_color => L::ColorAttachmentOptimal,
+ _ if is_color => L::General,
+ W::ATTACHMENT_WRITE => L::DepthStencilAttachmentOptimal,
+ _ => L::DepthStencilReadOnlyOptimal,
+ };
+
+ let mut access = A::empty();
+ if usage.contains(W::COPY_SRC) {
+ access |= A::TRANSFER_READ;
+ }
+ if usage.contains(W::COPY_DST) {
+ access |= A::TRANSFER_WRITE;
+ }
+ if usage.contains(W::SAMPLED) {
+ access |= A::SHADER_READ;
+ }
+ if usage.contains(W::ATTACHMENT_READ) {
+ access |= if is_color {
+ A::COLOR_ATTACHMENT_READ
+ } else {
+ A::DEPTH_STENCIL_ATTACHMENT_READ
+ };
+ }
+ if usage.contains(W::ATTACHMENT_WRITE) {
+ access |= if is_color {
+ A::COLOR_ATTACHMENT_WRITE
+ } else {
+ A::DEPTH_STENCIL_ATTACHMENT_WRITE
+ };
+ }
+ if usage.contains(W::STORAGE_LOAD) {
+ access |= A::SHADER_READ;
+ }
+ if usage.contains(W::STORAGE_STORE) {
+ access |= A::SHADER_WRITE;
+ }
+
+ (access, layout)
+}
+
+pub fn map_load_store_ops<V>(channel: &PassChannel<V>) -> hal::pass::AttachmentOps {
+ hal::pass::AttachmentOps {
+ load: match channel.load_op {
+ LoadOp::Clear => hal::pass::AttachmentLoadOp::Clear,
+ LoadOp::Load => hal::pass::AttachmentLoadOp::Load,
+ },
+ store: match channel.store_op {
+ StoreOp::Clear => hal::pass::AttachmentStoreOp::DontCare, //TODO!
+ StoreOp::Store => hal::pass::AttachmentStoreOp::Store,
+ },
+ }
+}
+
+pub fn map_color_f32(color: &wgt::Color) -> hal::pso::ColorValue {
+ [
+ color.r as f32,
+ color.g as f32,
+ color.b as f32,
+ color.a as f32,
+ ]
+}
+pub fn map_color_i32(color: &wgt::Color) -> [i32; 4] {
+ [
+ color.r as i32,
+ color.g as i32,
+ color.b as i32,
+ color.a as i32,
+ ]
+}
+pub fn map_color_u32(color: &wgt::Color) -> [u32; 4] {
+ [
+ color.r as u32,
+ color.g as u32,
+ color.b as u32,
+ color.a as u32,
+ ]
+}
+
+pub fn map_filter(filter: wgt::FilterMode) -> hal::image::Filter {
+ match filter {
+ wgt::FilterMode::Nearest => hal::image::Filter::Nearest,
+ wgt::FilterMode::Linear => hal::image::Filter::Linear,
+ }
+}
+
+pub fn map_wrap(address: wgt::AddressMode) -> hal::image::WrapMode {
+ use hal::image::WrapMode as W;
+ use wgt::AddressMode as Am;
+ match address {
+ Am::ClampToEdge => W::Clamp,
+ Am::Repeat => W::Tile,
+ Am::MirrorRepeat => W::Mirror,
+ Am::ClampToBorder => W::Border,
+ }
+}
+
+pub fn map_rasterization_state_descriptor(
+ desc: &wgt::RasterizationStateDescriptor,
+) -> hal::pso::Rasterizer {
+ use hal::pso;
+ pso::Rasterizer {
+ depth_clamping: desc.clamp_depth,
+ polygon_mode: match desc.polygon_mode {
+ wgt::PolygonMode::Fill => pso::PolygonMode::Fill,
+ wgt::PolygonMode::Line => pso::PolygonMode::Line,
+ wgt::PolygonMode::Point => pso::PolygonMode::Point,
+ },
+ cull_face: match desc.cull_mode {
+ wgt::CullMode::None => pso::Face::empty(),
+ wgt::CullMode::Front => pso::Face::FRONT,
+ wgt::CullMode::Back => pso::Face::BACK,
+ },
+ front_face: match desc.front_face {
+ wgt::FrontFace::Ccw => pso::FrontFace::CounterClockwise,
+ wgt::FrontFace::Cw => pso::FrontFace::Clockwise,
+ },
+ depth_bias: if desc.depth_bias != 0
+ || desc.depth_bias_slope_scale != 0.0
+ || desc.depth_bias_clamp != 0.0
+ {
+ Some(pso::State::Static(pso::DepthBias {
+ const_factor: desc.depth_bias as f32,
+ slope_factor: desc.depth_bias_slope_scale,
+ clamp: desc.depth_bias_clamp,
+ }))
+ } else {
+ None
+ },
+ conservative: false,
+ line_width: pso::State::Static(1.0),
+ }
+}
+
+pub fn map_index_format(index_format: wgt::IndexFormat) -> hal::IndexType {
+ match index_format {
+ wgt::IndexFormat::Uint16 => hal::IndexType::U16,
+ wgt::IndexFormat::Uint32 => hal::IndexType::U32,
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/alloc.rs b/gfx/wgpu/wgpu-core/src/device/alloc.rs
new file mode 100644
index 0000000000..893830ace6
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/alloc.rs
@@ -0,0 +1,294 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::DeviceError;
+use hal::device::Device;
+use std::{borrow::Cow, fmt, iter, ptr::NonNull, sync::Arc};
+
+pub struct MemoryAllocator<B: hal::Backend>(gpu_alloc::GpuAllocator<Arc<B::Memory>>);
+#[derive(Debug)]
+pub struct MemoryBlock<B: hal::Backend>(gpu_alloc::MemoryBlock<Arc<B::Memory>>);
+struct MemoryDevice<'a, B: hal::Backend>(&'a B::Device);
+
+//TODO: https://github.com/zakarumych/gpu-alloc/issues/9
+impl<B: hal::Backend> fmt::Debug for MemoryAllocator<B> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "MemoryAllocator")
+ }
+}
+
+impl<B: hal::Backend> MemoryAllocator<B> {
+ pub fn new(mem_props: hal::adapter::MemoryProperties, limits: hal::Limits) -> Self {
+ let mem_config = gpu_alloc::Config {
+ dedicated_treshold: 32 << 20,
+ preferred_dedicated_treshold: 8 << 20,
+ transient_dedicated_treshold: 128 << 20,
+ linear_chunk: 128 << 20,
+ minimal_buddy_size: 1 << 10,
+ initial_buddy_dedicated_size: 8 << 20,
+ };
+ let properties = gpu_alloc::DeviceProperties {
+ memory_types: Cow::Owned(
+ mem_props
+ .memory_types
+ .iter()
+ .map(|mt| gpu_alloc::MemoryType {
+ heap: mt.heap_index as u32,
+ props: gpu_alloc::MemoryPropertyFlags::from_bits_truncate(
+ mt.properties.bits() as u8,
+ ),
+ })
+ .collect::<Vec<_>>(),
+ ),
+ memory_heaps: Cow::Owned(
+ mem_props
+ .memory_heaps
+ .iter()
+ .map(|mh| gpu_alloc::MemoryHeap { size: mh.size })
+ .collect::<Vec<_>>(),
+ ),
+ max_memory_allocation_count: if limits.max_memory_allocation_count == 0 {
+ tracing::warn!("max_memory_allocation_count is not set by gfx-rs backend");
+ !0
+ } else {
+ limits.max_memory_allocation_count.min(!0u32 as usize) as u32
+ },
+ max_memory_allocation_size: !0,
+ non_coherent_atom_size: limits.non_coherent_atom_size as u64,
+ buffer_device_address: false,
+ };
+ MemoryAllocator(gpu_alloc::GpuAllocator::new(mem_config, properties))
+ }
+
+ pub fn allocate(
+ &mut self,
+ device: &B::Device,
+ requirements: hal::memory::Requirements,
+ usage: gpu_alloc::UsageFlags,
+ ) -> Result<MemoryBlock<B>, DeviceError> {
+ assert!(requirements.alignment.is_power_of_two());
+ let request = gpu_alloc::Request {
+ size: requirements.size,
+ align_mask: requirements.alignment - 1,
+ memory_types: requirements.type_mask,
+ usage,
+ };
+
+ unsafe { self.0.alloc(&MemoryDevice::<B>(device), request) }
+ .map(MemoryBlock)
+ .map_err(|err| match err {
+ gpu_alloc::AllocationError::OutOfHostMemory
+ | gpu_alloc::AllocationError::OutOfDeviceMemory => DeviceError::OutOfMemory,
+ _ => panic!("Unable to allocate memory: {:?}", err),
+ })
+ }
+
+ pub fn free(&mut self, device: &B::Device, block: MemoryBlock<B>) {
+ unsafe { self.0.dealloc(&MemoryDevice::<B>(device), block.0) }
+ }
+
+ pub fn clear(&mut self, device: &B::Device) {
+ unsafe { self.0.cleanup(&MemoryDevice::<B>(device)) }
+ }
+}
+
+impl<B: hal::Backend> MemoryBlock<B> {
+ pub fn bind_buffer(
+ &self,
+ device: &B::Device,
+ buffer: &mut B::Buffer,
+ ) -> Result<(), DeviceError> {
+ unsafe {
+ device
+ .bind_buffer_memory(self.0.memory(), self.0.offset(), buffer)
+ .map_err(DeviceError::from_bind)
+ }
+ }
+
+ pub fn bind_image(&self, device: &B::Device, image: &mut B::Image) -> Result<(), DeviceError> {
+ unsafe {
+ device
+ .bind_image_memory(self.0.memory(), self.0.offset(), image)
+ .map_err(DeviceError::from_bind)
+ }
+ }
+
+ pub fn is_coherent(&self) -> bool {
+ self.0
+ .props()
+ .contains(gpu_alloc::MemoryPropertyFlags::HOST_COHERENT)
+ }
+
+ pub fn map(
+ &mut self,
+ device: &B::Device,
+ inner_offset: wgt::BufferAddress,
+ size: wgt::BufferAddress,
+ ) -> Result<NonNull<u8>, DeviceError> {
+ let offset = inner_offset;
+ unsafe {
+ self.0
+ .map(&MemoryDevice::<B>(device), offset, size as usize)
+ .map_err(DeviceError::from)
+ }
+ }
+
+ pub fn unmap(&mut self, device: &B::Device) {
+ unsafe { self.0.unmap(&MemoryDevice::<B>(device)) };
+ }
+
+ pub fn write_bytes(
+ &mut self,
+ device: &B::Device,
+ inner_offset: wgt::BufferAddress,
+ data: &[u8],
+ ) -> Result<(), DeviceError> {
+ let offset = inner_offset;
+ unsafe {
+ self.0
+ .write_bytes(&MemoryDevice::<B>(device), offset, data)
+ .map_err(DeviceError::from)
+ }
+ }
+
+ pub fn read_bytes(
+ &mut self,
+ device: &B::Device,
+ inner_offset: wgt::BufferAddress,
+ data: &mut [u8],
+ ) -> Result<(), DeviceError> {
+ let offset = inner_offset;
+ unsafe {
+ self.0
+ .read_bytes(&MemoryDevice::<B>(device), offset, data)
+ .map_err(DeviceError::from)
+ }
+ }
+
+ fn segment(
+ &self,
+ inner_offset: wgt::BufferAddress,
+ size: Option<wgt::BufferAddress>,
+ ) -> hal::memory::Segment {
+ hal::memory::Segment {
+ offset: self.0.offset() + inner_offset,
+ size: size.or(Some(self.0.size())),
+ }
+ }
+
+ pub fn flush_range(
+ &self,
+ device: &B::Device,
+ inner_offset: wgt::BufferAddress,
+ size: Option<wgt::BufferAddress>,
+ ) -> Result<(), DeviceError> {
+ let segment = self.segment(inner_offset, size);
+ unsafe {
+ device
+ .flush_mapped_memory_ranges(iter::once((&**self.0.memory(), segment)))
+ .or(Err(DeviceError::OutOfMemory))
+ }
+ }
+
+ pub fn invalidate_range(
+ &self,
+ device: &B::Device,
+ inner_offset: wgt::BufferAddress,
+ size: Option<wgt::BufferAddress>,
+ ) -> Result<(), DeviceError> {
+ let segment = self.segment(inner_offset, size);
+ unsafe {
+ device
+ .invalidate_mapped_memory_ranges(iter::once((&**self.0.memory(), segment)))
+ .or(Err(DeviceError::OutOfMemory))
+ }
+ }
+}
+
+impl<B: hal::Backend> gpu_alloc::MemoryDevice<Arc<B::Memory>> for MemoryDevice<'_, B> {
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn allocate_memory(
+ &self,
+ size: u64,
+ memory_type: u32,
+ flags: gpu_alloc::AllocationFlags,
+ ) -> Result<Arc<B::Memory>, gpu_alloc::OutOfMemory> {
+ assert!(flags.is_empty());
+
+ self.0
+ .allocate_memory(hal::MemoryTypeId(memory_type as _), size)
+ .map(Arc::new)
+ .map_err(|_| gpu_alloc::OutOfMemory::OutOfDeviceMemory)
+ }
+
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn deallocate_memory(&self, memory: Arc<B::Memory>) {
+ let memory = Arc::try_unwrap(memory).expect("Memory must not be used anywhere");
+ self.0.free_memory(memory);
+ }
+
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn map_memory(
+ &self,
+ memory: &Arc<B::Memory>,
+ offset: u64,
+ size: u64,
+ ) -> Result<NonNull<u8>, gpu_alloc::DeviceMapError> {
+ match self.0.map_memory(
+ memory,
+ hal::memory::Segment {
+ offset,
+ size: Some(size),
+ },
+ ) {
+ Ok(ptr) => Ok(NonNull::new(ptr).expect("Pointer to memory mapping must not be null")),
+ Err(hal::device::MapError::OutOfMemory(_)) => {
+ Err(gpu_alloc::DeviceMapError::OutOfDeviceMemory)
+ }
+ Err(hal::device::MapError::MappingFailed) => Err(gpu_alloc::DeviceMapError::MapFailed),
+ Err(other) => panic!("Unexpected map error: {:?}", other),
+ }
+ }
+
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn unmap_memory(&self, memory: &Arc<B::Memory>) {
+ self.0.unmap_memory(memory);
+ }
+
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn invalidate_memory_ranges(
+ &self,
+ ranges: &[gpu_alloc::MappedMemoryRange<'_, Arc<B::Memory>>],
+ ) -> Result<(), gpu_alloc::OutOfMemory> {
+ self.0
+ .invalidate_mapped_memory_ranges(ranges.iter().map(|range| {
+ (
+ &**range.memory,
+ hal::memory::Segment {
+ offset: range.offset,
+ size: Some(range.size),
+ },
+ )
+ }))
+ .map_err(|_| gpu_alloc::OutOfMemory::OutOfHostMemory)
+ }
+
+ #[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
+ unsafe fn flush_memory_ranges(
+ &self,
+ ranges: &[gpu_alloc::MappedMemoryRange<'_, Arc<B::Memory>>],
+ ) -> Result<(), gpu_alloc::OutOfMemory> {
+ self.0
+ .flush_mapped_memory_ranges(ranges.iter().map(|range| {
+ (
+ &**range.memory,
+ hal::memory::Segment {
+ offset: range.offset,
+ size: Some(range.size),
+ },
+ )
+ }))
+ .map_err(|_| gpu_alloc::OutOfMemory::OutOfHostMemory)
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/descriptor.rs b/gfx/wgpu/wgpu-core/src/device/descriptor.rs
new file mode 100644
index 0000000000..92b896bd31
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/descriptor.rs
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::DeviceError;
+use arrayvec::ArrayVec;
+
+pub use gpu_descriptor::DescriptorTotalCount;
+
+pub type DescriptorSet<B> = gpu_descriptor::DescriptorSet<<B as hal::Backend>::DescriptorSet>;
+
+#[derive(Debug)]
+pub struct DescriptorAllocator<B: hal::Backend>(
+ gpu_descriptor::DescriptorAllocator<B::DescriptorPool, B::DescriptorSet>,
+);
+struct DescriptorDevice<'a, B: hal::Backend>(&'a B::Device);
+
+impl<B: hal::Backend> DescriptorAllocator<B> {
+ pub fn new() -> Self {
+ DescriptorAllocator(unsafe { gpu_descriptor::DescriptorAllocator::new(0) })
+ }
+
+ pub fn allocate(
+ &mut self,
+ device: &B::Device,
+ layout: &B::DescriptorSetLayout,
+ layout_descriptor_count: &DescriptorTotalCount,
+ count: u32,
+ ) -> Result<Vec<DescriptorSet<B>>, DeviceError> {
+ self.0
+ .allocate(
+ &DescriptorDevice::<B>(device),
+ layout,
+ gpu_descriptor::DescriptorSetLayoutCreateFlags::empty(),
+ layout_descriptor_count,
+ count,
+ )
+ .map_err(|err| {
+ tracing::warn!("Descriptor set allocation failed: {}", err);
+ DeviceError::OutOfMemory
+ })
+ }
+
+ pub fn free(&mut self, device: &B::Device, sets: impl IntoIterator<Item = DescriptorSet<B>>) {
+ unsafe { self.0.free(&DescriptorDevice::<B>(device), sets) }
+ }
+
+ pub fn cleanup(&mut self, device: &B::Device) {
+ self.0.cleanup(&DescriptorDevice::<B>(device))
+ }
+}
+
+impl<B: hal::Backend>
+ gpu_descriptor::DescriptorDevice<B::DescriptorSetLayout, B::DescriptorPool, B::DescriptorSet>
+ for DescriptorDevice<'_, B>
+{
+ unsafe fn create_descriptor_pool(
+ &self,
+ descriptor_count: &DescriptorTotalCount,
+ max_sets: u32,
+ flags: gpu_descriptor::DescriptorPoolCreateFlags,
+ ) -> Result<B::DescriptorPool, gpu_descriptor::CreatePoolError> {
+ let mut ranges = ArrayVec::<[_; 7]>::new();
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Sampler,
+ count: descriptor_count.sampler as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Image {
+ ty: hal::pso::ImageDescriptorType::Sampled {
+ with_sampler: false,
+ },
+ },
+ count: descriptor_count.sampled_image as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Image {
+ ty: hal::pso::ImageDescriptorType::Storage { read_only: false },
+ },
+ count: descriptor_count.storage_image as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Buffer {
+ ty: hal::pso::BufferDescriptorType::Uniform,
+ format: hal::pso::BufferDescriptorFormat::Structured {
+ dynamic_offset: false,
+ },
+ },
+ count: descriptor_count.uniform_buffer as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Buffer {
+ ty: hal::pso::BufferDescriptorType::Storage { read_only: false },
+ format: hal::pso::BufferDescriptorFormat::Structured {
+ dynamic_offset: false,
+ },
+ },
+ count: descriptor_count.storage_buffer as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Buffer {
+ ty: hal::pso::BufferDescriptorType::Uniform,
+ format: hal::pso::BufferDescriptorFormat::Structured {
+ dynamic_offset: true,
+ },
+ },
+ count: descriptor_count.uniform_buffer_dynamic as _,
+ });
+ ranges.push(hal::pso::DescriptorRangeDesc {
+ ty: hal::pso::DescriptorType::Buffer {
+ ty: hal::pso::BufferDescriptorType::Storage { read_only: false },
+ format: hal::pso::BufferDescriptorFormat::Structured {
+ dynamic_offset: true,
+ },
+ },
+ count: descriptor_count.storage_buffer_dynamic as _,
+ });
+ ranges.retain(|rd| rd.count != 0);
+
+ match hal::device::Device::create_descriptor_pool(
+ self.0,
+ max_sets as usize,
+ ranges,
+ hal::pso::DescriptorPoolCreateFlags::from_bits_truncate(flags.bits() as u32),
+ ) {
+ Ok(pool) => Ok(pool),
+ Err(hal::device::OutOfMemory::Host) => {
+ Err(gpu_descriptor::CreatePoolError::OutOfHostMemory)
+ }
+ Err(hal::device::OutOfMemory::Device) => {
+ Err(gpu_descriptor::CreatePoolError::OutOfDeviceMemory)
+ }
+ }
+ }
+
+ unsafe fn destroy_descriptor_pool(&self, pool: B::DescriptorPool) {
+ hal::device::Device::destroy_descriptor_pool(self.0, pool);
+ }
+
+ unsafe fn alloc_descriptor_sets<'a>(
+ &self,
+ pool: &mut B::DescriptorPool,
+ layouts: impl Iterator<Item = &'a B::DescriptorSetLayout>,
+ sets: &mut impl Extend<B::DescriptorSet>,
+ ) -> Result<(), gpu_descriptor::DeviceAllocationError> {
+ use gpu_descriptor::DeviceAllocationError as Dae;
+ match hal::pso::DescriptorPool::allocate(pool, layouts, sets) {
+ Ok(()) => Ok(()),
+ Err(hal::pso::AllocationError::OutOfMemory(oom)) => Err(match oom {
+ hal::device::OutOfMemory::Host => Dae::OutOfHostMemory,
+ hal::device::OutOfMemory::Device => Dae::OutOfDeviceMemory,
+ }),
+ Err(hal::pso::AllocationError::OutOfPoolMemory) => Err(Dae::OutOfPoolMemory),
+ Err(hal::pso::AllocationError::FragmentedPool) => Err(Dae::FragmentedPool),
+ Err(hal::pso::AllocationError::IncompatibleLayout) => {
+ panic!("Incompatible descriptor set layout")
+ }
+ }
+ }
+
+ unsafe fn dealloc_descriptor_sets<'a>(
+ &self,
+ pool: &mut B::DescriptorPool,
+ sets: impl Iterator<Item = B::DescriptorSet>,
+ ) {
+ hal::pso::DescriptorPool::free(pool, sets)
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/life.rs b/gfx/wgpu/wgpu-core/src/device/life.rs
new file mode 100644
index 0000000000..3c46b72362
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/life.rs
@@ -0,0 +1,760 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[cfg(feature = "trace")]
+use crate::device::trace;
+use crate::{
+ device::{
+ alloc,
+ descriptor::{DescriptorAllocator, DescriptorSet},
+ queue::TempResource,
+ DeviceError,
+ },
+ hub::{GfxBackend, GlobalIdentityHandlerFactory, Hub, Token},
+ id, resource,
+ track::TrackerSet,
+ FastHashMap, RefCount, Stored, SubmissionIndex,
+};
+
+use copyless::VecHelper as _;
+use hal::device::Device as _;
+use parking_lot::Mutex;
+use thiserror::Error;
+
+use std::sync::atomic::Ordering;
+
+const CLEANUP_WAIT_MS: u64 = 5000;
+
+/// A struct that keeps lists of resources that are no longer needed by the user.
+#[derive(Debug, Default)]
+pub struct SuspectedResources {
+ pub(crate) buffers: Vec<id::Valid<id::BufferId>>,
+ pub(crate) textures: Vec<id::Valid<id::TextureId>>,
+ pub(crate) texture_views: Vec<id::Valid<id::TextureViewId>>,
+ pub(crate) samplers: Vec<id::Valid<id::SamplerId>>,
+ pub(crate) bind_groups: Vec<id::Valid<id::BindGroupId>>,
+ pub(crate) compute_pipelines: Vec<id::Valid<id::ComputePipelineId>>,
+ pub(crate) render_pipelines: Vec<id::Valid<id::RenderPipelineId>>,
+ pub(crate) bind_group_layouts: Vec<id::Valid<id::BindGroupLayoutId>>,
+ pub(crate) pipeline_layouts: Vec<Stored<id::PipelineLayoutId>>,
+ pub(crate) render_bundles: Vec<id::Valid<id::RenderBundleId>>,
+}
+
+impl SuspectedResources {
+ pub(crate) fn clear(&mut self) {
+ self.buffers.clear();
+ self.textures.clear();
+ self.texture_views.clear();
+ self.samplers.clear();
+ self.bind_groups.clear();
+ self.compute_pipelines.clear();
+ self.render_pipelines.clear();
+ self.bind_group_layouts.clear();
+ self.pipeline_layouts.clear();
+ self.render_bundles.clear();
+ }
+
+ pub(crate) fn extend(&mut self, other: &Self) {
+ self.buffers.extend_from_slice(&other.buffers);
+ self.textures.extend_from_slice(&other.textures);
+ self.texture_views.extend_from_slice(&other.texture_views);
+ self.samplers.extend_from_slice(&other.samplers);
+ self.bind_groups.extend_from_slice(&other.bind_groups);
+ self.compute_pipelines
+ .extend_from_slice(&other.compute_pipelines);
+ self.render_pipelines
+ .extend_from_slice(&other.render_pipelines);
+ self.bind_group_layouts
+ .extend_from_slice(&other.bind_group_layouts);
+ self.pipeline_layouts
+ .extend_from_slice(&other.pipeline_layouts);
+ self.render_bundles.extend_from_slice(&other.render_bundles);
+ }
+
+ pub(crate) fn add_trackers(&mut self, trackers: &TrackerSet) {
+ self.buffers.extend(trackers.buffers.used());
+ self.textures.extend(trackers.textures.used());
+ self.texture_views.extend(trackers.views.used());
+ self.samplers.extend(trackers.samplers.used());
+ self.bind_groups.extend(trackers.bind_groups.used());
+ self.compute_pipelines.extend(trackers.compute_pipes.used());
+ self.render_pipelines.extend(trackers.render_pipes.used());
+ self.render_bundles.extend(trackers.bundles.used());
+ }
+}
+
+/// A struct that keeps lists of resources that are no longer needed.
+#[derive(Debug)]
+struct NonReferencedResources<B: hal::Backend> {
+ buffers: Vec<(B::Buffer, alloc::MemoryBlock<B>)>,
+ images: Vec<(B::Image, alloc::MemoryBlock<B>)>,
+ // Note: we keep the associated ID here in order to be able to check
+ // at any point what resources are used in a submission.
+ image_views: Vec<(id::Valid<id::TextureViewId>, B::ImageView)>,
+ samplers: Vec<B::Sampler>,
+ framebuffers: Vec<B::Framebuffer>,
+ desc_sets: Vec<DescriptorSet<B>>,
+ compute_pipes: Vec<B::ComputePipeline>,
+ graphics_pipes: Vec<B::GraphicsPipeline>,
+ descriptor_set_layouts: Vec<B::DescriptorSetLayout>,
+ pipeline_layouts: Vec<B::PipelineLayout>,
+}
+
+impl<B: hal::Backend> NonReferencedResources<B> {
+ fn new() -> Self {
+ Self {
+ buffers: Vec::new(),
+ images: Vec::new(),
+ image_views: Vec::new(),
+ samplers: Vec::new(),
+ framebuffers: Vec::new(),
+ desc_sets: Vec::new(),
+ compute_pipes: Vec::new(),
+ graphics_pipes: Vec::new(),
+ descriptor_set_layouts: Vec::new(),
+ pipeline_layouts: Vec::new(),
+ }
+ }
+
+ fn extend(&mut self, other: Self) {
+ self.buffers.extend(other.buffers);
+ self.images.extend(other.images);
+ self.image_views.extend(other.image_views);
+ self.samplers.extend(other.samplers);
+ self.framebuffers.extend(other.framebuffers);
+ self.desc_sets.extend(other.desc_sets);
+ self.compute_pipes.extend(other.compute_pipes);
+ self.graphics_pipes.extend(other.graphics_pipes);
+ assert!(other.descriptor_set_layouts.is_empty());
+ assert!(other.pipeline_layouts.is_empty());
+ }
+
+ unsafe fn clean(
+ &mut self,
+ device: &B::Device,
+ memory_allocator_mutex: &Mutex<alloc::MemoryAllocator<B>>,
+ descriptor_allocator_mutex: &Mutex<DescriptorAllocator<B>>,
+ ) {
+ if !self.buffers.is_empty() || !self.images.is_empty() {
+ let mut allocator = memory_allocator_mutex.lock();
+ for (raw, memory) in self.buffers.drain(..) {
+ tracing::trace!("Buffer {:?} is destroyed with memory {:?}", raw, memory);
+ device.destroy_buffer(raw);
+ allocator.free(device, memory);
+ }
+ for (raw, memory) in self.images.drain(..) {
+ tracing::trace!("Image {:?} is destroyed with memory {:?}", raw, memory);
+ device.destroy_image(raw);
+ allocator.free(device, memory);
+ }
+ }
+
+ for (_, raw) in self.image_views.drain(..) {
+ device.destroy_image_view(raw);
+ }
+ for raw in self.samplers.drain(..) {
+ device.destroy_sampler(raw);
+ }
+ for raw in self.framebuffers.drain(..) {
+ device.destroy_framebuffer(raw);
+ }
+
+ if !self.desc_sets.is_empty() {
+ descriptor_allocator_mutex
+ .lock()
+ .free(device, self.desc_sets.drain(..));
+ }
+
+ for raw in self.compute_pipes.drain(..) {
+ device.destroy_compute_pipeline(raw);
+ }
+ for raw in self.graphics_pipes.drain(..) {
+ device.destroy_graphics_pipeline(raw);
+ }
+ for raw in self.descriptor_set_layouts.drain(..) {
+ device.destroy_descriptor_set_layout(raw);
+ }
+ for raw in self.pipeline_layouts.drain(..) {
+ device.destroy_pipeline_layout(raw);
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ActiveSubmission<B: hal::Backend> {
+ index: SubmissionIndex,
+ fence: B::Fence,
+ last_resources: NonReferencedResources<B>,
+ mapped: Vec<id::Valid<id::BufferId>>,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum WaitIdleError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("GPU got stuck :(")]
+ StuckGpu,
+}
+
+/// A struct responsible for tracking resource lifetimes.
+///
+/// Here is how host mapping is handled:
+/// 1. When mapping is requested we add the buffer to the life_tracker list of `mapped` buffers.
+/// 2. When `triage_suspected` is called, it checks the last submission index associated with each of the mapped buffer,
+/// and register the buffer with either a submission in flight, or straight into `ready_to_map` vector.
+/// 3. When `ActiveSubmission` is retired, the mapped buffers associated with it are moved to `ready_to_map` vector.
+/// 4. Finally, `handle_mapping` issues all the callbacks.
+#[derive(Debug)]
+pub(crate) struct LifetimeTracker<B: hal::Backend> {
+ /// Resources that the user has requested be mapped, but are still in use.
+ mapped: Vec<Stored<id::BufferId>>,
+ /// Buffers can be used in a submission that is yet to be made, by the
+ /// means of `write_buffer()`, so we have a special place for them.
+ pub future_suspected_buffers: Vec<Stored<id::BufferId>>,
+ /// Textures can be used in the upcoming submission by `write_texture`.
+ pub future_suspected_textures: Vec<Stored<id::TextureId>>,
+ /// Resources that are suspected for destruction.
+ pub suspected_resources: SuspectedResources,
+ /// Resources that are not referenced any more but still used by GPU.
+ /// Grouped by submissions associated with a fence and a submission index.
+ /// The active submissions have to be stored in FIFO order: oldest come first.
+ active: Vec<ActiveSubmission<B>>,
+ /// Resources that are neither referenced or used, just life_tracker
+ /// actual deletion.
+ free_resources: NonReferencedResources<B>,
+ ready_to_map: Vec<id::Valid<id::BufferId>>,
+}
+
+impl<B: hal::Backend> LifetimeTracker<B> {
+ pub fn new() -> Self {
+ Self {
+ mapped: Vec::new(),
+ future_suspected_buffers: Vec::new(),
+ future_suspected_textures: Vec::new(),
+ suspected_resources: SuspectedResources::default(),
+ active: Vec::new(),
+ free_resources: NonReferencedResources::new(),
+ ready_to_map: Vec::new(),
+ }
+ }
+
+ pub fn track_submission(
+ &mut self,
+ index: SubmissionIndex,
+ fence: B::Fence,
+ new_suspects: &SuspectedResources,
+ temp_resources: impl Iterator<Item = (TempResource<B>, alloc::MemoryBlock<B>)>,
+ ) {
+ let mut last_resources = NonReferencedResources::new();
+ for (res, memory) in temp_resources {
+ match res {
+ TempResource::Buffer(raw) => last_resources.buffers.push((raw, memory)),
+ TempResource::Image(raw) => last_resources.images.push((raw, memory)),
+ }
+ }
+
+ self.suspected_resources.buffers.extend(
+ self.future_suspected_buffers
+ .drain(..)
+ .map(|stored| stored.value),
+ );
+ self.suspected_resources.textures.extend(
+ self.future_suspected_textures
+ .drain(..)
+ .map(|stored| stored.value),
+ );
+ self.suspected_resources.extend(new_suspects);
+
+ self.active.alloc().init(ActiveSubmission {
+ index,
+ fence,
+ last_resources,
+ mapped: Vec::new(),
+ });
+ }
+
+ pub(crate) fn map(&mut self, value: id::Valid<id::BufferId>, ref_count: RefCount) {
+ self.mapped.push(Stored { value, ref_count });
+ }
+
+ fn wait_idle(&self, device: &B::Device) -> Result<(), WaitIdleError> {
+ if !self.active.is_empty() {
+ tracing::debug!("Waiting for IDLE...");
+ let status = unsafe {
+ device
+ .wait_for_fences(
+ self.active.iter().map(|a| &a.fence),
+ hal::device::WaitFor::All,
+ CLEANUP_WAIT_MS * 1_000_000,
+ )
+ .map_err(DeviceError::from)?
+ };
+ tracing::debug!("...Done");
+
+ if status == false {
+ // We timed out while waiting for the fences
+ return Err(WaitIdleError::StuckGpu);
+ }
+ }
+ Ok(())
+ }
+
+ /// Returns the last submission index that is done.
+ pub fn triage_submissions(
+ &mut self,
+ device: &B::Device,
+ force_wait: bool,
+ ) -> Result<SubmissionIndex, WaitIdleError> {
+ if force_wait {
+ self.wait_idle(device)?;
+ }
+ //TODO: enable when `is_sorted_by_key` is stable
+ //debug_assert!(self.active.is_sorted_by_key(|a| a.index));
+ let done_count = self
+ .active
+ .iter()
+ .position(|a| unsafe { !device.get_fence_status(&a.fence).unwrap_or(false) })
+ .unwrap_or_else(|| self.active.len());
+ let last_done = if done_count != 0 {
+ self.active[done_count - 1].index
+ } else {
+ return Ok(0);
+ };
+
+ for a in self.active.drain(..done_count) {
+ tracing::trace!("Active submission {} is done", a.index);
+ self.free_resources.extend(a.last_resources);
+ self.ready_to_map.extend(a.mapped);
+ unsafe {
+ device.destroy_fence(a.fence);
+ }
+ }
+
+ Ok(last_done)
+ }
+
+ pub fn cleanup(
+ &mut self,
+ device: &B::Device,
+ memory_allocator_mutex: &Mutex<alloc::MemoryAllocator<B>>,
+ descriptor_allocator_mutex: &Mutex<DescriptorAllocator<B>>,
+ ) {
+ unsafe {
+ self.free_resources
+ .clean(device, memory_allocator_mutex, descriptor_allocator_mutex);
+ descriptor_allocator_mutex.lock().cleanup(device);
+ }
+ }
+
+ pub fn schedule_resource_destruction(
+ &mut self,
+ temp_resource: TempResource<B>,
+ memory: alloc::MemoryBlock<B>,
+ last_submit_index: SubmissionIndex,
+ ) {
+ let resources = self
+ .active
+ .iter_mut()
+ .find(|a| a.index == last_submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources);
+ match temp_resource {
+ TempResource::Buffer(raw) => resources.buffers.push((raw, memory)),
+ TempResource::Image(raw) => resources.images.push((raw, memory)),
+ }
+ }
+}
+
+impl<B: GfxBackend> LifetimeTracker<B> {
+ pub(crate) fn triage_suspected<G: GlobalIdentityHandlerFactory>(
+ &mut self,
+ hub: &Hub<B, G>,
+ trackers: &Mutex<TrackerSet>,
+ #[cfg(feature = "trace")] trace: Option<&Mutex<trace::Trace>>,
+ token: &mut Token<super::Device<B>>,
+ ) {
+ if !self.suspected_resources.render_bundles.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.render_bundles.write(token);
+
+ while let Some(id) = self.suspected_resources.render_bundles.pop() {
+ if trackers.bundles.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyRenderBundle(id.0)));
+
+ if let Some(res) = hub.render_bundles.unregister_locked(id.0, &mut *guard) {
+ self.suspected_resources.add_trackers(&res.used);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.bind_groups.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.bind_groups.write(token);
+
+ while let Some(id) = self.suspected_resources.bind_groups.pop() {
+ if trackers.bind_groups.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyBindGroup(id.0)));
+
+ if let Some(res) = hub.bind_groups.unregister_locked(id.0, &mut *guard) {
+ self.suspected_resources.add_trackers(&res.used);
+
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .desc_sets
+ .push(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.texture_views.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.texture_views.write(token);
+
+ for id in self.suspected_resources.texture_views.drain(..) {
+ if trackers.views.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyTextureView(id.0)));
+
+ if let Some(res) = hub.texture_views.unregister_locked(id.0, &mut *guard) {
+ let raw = match res.inner {
+ resource::TextureViewInner::Native { raw, source_id } => {
+ self.suspected_resources.textures.push(source_id.value);
+ raw
+ }
+ resource::TextureViewInner::SwapChain { .. } => unreachable!(),
+ };
+
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .image_views
+ .push((id, raw));
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.textures.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.textures.write(token);
+
+ for id in self.suspected_resources.textures.drain(..) {
+ if trackers.textures.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyTexture(id.0)));
+
+ if let Some(res) = hub.textures.unregister_locked(id.0, &mut *guard) {
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .images
+ .extend(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.samplers.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.samplers.write(token);
+
+ for id in self.suspected_resources.samplers.drain(..) {
+ if trackers.samplers.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroySampler(id.0)));
+
+ if let Some(res) = hub.samplers.unregister_locked(id.0, &mut *guard) {
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .samplers
+ .push(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.buffers.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.buffers.write(token);
+
+ for id in self.suspected_resources.buffers.drain(..) {
+ if trackers.buffers.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyBuffer(id.0)));
+ tracing::debug!("Buffer {:?} is detached", id);
+
+ if let Some(res) = hub.buffers.unregister_locked(id.0, &mut *guard) {
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .buffers
+ .extend(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.compute_pipelines.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.compute_pipelines.write(token);
+
+ for id in self.suspected_resources.compute_pipelines.drain(..) {
+ if trackers.compute_pipes.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyComputePipeline(id.0)));
+
+ if let Some(res) = hub.compute_pipelines.unregister_locked(id.0, &mut *guard) {
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .compute_pipes
+ .push(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.render_pipelines.is_empty() {
+ let mut trackers = trackers.lock();
+ let (mut guard, _) = hub.render_pipelines.write(token);
+
+ for id in self.suspected_resources.render_pipelines.drain(..) {
+ if trackers.render_pipes.remove_abandoned(id) {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyRenderPipeline(id.0)));
+
+ if let Some(res) = hub.render_pipelines.unregister_locked(id.0, &mut *guard) {
+ let submit_index = res.life_guard.submission_index.load(Ordering::Acquire);
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .graphics_pipes
+ .push(res.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.pipeline_layouts.is_empty() {
+ let (mut guard, _) = hub.pipeline_layouts.write(token);
+
+ for Stored {
+ value: id,
+ ref_count,
+ } in self.suspected_resources.pipeline_layouts.drain(..)
+ {
+ //Note: this has to happen after all the suspected pipelines are destroyed
+ if ref_count.load() == 1 {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyPipelineLayout(id.0)));
+
+ if let Some(lay) = hub.pipeline_layouts.unregister_locked(id.0, &mut *guard) {
+ self.suspected_resources
+ .bind_group_layouts
+ .extend_from_slice(&lay.bind_group_layout_ids);
+ self.free_resources.pipeline_layouts.push(lay.raw);
+ }
+ }
+ }
+ }
+
+ if !self.suspected_resources.bind_group_layouts.is_empty() {
+ let (mut guard, _) = hub.bind_group_layouts.write(token);
+
+ for id in self.suspected_resources.bind_group_layouts.drain(..) {
+ //Note: this has to happen after all the suspected pipelines are destroyed
+ //Note: nothing else can bump the refcount since the guard is locked exclusively
+ //Note: same BGL can appear multiple times in the list, but only the last
+ // encounter could drop the refcount to 0.
+ if guard[id].multi_ref_count.dec_and_check_empty() {
+ #[cfg(feature = "trace")]
+ trace.map(|t| t.lock().add(trace::Action::DestroyBindGroupLayout(id.0)));
+ if let Some(lay) = hub.bind_group_layouts.unregister_locked(id.0, &mut *guard) {
+ self.free_resources.descriptor_set_layouts.push(lay.raw);
+ }
+ }
+ }
+ }
+ }
+
+ pub(crate) fn triage_mapped<G: GlobalIdentityHandlerFactory>(
+ &mut self,
+ hub: &Hub<B, G>,
+ token: &mut Token<super::Device<B>>,
+ ) {
+ if self.mapped.is_empty() {
+ return;
+ }
+ let (buffer_guard, _) = hub.buffers.read(token);
+
+ for stored in self.mapped.drain(..) {
+ let resource_id = stored.value;
+ let buf = &buffer_guard[resource_id];
+
+ let submit_index = buf.life_guard.submission_index.load(Ordering::Acquire);
+ tracing::trace!(
+ "Mapping of {:?} at submission {:?} gets assigned to active {:?}",
+ resource_id,
+ submit_index,
+ self.active.iter().position(|a| a.index == submit_index)
+ );
+
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.ready_to_map, |a| &mut a.mapped)
+ .push(resource_id);
+ }
+ }
+
+ pub(crate) fn triage_framebuffers<G: GlobalIdentityHandlerFactory>(
+ &mut self,
+ hub: &Hub<B, G>,
+ framebuffers: &mut FastHashMap<super::FramebufferKey, B::Framebuffer>,
+ token: &mut Token<super::Device<B>>,
+ ) {
+ let (texture_view_guard, _) = hub.texture_views.read(token);
+ let remove_list = framebuffers
+ .keys()
+ .filter_map(|key| {
+ let mut last_submit = None;
+ let mut needs_cleanup = false;
+
+ // A framebuffer needs to be scheduled for cleanup, if there's at least one
+ // attachment is no longer valid.
+
+ for &at in key.all() {
+ // If this attachment is still registered, it's still valid
+ if texture_view_guard.contains(at.0) {
+ continue;
+ }
+
+ // This attachment is no longer registered, this framebuffer needs cleanup
+ needs_cleanup = true;
+
+ // Check if there's any active submissions that are still referring to this
+ // attachment, if there are we need to get the greatest submission index, as
+ // that's the last time this attachment is still valid
+ let mut attachment_last_submit = None;
+ for a in &self.active {
+ if a.last_resources.image_views.iter().any(|&(id, _)| id == at) {
+ let max = attachment_last_submit.unwrap_or(0).max(a.index);
+ attachment_last_submit = Some(max);
+ }
+ }
+
+ // Between all attachments, we need the smallest index, because that's the last
+ // time this framebuffer is still valid
+ if let Some(attachment_last_submit) = attachment_last_submit {
+ let min = last_submit
+ .unwrap_or(std::usize::MAX)
+ .min(attachment_last_submit);
+ last_submit = Some(min);
+ }
+ }
+
+ if needs_cleanup {
+ Some((key.clone(), last_submit.unwrap_or(0)))
+ } else {
+ None
+ }
+ })
+ .collect::<FastHashMap<_, _>>();
+
+ if !remove_list.is_empty() {
+ tracing::debug!("Free framebuffers {:?}", remove_list);
+ for (ref key, submit_index) in remove_list {
+ let framebuffer = framebuffers.remove(key).unwrap();
+ self.active
+ .iter_mut()
+ .find(|a| a.index == submit_index)
+ .map_or(&mut self.free_resources, |a| &mut a.last_resources)
+ .framebuffers
+ .push(framebuffer);
+ }
+ }
+ }
+
+ pub(crate) fn handle_mapping<G: GlobalIdentityHandlerFactory>(
+ &mut self,
+ hub: &Hub<B, G>,
+ raw: &B::Device,
+ trackers: &Mutex<TrackerSet>,
+ token: &mut Token<super::Device<B>>,
+ ) -> Vec<super::BufferMapPendingCallback> {
+ if self.ready_to_map.is_empty() {
+ return Vec::new();
+ }
+ let (mut buffer_guard, _) = hub.buffers.write(token);
+ let mut pending_callbacks: Vec<super::BufferMapPendingCallback> =
+ Vec::with_capacity(self.ready_to_map.len());
+ let mut trackers = trackers.lock();
+ for buffer_id in self.ready_to_map.drain(..) {
+ let buffer = &mut buffer_guard[buffer_id];
+ if buffer.life_guard.ref_count.is_none() && trackers.buffers.remove_abandoned(buffer_id)
+ {
+ buffer.map_state = resource::BufferMapState::Idle;
+ tracing::debug!("Mapping request is dropped because the buffer is destroyed.");
+ if let Some(buf) = hub
+ .buffers
+ .unregister_locked(buffer_id.0, &mut *buffer_guard)
+ {
+ self.free_resources.buffers.extend(buf.raw);
+ }
+ } else {
+ let mapping = match std::mem::replace(
+ &mut buffer.map_state,
+ resource::BufferMapState::Idle,
+ ) {
+ resource::BufferMapState::Waiting(pending_mapping) => pending_mapping,
+ _ => panic!("No pending mapping."),
+ };
+ let status = if mapping.range.start != mapping.range.end {
+ tracing::debug!("Buffer {:?} map state -> Active", buffer_id);
+ let host = mapping.op.host;
+ let size = mapping.range.end - mapping.range.start;
+ match super::map_buffer(raw, buffer, mapping.range.start, size, host) {
+ Ok(ptr) => {
+ buffer.map_state = resource::BufferMapState::Active {
+ ptr,
+ sub_range: hal::buffer::SubRange {
+ offset: mapping.range.start,
+ size: Some(size),
+ },
+ host,
+ };
+ resource::BufferMapAsyncStatus::Success
+ }
+ Err(e) => {
+ tracing::error!("Mapping failed {:?}", e);
+ resource::BufferMapAsyncStatus::Error
+ }
+ }
+ } else {
+ resource::BufferMapAsyncStatus::Success
+ };
+ pending_callbacks.push((mapping.op, status));
+ }
+ }
+ pending_callbacks
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/mod.rs b/gfx/wgpu/wgpu-core/src/device/mod.rs
new file mode 100644
index 0000000000..b1b7105fe3
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/mod.rs
@@ -0,0 +1,4217 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ binding_model, command, conv,
+ device::life::WaitIdleError,
+ hub::{
+ GfxBackend, Global, GlobalIdentityHandlerFactory, Hub, Input, InvalidId, Storage, Token,
+ },
+ id, pipeline, resource, span, swap_chain,
+ track::{BufferState, TextureSelector, TextureState, TrackerSet},
+ validation::{self, check_buffer_usage, check_texture_usage},
+ FastHashMap, Label, LabelHelpers, LifeGuard, MultiRefCount, PrivateFeatures, Stored,
+ SubmissionIndex, MAX_BIND_GROUPS,
+};
+
+use arrayvec::ArrayVec;
+use copyless::VecHelper as _;
+use hal::{
+ command::CommandBuffer as _,
+ device::Device as _,
+ window::{PresentationSurface as _, Surface as _},
+};
+use parking_lot::{Mutex, MutexGuard};
+use thiserror::Error;
+use wgt::{
+ BufferAddress, BufferSize, InputStepMode, TextureDimension, TextureFormat, TextureViewDimension,
+};
+
+use std::{
+ borrow::Cow,
+ collections::{hash_map::Entry, BTreeMap},
+ iter,
+ marker::PhantomData,
+ mem,
+ ops::Range,
+ ptr,
+ sync::atomic::Ordering,
+};
+
+pub mod alloc;
+pub mod descriptor;
+mod life;
+mod queue;
+#[cfg(any(feature = "trace", feature = "replay"))]
+pub mod trace;
+
+use smallvec::SmallVec;
+#[cfg(feature = "trace")]
+use trace::{Action, Trace};
+
+pub const MAX_COLOR_TARGETS: usize = 4;
+pub const MAX_MIP_LEVELS: u32 = 16;
+pub const MAX_VERTEX_BUFFERS: usize = 16;
+pub const MAX_ANISOTROPY: u8 = 16;
+pub const SHADER_STAGE_COUNT: usize = 3;
+
+pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor<Label<'a>>;
+
+pub fn all_buffer_stages() -> hal::pso::PipelineStage {
+ use hal::pso::PipelineStage as Ps;
+ Ps::DRAW_INDIRECT
+ | Ps::VERTEX_INPUT
+ | Ps::VERTEX_SHADER
+ | Ps::FRAGMENT_SHADER
+ | Ps::COMPUTE_SHADER
+ | Ps::TRANSFER
+ | Ps::HOST
+}
+pub fn all_image_stages() -> hal::pso::PipelineStage {
+ use hal::pso::PipelineStage as Ps;
+ Ps::EARLY_FRAGMENT_TESTS
+ | Ps::LATE_FRAGMENT_TESTS
+ | Ps::COLOR_ATTACHMENT_OUTPUT
+ | Ps::VERTEX_SHADER
+ | Ps::FRAGMENT_SHADER
+ | Ps::COMPUTE_SHADER
+ | Ps::TRANSFER
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum HostMap {
+ Read,
+ Write,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq)]
+#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
+pub(crate) struct AttachmentData<T> {
+ pub colors: ArrayVec<[T; MAX_COLOR_TARGETS]>,
+ pub resolves: ArrayVec<[T; MAX_COLOR_TARGETS]>,
+ pub depth_stencil: Option<T>,
+}
+impl<T: PartialEq> Eq for AttachmentData<T> {}
+impl<T> AttachmentData<T> {
+ pub(crate) fn all(&self) -> impl Iterator<Item = &T> {
+ self.colors
+ .iter()
+ .chain(&self.resolves)
+ .chain(&self.depth_stencil)
+ }
+}
+
+pub(crate) type AttachmentDataVec<T> = ArrayVec<[T; MAX_COLOR_TARGETS + MAX_COLOR_TARGETS + 1]>;
+
+pub(crate) type RenderPassKey = AttachmentData<(hal::pass::Attachment, hal::image::Layout)>;
+pub(crate) type FramebufferKey = AttachmentData<id::Valid<id::TextureViewId>>;
+
+#[derive(Clone, Debug, Hash, PartialEq)]
+#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
+pub(crate) struct RenderPassContext {
+ pub attachments: AttachmentData<TextureFormat>,
+ pub sample_count: u8,
+}
+#[derive(Clone, Debug, Error)]
+pub enum RenderPassCompatibilityError {
+ #[error("Incompatible color attachment: {0:?} != {1:?}")]
+ IncompatibleColorAttachment(
+ ArrayVec<[TextureFormat; MAX_COLOR_TARGETS]>,
+ ArrayVec<[TextureFormat; MAX_COLOR_TARGETS]>,
+ ),
+ #[error("Incompatible depth-stencil attachment: {0:?} != {1:?}")]
+ IncompatibleDepthStencilAttachment(Option<TextureFormat>, Option<TextureFormat>),
+ #[error("Incompatible sample count: {0:?} != {1:?}")]
+ IncompatibleSampleCount(u8, u8),
+}
+
+impl RenderPassContext {
+ // Assumed the renderpass only contains one subpass
+ pub(crate) fn check_compatible(
+ &self,
+ other: &RenderPassContext,
+ ) -> Result<(), RenderPassCompatibilityError> {
+ if self.attachments.colors != other.attachments.colors {
+ return Err(RenderPassCompatibilityError::IncompatibleColorAttachment(
+ self.attachments.colors.clone(),
+ other.attachments.colors.clone(),
+ ));
+ }
+ if self.attachments.depth_stencil != other.attachments.depth_stencil {
+ return Err(
+ RenderPassCompatibilityError::IncompatibleDepthStencilAttachment(
+ self.attachments.depth_stencil.clone(),
+ other.attachments.depth_stencil.clone(),
+ ),
+ );
+ }
+ if self.sample_count != other.sample_count {
+ return Err(RenderPassCompatibilityError::IncompatibleSampleCount(
+ self.sample_count,
+ other.sample_count,
+ ));
+ }
+ Ok(())
+ }
+}
+
+type BufferMapPendingCallback = (resource::BufferMapOperation, resource::BufferMapAsyncStatus);
+
+fn map_buffer<B: hal::Backend>(
+ raw: &B::Device,
+ buffer: &mut resource::Buffer<B>,
+ offset: hal::buffer::Offset,
+ size: BufferAddress,
+ kind: HostMap,
+) -> Result<ptr::NonNull<u8>, resource::BufferAccessError> {
+ let &mut (_, ref mut block) = buffer
+ .raw
+ .as_mut()
+ .ok_or(resource::BufferAccessError::Destroyed)?;
+ let ptr = block.map(raw, offset, size).map_err(DeviceError::from)?;
+
+ buffer.sync_mapped_writes = match kind {
+ HostMap::Read if !block.is_coherent() => {
+ block.invalidate_range(raw, offset, Some(size))?;
+ None
+ }
+ HostMap::Write if !block.is_coherent() => Some(hal::memory::Segment {
+ offset,
+ size: Some(size),
+ }),
+ _ => None,
+ };
+ Ok(ptr)
+}
+
+fn unmap_buffer<B: hal::Backend>(
+ raw: &B::Device,
+ buffer: &mut resource::Buffer<B>,
+) -> Result<(), resource::BufferAccessError> {
+ let &mut (_, ref mut block) = buffer
+ .raw
+ .as_mut()
+ .ok_or(resource::BufferAccessError::Destroyed)?;
+ if let Some(segment) = buffer.sync_mapped_writes.take() {
+ block.flush_range(raw, segment.offset, segment.size)?;
+ }
+ block.unmap(raw);
+ Ok(())
+}
+
+//Note: this logic is specifically moved out of `handle_mapping()` in order to
+// have nothing locked by the time we execute users callback code.
+fn fire_map_callbacks<I: IntoIterator<Item = BufferMapPendingCallback>>(callbacks: I) {
+ for (operation, status) in callbacks {
+ unsafe { (operation.callback)(status, operation.user_data) }
+ }
+}
+
+#[derive(Debug)]
+pub struct Device<B: hal::Backend> {
+ pub(crate) raw: B::Device,
+ pub(crate) adapter_id: Stored<id::AdapterId>,
+ pub(crate) queue_group: hal::queue::QueueGroup<B>,
+ pub(crate) cmd_allocator: command::CommandAllocator<B>,
+ mem_allocator: Mutex<alloc::MemoryAllocator<B>>,
+ desc_allocator: Mutex<descriptor::DescriptorAllocator<B>>,
+ //Note: The submission index here corresponds to the last submission that is done.
+ pub(crate) life_guard: LifeGuard,
+ pub(crate) active_submission_index: SubmissionIndex,
+ pub(crate) trackers: Mutex<TrackerSet>,
+ pub(crate) render_passes: Mutex<FastHashMap<RenderPassKey, B::RenderPass>>,
+ pub(crate) framebuffers: Mutex<FastHashMap<FramebufferKey, B::Framebuffer>>,
+ // Life tracker should be locked right after the device and before anything else.
+ life_tracker: Mutex<life::LifetimeTracker<B>>,
+ temp_suspected: life::SuspectedResources,
+ pub(crate) hal_limits: hal::Limits,
+ pub(crate) private_features: PrivateFeatures,
+ pub(crate) limits: wgt::Limits,
+ pub(crate) features: wgt::Features,
+ //TODO: move this behind another mutex. This would allow several methods to switch
+ // to borrow Device immutably, such as `write_buffer`, `write_texture`, and `buffer_unmap`.
+ pending_writes: queue::PendingWrites<B>,
+ #[cfg(feature = "trace")]
+ pub(crate) trace: Option<Mutex<Trace>>,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateDeviceError {
+ #[error("not enough memory left")]
+ OutOfMemory,
+}
+
+impl<B: GfxBackend> Device<B> {
+ pub(crate) fn new(
+ raw: B::Device,
+ adapter_id: Stored<id::AdapterId>,
+ queue_group: hal::queue::QueueGroup<B>,
+ mem_props: hal::adapter::MemoryProperties,
+ hal_limits: hal::Limits,
+ private_features: PrivateFeatures,
+ desc: &DeviceDescriptor,
+ trace_path: Option<&std::path::Path>,
+ ) -> Result<Self, CreateDeviceError> {
+ let cmd_allocator = command::CommandAllocator::new(queue_group.family, &raw)
+ .or(Err(CreateDeviceError::OutOfMemory))?;
+
+ let mem_allocator = alloc::MemoryAllocator::new(mem_props, hal_limits);
+ let descriptors = descriptor::DescriptorAllocator::new();
+ #[cfg(not(feature = "trace"))]
+ match trace_path {
+ Some(_) => tracing::error!("Feature 'trace' is not enabled"),
+ None => (),
+ }
+
+ Ok(Self {
+ raw,
+ adapter_id,
+ cmd_allocator,
+ mem_allocator: Mutex::new(mem_allocator),
+ desc_allocator: Mutex::new(descriptors),
+ queue_group,
+ life_guard: LifeGuard::new("<device>"),
+ active_submission_index: 0,
+ trackers: Mutex::new(TrackerSet::new(B::VARIANT)),
+ render_passes: Mutex::new(FastHashMap::default()),
+ framebuffers: Mutex::new(FastHashMap::default()),
+ life_tracker: Mutex::new(life::LifetimeTracker::new()),
+ temp_suspected: life::SuspectedResources::default(),
+ #[cfg(feature = "trace")]
+ trace: trace_path.and_then(|path| match Trace::new(path) {
+ Ok(mut trace) => {
+ trace.add(Action::Init {
+ desc: desc.clone(),
+ backend: B::VARIANT,
+ });
+ Some(Mutex::new(trace))
+ }
+ Err(e) => {
+ tracing::error!("Unable to start a trace in '{:?}': {:?}", path, e);
+ None
+ }
+ }),
+ hal_limits,
+ private_features,
+ limits: desc.limits.clone(),
+ features: desc.features.clone(),
+ pending_writes: queue::PendingWrites::new(),
+ })
+ }
+
+ pub(crate) fn last_completed_submission_index(&self) -> SubmissionIndex {
+ self.life_guard.submission_index.load(Ordering::Acquire)
+ }
+
+ fn lock_life_internal<'this, 'token: 'this>(
+ tracker: &'this Mutex<life::LifetimeTracker<B>>,
+ _token: &mut Token<'token, Self>,
+ ) -> MutexGuard<'this, life::LifetimeTracker<B>> {
+ tracker.lock()
+ }
+
+ pub(crate) fn lock_life<'this, 'token: 'this>(
+ &'this self,
+ //TODO: fix this - the token has to be borrowed for the lock
+ token: &mut Token<'token, Self>,
+ ) -> MutexGuard<'this, life::LifetimeTracker<B>> {
+ Self::lock_life_internal(&self.life_tracker, token)
+ }
+
+ fn maintain<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>(
+ &'this self,
+ hub: &Hub<B, G>,
+ force_wait: bool,
+ token: &mut Token<'token, Self>,
+ ) -> Result<Vec<BufferMapPendingCallback>, WaitIdleError> {
+ let mut life_tracker = self.lock_life(token);
+
+ life_tracker.triage_suspected(
+ hub,
+ &self.trackers,
+ #[cfg(feature = "trace")]
+ self.trace.as_ref(),
+ token,
+ );
+ life_tracker.triage_mapped(hub, token);
+ life_tracker.triage_framebuffers(hub, &mut *self.framebuffers.lock(), token);
+ let last_done = life_tracker.triage_submissions(&self.raw, force_wait)?;
+ let callbacks = life_tracker.handle_mapping(hub, &self.raw, &self.trackers, token);
+ life_tracker.cleanup(&self.raw, &self.mem_allocator, &self.desc_allocator);
+
+ self.life_guard
+ .submission_index
+ .store(last_done, Ordering::Release);
+ self.cmd_allocator.maintain(&self.raw, last_done);
+ Ok(callbacks)
+ }
+
+ fn untrack<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>(
+ &'this mut self,
+ hub: &Hub<B, G>,
+ trackers: &TrackerSet,
+ mut token: &mut Token<'token, Self>,
+ ) {
+ self.temp_suspected.clear();
+ // As the tracker is cleared/dropped, we need to consider all the resources
+ // that it references for destruction in the next GC pass.
+ {
+ let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
+ let (compute_pipe_guard, mut token) = hub.compute_pipelines.read(&mut token);
+ let (render_pipe_guard, mut token) = hub.render_pipelines.read(&mut token);
+ let (buffer_guard, mut token) = hub.buffers.read(&mut token);
+ let (texture_guard, mut token) = hub.textures.read(&mut token);
+ let (texture_view_guard, mut token) = hub.texture_views.read(&mut token);
+ let (sampler_guard, _) = hub.samplers.read(&mut token);
+
+ for id in trackers.buffers.used() {
+ if buffer_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.buffers.push(id);
+ }
+ }
+ for id in trackers.textures.used() {
+ if texture_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.textures.push(id);
+ }
+ }
+ for id in trackers.views.used() {
+ if texture_view_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.texture_views.push(id);
+ }
+ }
+ for id in trackers.bind_groups.used() {
+ if bind_group_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.bind_groups.push(id);
+ }
+ }
+ for id in trackers.samplers.used() {
+ if sampler_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.samplers.push(id);
+ }
+ }
+ for id in trackers.compute_pipes.used() {
+ if compute_pipe_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.compute_pipelines.push(id);
+ }
+ }
+ for id in trackers.render_pipes.used() {
+ if render_pipe_guard[id].life_guard.ref_count.is_none() {
+ self.temp_suspected.render_pipelines.push(id);
+ }
+ }
+ }
+
+ self.lock_life(&mut token)
+ .suspected_resources
+ .extend(&self.temp_suspected);
+ }
+
+ fn create_buffer(
+ &self,
+ self_id: id::DeviceId,
+ desc: &resource::BufferDescriptor,
+ transient: bool,
+ ) -> Result<resource::Buffer<B>, resource::CreateBufferError> {
+ debug_assert_eq!(self_id.backend(), B::VARIANT);
+ let (mut usage, _memory_properties) = conv::map_buffer_usage(desc.usage);
+ if desc.mapped_at_creation {
+ if desc.size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ return Err(resource::CreateBufferError::UnalignedSize);
+ }
+ if !desc.usage.contains(wgt::BufferUsage::MAP_WRITE) {
+ // we are going to be copying into it, internally
+ usage |= hal::buffer::Usage::TRANSFER_DST;
+ }
+ }
+
+ let mem_usage = {
+ use gpu_alloc::UsageFlags as Uf;
+ use wgt::BufferUsage as Bu;
+
+ let mut flags = Uf::empty();
+ let map_flags = desc.usage & (Bu::MAP_READ | Bu::MAP_WRITE);
+ if !(desc.usage - map_flags).is_empty() {
+ flags |= Uf::FAST_DEVICE_ACCESS;
+ }
+ if transient {
+ flags |= Uf::TRANSIENT;
+ }
+
+ if !map_flags.is_empty() {
+ let upload_usage = Bu::MAP_WRITE | Bu::COPY_SRC;
+ let download_usage = Bu::MAP_READ | Bu::COPY_DST;
+
+ flags |= Uf::HOST_ACCESS;
+ if desc.usage.contains(upload_usage) {
+ flags |= Uf::UPLOAD;
+ }
+ if desc.usage.contains(download_usage) {
+ flags |= Uf::DOWNLOAD;
+ }
+
+ let is_native_only = self
+ .features
+ .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS);
+ if !is_native_only
+ && !upload_usage.contains(desc.usage)
+ && !download_usage.contains(desc.usage)
+ {
+ return Err(resource::CreateBufferError::UsageMismatch(desc.usage));
+ }
+ }
+
+ flags
+ };
+
+ let mut buffer = unsafe { self.raw.create_buffer(desc.size.max(1), usage) }.map_err(
+ |err| match err {
+ hal::buffer::CreationError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ _ => panic!("failed to create buffer: {}", err),
+ },
+ )?;
+ if let Some(ref label) = desc.label {
+ unsafe { self.raw.set_buffer_name(&mut buffer, label) };
+ }
+
+ let requirements = unsafe { self.raw.get_buffer_requirements(&buffer) };
+ let block = self
+ .mem_allocator
+ .lock()
+ .allocate(&self.raw, requirements, mem_usage)?;
+ block.bind_buffer(&self.raw, &mut buffer)?;
+
+ Ok(resource::Buffer {
+ raw: Some((buffer, block)),
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ usage: desc.usage,
+ size: desc.size,
+ full_range: (),
+ sync_mapped_writes: None,
+ map_state: resource::BufferMapState::Idle,
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ })
+ }
+
+ fn create_texture(
+ &self,
+ self_id: id::DeviceId,
+ desc: &resource::TextureDescriptor,
+ ) -> Result<resource::Texture<B>, resource::CreateTextureError> {
+ debug_assert_eq!(self_id.backend(), B::VARIANT);
+
+ let features = conv::texture_features(desc.format);
+ if !self.features.contains(features) {
+ return Err(resource::CreateTextureError::MissingFeature(
+ features,
+ desc.format,
+ ));
+ }
+
+ // Ensure `D24Plus` textures cannot be copied
+ match desc.format {
+ TextureFormat::Depth24Plus | TextureFormat::Depth24PlusStencil8 => {
+ if desc
+ .usage
+ .intersects(wgt::TextureUsage::COPY_SRC | wgt::TextureUsage::COPY_DST)
+ {
+ return Err(resource::CreateTextureError::CannotCopyD24Plus);
+ }
+ }
+ _ => {}
+ }
+
+ let kind = conv::map_texture_dimension_size(desc.dimension, desc.size, desc.sample_count)?;
+ let format = conv::map_texture_format(desc.format, self.private_features);
+ let aspects = format.surface_desc().aspects;
+ let usage = conv::map_texture_usage(desc.usage, aspects);
+
+ let mip_level_count = desc.mip_level_count;
+ if mip_level_count == 0
+ || mip_level_count > MAX_MIP_LEVELS
+ || mip_level_count > kind.compute_num_levels() as u32
+ {
+ return Err(resource::CreateTextureError::InvalidMipLevelCount(
+ mip_level_count,
+ ));
+ }
+ let mut view_capabilities = hal::image::ViewCapabilities::empty();
+
+ // 2D textures with array layer counts that are multiples of 6 could be cubemaps
+ // Following gpuweb/gpuweb#68 always add the hint in that case
+ if desc.dimension == TextureDimension::D2 && desc.size.depth % 6 == 0 {
+ view_capabilities |= hal::image::ViewCapabilities::KIND_CUBE;
+ };
+
+ // TODO: 2D arrays, cubemap arrays
+
+ let mut image = unsafe {
+ let mut image = self
+ .raw
+ .create_image(
+ kind,
+ desc.mip_level_count as hal::image::Level,
+ format,
+ hal::image::Tiling::Optimal,
+ usage,
+ view_capabilities,
+ )
+ .map_err(|err| match err {
+ hal::image::CreationError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ _ => panic!("failed to create texture: {}", err),
+ })?;
+ if let Some(ref label) = desc.label {
+ self.raw.set_image_name(&mut image, label);
+ }
+ image
+ };
+
+ let requirements = unsafe { self.raw.get_image_requirements(&image) };
+ let block = self.mem_allocator.lock().allocate(
+ &self.raw,
+ requirements,
+ gpu_alloc::UsageFlags::FAST_DEVICE_ACCESS,
+ )?;
+ block.bind_image(&self.raw, &mut image)?;
+
+ Ok(resource::Texture {
+ raw: Some((image, block)),
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ usage: desc.usage,
+ aspects,
+ dimension: desc.dimension,
+ kind,
+ format: desc.format,
+ full_range: TextureSelector {
+ levels: 0..desc.mip_level_count as hal::image::Level,
+ layers: 0..kind.num_layers(),
+ },
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ })
+ }
+
+ fn create_texture_view(
+ &self,
+ texture: &resource::Texture<B>,
+ texture_id: id::TextureId,
+ desc: &resource::TextureViewDescriptor,
+ ) -> Result<resource::TextureView<B>, resource::CreateTextureViewError> {
+ let &(ref texture_raw, _) = texture
+ .raw
+ .as_ref()
+ .ok_or(resource::CreateTextureViewError::InvalidTexture)?;
+
+ let view_dim =
+ match desc.dimension {
+ Some(dim) => {
+ use hal::image::Kind;
+
+ let required_tex_dim = dim.compatible_texture_dimension();
+
+ if required_tex_dim != texture.dimension {
+ return Err(
+ resource::CreateTextureViewError::InvalidTextureViewDimension {
+ view: dim,
+ image: texture.dimension,
+ },
+ );
+ }
+
+ if let Kind::D2(_, _, depth, _) = texture.kind {
+ match dim {
+ TextureViewDimension::Cube if depth != 6 => {
+ return Err(
+ resource::CreateTextureViewError::InvalidCubemapTextureDepth {
+ depth,
+ },
+ )
+ }
+ TextureViewDimension::CubeArray if depth % 6 != 0 => return Err(
+ resource::CreateTextureViewError::InvalidCubemapArrayTextureDepth {
+ depth,
+ },
+ ),
+ _ => {}
+ }
+ }
+
+ dim
+ }
+ None => match texture.kind {
+ hal::image::Kind::D1(..) => wgt::TextureViewDimension::D1,
+ hal::image::Kind::D2(_, _, depth, _)
+ if depth > 1 && desc.array_layer_count.is_none() =>
+ {
+ wgt::TextureViewDimension::D2Array
+ }
+ hal::image::Kind::D2(..) => wgt::TextureViewDimension::D2,
+ hal::image::Kind::D3(..) => wgt::TextureViewDimension::D3,
+ },
+ };
+
+ let required_level_count =
+ desc.base_mip_level + desc.level_count.map_or(1, |count| count.get());
+ let required_layer_count =
+ desc.base_array_layer + desc.array_layer_count.map_or(1, |count| count.get());
+ let level_end = texture.full_range.levels.end;
+ let layer_end = texture.full_range.layers.end;
+ if required_level_count > level_end as u32 {
+ return Err(resource::CreateTextureViewError::TooManyMipLevels {
+ requested: required_level_count,
+ total: level_end,
+ });
+ }
+ if required_layer_count > layer_end as u32 {
+ return Err(resource::CreateTextureViewError::TooManyArrayLayers {
+ requested: required_layer_count,
+ total: layer_end,
+ });
+ };
+
+ let aspects = match desc.aspect {
+ wgt::TextureAspect::All => texture.aspects,
+ wgt::TextureAspect::DepthOnly => hal::format::Aspects::DEPTH,
+ wgt::TextureAspect::StencilOnly => hal::format::Aspects::STENCIL,
+ };
+ if !texture.aspects.contains(aspects) {
+ return Err(resource::CreateTextureViewError::InvalidAspect {
+ requested: aspects,
+ total: texture.aspects,
+ });
+ }
+
+ let end_level = desc
+ .level_count
+ .map_or(level_end, |_| required_level_count as u8);
+ let end_layer = desc
+ .array_layer_count
+ .map_or(layer_end, |_| required_layer_count as u16);
+ let selector = TextureSelector {
+ levels: desc.base_mip_level as u8..end_level,
+ layers: desc.base_array_layer as u16..end_layer,
+ };
+
+ let view_layer_count = (selector.layers.end - selector.layers.start) as u32;
+ let layer_check_ok = match view_dim {
+ wgt::TextureViewDimension::D1
+ | wgt::TextureViewDimension::D2
+ | wgt::TextureViewDimension::D3 => view_layer_count == 1,
+ wgt::TextureViewDimension::D2Array => true,
+ wgt::TextureViewDimension::Cube => view_layer_count == 6,
+ wgt::TextureViewDimension::CubeArray => view_layer_count % 6 == 0,
+ };
+ if !layer_check_ok {
+ return Err(resource::CreateTextureViewError::InvalidArrayLayerCount {
+ requested: view_layer_count,
+ dim: view_dim,
+ });
+ }
+
+ let format = desc.format.unwrap_or(texture.format);
+ let range = hal::image::SubresourceRange {
+ aspects,
+ level_start: desc.base_mip_level as _,
+ level_count: desc.level_count.map(|v| v.get() as _),
+ layer_start: desc.base_array_layer as _,
+ layer_count: desc.array_layer_count.map(|v| v.get() as _),
+ };
+
+ let raw = unsafe {
+ self.raw
+ .create_image_view(
+ texture_raw,
+ conv::map_texture_view_dimension(view_dim),
+ conv::map_texture_format(format, self.private_features),
+ hal::format::Swizzle::NO,
+ range.clone(),
+ )
+ .or(Err(resource::CreateTextureViewError::OutOfMemory))?
+ };
+
+ Ok(resource::TextureView {
+ inner: resource::TextureViewInner::Native {
+ raw,
+ source_id: Stored {
+ value: id::Valid(texture_id),
+ ref_count: texture.life_guard.add_ref(),
+ },
+ },
+ aspects,
+ format: texture.format,
+ extent: texture.kind.extent().at_level(desc.base_mip_level as _),
+ samples: texture.kind.num_samples(),
+ selector,
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ })
+ }
+
+ fn create_sampler(
+ &self,
+ self_id: id::DeviceId,
+ desc: &resource::SamplerDescriptor,
+ ) -> Result<resource::Sampler<B>, resource::CreateSamplerError> {
+ let clamp_to_border_enabled = self
+ .features
+ .contains(wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER);
+ let clamp_to_border_found = desc
+ .address_modes
+ .iter()
+ .any(|am| am == &wgt::AddressMode::ClampToBorder);
+ if clamp_to_border_found && !clamp_to_border_enabled {
+ return Err(resource::CreateSamplerError::MissingFeature(
+ wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
+ ));
+ }
+
+ let actual_clamp = if let Some(clamp) = desc.anisotropy_clamp {
+ let clamp = clamp.get();
+ let valid_clamp = clamp <= MAX_ANISOTROPY && conv::is_power_of_two(clamp as u32);
+ if !valid_clamp {
+ return Err(resource::CreateSamplerError::InvalidClamp(clamp));
+ }
+ if self.private_features.anisotropic_filtering {
+ Some(clamp)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ let border = match desc.border_color {
+ None | Some(wgt::SamplerBorderColor::TransparentBlack) => {
+ hal::image::BorderColor::TransparentBlack
+ }
+ Some(wgt::SamplerBorderColor::OpaqueBlack) => hal::image::BorderColor::OpaqueBlack,
+ Some(wgt::SamplerBorderColor::OpaqueWhite) => hal::image::BorderColor::OpaqueWhite,
+ };
+
+ let info = hal::image::SamplerDesc {
+ min_filter: conv::map_filter(desc.min_filter),
+ mag_filter: conv::map_filter(desc.mag_filter),
+ mip_filter: conv::map_filter(desc.mipmap_filter),
+ wrap_mode: (
+ conv::map_wrap(desc.address_modes[0]),
+ conv::map_wrap(desc.address_modes[1]),
+ conv::map_wrap(desc.address_modes[2]),
+ ),
+ lod_bias: hal::image::Lod(0.0),
+ lod_range: hal::image::Lod(desc.lod_min_clamp)..hal::image::Lod(desc.lod_max_clamp),
+ comparison: desc.compare.map(conv::map_compare_function),
+ border,
+ normalized: true,
+ anisotropy_clamp: actual_clamp,
+ };
+
+ let raw = unsafe {
+ self.raw.create_sampler(&info).map_err(|err| match err {
+ hal::device::AllocationError::OutOfMemory(_) => {
+ resource::CreateSamplerError::Device(DeviceError::OutOfMemory)
+ }
+ hal::device::AllocationError::TooManyObjects => {
+ resource::CreateSamplerError::TooManyObjects
+ }
+ })?
+ };
+ Ok(resource::Sampler {
+ raw,
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ comparison: info.comparison.is_some(),
+ })
+ }
+
+ fn create_shader_module<'a>(
+ &self,
+ self_id: id::DeviceId,
+ desc: &'a pipeline::ShaderModuleDescriptor<'a>,
+ ) -> Result<(pipeline::ShaderModule<B>, Cow<'a, [u32]>), pipeline::CreateShaderModuleError>
+ {
+ let spv_flags = if cfg!(debug_assertions) {
+ naga::back::spv::WriterFlags::DEBUG
+ } else {
+ naga::back::spv::WriterFlags::empty()
+ };
+
+ let (spv, naga) = match desc.source {
+ pipeline::ShaderModuleSource::SpirV(ref spv) => {
+ let module = if self.private_features.shader_validation {
+ // Parse the given shader code and store its representation.
+ let spv_iter = spv.iter().cloned();
+ naga::front::spv::Parser::new(spv_iter, &Default::default())
+ .parse()
+ .map_err(|err| {
+ // TODO: eventually, when Naga gets support for all features,
+ // we want to convert these to a hard error,
+ tracing::warn!("Failed to parse shader SPIR-V code: {:?}", err);
+ tracing::warn!("Shader module will not be validated");
+ })
+ .ok()
+ } else {
+ None
+ };
+ (Cow::Borrowed(&**spv), module)
+ }
+ pipeline::ShaderModuleSource::Wgsl(ref code) => {
+ // TODO: refactor the corresponding Naga error to be owned, and then
+ // display it instead of unwrapping
+ let module = naga::front::wgsl::parse_str(code).unwrap();
+ let spv = naga::back::spv::Writer::new(&module.header, spv_flags).write(&module);
+ (
+ Cow::Owned(spv),
+ if self.private_features.shader_validation {
+ Some(module)
+ } else {
+ None
+ },
+ )
+ } /*
+ pipeline::ShaderModuleSource::Naga(module) => {
+ let spv = naga::back::spv::Writer::new(&module.header, spv_flags).write(&module);
+ (
+ Cow::Owned(spv),
+ if device.private_features.shader_validation {
+ Some(module)
+ } else {
+ None
+ },
+ )
+ }*/
+ };
+
+ if let Some(ref module) = naga {
+ naga::proc::Validator::new().validate(module)?;
+ }
+
+ let raw = unsafe {
+ self.raw
+ .create_shader_module(&spv)
+ .map_err(|err| match err {
+ hal::device::ShaderError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ _ => panic!("failed to create shader module: {}", err),
+ })?
+ };
+ let shader = pipeline::ShaderModule {
+ raw,
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ module: naga,
+ #[cfg(debug_assertions)]
+ label: desc.label.to_string_or_default(),
+ };
+ Ok((shader, spv))
+ }
+
+ /// Create a compatible render pass with a given key.
+ ///
+ /// This functions doesn't consider the following aspects for compatibility:
+ /// - image layouts
+ /// - resolve attachments
+ fn create_compatible_render_pass(
+ &self,
+ key: &RenderPassKey,
+ ) -> Result<B::RenderPass, hal::device::OutOfMemory> {
+ let mut color_ids = [(0, hal::image::Layout::ColorAttachmentOptimal); MAX_COLOR_TARGETS];
+ for i in 0..key.colors.len() {
+ color_ids[i].0 = i;
+ }
+ let depth_id = key.depth_stencil.as_ref().map(|_| {
+ (
+ key.colors.len(),
+ hal::image::Layout::DepthStencilAttachmentOptimal,
+ )
+ });
+
+ let subpass = hal::pass::SubpassDesc {
+ colors: &color_ids[..key.colors.len()],
+ depth_stencil: depth_id.as_ref(),
+ inputs: &[],
+ resolves: &[],
+ preserves: &[],
+ };
+ let all = key
+ .all()
+ .map(|(at, _)| at)
+ .collect::<AttachmentDataVec<_>>();
+
+ unsafe { self.raw.create_render_pass(all, iter::once(subpass), &[]) }
+ }
+
+ fn deduplicate_bind_group_layout(
+ self_id: id::DeviceId,
+ entry_map: &binding_model::BindEntryMap,
+ guard: &Storage<binding_model::BindGroupLayout<B>, id::BindGroupLayoutId>,
+ ) -> Option<id::BindGroupLayoutId> {
+ guard
+ .iter(self_id.backend())
+ .find(|(_, bgl)| bgl.device_id.value.0 == self_id && bgl.entries == *entry_map)
+ .map(|(id, value)| {
+ value.multi_ref_count.inc();
+ id
+ })
+ }
+
+ fn get_introspection_bind_group_layouts<'a>(
+ pipeline_layout: &binding_model::PipelineLayout<B>,
+ bgl_guard: &'a Storage<binding_model::BindGroupLayout<B>, id::BindGroupLayoutId>,
+ ) -> validation::IntrospectionBindGroupLayouts<'a> {
+ validation::IntrospectionBindGroupLayouts::Given(
+ pipeline_layout
+ .bind_group_layout_ids
+ .iter()
+ .map(|&id| &bgl_guard[id].entries)
+ .collect(),
+ )
+ }
+
+ fn create_bind_group_layout(
+ &self,
+ self_id: id::DeviceId,
+ label: Option<&str>,
+ entry_map: binding_model::BindEntryMap,
+ ) -> Result<binding_model::BindGroupLayout<B>, binding_model::CreateBindGroupLayoutError> {
+ let mut desc_count = descriptor::DescriptorTotalCount::default();
+ for binding in entry_map.values() {
+ use wgt::BindingType as Bt;
+ let (counter, array_feature) = match binding.ty {
+ Bt::Buffer {
+ ty: wgt::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: _,
+ } => (&mut desc_count.uniform_buffer, None),
+ Bt::Buffer {
+ ty: wgt::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
+ min_binding_size: _,
+ } => (&mut desc_count.uniform_buffer_dynamic, None),
+ Bt::Buffer {
+ ty: wgt::BufferBindingType::Storage { .. },
+ has_dynamic_offset: false,
+ min_binding_size: _,
+ } => (&mut desc_count.storage_buffer, None),
+ Bt::Buffer {
+ ty: wgt::BufferBindingType::Storage { .. },
+ has_dynamic_offset: true,
+ min_binding_size: _,
+ } => (&mut desc_count.storage_buffer_dynamic, None),
+ Bt::Sampler { .. } => (&mut desc_count.sampler, None),
+ Bt::Texture { .. } => (
+ &mut desc_count.sampled_image,
+ Some(wgt::Features::SAMPLED_TEXTURE_BINDING_ARRAY),
+ ),
+ Bt::StorageTexture { .. } => (&mut desc_count.storage_image, None),
+ };
+ *counter += match binding.count {
+ // Validate the count parameter
+ Some(count) => {
+ let feature = array_feature
+ .ok_or(binding_model::CreateBindGroupLayoutError::ArrayUnsupported)?;
+ if !self.features.contains(feature) {
+ return Err(binding_model::CreateBindGroupLayoutError::MissingFeature(
+ feature,
+ ));
+ }
+ count.get()
+ }
+ None => 1,
+ };
+ }
+
+ let raw_bindings = entry_map
+ .values()
+ .map(|entry| hal::pso::DescriptorSetLayoutBinding {
+ binding: entry.binding,
+ ty: conv::map_binding_type(entry),
+ count: entry
+ .count
+ .map_or(1, |v| v.get() as hal::pso::DescriptorArrayIndex), //TODO: consolidate
+ stage_flags: conv::map_shader_stage_flags(entry.visibility),
+ immutable_samplers: false, // TODO
+ });
+ let raw = unsafe {
+ let mut raw_layout = self
+ .raw
+ .create_descriptor_set_layout(raw_bindings, &[])
+ .or(Err(DeviceError::OutOfMemory))?;
+ if let Some(label) = label {
+ self.raw
+ .set_descriptor_set_layout_name(&mut raw_layout, label);
+ }
+ raw_layout
+ };
+
+ let mut count_validator = binding_model::BindingTypeMaxCountValidator::default();
+ for entry in entry_map.values() {
+ count_validator.add_binding(entry);
+ }
+ // If a single bind group layout violates limits, the pipeline layout is definitely
+ // going to violate limits too, lets catch it now.
+ count_validator
+ .validate(&self.limits)
+ .map_err(binding_model::CreateBindGroupLayoutError::TooManyBindings)?;
+
+ Ok(binding_model::BindGroupLayout {
+ raw,
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ multi_ref_count: MultiRefCount::new(),
+ desc_count,
+ dynamic_count: entry_map
+ .values()
+ .filter(|b| b.ty.has_dynamic_offset())
+ .count(),
+ count_validator,
+ entries: entry_map,
+ #[cfg(debug_assertions)]
+ label: label.unwrap_or("").to_string(),
+ })
+ }
+
+ fn create_bind_group<G: GlobalIdentityHandlerFactory>(
+ &self,
+ self_id: id::DeviceId,
+ layout: &binding_model::BindGroupLayout<B>,
+ desc: &binding_model::BindGroupDescriptor,
+ hub: &Hub<B, G>,
+ token: &mut Token<binding_model::BindGroupLayout<B>>,
+ ) -> Result<binding_model::BindGroup<B>, binding_model::CreateBindGroupError> {
+ use crate::binding_model::{BindingResource as Br, CreateBindGroupError as Error};
+ {
+ // Check that the number of entries in the descriptor matches
+ // the number of entries in the layout.
+ let actual = desc.entries.len();
+ let expected = layout.entries.len();
+ if actual != expected {
+ return Err(Error::BindingsNumMismatch { expected, actual });
+ }
+ }
+
+ // TODO: arrayvec/smallvec
+ // Record binding info for dynamic offset validation
+ let mut dynamic_binding_info = Vec::new();
+ // fill out the descriptors
+ let mut used = TrackerSet::new(B::VARIANT);
+
+ let (buffer_guard, mut token) = hub.buffers.read(token);
+ let (texture_guard, mut token) = hub.textures.read(&mut token); //skip token
+ let (texture_view_guard, mut token) = hub.texture_views.read(&mut token);
+ let (sampler_guard, _) = hub.samplers.read(&mut token);
+
+ // `BTreeMap` has ordered bindings as keys, which allows us to coalesce
+ // the descriptor writes into a single transaction.
+ let mut write_map = BTreeMap::new();
+ for entry in desc.entries.iter() {
+ let binding = entry.binding;
+ // Find the corresponding declaration in the layout
+ let decl = layout
+ .entries
+ .get(&binding)
+ .ok_or(Error::MissingBindingDeclaration(binding))?;
+ let descriptors: SmallVec<[_; 1]> = match entry.resource {
+ Br::Buffer(ref bb) => {
+ let (binding_ty, dynamic, min_size) = match decl.ty {
+ wgt::BindingType::Buffer {
+ ty,
+ has_dynamic_offset,
+ min_binding_size,
+ } => (ty, has_dynamic_offset, min_binding_size),
+ _ => {
+ return Err(Error::WrongBindingType {
+ binding,
+ actual: decl.ty.clone(),
+ expected: "UniformBuffer, StorageBuffer or ReadonlyStorageBuffer",
+ })
+ }
+ };
+ let (pub_usage, internal_use) = match binding_ty {
+ wgt::BufferBindingType::Uniform => {
+ (wgt::BufferUsage::UNIFORM, resource::BufferUse::UNIFORM)
+ }
+ wgt::BufferBindingType::Storage { read_only } => (
+ wgt::BufferUsage::STORAGE,
+ if read_only {
+ resource::BufferUse::STORAGE_LOAD
+ } else {
+ resource::BufferUse::STORAGE_STORE
+ },
+ ),
+ };
+
+ if bb.offset % wgt::BIND_BUFFER_ALIGNMENT != 0 {
+ return Err(Error::UnalignedBufferOffset(bb.offset));
+ }
+
+ let buffer = used
+ .buffers
+ .use_extend(&*buffer_guard, bb.buffer_id, (), internal_use)
+ .map_err(|_| Error::InvalidBuffer(bb.buffer_id))?;
+ check_buffer_usage(buffer.usage, pub_usage)?;
+ let &(ref buffer_raw, _) = buffer
+ .raw
+ .as_ref()
+ .ok_or(Error::InvalidBuffer(bb.buffer_id))?;
+
+ let (bind_size, bind_end) = match bb.size {
+ Some(size) => {
+ let end = bb.offset + size.get();
+ if end > buffer.size {
+ return Err(Error::BindingRangeTooLarge {
+ range: bb.offset..end,
+ size: buffer.size,
+ });
+ }
+ (size.get(), end)
+ }
+ None => (buffer.size - bb.offset, buffer.size),
+ };
+
+ if binding_ty == wgt::BufferBindingType::Uniform
+ && (self.limits.max_uniform_buffer_binding_size as u64) < bind_size
+ {
+ return Err(Error::UniformBufferRangeTooLarge);
+ }
+
+ // Record binding info for validating dynamic offsets
+ if dynamic {
+ dynamic_binding_info.push(binding_model::BindGroupDynamicBindingData {
+ maximum_dynamic_offset: buffer.size - bind_end,
+ });
+ }
+
+ if let Some(non_zero) = min_size {
+ let min_size = non_zero.get();
+ if min_size > bind_size {
+ return Err(Error::BindingSizeTooSmall {
+ actual: bind_size,
+ min: min_size,
+ });
+ }
+ }
+
+ let sub_range = hal::buffer::SubRange {
+ offset: bb.offset,
+ size: Some(bind_size),
+ };
+ SmallVec::from([hal::pso::Descriptor::Buffer(buffer_raw, sub_range)])
+ }
+ Br::Sampler(id) => {
+ match decl.ty {
+ wgt::BindingType::Sampler {
+ filtering: _,
+ comparison,
+ } => {
+ let sampler = used
+ .samplers
+ .use_extend(&*sampler_guard, id, (), ())
+ .map_err(|_| Error::InvalidSampler(id))?;
+
+ // Check the actual sampler to also (not) be a comparison sampler
+ if sampler.comparison != comparison {
+ return Err(Error::WrongSamplerComparison);
+ }
+
+ SmallVec::from([hal::pso::Descriptor::Sampler(&sampler.raw)])
+ }
+ _ => {
+ return Err(Error::WrongBindingType {
+ binding,
+ actual: decl.ty.clone(),
+ expected: "Sampler",
+ })
+ }
+ }
+ }
+ Br::TextureView(id) => {
+ let view = used
+ .views
+ .use_extend(&*texture_view_guard, id, (), ())
+ .map_err(|_| Error::InvalidTextureView(id))?;
+ let (pub_usage, internal_use) = match decl.ty {
+ wgt::BindingType::Texture { .. } => {
+ (wgt::TextureUsage::SAMPLED, resource::TextureUse::SAMPLED)
+ }
+ wgt::BindingType::StorageTexture { access, .. } => (
+ wgt::TextureUsage::STORAGE,
+ match access {
+ wgt::StorageTextureAccess::ReadOnly => {
+ resource::TextureUse::STORAGE_LOAD
+ }
+ wgt::StorageTextureAccess::WriteOnly => {
+ resource::TextureUse::STORAGE_STORE
+ }
+ },
+ ),
+ _ => return Err(Error::WrongBindingType {
+ binding,
+ actual: decl.ty.clone(),
+ expected:
+ "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture",
+ }),
+ };
+ if view
+ .aspects
+ .contains(hal::format::Aspects::DEPTH | hal::format::Aspects::STENCIL)
+ {
+ return Err(Error::DepthStencilAspect);
+ }
+ match view.inner {
+ resource::TextureViewInner::Native {
+ ref raw,
+ ref source_id,
+ } => {
+ // Careful here: the texture may no longer have its own ref count,
+ // if it was deleted by the user.
+ let texture = &texture_guard[source_id.value];
+ used.textures
+ .change_extend(
+ source_id.value,
+ &source_id.ref_count,
+ view.selector.clone(),
+ internal_use,
+ )
+ .unwrap();
+ check_texture_usage(texture.usage, pub_usage)?;
+ let image_layout =
+ conv::map_texture_state(internal_use, view.aspects).1;
+ SmallVec::from([hal::pso::Descriptor::Image(raw, image_layout)])
+ }
+ resource::TextureViewInner::SwapChain { .. } => {
+ return Err(Error::SwapChainImage);
+ }
+ }
+ }
+ Br::TextureViewArray(ref bindings_array) => {
+ let required_feats = wgt::Features::SAMPLED_TEXTURE_BINDING_ARRAY;
+ if !self.features.contains(required_feats) {
+ return Err(Error::MissingFeatures(required_feats));
+ }
+
+ if let Some(count) = decl.count {
+ let count = count.get() as usize;
+ let num_bindings = bindings_array.len();
+ if count != num_bindings {
+ return Err(Error::BindingArrayLengthMismatch {
+ actual: num_bindings,
+ expected: count,
+ });
+ }
+ } else {
+ return Err(Error::SingleBindingExpected);
+ }
+
+ let (pub_usage, internal_use) = match decl.ty {
+ wgt::BindingType::Texture { .. } => {
+ (wgt::TextureUsage::SAMPLED, resource::TextureUse::SAMPLED)
+ }
+ _ => {
+ return Err(Error::WrongBindingType {
+ binding,
+ actual: decl.ty.clone(),
+ expected: "SampledTextureArray",
+ })
+ }
+ };
+ bindings_array
+ .iter()
+ .map(|&id| {
+ let view = used
+ .views
+ .use_extend(&*texture_view_guard, id, (), ())
+ .map_err(|_| Error::InvalidTextureView(id))?;
+ match view.inner {
+ resource::TextureViewInner::Native {
+ ref raw,
+ ref source_id,
+ } => {
+ // Careful here: the texture may no longer have its own ref count,
+ // if it was deleted by the user.
+ let texture = &texture_guard[source_id.value];
+ used.textures
+ .change_extend(
+ source_id.value,
+ &source_id.ref_count,
+ view.selector.clone(),
+ internal_use,
+ )
+ .unwrap();
+ check_texture_usage(texture.usage, pub_usage)?;
+ let image_layout =
+ conv::map_texture_state(internal_use, view.aspects).1;
+ Ok(hal::pso::Descriptor::Image(raw, image_layout))
+ }
+ resource::TextureViewInner::SwapChain { .. } => {
+ Err(Error::SwapChainImage)
+ }
+ }
+ })
+ .collect::<Result<_, _>>()?
+ }
+ };
+ if write_map.insert(binding, descriptors).is_some() {
+ return Err(Error::DuplicateBinding(binding));
+ }
+ }
+
+ let mut desc_sets =
+ self.desc_allocator
+ .lock()
+ .allocate(&self.raw, &layout.raw, &layout.desc_count, 1)?;
+ let mut desc_set = desc_sets.pop().unwrap();
+
+ // Set the descriptor set's label for easier debugging.
+ if let Some(label) = desc.label.as_ref() {
+ unsafe {
+ self.raw.set_descriptor_set_name(desc_set.raw_mut(), &label);
+ }
+ }
+
+ if let Some(start_binding) = write_map.keys().next().cloned() {
+ let descriptors = write_map
+ .into_iter()
+ .flat_map(|(_, list)| list)
+ .collect::<Vec<_>>();
+ let write = hal::pso::DescriptorSetWrite {
+ set: desc_set.raw(),
+ binding: start_binding,
+ array_offset: 0,
+ descriptors,
+ };
+ unsafe {
+ self.raw.write_descriptor_sets(iter::once(write));
+ }
+ }
+
+ Ok(binding_model::BindGroup {
+ raw: desc_set,
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ layout_id: id::Valid(desc.layout),
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ used,
+ dynamic_binding_info,
+ })
+ }
+
+ fn create_pipeline_layout(
+ &self,
+ self_id: id::DeviceId,
+ desc: &binding_model::PipelineLayoutDescriptor,
+ bgl_guard: &Storage<binding_model::BindGroupLayout<B>, id::BindGroupLayoutId>,
+ ) -> Result<binding_model::PipelineLayout<B>, binding_model::CreatePipelineLayoutError> {
+ use crate::binding_model::CreatePipelineLayoutError as Error;
+
+ let bind_group_layouts_count = desc.bind_group_layouts.len();
+ let device_max_bind_groups = self.limits.max_bind_groups as usize;
+ if bind_group_layouts_count > device_max_bind_groups {
+ return Err(Error::TooManyGroups {
+ actual: bind_group_layouts_count,
+ max: device_max_bind_groups,
+ });
+ }
+
+ if !desc.push_constant_ranges.is_empty()
+ && !self.features.contains(wgt::Features::PUSH_CONSTANTS)
+ {
+ return Err(Error::MissingFeature(wgt::Features::PUSH_CONSTANTS));
+ }
+ let mut used_stages = wgt::ShaderStage::empty();
+ for (index, pc) in desc.push_constant_ranges.iter().enumerate() {
+ if pc.stages.intersects(used_stages) {
+ return Err(Error::MoreThanOnePushConstantRangePerStage {
+ index,
+ provided: pc.stages,
+ intersected: pc.stages & used_stages,
+ });
+ }
+ used_stages |= pc.stages;
+
+ let device_max_pc_size = self.limits.max_push_constant_size;
+ if device_max_pc_size < pc.range.end {
+ return Err(Error::PushConstantRangeTooLarge {
+ index,
+ range: pc.range.clone(),
+ max: device_max_pc_size,
+ });
+ }
+
+ if pc.range.start % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
+ return Err(Error::MisalignedPushConstantRange {
+ index,
+ bound: pc.range.start,
+ });
+ }
+ if pc.range.end % wgt::PUSH_CONSTANT_ALIGNMENT != 0 {
+ return Err(Error::MisalignedPushConstantRange {
+ index,
+ bound: pc.range.end,
+ });
+ }
+ }
+
+ let mut count_validator = binding_model::BindingTypeMaxCountValidator::default();
+
+ // validate total resource counts
+ for &id in desc.bind_group_layouts.iter() {
+ let bind_group_layout = bgl_guard
+ .get(id)
+ .map_err(|_| Error::InvalidBindGroupLayout(id))?;
+ count_validator.merge(&bind_group_layout.count_validator);
+ }
+ count_validator
+ .validate(&self.limits)
+ .map_err(Error::TooManyBindings)?;
+
+ let descriptor_set_layouts = desc
+ .bind_group_layouts
+ .iter()
+ .map(|&id| &bgl_guard.get(id).unwrap().raw);
+ let push_constants = desc
+ .push_constant_ranges
+ .iter()
+ .map(|pc| (conv::map_shader_stage_flags(pc.stages), pc.range.clone()));
+
+ let raw = unsafe {
+ let raw_layout = self
+ .raw
+ .create_pipeline_layout(descriptor_set_layouts, push_constants)
+ .or(Err(DeviceError::OutOfMemory))?;
+ if let Some(_) = desc.label {
+ //TODO-0.6: needs gfx changes published
+ //self.raw.set_pipeline_layout_name(&mut raw_layout, label);
+ }
+ raw_layout
+ };
+
+ Ok(binding_model::PipelineLayout {
+ raw,
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ bind_group_layout_ids: desc
+ .bind_group_layouts
+ .iter()
+ .map(|&id| {
+ bgl_guard.get(id).unwrap().multi_ref_count.inc();
+ id::Valid(id)
+ })
+ .collect(),
+ push_constant_ranges: desc.push_constant_ranges.iter().cloned().collect(),
+ })
+ }
+
+ //TODO: refactor this. It's the only method of `Device` that registers new objects
+ // (the pipeline layout).
+ fn derive_pipeline_layout<G: GlobalIdentityHandlerFactory>(
+ &self,
+ self_id: id::DeviceId,
+ implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>,
+ mut derived_group_layouts: ArrayVec<[binding_model::BindEntryMap; MAX_BIND_GROUPS]>,
+ bgl_guard: &mut Storage<binding_model::BindGroupLayout<B>, id::BindGroupLayoutId>,
+ pipeline_layout_guard: &mut Storage<binding_model::PipelineLayout<B>, id::PipelineLayoutId>,
+ hub: &Hub<B, G>,
+ ) -> Result<
+ (id::PipelineLayoutId, pipeline::ImplicitBindGroupCount),
+ pipeline::ImplicitLayoutError,
+ > {
+ let derived_bind_group_count =
+ derived_group_layouts.len() as pipeline::ImplicitBindGroupCount;
+
+ while derived_group_layouts
+ .last()
+ .map_or(false, |map| map.is_empty())
+ {
+ derived_group_layouts.pop();
+ }
+ let ids = implicit_pipeline_ids
+ .as_ref()
+ .ok_or(pipeline::ImplicitLayoutError::MissingIds(0))?;
+ if ids.group_ids.len() < derived_group_layouts.len() {
+ tracing::error!(
+ "Not enough bind group IDs ({}) specified for the implicit layout ({})",
+ ids.group_ids.len(),
+ derived_group_layouts.len()
+ );
+ return Err(pipeline::ImplicitLayoutError::MissingIds(
+ derived_bind_group_count,
+ ));
+ }
+
+ let mut derived_group_layout_ids =
+ ArrayVec::<[id::BindGroupLayoutId; MAX_BIND_GROUPS]>::new();
+ for (bgl_id, map) in ids.group_ids.iter().zip(derived_group_layouts) {
+ let processed_id = match Device::deduplicate_bind_group_layout(self_id, &map, bgl_guard)
+ {
+ Some(dedup_id) => dedup_id,
+ None => {
+ #[cfg(feature = "trace")]
+ let bgl_desc = binding_model::BindGroupLayoutDescriptor {
+ label: None,
+ entries: if self.trace.is_some() {
+ Cow::Owned(map.values().cloned().collect())
+ } else {
+ Cow::Borrowed(&[])
+ },
+ };
+ let bgl = self.create_bind_group_layout(self_id, None, map)?;
+ let out_id = hub.bind_group_layouts.register_identity_locked(
+ bgl_id.clone(),
+ bgl,
+ bgl_guard,
+ );
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = self.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreateBindGroupLayout(out_id.0, bgl_desc));
+ }
+ out_id.0
+ }
+ };
+ derived_group_layout_ids.push(processed_id);
+ }
+
+ let layout_desc = binding_model::PipelineLayoutDescriptor {
+ label: None,
+ bind_group_layouts: Cow::Borrowed(&derived_group_layout_ids),
+ push_constant_ranges: Cow::Borrowed(&[]), //TODO?
+ };
+ let layout = self.create_pipeline_layout(self_id, &layout_desc, bgl_guard)?;
+ let layout_id = hub.pipeline_layouts.register_identity_locked(
+ ids.root_id.clone(),
+ layout,
+ pipeline_layout_guard,
+ );
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = self.trace {
+ trace.lock().add(trace::Action::CreatePipelineLayout(
+ layout_id.0,
+ layout_desc,
+ ));
+ }
+ Ok((layout_id.0, derived_bind_group_count))
+ }
+
+ fn create_compute_pipeline<G: GlobalIdentityHandlerFactory>(
+ &self,
+ self_id: id::DeviceId,
+ desc: &pipeline::ComputePipelineDescriptor,
+ implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>,
+ hub: &Hub<B, G>,
+ token: &mut Token<Self>,
+ ) -> Result<
+ (
+ pipeline::ComputePipeline<B>,
+ pipeline::ImplicitBindGroupCount,
+ id::PipelineLayoutId,
+ ),
+ pipeline::CreateComputePipelineError,
+ > {
+ //TODO: only lock mutable if the layout is derived
+ let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(token);
+ let (mut bgl_guard, mut token) = hub.bind_group_layouts.write(&mut token);
+
+ let mut derived_group_layouts =
+ ArrayVec::<[binding_model::BindEntryMap; MAX_BIND_GROUPS]>::new();
+
+ let interface = validation::StageInterface::default();
+ let pipeline_stage = &desc.compute_stage;
+ let (shader_module_guard, _) = hub.shader_modules.read(&mut token);
+
+ let entry_point_name = &pipeline_stage.entry_point;
+ let shader_module = shader_module_guard
+ .get(pipeline_stage.module)
+ .map_err(|_| {
+ pipeline::CreateComputePipelineError::Stage(validation::StageError::InvalidModule)
+ })?;
+
+ let flag = wgt::ShaderStage::COMPUTE;
+ if let Some(ref module) = shader_module.module {
+ let group_layouts = match desc.layout {
+ Some(pipeline_layout_id) => Device::get_introspection_bind_group_layouts(
+ pipeline_layout_guard
+ .get(pipeline_layout_id)
+ .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?,
+ &*bgl_guard,
+ ),
+ None => {
+ for _ in 0..self.limits.max_bind_groups {
+ derived_group_layouts.push(binding_model::BindEntryMap::default());
+ }
+ validation::IntrospectionBindGroupLayouts::Derived(&mut derived_group_layouts)
+ }
+ };
+ let _ =
+ validation::check_stage(module, group_layouts, &entry_point_name, flag, interface)
+ .map_err(pipeline::CreateComputePipelineError::Stage)?;
+ } else if desc.layout.is_none() {
+ return Err(pipeline::ImplicitLayoutError::ReflectionError(flag).into());
+ }
+
+ let shader = hal::pso::EntryPoint::<B> {
+ entry: &entry_point_name, // TODO
+ module: &shader_module.raw,
+ specialization: hal::pso::Specialization::EMPTY,
+ };
+
+ // TODO
+ let flags = hal::pso::PipelineCreationFlags::empty();
+ // TODO
+ let parent = hal::pso::BasePipeline::None;
+
+ let (pipeline_layout_id, derived_bind_group_count) = match desc.layout {
+ Some(id) => (id, 0),
+ None => self.derive_pipeline_layout(
+ self_id,
+ implicit_pipeline_ids,
+ derived_group_layouts,
+ &mut *bgl_guard,
+ &mut *pipeline_layout_guard,
+ &hub,
+ )?,
+ };
+ let layout = pipeline_layout_guard
+ .get(pipeline_layout_id)
+ .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?;
+
+ let pipeline_desc = hal::pso::ComputePipelineDesc {
+ shader,
+ layout: &layout.raw,
+ flags,
+ parent,
+ };
+
+ let raw = match unsafe { self.raw.create_compute_pipeline(&pipeline_desc, None) } {
+ Ok(pipeline) => pipeline,
+ Err(hal::pso::CreationError::OutOfMemory(_)) => {
+ return Err(pipeline::CreateComputePipelineError::Device(
+ DeviceError::OutOfMemory,
+ ))
+ }
+ other => panic!("Compute pipeline creation error: {:?}", other),
+ };
+ if let Some(_) = desc.label {
+ //TODO-0.6: self.raw.set_compute_pipeline_name(&mut raw, label);
+ }
+
+ let pipeline = pipeline::ComputePipeline {
+ raw,
+ layout_id: Stored {
+ value: id::Valid(pipeline_layout_id),
+ ref_count: layout.life_guard.add_ref(),
+ },
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ };
+ Ok((pipeline, derived_bind_group_count, pipeline_layout_id))
+ }
+
+ fn create_render_pipeline<G: GlobalIdentityHandlerFactory>(
+ &self,
+ self_id: id::DeviceId,
+ desc: &pipeline::RenderPipelineDescriptor,
+ implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>,
+ hub: &Hub<B, G>,
+ token: &mut Token<Self>,
+ ) -> Result<
+ (
+ pipeline::RenderPipeline<B>,
+ pipeline::ImplicitBindGroupCount,
+ id::PipelineLayoutId,
+ ),
+ pipeline::CreateRenderPipelineError,
+ > {
+ //TODO: only lock mutable if the layout is derived
+ let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(token);
+ let (mut bgl_guard, mut token) = hub.bind_group_layouts.write(&mut token);
+
+ let mut derived_group_layouts =
+ ArrayVec::<[binding_model::BindEntryMap; MAX_BIND_GROUPS]>::new();
+
+ let samples = {
+ let sc = desc.sample_count;
+ if sc == 0 || sc > 32 || !conv::is_power_of_two(sc) {
+ return Err(pipeline::CreateRenderPipelineError::InvalidSampleCount(sc));
+ }
+ sc as u8
+ };
+
+ let color_states = &desc.color_states;
+ let depth_stencil_state = desc.depth_stencil_state.as_ref();
+
+ let rasterization_state = desc
+ .rasterization_state
+ .as_ref()
+ .cloned()
+ .unwrap_or_default();
+ let rasterizer = conv::map_rasterization_state_descriptor(&rasterization_state);
+
+ let mut interface = validation::StageInterface::default();
+ let mut validated_stages = wgt::ShaderStage::empty();
+
+ let desc_vbs = &desc.vertex_state.vertex_buffers;
+ let mut vertex_strides = Vec::with_capacity(desc_vbs.len());
+ let mut vertex_buffers = Vec::with_capacity(desc_vbs.len());
+ let mut attributes = Vec::new();
+ for (i, vb_state) in desc_vbs.iter().enumerate() {
+ vertex_strides
+ .alloc()
+ .init((vb_state.stride, vb_state.step_mode));
+ if vb_state.attributes.is_empty() {
+ continue;
+ }
+ if vb_state.stride % wgt::VERTEX_STRIDE_ALIGNMENT != 0 {
+ return Err(pipeline::CreateRenderPipelineError::UnalignedVertexStride {
+ index: i as u32,
+ stride: vb_state.stride,
+ });
+ }
+ vertex_buffers.alloc().init(hal::pso::VertexBufferDesc {
+ binding: i as u32,
+ stride: vb_state.stride as u32,
+ rate: match vb_state.step_mode {
+ InputStepMode::Vertex => hal::pso::VertexInputRate::Vertex,
+ InputStepMode::Instance => hal::pso::VertexInputRate::Instance(1),
+ },
+ });
+ let desc_atts = &vb_state.attributes;
+ for attribute in desc_atts.iter() {
+ if attribute.offset >= 0x10000000 {
+ return Err(
+ pipeline::CreateRenderPipelineError::InvalidVertexAttributeOffset {
+ location: attribute.shader_location,
+ offset: attribute.offset,
+ },
+ );
+ }
+ attributes.alloc().init(hal::pso::AttributeDesc {
+ location: attribute.shader_location,
+ binding: i as u32,
+ element: hal::pso::Element {
+ format: conv::map_vertex_format(attribute.format),
+ offset: attribute.offset as u32,
+ },
+ });
+ interface.insert(
+ attribute.shader_location,
+ validation::MaybeOwned::Owned(validation::map_vertex_format(attribute.format)),
+ );
+ }
+ }
+
+ let input_assembler = hal::pso::InputAssemblerDesc {
+ primitive: conv::map_primitive_topology(desc.primitive_topology),
+ with_adjacency: false,
+ restart_index: None, //TODO
+ };
+
+ let blender = hal::pso::BlendDesc {
+ logic_op: None, // TODO
+ targets: color_states
+ .iter()
+ .map(conv::map_color_state_descriptor)
+ .collect(),
+ };
+ let depth_stencil = depth_stencil_state
+ .map(conv::map_depth_stencil_state_descriptor)
+ .unwrap_or_default();
+
+ let multisampling: Option<hal::pso::Multisampling> = if samples == 1 {
+ None
+ } else {
+ Some(hal::pso::Multisampling {
+ rasterization_samples: samples,
+ sample_shading: None,
+ sample_mask: desc.sample_mask as u64,
+ alpha_coverage: desc.alpha_to_coverage_enabled,
+ alpha_to_one: false,
+ })
+ };
+
+ // TODO
+ let baked_states = hal::pso::BakedStates {
+ viewport: None,
+ scissor: None,
+ blend_color: None,
+ depth_bounds: None,
+ };
+
+ if rasterization_state.clamp_depth && !self.features.contains(wgt::Features::DEPTH_CLAMPING)
+ {
+ return Err(pipeline::CreateRenderPipelineError::MissingFeature(
+ wgt::Features::DEPTH_CLAMPING,
+ ));
+ }
+ if rasterization_state.polygon_mode != wgt::PolygonMode::Fill
+ && !self.features.contains(wgt::Features::NON_FILL_POLYGON_MODE)
+ {
+ return Err(pipeline::CreateRenderPipelineError::MissingFeature(
+ wgt::Features::NON_FILL_POLYGON_MODE,
+ ));
+ }
+
+ if desc.layout.is_none() {
+ for _ in 0..self.limits.max_bind_groups {
+ derived_group_layouts.push(binding_model::BindEntryMap::default());
+ }
+ }
+
+ let (shader_module_guard, _) = hub.shader_modules.read(&mut token);
+
+ let rp_key = RenderPassKey {
+ colors: color_states
+ .iter()
+ .map(|state| {
+ let at = hal::pass::Attachment {
+ format: Some(conv::map_texture_format(
+ state.format,
+ self.private_features,
+ )),
+ samples,
+ ops: hal::pass::AttachmentOps::PRESERVE,
+ stencil_ops: hal::pass::AttachmentOps::DONT_CARE,
+ layouts: hal::image::Layout::General..hal::image::Layout::General,
+ };
+ (at, hal::image::Layout::ColorAttachmentOptimal)
+ })
+ .collect(),
+ // We can ignore the resolves as the vulkan specs says:
+ // As an additional special case, if two render passes have a single subpass,
+ // they are compatible even if they have different resolve attachment references
+ // or depth/stencil resolve modes but satisfy the other compatibility conditions.
+ resolves: ArrayVec::new(),
+ depth_stencil: depth_stencil_state.map(|state| {
+ let at = hal::pass::Attachment {
+ format: Some(conv::map_texture_format(
+ state.format,
+ self.private_features,
+ )),
+ samples,
+ ops: hal::pass::AttachmentOps::PRESERVE,
+ stencil_ops: hal::pass::AttachmentOps::PRESERVE,
+ layouts: hal::image::Layout::General..hal::image::Layout::General,
+ };
+ (at, hal::image::Layout::DepthStencilAttachmentOptimal)
+ }),
+ };
+
+ let vertex = {
+ let entry_point_name = &desc.vertex_stage.entry_point;
+ let flag = wgt::ShaderStage::VERTEX;
+
+ let shader_module =
+ shader_module_guard
+ .get(desc.vertex_stage.module)
+ .map_err(|_| pipeline::CreateRenderPipelineError::Stage {
+ flag,
+ error: validation::StageError::InvalidModule,
+ })?;
+
+ if let Some(ref module) = shader_module.module {
+ let group_layouts = match desc.layout {
+ Some(pipeline_layout_id) => Device::get_introspection_bind_group_layouts(
+ pipeline_layout_guard
+ .get(pipeline_layout_id)
+ .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?,
+ &*bgl_guard,
+ ),
+ None => validation::IntrospectionBindGroupLayouts::Derived(
+ &mut derived_group_layouts,
+ ),
+ };
+
+ interface = validation::check_stage(
+ module,
+ group_layouts,
+ &entry_point_name,
+ flag,
+ interface,
+ )
+ .map_err(|error| pipeline::CreateRenderPipelineError::Stage { flag, error })?;
+ validated_stages |= flag;
+ }
+
+ hal::pso::EntryPoint::<B> {
+ entry: &entry_point_name, // TODO
+ module: &shader_module.raw,
+ specialization: hal::pso::Specialization::EMPTY,
+ }
+ };
+
+ let fragment = match &desc.fragment_stage {
+ Some(stage) => {
+ let entry_point_name = &stage.entry_point;
+ let flag = wgt::ShaderStage::FRAGMENT;
+
+ let shader_module = shader_module_guard.get(stage.module).map_err(|_| {
+ pipeline::CreateRenderPipelineError::Stage {
+ flag,
+ error: validation::StageError::InvalidModule,
+ }
+ })?;
+
+ let group_layouts = match desc.layout {
+ Some(pipeline_layout_id) => Device::get_introspection_bind_group_layouts(
+ pipeline_layout_guard
+ .get(pipeline_layout_id)
+ .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?,
+ &*bgl_guard,
+ ),
+ None => validation::IntrospectionBindGroupLayouts::Derived(
+ &mut derived_group_layouts,
+ ),
+ };
+
+ if validated_stages == wgt::ShaderStage::VERTEX {
+ if let Some(ref module) = shader_module.module {
+ interface = validation::check_stage(
+ module,
+ group_layouts,
+ &entry_point_name,
+ flag,
+ interface,
+ )
+ .map_err(|error| {
+ pipeline::CreateRenderPipelineError::Stage { flag, error }
+ })?;
+ validated_stages |= flag;
+ }
+ }
+
+ Some(hal::pso::EntryPoint::<B> {
+ entry: &entry_point_name,
+ module: &shader_module.raw,
+ specialization: hal::pso::Specialization::EMPTY,
+ })
+ }
+ None => None,
+ };
+
+ if validated_stages.contains(wgt::ShaderStage::FRAGMENT) {
+ for (i, state) in color_states.iter().enumerate() {
+ match interface.get(&(i as wgt::ShaderLocation)) {
+ Some(output) if validation::check_texture_format(state.format, output) => {}
+ Some(output) => {
+ tracing::warn!(
+ "Incompatible fragment output[{}] from shader: {:?}, expected {:?}",
+ i,
+ &**output,
+ state.format,
+ );
+ return Err(
+ pipeline::CreateRenderPipelineError::IncompatibleOutputFormat {
+ index: i as u8,
+ },
+ );
+ }
+ None if state.write_mask.is_empty() => {}
+ None => {
+ tracing::warn!("Missing fragment output[{}], expected {:?}", i, state,);
+ return Err(pipeline::CreateRenderPipelineError::MissingOutput {
+ index: i as u8,
+ });
+ }
+ }
+ }
+ }
+ let last_stage = match desc.fragment_stage {
+ Some(_) => wgt::ShaderStage::FRAGMENT,
+ None => wgt::ShaderStage::VERTEX,
+ };
+ if desc.layout.is_none() && !validated_stages.contains(last_stage) {
+ return Err(pipeline::ImplicitLayoutError::ReflectionError(last_stage).into());
+ }
+
+ let primitive_assembler = hal::pso::PrimitiveAssemblerDesc::Vertex {
+ buffers: &vertex_buffers,
+ attributes: &attributes,
+ input_assembler,
+ vertex,
+ tessellation: None,
+ geometry: None,
+ };
+
+ // TODO
+ let flags = hal::pso::PipelineCreationFlags::empty();
+ // TODO
+ let parent = hal::pso::BasePipeline::None;
+
+ let (pipeline_layout_id, derived_bind_group_count) = match desc.layout {
+ Some(id) => (id, 0),
+ None => self.derive_pipeline_layout(
+ self_id,
+ implicit_pipeline_ids,
+ derived_group_layouts,
+ &mut *bgl_guard,
+ &mut *pipeline_layout_guard,
+ &hub,
+ )?,
+ };
+ let layout = pipeline_layout_guard
+ .get(pipeline_layout_id)
+ .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?;
+
+ let mut render_pass_cache = self.render_passes.lock();
+ let pipeline_desc = hal::pso::GraphicsPipelineDesc {
+ primitive_assembler,
+ rasterizer,
+ fragment,
+ blender,
+ depth_stencil,
+ multisampling,
+ baked_states,
+ layout: &layout.raw,
+ subpass: hal::pass::Subpass {
+ index: 0,
+ main_pass: match render_pass_cache.entry(rp_key) {
+ Entry::Occupied(e) => e.into_mut(),
+ Entry::Vacant(e) => {
+ let pass = self
+ .create_compatible_render_pass(e.key())
+ .or(Err(DeviceError::OutOfMemory))?;
+ e.insert(pass)
+ }
+ },
+ },
+ flags,
+ parent,
+ };
+ // TODO: cache
+ let raw = unsafe {
+ self.raw
+ .create_graphics_pipeline(&pipeline_desc, None)
+ .map_err(|err| match err {
+ hal::pso::CreationError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ _ => panic!("failed to create graphics pipeline: {}", err),
+ })?
+ };
+ if let Some(_) = desc.label {
+ //TODO-0.6: self.set_graphics_pipeline_name(&mut raw, label)
+ }
+
+ let pass_context = RenderPassContext {
+ attachments: AttachmentData {
+ colors: color_states.iter().map(|state| state.format).collect(),
+ resolves: ArrayVec::new(),
+ depth_stencil: depth_stencil_state
+ .as_ref()
+ .map(|state| state.format.clone()),
+ },
+ sample_count: samples,
+ };
+
+ let mut flags = pipeline::PipelineFlags::empty();
+ for state in color_states.iter() {
+ if state.color_blend.uses_color() | state.alpha_blend.uses_color() {
+ flags |= pipeline::PipelineFlags::BLEND_COLOR;
+ }
+ }
+ if let Some(ds) = depth_stencil_state.as_ref() {
+ if ds.stencil.is_enabled() && ds.stencil.needs_ref_value() {
+ flags |= pipeline::PipelineFlags::STENCIL_REFERENCE;
+ }
+ if !ds.is_read_only() {
+ flags |= pipeline::PipelineFlags::WRITES_DEPTH_STENCIL;
+ }
+ }
+
+ let pipeline = pipeline::RenderPipeline {
+ raw,
+ layout_id: Stored {
+ value: id::Valid(pipeline_layout_id),
+ ref_count: layout.life_guard.add_ref(),
+ },
+ device_id: Stored {
+ value: id::Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ pass_context,
+ flags,
+ index_format: desc.vertex_state.index_format,
+ vertex_strides,
+ life_guard: LifeGuard::new(desc.label.borrow_or_default()),
+ };
+ Ok((pipeline, derived_bind_group_count, pipeline_layout_id))
+ }
+
+ fn wait_for_submit(
+ &self,
+ submission_index: SubmissionIndex,
+ token: &mut Token<Self>,
+ ) -> Result<(), WaitIdleError> {
+ if self.last_completed_submission_index() <= submission_index {
+ tracing::info!("Waiting for submission {:?}", submission_index);
+ self.lock_life(token)
+ .triage_submissions(&self.raw, true)
+ .map(|_| ())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl<B: hal::Backend> Device<B> {
+ pub(crate) fn destroy_bind_group(&self, bind_group: binding_model::BindGroup<B>) {
+ self.desc_allocator
+ .lock()
+ .free(&self.raw, iter::once(bind_group.raw));
+ }
+
+ pub(crate) fn destroy_buffer(&self, buffer: resource::Buffer<B>) {
+ if let Some((raw, memory)) = buffer.raw {
+ unsafe {
+ self.mem_allocator.lock().free(&self.raw, memory);
+ self.raw.destroy_buffer(raw);
+ }
+ }
+ }
+
+ pub(crate) fn destroy_texture(&self, texture: resource::Texture<B>) {
+ if let Some((raw, memory)) = texture.raw {
+ unsafe {
+ self.mem_allocator.lock().free(&self.raw, memory);
+ self.raw.destroy_image(raw);
+ }
+ }
+ }
+
+ /// Wait for idle and remove resources that we can, before we die.
+ pub(crate) fn prepare_to_die(&mut self) {
+ let mut life_tracker = self.life_tracker.lock();
+ if let Err(error) = life_tracker.triage_submissions(&self.raw, true) {
+ tracing::error!("failed to triage submissions: {}", error);
+ }
+ life_tracker.cleanup(&self.raw, &self.mem_allocator, &self.desc_allocator);
+ }
+
+ pub(crate) fn dispose(self) {
+ let mut desc_alloc = self.desc_allocator.into_inner();
+ let mut mem_alloc = self.mem_allocator.into_inner();
+ self.pending_writes
+ .dispose(&self.raw, &self.cmd_allocator, &mut mem_alloc);
+ self.cmd_allocator.destroy(&self.raw);
+ unsafe {
+ desc_alloc.cleanup(&self.raw);
+ mem_alloc.clear(&self.raw);
+ for (_, rp) in self.render_passes.lock().drain() {
+ self.raw.destroy_render_pass(rp);
+ }
+ for (_, fbo) in self.framebuffers.lock().drain() {
+ self.raw.destroy_framebuffer(fbo);
+ }
+ }
+ }
+}
+
+impl<B: hal::Backend> crate::hub::Resource for Device<B> {
+ const TYPE: &'static str = "Device";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+#[error("device is invalid")]
+#[derive(Clone, Debug, Error)]
+pub struct InvalidDevice;
+
+#[derive(Clone, Debug, Error)]
+pub enum DeviceError {
+ #[error("parent device is invalid")]
+ Invalid,
+ #[error("parent device is lost")]
+ Lost,
+ #[error("not enough memory left")]
+ OutOfMemory,
+}
+
+impl From<hal::device::OomOrDeviceLost> for DeviceError {
+ fn from(err: hal::device::OomOrDeviceLost) -> Self {
+ match err {
+ hal::device::OomOrDeviceLost::OutOfMemory(_) => Self::OutOfMemory,
+ hal::device::OomOrDeviceLost::DeviceLost(_) => Self::Lost,
+ }
+ }
+}
+
+impl From<gpu_alloc::MapError> for DeviceError {
+ fn from(err: gpu_alloc::MapError) -> Self {
+ match err {
+ gpu_alloc::MapError::OutOfDeviceMemory | gpu_alloc::MapError::OutOfHostMemory => {
+ DeviceError::OutOfMemory
+ }
+ _ => panic!("failed to map buffer: {}", err),
+ }
+ }
+}
+
+impl DeviceError {
+ fn from_bind(err: hal::device::BindError) -> Self {
+ match err {
+ hal::device::BindError::OutOfMemory(_) => Self::OutOfMemory,
+ _ => panic!("failed to bind memory: {}", err),
+ }
+ }
+}
+
+pub struct ImplicitPipelineIds<'a, G: GlobalIdentityHandlerFactory> {
+ pub root_id: Input<G, id::PipelineLayoutId>,
+ pub group_ids: &'a [Input<G, id::BindGroupLayoutId>],
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn device_features<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ ) -> Result<wgt::Features, InvalidDevice> {
+ span!(_guard, INFO, "Device::features");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, _) = hub.devices.read(&mut token);
+ let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
+
+ Ok(device.features)
+ }
+
+ pub fn device_limits<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ ) -> Result<wgt::Limits, InvalidDevice> {
+ span!(_guard, INFO, "Device::limits");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, _) = hub.devices.read(&mut token);
+ let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
+
+ Ok(device.limits.clone())
+ }
+
+ pub fn device_create_buffer<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &resource::BufferDescriptor,
+ id_in: Input<G, id::BufferId>,
+ ) -> (id::BufferId, Option<resource::CreateBufferError>) {
+ span!(_guard, INFO, "Device::create_buffer");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ tracing::info!("Create buffer {:?} with ID {:?}", desc, id_in);
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let mut buffer = match device.create_buffer(device_id, desc, false) {
+ Ok(buffer) => buffer,
+ Err(e) => break e,
+ };
+ let ref_count = buffer.life_guard.add_ref();
+
+ let buffer_use = if !desc.mapped_at_creation {
+ resource::BufferUse::EMPTY
+ } else if desc.usage.contains(wgt::BufferUsage::MAP_WRITE) {
+ // buffer is mappable, so we are just doing that at start
+ let map_size = buffer.size;
+ let ptr = match map_buffer(&device.raw, &mut buffer, 0, map_size, HostMap::Write) {
+ Ok(ptr) => ptr,
+ Err(e) => {
+ let (raw, memory) = buffer.raw.unwrap();
+ device.lock_life(&mut token).schedule_resource_destruction(
+ queue::TempResource::Buffer(raw),
+ memory,
+ !0,
+ );
+ break e.into();
+ }
+ };
+ buffer.map_state = resource::BufferMapState::Active {
+ ptr,
+ sub_range: hal::buffer::SubRange::WHOLE,
+ host: HostMap::Write,
+ };
+ resource::BufferUse::MAP_WRITE
+ } else {
+ // buffer needs staging area for initialization only
+ let stage_desc = wgt::BufferDescriptor {
+ label: Some(Cow::Borrowed("<init_buffer>")),
+ size: desc.size,
+ usage: wgt::BufferUsage::MAP_WRITE | wgt::BufferUsage::COPY_SRC,
+ mapped_at_creation: false,
+ };
+ let stage = match device.create_buffer(device_id, &stage_desc, true) {
+ Ok(stage) => stage,
+ Err(e) => {
+ let (raw, memory) = buffer.raw.unwrap();
+ device.lock_life(&mut token).schedule_resource_destruction(
+ queue::TempResource::Buffer(raw),
+ memory,
+ !0,
+ );
+ break e;
+ }
+ };
+ let (stage_buffer, mut stage_memory) = stage.raw.unwrap();
+ let ptr = match stage_memory.map(&device.raw, 0, stage.size) {
+ Ok(ptr) => ptr,
+ Err(e) => {
+ let (raw, memory) = buffer.raw.unwrap();
+ let mut life_lock = device.lock_life(&mut token);
+ life_lock.schedule_resource_destruction(
+ queue::TempResource::Buffer(raw),
+ memory,
+ !0,
+ );
+ life_lock.schedule_resource_destruction(
+ queue::TempResource::Buffer(stage_buffer),
+ stage_memory,
+ !0,
+ );
+ break e.into();
+ }
+ };
+ buffer.map_state = resource::BufferMapState::Init {
+ ptr,
+ needs_flush: !stage_memory.is_coherent(),
+ stage_buffer,
+ stage_memory,
+ };
+ resource::BufferUse::COPY_DST
+ };
+
+ let id = hub.buffers.register_identity(id_in, buffer, &mut token);
+ tracing::info!("Created buffer {:?} with {:?}", id, desc);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut desc = desc.clone();
+ let mapped_at_creation = mem::replace(&mut desc.mapped_at_creation, false);
+ if mapped_at_creation && !desc.usage.contains(wgt::BufferUsage::MAP_WRITE) {
+ desc.usage |= wgt::BufferUsage::COPY_DST;
+ }
+ trace.lock().add(trace::Action::CreateBuffer(id.0, desc));
+ }
+
+ device
+ .trackers
+ .lock()
+ .buffers
+ .init(id, ref_count, BufferState::with_usage(buffer_use))
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .buffers
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn device_wait_for_buffer<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ buffer_id: id::BufferId,
+ ) -> Result<(), WaitIdleError> {
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let last_submission = {
+ let (buffer_guard, _) = hub.buffers.write(&mut token);
+ match buffer_guard.get(buffer_id) {
+ Ok(buffer) => buffer.life_guard.submission_index.load(Ordering::Acquire),
+ Err(_) => return Ok(()),
+ }
+ };
+
+ device_guard
+ .get(device_id)
+ .map_err(|_| DeviceError::Invalid)?
+ .wait_for_submit(last_submission, &mut token)
+ }
+
+ pub fn device_set_buffer_sub_data<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ data: &[u8],
+ ) -> Result<(), resource::BufferAccessError> {
+ span!(_guard, INFO, "Device::set_buffer_sub_data");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ let device = device_guard
+ .get(device_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let buffer = buffer_guard
+ .get_mut(buffer_id)
+ .map_err(|_| resource::BufferAccessError::Invalid)?;
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::MAP_WRITE)?;
+ //assert!(buffer isn't used by the GPU);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let data_path = trace.make_binary("bin", data);
+ trace.add(trace::Action::WriteBuffer {
+ id: buffer_id,
+ data: data_path,
+ range: offset..offset + data.len() as BufferAddress,
+ queued: false,
+ });
+ }
+
+ let (_, block) = buffer.raw.as_mut().unwrap();
+ block.write_bytes(&device.raw, offset, data)?;
+
+ Ok(())
+ }
+
+ pub fn device_get_buffer_sub_data<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ data: &mut [u8],
+ ) -> Result<(), resource::BufferAccessError> {
+ span!(_guard, INFO, "Device::get_buffer_sub_data");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ let device = device_guard
+ .get(device_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let buffer = buffer_guard
+ .get_mut(buffer_id)
+ .map_err(|_| resource::BufferAccessError::Invalid)?;
+ check_buffer_usage(buffer.usage, wgt::BufferUsage::MAP_READ)?;
+ //assert!(buffer isn't used by the GPU);
+
+ let (_, block) = buffer.raw.as_mut().unwrap();
+ block.read_bytes(&device.raw, offset, data)?;
+
+ Ok(())
+ }
+
+ pub fn buffer_label<B: GfxBackend>(&self, id: id::BufferId) -> String {
+ B::hub(self).buffers.label_for_resource(id)
+ }
+
+ pub fn buffer_destroy<B: GfxBackend>(
+ &self,
+ buffer_id: id::BufferId,
+ ) -> Result<(), resource::DestroyError> {
+ span!(_guard, INFO, "Buffer::destroy");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ //TODO: lock pending writes separately, keep the device read-only
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+
+ tracing::info!("Buffer {:?} is destroyed", buffer_id);
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ let buffer = buffer_guard
+ .get_mut(buffer_id)
+ .map_err(|_| resource::DestroyError::Invalid)?;
+
+ let device = &mut device_guard[buffer.device_id.value];
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(trace::Action::FreeBuffer(buffer_id));
+ }
+
+ let (raw, memory) = buffer
+ .raw
+ .take()
+ .ok_or(resource::DestroyError::AlreadyDestroyed)?;
+ let temp = queue::TempResource::Buffer(raw);
+
+ if device.pending_writes.dst_buffers.contains(&buffer_id) {
+ device.pending_writes.temp_resources.push((temp, memory));
+ } else {
+ let last_submit_index = buffer.life_guard.submission_index.load(Ordering::Acquire);
+ drop(buffer_guard);
+ device.lock_life(&mut token).schedule_resource_destruction(
+ temp,
+ memory,
+ last_submit_index,
+ );
+ }
+
+ Ok(())
+ }
+
+ pub fn buffer_drop<B: GfxBackend>(&self, buffer_id: id::BufferId, wait: bool) {
+ span!(_guard, INFO, "Buffer::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ tracing::info!("Buffer {:?} is dropped", buffer_id);
+ let (ref_count, last_submit_index, device_id) = {
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ match buffer_guard.get_mut(buffer_id) {
+ Ok(buffer) => {
+ let ref_count = buffer.life_guard.ref_count.take().unwrap();
+ let last_submit_index =
+ buffer.life_guard.submission_index.load(Ordering::Acquire);
+ (ref_count, last_submit_index, buffer.device_id.value)
+ }
+ Err(InvalidId) => {
+ hub.buffers.unregister_locked(buffer_id, &mut *buffer_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let device = &device_guard[device_id];
+ {
+ let mut life_lock = device.lock_life(&mut token);
+ if device.pending_writes.dst_buffers.contains(&buffer_id) {
+ life_lock.future_suspected_buffers.push(Stored {
+ value: id::Valid(buffer_id),
+ ref_count,
+ });
+ } else {
+ drop(ref_count);
+ life_lock
+ .suspected_resources
+ .buffers
+ .push(id::Valid(buffer_id));
+ }
+ }
+
+ if wait {
+ match device.wait_for_submit(last_submit_index, &mut token) {
+ Ok(()) => (),
+ Err(e) => tracing::error!("Failed to wait for buffer {:?}: {:?}", buffer_id, e),
+ }
+ }
+ }
+
+ pub fn device_create_texture<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &resource::TextureDescriptor,
+ id_in: Input<G, id::TextureId>,
+ ) -> (id::TextureId, Option<resource::CreateTextureError>) {
+ span!(_guard, INFO, "Device::create_texture");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let texture = match device.create_texture(device_id, desc) {
+ Ok(texture) => texture,
+ Err(error) => break error,
+ };
+ let num_levels = texture.full_range.levels.end;
+ let num_layers = texture.full_range.layers.end;
+ let ref_count = texture.life_guard.add_ref();
+
+ let id = hub.textures.register_identity(id_in, texture, &mut token);
+ tracing::info!("Created texture {:?} with {:?}", id, desc);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreateTexture(id.0, desc.clone()));
+ }
+
+ device
+ .trackers
+ .lock()
+ .textures
+ .init(id, ref_count, TextureState::new(num_levels, num_layers))
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .textures
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn texture_label<B: GfxBackend>(&self, id: id::TextureId) -> String {
+ B::hub(self).textures.label_for_resource(id)
+ }
+
+ pub fn texture_destroy<B: GfxBackend>(
+ &self,
+ texture_id: id::TextureId,
+ ) -> Result<(), resource::DestroyError> {
+ span!(_guard, INFO, "Texture::destroy");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ //TODO: lock pending writes separately, keep the device read-only
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+
+ tracing::info!("Buffer {:?} is destroyed", texture_id);
+ let (mut texture_guard, _) = hub.textures.write(&mut token);
+ let texture = texture_guard
+ .get_mut(texture_id)
+ .map_err(|_| resource::DestroyError::Invalid)?;
+
+ let device = &mut device_guard[texture.device_id.value];
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(trace::Action::FreeTexture(texture_id));
+ }
+
+ let (raw, memory) = texture
+ .raw
+ .take()
+ .ok_or(resource::DestroyError::AlreadyDestroyed)?;
+ let temp = queue::TempResource::Image(raw);
+
+ if device.pending_writes.dst_textures.contains(&texture_id) {
+ device.pending_writes.temp_resources.push((temp, memory));
+ } else {
+ let last_submit_index = texture.life_guard.submission_index.load(Ordering::Acquire);
+ drop(texture_guard);
+ device.lock_life(&mut token).schedule_resource_destruction(
+ temp,
+ memory,
+ last_submit_index,
+ );
+ }
+
+ Ok(())
+ }
+
+ pub fn texture_drop<B: GfxBackend>(&self, texture_id: id::TextureId, wait: bool) {
+ span!(_guard, INFO, "Texture::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (ref_count, last_submit_index, device_id) = {
+ let (mut texture_guard, _) = hub.textures.write(&mut token);
+ match texture_guard.get_mut(texture_id) {
+ Ok(texture) => {
+ let ref_count = texture.life_guard.ref_count.take().unwrap();
+ let last_submit_index =
+ texture.life_guard.submission_index.load(Ordering::Acquire);
+ (ref_count, last_submit_index, texture.device_id.value)
+ }
+ Err(InvalidId) => {
+ hub.textures
+ .unregister_locked(texture_id, &mut *texture_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let device = &device_guard[device_id];
+ {
+ let mut life_lock = device.lock_life(&mut token);
+ if device.pending_writes.dst_textures.contains(&texture_id) {
+ life_lock.future_suspected_textures.push(Stored {
+ value: id::Valid(texture_id),
+ ref_count,
+ });
+ } else {
+ drop(ref_count);
+ life_lock
+ .suspected_resources
+ .textures
+ .push(id::Valid(texture_id));
+ }
+ }
+
+ if wait {
+ match device.wait_for_submit(last_submit_index, &mut token) {
+ Ok(()) => (),
+ Err(e) => tracing::error!("Failed to wait for texture {:?}: {:?}", texture_id, e),
+ }
+ }
+ }
+
+ pub fn texture_create_view<B: GfxBackend>(
+ &self,
+ texture_id: id::TextureId,
+ desc: &resource::TextureViewDescriptor,
+ id_in: Input<G, id::TextureViewId>,
+ ) -> (id::TextureViewId, Option<resource::CreateTextureViewError>) {
+ span!(_guard, INFO, "Texture::create_view");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (texture_guard, mut token) = hub.textures.read(&mut token);
+ let error = loop {
+ let texture = match texture_guard.get(texture_id) {
+ Ok(texture) => texture,
+ Err(_) => break resource::CreateTextureViewError::InvalidTexture,
+ };
+ let device = &device_guard[texture.device_id.value];
+
+ let view = match device.create_texture_view(texture, texture_id, desc) {
+ Ok(view) => view,
+ Err(e) => break e,
+ };
+ let ref_count = view.life_guard.add_ref();
+
+ let id = hub.texture_views.register_identity(id_in, view, &mut token);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(trace::Action::CreateTextureView {
+ id: id.0,
+ parent_id: texture_id,
+ desc: desc.clone(),
+ });
+ }
+
+ device
+ .trackers
+ .lock()
+ .views
+ .init(id, ref_count, PhantomData)
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id =
+ hub.texture_views
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn texture_view_label<B: GfxBackend>(&self, id: id::TextureViewId) -> String {
+ B::hub(self).texture_views.label_for_resource(id)
+ }
+
+ pub fn texture_view_drop<B: GfxBackend>(
+ &self,
+ texture_view_id: id::TextureViewId,
+ ) -> Result<(), resource::TextureViewDestroyError> {
+ span!(_guard, INFO, "TextureView::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let device_id = {
+ let (texture_guard, mut token) = hub.textures.read(&mut token);
+ let (mut texture_view_guard, _) = hub.texture_views.write(&mut token);
+
+ match texture_view_guard.get_mut(texture_view_id) {
+ Ok(view) => {
+ view.life_guard.ref_count.take();
+ match view.inner {
+ resource::TextureViewInner::Native { ref source_id, .. } => {
+ texture_guard[source_id.value].device_id.value
+ }
+ resource::TextureViewInner::SwapChain { .. } => {
+ return Err(resource::TextureViewDestroyError::SwapChainImage)
+ }
+ }
+ }
+ Err(InvalidId) => {
+ hub.texture_views
+ .unregister_locked(texture_view_id, &mut *texture_view_guard);
+ return Ok(());
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .texture_views
+ .push(id::Valid(texture_view_id));
+ Ok(())
+ }
+
+ pub fn device_create_sampler<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &resource::SamplerDescriptor,
+ id_in: Input<G, id::SamplerId>,
+ ) -> (id::SamplerId, Option<resource::CreateSamplerError>) {
+ span!(_guard, INFO, "Device::create_sampler");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+
+ let sampler = match device.create_sampler(device_id, desc) {
+ Ok(sampler) => sampler,
+ Err(e) => break e,
+ };
+ let ref_count = sampler.life_guard.add_ref();
+
+ let id = hub.samplers.register_identity(id_in, sampler, &mut token);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreateSampler(id.0, desc.clone()));
+ }
+
+ device
+ .trackers
+ .lock()
+ .samplers
+ .init(id, ref_count, PhantomData)
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .samplers
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn sampler_label<B: GfxBackend>(&self, id: id::SamplerId) -> String {
+ B::hub(self).samplers.label_for_resource(id)
+ }
+
+ pub fn sampler_drop<B: GfxBackend>(&self, sampler_id: id::SamplerId) {
+ span!(_guard, INFO, "Sampler::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let device_id = {
+ let (mut sampler_guard, _) = hub.samplers.write(&mut token);
+ match sampler_guard.get_mut(sampler_id) {
+ Ok(sampler) => {
+ sampler.life_guard.ref_count.take();
+ sampler.device_id.value
+ }
+ Err(InvalidId) => {
+ hub.samplers
+ .unregister_locked(sampler_id, &mut *sampler_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .samplers
+ .push(id::Valid(sampler_id));
+ }
+
+ pub fn device_create_bind_group_layout<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &binding_model::BindGroupLayoutDescriptor,
+ id_in: Input<G, id::BindGroupLayoutId>,
+ ) -> (
+ id::BindGroupLayoutId,
+ Option<binding_model::CreateBindGroupLayoutError>,
+ ) {
+ span!(_guard, INFO, "Device::create_bind_group_layout");
+
+ let mut token = Token::root();
+ let hub = B::hub(self);
+
+ let error = 'outer: loop {
+ let mut entry_map = FastHashMap::default();
+ for entry in desc.entries.iter() {
+ if entry_map.insert(entry.binding, entry.clone()).is_some() {
+ break 'outer binding_model::CreateBindGroupLayoutError::ConflictBinding(
+ entry.binding,
+ );
+ }
+ }
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+
+ // If there is an equivalent BGL, just bump the refcount and return it.
+ // This is only applicable for identity filters that are generating new IDs,
+ // so their inputs are `PhantomData` of size 0.
+ if mem::size_of::<Input<G, id::BindGroupLayoutId>>() == 0 {
+ let (bgl_guard, _) = hub.bind_group_layouts.read(&mut token);
+ if let Some(id) =
+ Device::deduplicate_bind_group_layout(device_id, &entry_map, &*bgl_guard)
+ {
+ return (id, None);
+ }
+ }
+
+ let layout = match device.create_bind_group_layout(
+ device_id,
+ desc.label.as_ref().map(|cow| cow.as_ref()),
+ entry_map,
+ ) {
+ Ok(layout) => layout,
+ Err(e) => break e,
+ };
+
+ let id = hub
+ .bind_group_layouts
+ .register_identity(id_in, layout, &mut token);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreateBindGroupLayout(id.0, desc.clone()));
+ }
+ return (id.0, None);
+ };
+
+ let id = hub.bind_group_layouts.register_error(
+ id_in,
+ desc.label.borrow_or_default(),
+ &mut token,
+ );
+ (id, Some(error))
+ }
+
+ pub fn bind_group_layout_label<B: GfxBackend>(&self, id: id::BindGroupLayoutId) -> String {
+ B::hub(self).bind_group_layouts.label_for_resource(id)
+ }
+
+ pub fn bind_group_layout_drop<B: GfxBackend>(
+ &self,
+ bind_group_layout_id: id::BindGroupLayoutId,
+ ) {
+ span!(_guard, INFO, "BindGroupLayout::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let device_id = {
+ let (mut bind_group_layout_guard, _) = hub.bind_group_layouts.write(&mut token);
+ match bind_group_layout_guard.get_mut(bind_group_layout_id) {
+ Ok(layout) => layout.device_id.value,
+ Err(InvalidId) => {
+ hub.bind_group_layouts
+ .unregister_locked(bind_group_layout_id, &mut *bind_group_layout_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .bind_group_layouts
+ .push(id::Valid(bind_group_layout_id));
+ }
+
+ pub fn device_create_pipeline_layout<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &binding_model::PipelineLayoutDescriptor,
+ id_in: Input<G, id::PipelineLayoutId>,
+ ) -> (
+ id::PipelineLayoutId,
+ Option<binding_model::CreatePipelineLayoutError>,
+ ) {
+ span!(_guard, INFO, "Device::create_pipeline_layout");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+
+ let layout = {
+ let (bgl_guard, _) = hub.bind_group_layouts.read(&mut token);
+ match device.create_pipeline_layout(device_id, desc, &*bgl_guard) {
+ Ok(layout) => layout,
+ Err(e) => break e,
+ }
+ };
+
+ let id = hub
+ .pipeline_layouts
+ .register_identity(id_in, layout, &mut token);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreatePipelineLayout(id.0, desc.clone()));
+ }
+ return (id.0, None);
+ };
+
+ let id =
+ hub.pipeline_layouts
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn pipeline_layout_label<B: GfxBackend>(&self, id: id::PipelineLayoutId) -> String {
+ B::hub(self).pipeline_layouts.label_for_resource(id)
+ }
+
+ pub fn pipeline_layout_drop<B: GfxBackend>(&self, pipeline_layout_id: id::PipelineLayoutId) {
+ span!(_guard, INFO, "PipelineLayout::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_id, ref_count) = {
+ let (mut pipeline_layout_guard, _) = hub.pipeline_layouts.write(&mut token);
+ match pipeline_layout_guard.get_mut(pipeline_layout_id) {
+ Ok(layout) => (
+ layout.device_id.value,
+ layout.life_guard.ref_count.take().unwrap(),
+ ),
+ Err(InvalidId) => {
+ hub.pipeline_layouts
+ .unregister_locked(pipeline_layout_id, &mut *pipeline_layout_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .pipeline_layouts
+ .push(Stored {
+ value: id::Valid(pipeline_layout_id),
+ ref_count,
+ });
+ }
+
+ pub fn device_create_bind_group<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &binding_model::BindGroupDescriptor,
+ id_in: Input<G, id::BindGroupId>,
+ ) -> (id::BindGroupId, Option<binding_model::CreateBindGroupError>) {
+ span!(_guard, INFO, "Device::create_bind_group");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (bind_group_layout_guard, mut token) = hub.bind_group_layouts.read(&mut token);
+
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let bind_group_layout = match bind_group_layout_guard.get(desc.layout) {
+ Ok(layout) => layout,
+ Err(_) => break binding_model::CreateBindGroupError::InvalidLayout,
+ };
+
+ let bind_group = match device.create_bind_group(
+ device_id,
+ bind_group_layout,
+ desc,
+ &hub,
+ &mut token,
+ ) {
+ Ok(bind_group) => bind_group,
+ Err(e) => break e,
+ };
+ let ref_count = bind_group.life_guard.add_ref();
+
+ let id = hub
+ .bind_groups
+ .register_identity(id_in, bind_group, &mut token);
+ tracing::debug!(
+ "Bind group {:?} {:#?}",
+ id,
+ hub.bind_groups.read(&mut token).0[id].used
+ );
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::CreateBindGroup(id.0, desc.clone()));
+ }
+
+ device
+ .trackers
+ .lock()
+ .bind_groups
+ .init(id, ref_count, PhantomData)
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .bind_groups
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn bind_group_label<B: GfxBackend>(&self, id: id::BindGroupId) -> String {
+ B::hub(self).bind_groups.label_for_resource(id)
+ }
+
+ pub fn bind_group_drop<B: GfxBackend>(&self, bind_group_id: id::BindGroupId) {
+ span!(_guard, INFO, "BindGroup::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let device_id = {
+ let (mut bind_group_guard, _) = hub.bind_groups.write(&mut token);
+ match bind_group_guard.get_mut(bind_group_id) {
+ Ok(bind_group) => {
+ bind_group.life_guard.ref_count.take();
+ bind_group.device_id.value
+ }
+ Err(InvalidId) => {
+ hub.bind_groups
+ .unregister_locked(bind_group_id, &mut *bind_group_guard);
+ return;
+ }
+ }
+ };
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .bind_groups
+ .push(id::Valid(bind_group_id));
+ }
+
+ pub fn device_create_shader_module<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &pipeline::ShaderModuleDescriptor,
+ id_in: Input<G, id::ShaderModuleId>,
+ ) -> (
+ id::ShaderModuleId,
+ Option<pipeline::CreateShaderModuleError>,
+ ) {
+ span!(_guard, INFO, "Device::create_shader_module");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let (shader, spv) = match device.create_shader_module(device_id, desc) {
+ Ok(pair) => pair,
+ Err(e) => break e,
+ };
+
+ let id = hub
+ .shader_modules
+ .register_identity(id_in, shader, &mut token);
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let data = trace.make_binary("spv", unsafe {
+ std::slice::from_raw_parts(spv.as_ptr() as *const u8, spv.len() * 4)
+ });
+ let label = desc.label.clone();
+ trace.add(trace::Action::CreateShaderModule {
+ id: id.0,
+ data,
+ label,
+ });
+ }
+
+ let _ = spv;
+ return (id.0, None);
+ };
+
+ let id =
+ hub.shader_modules
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+
+ pub fn shader_module_label<B: GfxBackend>(&self, id: id::ShaderModuleId) -> String {
+ B::hub(self).shader_modules.label_for_resource(id)
+ }
+
+ pub fn shader_module_drop<B: GfxBackend>(&self, shader_module_id: id::ShaderModuleId) {
+ span!(_guard, INFO, "ShaderModule::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (module, _) = hub.shader_modules.unregister(shader_module_id, &mut token);
+ if let Some(module) = module {
+ let device = &device_guard[module.device_id.value];
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(trace::Action::DestroyShaderModule(shader_module_id));
+ }
+ unsafe {
+ device.raw.destroy_shader_module(module.raw);
+ }
+ }
+ }
+
+ pub fn device_create_command_encoder<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &wgt::CommandEncoderDescriptor<Label>,
+ id_in: Input<G, id::CommandEncoderId>,
+ ) -> (id::CommandEncoderId, Option<command::CommandAllocatorError>) {
+ span!(_guard, INFO, "Device::create_command_encoder");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+
+ let dev_stored = Stored {
+ value: id::Valid(device_id),
+ ref_count: device.life_guard.add_ref(),
+ };
+
+ let mut command_buffer = match device.cmd_allocator.allocate(
+ dev_stored,
+ &device.raw,
+ device.limits.clone(),
+ device.private_features,
+ &desc.label,
+ #[cfg(feature = "trace")]
+ device.trace.is_some(),
+ ) {
+ Ok(cmd_buf) => cmd_buf,
+ Err(e) => break e,
+ };
+
+ unsafe {
+ let raw_command_buffer = command_buffer.raw.last_mut().unwrap();
+ if let Some(ref label) = desc.label {
+ device
+ .raw
+ .set_command_buffer_name(raw_command_buffer, label);
+ }
+ raw_command_buffer.begin_primary(hal::command::CommandBufferFlags::ONE_TIME_SUBMIT);
+ }
+
+ let id = hub
+ .command_buffers
+ .register_identity(id_in, command_buffer, &mut token);
+
+ return (id.0, None);
+ };
+
+ let id = B::hub(self).command_buffers.register_error(
+ id_in,
+ desc.label.borrow_or_default(),
+ &mut token,
+ );
+ (id, Some(error))
+ }
+
+ pub fn command_buffer_label<B: GfxBackend>(&self, id: id::CommandBufferId) -> String {
+ B::hub(self).command_buffers.label_for_resource(id)
+ }
+
+ pub fn command_encoder_drop<B: GfxBackend>(&self, command_encoder_id: id::CommandEncoderId) {
+ span!(_guard, INFO, "CommandEncoder::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let (cmdbuf, _) = hub
+ .command_buffers
+ .unregister(command_encoder_id, &mut token);
+ if let Some(cmdbuf) = cmdbuf {
+ let device = &mut device_guard[cmdbuf.device_id.value];
+ device.untrack::<G>(&hub, &cmdbuf.trackers, &mut token);
+ device.cmd_allocator.discard(cmdbuf);
+ }
+ }
+
+ pub fn command_buffer_drop<B: GfxBackend>(&self, command_buffer_id: id::CommandBufferId) {
+ span!(_guard, INFO, "CommandBuffer::drop");
+ self.command_encoder_drop::<B>(command_buffer_id)
+ }
+
+ pub fn device_create_render_bundle_encoder(
+ &self,
+ device_id: id::DeviceId,
+ desc: &command::RenderBundleEncoderDescriptor,
+ ) -> (
+ id::RenderBundleEncoderId,
+ Option<command::CreateRenderBundleError>,
+ ) {
+ span!(_guard, INFO, "Device::create_render_bundle_encoder");
+ let (encoder, error) = match command::RenderBundleEncoder::new(desc, device_id, None) {
+ Ok(encoder) => (encoder, None),
+ Err(e) => (command::RenderBundleEncoder::dummy(device_id), Some(e)),
+ };
+ (Box::into_raw(Box::new(encoder)), error)
+ }
+
+ pub fn render_bundle_encoder_finish<B: GfxBackend>(
+ &self,
+ bundle_encoder: command::RenderBundleEncoder,
+ desc: &command::RenderBundleDescriptor,
+ id_in: Input<G, id::RenderBundleId>,
+ ) -> (id::RenderBundleId, Option<command::RenderBundleError>) {
+ span!(_guard, INFO, "RenderBundleEncoder::finish");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+
+ let error = loop {
+ let device = match device_guard.get(bundle_encoder.parent()) {
+ Ok(device) => device,
+ Err(_) => break command::RenderBundleError::INVALID_DEVICE,
+ };
+
+ let render_bundle = match bundle_encoder.finish(desc, device, &hub, &mut token) {
+ Ok(bundle) => bundle,
+ Err(e) => break e,
+ };
+
+ tracing::debug!("Render bundle {:?} = {:#?}", id_in, render_bundle.used);
+
+ let ref_count = render_bundle.life_guard.add_ref();
+ let id = hub
+ .render_bundles
+ .register_identity(id_in, render_bundle, &mut token);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let (bundle_guard, _) = hub.render_bundles.read(&mut token);
+ let bundle = &bundle_guard[id];
+ let label = desc.label.as_ref().map(|l| l.as_ref());
+ trace.lock().add(trace::Action::CreateRenderBundle {
+ id: id.0,
+ desc: trace::new_render_bundle_encoder_descriptor(label, &bundle.context),
+ base: bundle.to_base_pass(),
+ });
+ }
+
+ device
+ .trackers
+ .lock()
+ .bundles
+ .init(id, ref_count, PhantomData)
+ .unwrap();
+ return (id.0, None);
+ };
+
+ let id = B::hub(self).render_bundles.register_error(
+ id_in,
+ desc.label.borrow_or_default(),
+ &mut token,
+ );
+ (id, Some(error))
+ }
+
+ pub fn render_bundle_label<B: GfxBackend>(&self, id: id::RenderBundleId) -> String {
+ B::hub(self).render_bundles.label_for_resource(id)
+ }
+
+ pub fn render_bundle_drop<B: GfxBackend>(&self, render_bundle_id: id::RenderBundleId) {
+ span!(_guard, INFO, "RenderBundle::drop");
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let device_id = {
+ let (mut bundle_guard, _) = hub.render_bundles.write(&mut token);
+ match bundle_guard.get_mut(render_bundle_id) {
+ Ok(bundle) => {
+ bundle.life_guard.ref_count.take();
+ bundle.device_id.value
+ }
+ Err(InvalidId) => {
+ hub.render_bundles
+ .unregister_locked(render_bundle_id, &mut *bundle_guard);
+ return;
+ }
+ }
+ };
+
+ device_guard[device_id]
+ .lock_life(&mut token)
+ .suspected_resources
+ .render_bundles
+ .push(id::Valid(render_bundle_id));
+ }
+
+ pub fn device_create_render_pipeline<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &pipeline::RenderPipelineDescriptor,
+ id_in: Input<G, id::RenderPipelineId>,
+ implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>,
+ ) -> (
+ id::RenderPipelineId,
+ pipeline::ImplicitBindGroupCount,
+ Option<pipeline::CreateRenderPipelineError>,
+ ) {
+ span!(_guard, INFO, "Device::create_render_pipeline");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let (pipeline, derived_bind_group_count, layout_id) = match device
+ .create_render_pipeline(device_id, desc, implicit_pipeline_ids, &hub, &mut token)
+ {
+ Ok(pair) => pair,
+ Err(e) => break e,
+ };
+
+ let id = hub
+ .render_pipelines
+ .register_identity(id_in, pipeline, &mut token);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(trace::Action::CreateRenderPipeline(
+ id.0,
+ pipeline::RenderPipelineDescriptor {
+ layout: Some(layout_id),
+ ..desc.clone()
+ },
+ ));
+ }
+ let _ = layout_id;
+ return (id.0, derived_bind_group_count, None);
+ };
+
+ let id =
+ hub.render_pipelines
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, 0, Some(error))
+ }
+
+ /// Get an ID of one of the bind group layouts. The ID adds a refcount,
+ /// which needs to be released by calling `bind_group_layout_drop`.
+ pub fn render_pipeline_get_bind_group_layout<B: GfxBackend>(
+ &self,
+ pipeline_id: id::RenderPipelineId,
+ index: u32,
+ id_in: Input<G, id::BindGroupLayoutId>,
+ ) -> (
+ id::BindGroupLayoutId,
+ Option<binding_model::GetBindGroupLayoutError>,
+ ) {
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token);
+
+ let error = loop {
+ let (bgl_guard, mut token) = hub.bind_group_layouts.read(&mut token);
+ let (_, mut token) = hub.bind_groups.read(&mut token);
+ let (pipeline_guard, _) = hub.render_pipelines.read(&mut token);
+
+ let pipeline = match pipeline_guard.get(pipeline_id) {
+ Ok(pipeline) => pipeline,
+ Err(_) => break binding_model::GetBindGroupLayoutError::InvalidPipeline,
+ };
+ let id = match pipeline_layout_guard[pipeline.layout_id.value]
+ .bind_group_layout_ids
+ .get(index as usize)
+ {
+ Some(id) => id,
+ None => break binding_model::GetBindGroupLayoutError::InvalidGroupIndex(index),
+ };
+
+ bgl_guard[*id].multi_ref_count.inc();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .bind_group_layouts
+ .register_error(id_in, "<derived>", &mut token);
+ (id, Some(error))
+ }
+
+ pub fn render_pipeline_label<B: GfxBackend>(&self, id: id::RenderPipelineId) -> String {
+ B::hub(self).render_pipelines.label_for_resource(id)
+ }
+
+ pub fn render_pipeline_drop<B: GfxBackend>(&self, render_pipeline_id: id::RenderPipelineId) {
+ span!(_guard, INFO, "RenderPipeline::drop");
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+
+ let (device_id, layout_id) = {
+ let (mut pipeline_guard, _) = hub.render_pipelines.write(&mut token);
+ match pipeline_guard.get_mut(render_pipeline_id) {
+ Ok(pipeline) => {
+ pipeline.life_guard.ref_count.take();
+ (pipeline.device_id.value, pipeline.layout_id.clone())
+ }
+ Err(InvalidId) => {
+ hub.render_pipelines
+ .unregister_locked(render_pipeline_id, &mut *pipeline_guard);
+ return;
+ }
+ }
+ };
+
+ let mut life_lock = device_guard[device_id].lock_life(&mut token);
+ life_lock
+ .suspected_resources
+ .render_pipelines
+ .push(id::Valid(render_pipeline_id));
+ life_lock
+ .suspected_resources
+ .pipeline_layouts
+ .push(layout_id);
+ }
+
+ pub fn device_create_compute_pipeline<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ desc: &pipeline::ComputePipelineDescriptor,
+ id_in: Input<G, id::ComputePipelineId>,
+ implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>,
+ ) -> (
+ id::ComputePipelineId,
+ pipeline::ImplicitBindGroupCount,
+ Option<pipeline::CreateComputePipelineError>,
+ ) {
+ span!(_guard, INFO, "Device::create_compute_pipeline");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let error = loop {
+ let device = match device_guard.get(device_id) {
+ Ok(device) => device,
+ Err(_) => break DeviceError::Invalid.into(),
+ };
+ let (pipeline, derived_bind_group_count, layout_id) = match device
+ .create_compute_pipeline(device_id, desc, implicit_pipeline_ids, &hub, &mut token)
+ {
+ Ok(pair) => pair,
+ Err(e) => break e,
+ };
+
+ let id = hub
+ .compute_pipelines
+ .register_identity(id_in, pipeline, &mut token);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(trace::Action::CreateComputePipeline(
+ id.0,
+ pipeline::ComputePipelineDescriptor {
+ layout: Some(layout_id),
+ ..desc.clone()
+ },
+ ));
+ }
+ let _ = layout_id;
+ return (id.0, derived_bind_group_count, None);
+ };
+
+ let id =
+ hub.compute_pipelines
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, 0, Some(error))
+ }
+
+ /// Get an ID of one of the bind group layouts. The ID adds a refcount,
+ /// which needs to be released by calling `bind_group_layout_drop`.
+ pub fn compute_pipeline_get_bind_group_layout<B: GfxBackend>(
+ &self,
+ pipeline_id: id::ComputePipelineId,
+ index: u32,
+ id_in: Input<G, id::BindGroupLayoutId>,
+ ) -> (
+ id::BindGroupLayoutId,
+ Option<binding_model::GetBindGroupLayoutError>,
+ ) {
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(&mut token);
+
+ let error = loop {
+ let (bgl_guard, mut token) = hub.bind_group_layouts.read(&mut token);
+ let (_, mut token) = hub.bind_groups.read(&mut token);
+ let (pipeline_guard, _) = hub.compute_pipelines.read(&mut token);
+
+ let pipeline = match pipeline_guard.get(pipeline_id) {
+ Ok(pipeline) => pipeline,
+ Err(_) => break binding_model::GetBindGroupLayoutError::InvalidPipeline,
+ };
+ let id = match pipeline_layout_guard[pipeline.layout_id.value]
+ .bind_group_layout_ids
+ .get(index as usize)
+ {
+ Some(id) => id,
+ None => break binding_model::GetBindGroupLayoutError::InvalidGroupIndex(index),
+ };
+
+ bgl_guard[*id].multi_ref_count.inc();
+ return (id.0, None);
+ };
+
+ let id = hub
+ .bind_group_layouts
+ .register_error(id_in, "<derived>", &mut token);
+ (id, Some(error))
+ }
+
+ pub fn compute_pipeline_label<B: GfxBackend>(&self, id: id::ComputePipelineId) -> String {
+ B::hub(self).compute_pipelines.label_for_resource(id)
+ }
+
+ pub fn compute_pipeline_drop<B: GfxBackend>(&self, compute_pipeline_id: id::ComputePipelineId) {
+ span!(_guard, INFO, "ComputePipeline::drop");
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+
+ let (device_id, layout_id) = {
+ let (mut pipeline_guard, _) = hub.compute_pipelines.write(&mut token);
+ match pipeline_guard.get_mut(compute_pipeline_id) {
+ Ok(pipeline) => {
+ pipeline.life_guard.ref_count.take();
+ (pipeline.device_id.value, pipeline.layout_id.clone())
+ }
+ Err(InvalidId) => {
+ hub.compute_pipelines
+ .unregister_locked(compute_pipeline_id, &mut *pipeline_guard);
+ return;
+ }
+ }
+ };
+
+ let mut life_lock = device_guard[device_id].lock_life(&mut token);
+ life_lock
+ .suspected_resources
+ .compute_pipelines
+ .push(id::Valid(compute_pipeline_id));
+ life_lock
+ .suspected_resources
+ .pipeline_layouts
+ .push(layout_id);
+ }
+
+ pub fn device_get_swap_chain_preferred_format<B: GfxBackend>(
+ &self,
+ _device_id: id::DeviceId,
+ ) -> Result<TextureFormat, InvalidDevice> {
+ span!(_guard, INFO, "Device::get_swap_chain_preferred_format");
+ //TODO: we can query the formats like done in `device_create_swapchain`,
+ // but its not clear which format in the list to return.
+ // For now, return `Bgra8UnormSrgb` that we know is supported everywhere.
+ Ok(TextureFormat::Bgra8UnormSrgb)
+ }
+
+ pub fn device_create_swap_chain<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ surface_id: id::SurfaceId,
+ desc: &wgt::SwapChainDescriptor,
+ ) -> Result<id::SwapChainId, swap_chain::CreateSwapChainError> {
+ span!(_guard, INFO, "Device::create_swap_chain");
+
+ fn validate_swap_chain_descriptor(
+ config: &mut hal::window::SwapchainConfig,
+ caps: &hal::window::SurfaceCapabilities,
+ ) {
+ let width = config.extent.width;
+ let height = config.extent.height;
+ if width < caps.extents.start().width
+ || width > caps.extents.end().width
+ || height < caps.extents.start().height
+ || height > caps.extents.end().height
+ {
+ tracing::warn!(
+ "Requested size {}x{} is outside of the supported range: {:?}",
+ width,
+ height,
+ caps.extents
+ );
+ }
+ if !caps.present_modes.contains(config.present_mode) {
+ tracing::warn!(
+ "Surface does not support present mode: {:?}, falling back to {:?}",
+ config.present_mode,
+ hal::window::PresentMode::FIFO
+ );
+ config.present_mode = hal::window::PresentMode::FIFO;
+ }
+ }
+
+ tracing::info!("creating swap chain {:?}", desc);
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut surface_guard, mut token) = self.surfaces.write(&mut token);
+ let (adapter_guard, mut token) = hub.adapters.read(&mut token);
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (mut swap_chain_guard, _) = hub.swap_chains.write(&mut token);
+ let device = device_guard
+ .get(device_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let surface = surface_guard
+ .get_mut(surface_id)
+ .map_err(|_| swap_chain::CreateSwapChainError::InvalidSurface)?;
+
+ let (caps, formats) = {
+ let surface = B::get_surface_mut(surface);
+ let adapter = &adapter_guard[device.adapter_id.value];
+ let queue_family = &adapter.raw.queue_families[0];
+ if !surface.supports_queue_family(queue_family) {
+ return Err(swap_chain::CreateSwapChainError::UnsupportedQueueFamily);
+ }
+ let formats = surface.supported_formats(&adapter.raw.physical_device);
+ let caps = surface.capabilities(&adapter.raw.physical_device);
+ (caps, formats)
+ };
+ let num_frames = swap_chain::DESIRED_NUM_FRAMES
+ .max(*caps.image_count.start())
+ .min(*caps.image_count.end());
+ let mut config =
+ swap_chain::swap_chain_descriptor_to_hal(&desc, num_frames, device.private_features);
+ if let Some(formats) = formats {
+ if !formats.contains(&config.format) {
+ return Err(swap_chain::CreateSwapChainError::UnsupportedFormat {
+ requested: config.format,
+ available: formats,
+ });
+ }
+ }
+ validate_swap_chain_descriptor(&mut config, &caps);
+
+ unsafe {
+ B::get_surface_mut(surface)
+ .configure_swapchain(&device.raw, config)
+ .map_err(|err| match err {
+ hal::window::CreationError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ hal::window::CreationError::DeviceLost(_) => DeviceError::Lost,
+ _ => panic!("failed to configure swap chain on creation: {}", err),
+ })?;
+ }
+
+ let sc_id = surface_id.to_swap_chain_id(B::VARIANT);
+ if let Some(sc) = swap_chain_guard.try_remove(sc_id) {
+ if !sc.acquired_view_id.is_none() {
+ return Err(swap_chain::CreateSwapChainError::SwapChainOutputExists);
+ }
+ unsafe {
+ device.raw.destroy_semaphore(sc.semaphore);
+ }
+ }
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace
+ .lock()
+ .add(Action::CreateSwapChain(sc_id, desc.clone()));
+ }
+
+ let swap_chain = swap_chain::SwapChain {
+ life_guard: LifeGuard::new("<SwapChain>"),
+ device_id: Stored {
+ value: id::Valid(device_id),
+ ref_count: device.life_guard.add_ref(),
+ },
+ desc: desc.clone(),
+ num_frames,
+ semaphore: device
+ .raw
+ .create_semaphore()
+ .or(Err(DeviceError::OutOfMemory))?,
+ acquired_view_id: None,
+ acquired_framebuffers: Vec::new(),
+ active_submission_index: 0,
+ };
+ swap_chain_guard.insert(sc_id, swap_chain);
+ Ok(sc_id)
+ }
+
+ #[cfg(feature = "replay")]
+ /// Only triange suspected resource IDs. This helps us to avoid ID collisions
+ /// upon creating new resources when re-playing a trace.
+ pub fn device_maintain_ids<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ ) -> Result<(), InvalidDevice> {
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let device = device_guard.get(device_id).map_err(|_| InvalidDevice)?;
+ device.lock_life(&mut token).triage_suspected(
+ &hub,
+ &device.trackers,
+ #[cfg(feature = "trace")]
+ None,
+ &mut token,
+ );
+ Ok(())
+ }
+
+ pub fn device_poll<B: GfxBackend>(
+ &self,
+ device_id: id::DeviceId,
+ force_wait: bool,
+ ) -> Result<(), WaitIdleError> {
+ span!(_guard, INFO, "Device::poll");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let callbacks = {
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ device_guard
+ .get(device_id)
+ .map_err(|_| DeviceError::Invalid)?
+ .maintain(&hub, force_wait, &mut token)?
+ };
+ fire_map_callbacks(callbacks);
+ Ok(())
+ }
+
+ fn poll_devices<B: GfxBackend>(
+ &self,
+ force_wait: bool,
+ callbacks: &mut Vec<BufferMapPendingCallback>,
+ ) -> Result<(), WaitIdleError> {
+ span!(_guard, INFO, "Device::poll_devices");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ for (_, device) in device_guard.iter(B::VARIANT) {
+ let cbs = device.maintain(&hub, force_wait, &mut token)?;
+ callbacks.extend(cbs);
+ }
+ Ok(())
+ }
+
+ pub fn poll_all_devices(&self, force_wait: bool) -> Result<(), WaitIdleError> {
+ use crate::backend;
+ let mut callbacks = Vec::new();
+
+ #[cfg(vulkan)]
+ {
+ self.poll_devices::<backend::Vulkan>(force_wait, &mut callbacks)?;
+ }
+ #[cfg(metal)]
+ {
+ self.poll_devices::<backend::Metal>(force_wait, &mut callbacks)?;
+ }
+ #[cfg(dx12)]
+ {
+ self.poll_devices::<backend::Dx12>(force_wait, &mut callbacks)?;
+ }
+ #[cfg(dx11)]
+ {
+ self.poll_devices::<backend::Dx11>(force_wait, &mut callbacks)?;
+ }
+
+ fire_map_callbacks(callbacks);
+
+ Ok(())
+ }
+
+ pub fn device_label<B: GfxBackend>(&self, id: id::DeviceId) -> String {
+ B::hub(self).devices.label_for_resource(id)
+ }
+
+ pub fn device_drop<B: GfxBackend>(&self, device_id: id::DeviceId) {
+ span!(_guard, INFO, "Device::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device, _) = hub.devices.unregister(device_id, &mut token);
+ if let Some(mut device) = device {
+ device.prepare_to_die();
+
+ // Adapter is only referenced by the device and itself.
+ // This isn't a robust way to destroy them, we should find a better one.
+ if device.adapter_id.ref_count.load() == 1 {
+ let (_adapter, _) = hub
+ .adapters
+ .unregister(device.adapter_id.value.0, &mut token);
+ }
+
+ device.dispose();
+ }
+ }
+
+ pub fn buffer_map_async<B: GfxBackend>(
+ &self,
+ buffer_id: id::BufferId,
+ range: Range<BufferAddress>,
+ op: resource::BufferMapOperation,
+ ) -> Result<(), resource::BufferAccessError> {
+ span!(_guard, INFO, "Device::buffer_map_async");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (pub_usage, internal_use) = match op.host {
+ HostMap::Read => (wgt::BufferUsage::MAP_READ, resource::BufferUse::MAP_READ),
+ HostMap::Write => (wgt::BufferUsage::MAP_WRITE, resource::BufferUse::MAP_WRITE),
+ };
+
+ if range.start % wgt::COPY_BUFFER_ALIGNMENT != 0
+ || range.end % wgt::COPY_BUFFER_ALIGNMENT != 0
+ {
+ return Err(resource::BufferAccessError::UnalignedRange);
+ }
+
+ let (device_id, ref_count) = {
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ let buffer = buffer_guard
+ .get_mut(buffer_id)
+ .map_err(|_| resource::BufferAccessError::Invalid)?;
+
+ check_buffer_usage(buffer.usage, pub_usage)?;
+ buffer.map_state = match buffer.map_state {
+ resource::BufferMapState::Init { .. } | resource::BufferMapState::Active { .. } => {
+ return Err(resource::BufferAccessError::AlreadyMapped);
+ }
+ resource::BufferMapState::Waiting(_) => {
+ op.call_error();
+ return Ok(());
+ }
+ resource::BufferMapState::Idle => {
+ resource::BufferMapState::Waiting(resource::BufferPendingMapping {
+ range,
+ op,
+ parent_ref_count: buffer.life_guard.add_ref(),
+ })
+ }
+ };
+ tracing::debug!("Buffer {:?} map state -> Waiting", buffer_id);
+
+ (buffer.device_id.value, buffer.life_guard.add_ref())
+ };
+
+ let device = &device_guard[device_id];
+ device.trackers.lock().buffers.change_replace(
+ id::Valid(buffer_id),
+ &ref_count,
+ (),
+ internal_use,
+ );
+
+ device
+ .lock_life(&mut token)
+ .map(id::Valid(buffer_id), ref_count);
+
+ Ok(())
+ }
+
+ pub fn buffer_get_mapped_range<B: GfxBackend>(
+ &self,
+ buffer_id: id::BufferId,
+ offset: BufferAddress,
+ _size: Option<BufferSize>,
+ ) -> Result<*mut u8, resource::BufferAccessError> {
+ span!(_guard, INFO, "Device::buffer_get_mapped_range");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (buffer_guard, _) = hub.buffers.read(&mut token);
+ let buffer = buffer_guard
+ .get(buffer_id)
+ .map_err(|_| resource::BufferAccessError::Invalid)?;
+
+ match buffer.map_state {
+ resource::BufferMapState::Init { ptr, .. }
+ | resource::BufferMapState::Active { ptr, .. } => unsafe {
+ Ok(ptr.as_ptr().offset(offset as isize))
+ },
+ resource::BufferMapState::Idle | resource::BufferMapState::Waiting(_) => {
+ Err(resource::BufferAccessError::NotMapped)
+ }
+ }
+ }
+
+ pub fn buffer_unmap<B: GfxBackend>(
+ &self,
+ buffer_id: id::BufferId,
+ ) -> Result<(), resource::BufferAccessError> {
+ span!(_guard, INFO, "Device::buffer_unmap");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let (mut buffer_guard, _) = hub.buffers.write(&mut token);
+ let buffer = buffer_guard
+ .get_mut(buffer_id)
+ .map_err(|_| resource::BufferAccessError::Invalid)?;
+ let device = &mut device_guard[buffer.device_id.value];
+
+ tracing::debug!("Buffer {:?} map state -> Idle", buffer_id);
+ match mem::replace(&mut buffer.map_state, resource::BufferMapState::Idle) {
+ resource::BufferMapState::Init {
+ ptr,
+ stage_buffer,
+ stage_memory,
+ needs_flush,
+ } => {
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let data = trace.make_binary("bin", unsafe {
+ std::slice::from_raw_parts(ptr.as_ptr(), buffer.size as usize)
+ });
+ trace.add(trace::Action::WriteBuffer {
+ id: buffer_id,
+ data,
+ range: 0..buffer.size,
+ queued: true,
+ });
+ }
+ let _ = ptr;
+
+ if needs_flush {
+ stage_memory.flush_range(&device.raw, 0, None)?;
+ }
+
+ let &(ref buf_raw, _) = buffer
+ .raw
+ .as_ref()
+ .ok_or(resource::BufferAccessError::Destroyed)?;
+
+ buffer.life_guard.use_at(device.active_submission_index + 1);
+ let region = hal::command::BufferCopy {
+ src: 0,
+ dst: 0,
+ size: buffer.size,
+ };
+ let transition_src = hal::memory::Barrier::Buffer {
+ states: hal::buffer::Access::HOST_WRITE..hal::buffer::Access::TRANSFER_READ,
+ target: &stage_buffer,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ };
+ let transition_dst = hal::memory::Barrier::Buffer {
+ states: hal::buffer::Access::empty()..hal::buffer::Access::TRANSFER_WRITE,
+ target: buf_raw,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ };
+ unsafe {
+ let cmdbuf = device.borrow_pending_writes();
+ cmdbuf.pipeline_barrier(
+ hal::pso::PipelineStage::HOST..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ iter::once(transition_src).chain(iter::once(transition_dst)),
+ );
+ if buffer.size > 0 {
+ cmdbuf.copy_buffer(&stage_buffer, buf_raw, iter::once(region));
+ }
+ }
+ device
+ .pending_writes
+ .consume_temp(queue::TempResource::Buffer(stage_buffer), stage_memory);
+ device.pending_writes.dst_buffers.insert(buffer_id);
+ }
+ resource::BufferMapState::Idle => {
+ return Err(resource::BufferAccessError::NotMapped);
+ }
+ resource::BufferMapState::Waiting(_) => {}
+ resource::BufferMapState::Active {
+ ptr,
+ sub_range,
+ host,
+ } => {
+ if host == HostMap::Write {
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let size = sub_range.size_to(buffer.size);
+ let data = trace.make_binary("bin", unsafe {
+ std::slice::from_raw_parts(ptr.as_ptr(), size as usize)
+ });
+ trace.add(trace::Action::WriteBuffer {
+ id: buffer_id,
+ data,
+ range: sub_range.offset..sub_range.offset + size,
+ queued: false,
+ });
+ }
+ let _ = (ptr, sub_range);
+ }
+ unmap_buffer(&device.raw, buffer)?;
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/queue.rs b/gfx/wgpu/wgpu-core/src/device/queue.rs
new file mode 100644
index 0000000000..ccd55b185e
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/queue.rs
@@ -0,0 +1,696 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[cfg(feature = "trace")]
+use crate::device::trace::Action;
+use crate::{
+ command::{
+ texture_copy_view_to_hal, validate_linear_texture_data, validate_texture_copy_range,
+ CommandAllocator, CommandBuffer, CopySide, TextureCopyView, TransferError, BITS_PER_BYTE,
+ },
+ conv,
+ device::{alloc, DeviceError, WaitIdleError},
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Token},
+ id,
+ resource::{BufferAccessError, BufferMapState, BufferUse, TextureUse},
+ span, FastHashSet,
+};
+
+use hal::{command::CommandBuffer as _, device::Device as _, queue::CommandQueue as _};
+use smallvec::SmallVec;
+use std::{iter, ptr};
+use thiserror::Error;
+
+struct StagingData<B: hal::Backend> {
+ buffer: B::Buffer,
+ memory: alloc::MemoryBlock<B>,
+ cmdbuf: B::CommandBuffer,
+}
+
+#[derive(Debug)]
+pub enum TempResource<B: hal::Backend> {
+ Buffer(B::Buffer),
+ Image(B::Image),
+}
+
+#[derive(Debug)]
+pub(crate) struct PendingWrites<B: hal::Backend> {
+ pub command_buffer: Option<B::CommandBuffer>,
+ pub temp_resources: Vec<(TempResource<B>, alloc::MemoryBlock<B>)>,
+ pub dst_buffers: FastHashSet<id::BufferId>,
+ pub dst_textures: FastHashSet<id::TextureId>,
+}
+
+impl<B: hal::Backend> PendingWrites<B> {
+ pub fn new() -> Self {
+ Self {
+ command_buffer: None,
+ temp_resources: Vec::new(),
+ dst_buffers: FastHashSet::default(),
+ dst_textures: FastHashSet::default(),
+ }
+ }
+
+ pub fn dispose(
+ self,
+ device: &B::Device,
+ cmd_allocator: &CommandAllocator<B>,
+ mem_allocator: &mut alloc::MemoryAllocator<B>,
+ ) {
+ if let Some(raw) = self.command_buffer {
+ cmd_allocator.discard_internal(raw);
+ }
+ for (resource, memory) in self.temp_resources {
+ mem_allocator.free(device, memory);
+ match resource {
+ TempResource::Buffer(buffer) => unsafe {
+ device.destroy_buffer(buffer);
+ },
+ TempResource::Image(image) => unsafe {
+ device.destroy_image(image);
+ },
+ }
+ }
+ }
+
+ pub fn consume_temp(&mut self, resource: TempResource<B>, memory: alloc::MemoryBlock<B>) {
+ self.temp_resources.push((resource, memory));
+ }
+
+ fn consume(&mut self, stage: StagingData<B>) {
+ self.temp_resources
+ .push((TempResource::Buffer(stage.buffer), stage.memory));
+ self.command_buffer = Some(stage.cmdbuf);
+ }
+
+ #[must_use]
+ fn finish(&mut self) -> Option<B::CommandBuffer> {
+ self.dst_buffers.clear();
+ self.dst_textures.clear();
+ self.command_buffer.take().map(|mut cmd_buf| unsafe {
+ cmd_buf.finish();
+ cmd_buf
+ })
+ }
+}
+
+impl<B: hal::Backend> super::Device<B> {
+ pub fn borrow_pending_writes(&mut self) -> &mut B::CommandBuffer {
+ if self.pending_writes.command_buffer.is_none() {
+ let mut cmdbuf = self.cmd_allocator.allocate_internal();
+ unsafe {
+ cmdbuf.begin_primary(hal::command::CommandBufferFlags::ONE_TIME_SUBMIT);
+ }
+ self.pending_writes.command_buffer = Some(cmdbuf);
+ }
+ self.pending_writes.command_buffer.as_mut().unwrap()
+ }
+
+ fn prepare_stage(&mut self, size: wgt::BufferAddress) -> Result<StagingData<B>, DeviceError> {
+ let mut buffer = unsafe {
+ self.raw
+ .create_buffer(size, hal::buffer::Usage::TRANSFER_SRC)
+ .map_err(|err| match err {
+ hal::buffer::CreationError::OutOfMemory(_) => DeviceError::OutOfMemory,
+ _ => panic!("failed to create staging buffer: {}", err),
+ })?
+ };
+ //TODO: do we need to transition into HOST_WRITE access first?
+ let requirements = unsafe {
+ self.raw.set_buffer_name(&mut buffer, "<write_buffer_temp>");
+ self.raw.get_buffer_requirements(&buffer)
+ };
+
+ let block = self.mem_allocator.lock().allocate(
+ &self.raw,
+ requirements,
+ gpu_alloc::UsageFlags::UPLOAD | gpu_alloc::UsageFlags::TRANSIENT,
+ )?;
+ block.bind_buffer(&self.raw, &mut buffer)?;
+
+ let cmdbuf = match self.pending_writes.command_buffer.take() {
+ Some(cmdbuf) => cmdbuf,
+ None => {
+ let mut cmdbuf = self.cmd_allocator.allocate_internal();
+ unsafe {
+ cmdbuf.begin_primary(hal::command::CommandBufferFlags::ONE_TIME_SUBMIT);
+ }
+ cmdbuf
+ }
+ };
+ Ok(StagingData {
+ buffer,
+ memory: block,
+ cmdbuf,
+ })
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum QueueWriteError {
+ #[error(transparent)]
+ Queue(#[from] DeviceError),
+ #[error(transparent)]
+ Transfer(#[from] TransferError),
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum QueueSubmitError {
+ #[error(transparent)]
+ Queue(#[from] DeviceError),
+ #[error("command buffer {0:?} is invalid")]
+ InvalidCommandBuffer(id::CommandBufferId),
+ #[error("buffer {0:?} is destroyed")]
+ DestroyedBuffer(id::BufferId),
+ #[error("texture {0:?} is destroyed")]
+ DestroyedTexture(id::TextureId),
+ #[error(transparent)]
+ Unmap(#[from] BufferAccessError),
+ #[error("swap chain output was dropped before the command buffer got submitted")]
+ SwapChainOutputDropped,
+ #[error("GPU got stuck :(")]
+ StuckGpu,
+}
+
+//TODO: move out common parts of write_xxx.
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn queue_write_buffer<B: GfxBackend>(
+ &self,
+ queue_id: id::QueueId,
+ buffer_id: id::BufferId,
+ buffer_offset: wgt::BufferAddress,
+ data: &[u8],
+ ) -> Result<(), QueueWriteError> {
+ span!(_guard, INFO, "Queue::write_buffer");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let device = device_guard
+ .get_mut(queue_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let (buffer_guard, _) = hub.buffers.read(&mut token);
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let data_path = trace.make_binary("bin", data);
+ trace.add(Action::WriteBuffer {
+ id: buffer_id,
+ data: data_path,
+ range: buffer_offset..buffer_offset + data.len() as wgt::BufferAddress,
+ queued: true,
+ });
+ }
+
+ let data_size = data.len() as wgt::BufferAddress;
+ if data_size == 0 {
+ tracing::trace!("Ignoring write_buffer of size 0");
+ return Ok(());
+ }
+
+ let mut stage = device.prepare_stage(data_size)?;
+ stage.memory.write_bytes(&device.raw, 0, data)?;
+
+ let mut trackers = device.trackers.lock();
+ let (dst, transition) = trackers
+ .buffers
+ .use_replace(&*buffer_guard, buffer_id, (), BufferUse::COPY_DST)
+ .map_err(TransferError::InvalidBuffer)?;
+ let &(ref dst_raw, _) = dst
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidBuffer(buffer_id))?;
+ if !dst.usage.contains(wgt::BufferUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ Some(buffer_id),
+ None,
+ ))?;
+ }
+ dst.life_guard.use_at(device.active_submission_index + 1);
+
+ if data_size % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ Err(TransferError::UnalignedCopySize(data_size))?
+ }
+ if buffer_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 {
+ Err(TransferError::UnalignedBufferOffset(buffer_offset))?
+ }
+ if buffer_offset + data_size > dst.size {
+ Err(TransferError::BufferOverrun {
+ start_offset: buffer_offset,
+ end_offset: buffer_offset + data_size,
+ buffer_size: dst.size,
+ side: CopySide::Destination,
+ })?
+ }
+
+ let region = hal::command::BufferCopy {
+ src: 0,
+ dst: buffer_offset,
+ size: data.len() as _,
+ };
+ unsafe {
+ stage.cmdbuf.pipeline_barrier(
+ super::all_buffer_stages()..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ iter::once(hal::memory::Barrier::Buffer {
+ states: hal::buffer::Access::HOST_WRITE..hal::buffer::Access::TRANSFER_READ,
+ target: &stage.buffer,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ })
+ .chain(transition.map(|pending| pending.into_hal(dst))),
+ );
+ stage
+ .cmdbuf
+ .copy_buffer(&stage.buffer, dst_raw, iter::once(region));
+ }
+
+ device.pending_writes.consume(stage);
+ device.pending_writes.dst_buffers.insert(buffer_id);
+
+ Ok(())
+ }
+
+ pub fn queue_write_texture<B: GfxBackend>(
+ &self,
+ queue_id: id::QueueId,
+ destination: &TextureCopyView,
+ data: &[u8],
+ data_layout: &wgt::TextureDataLayout,
+ size: &wgt::Extent3d,
+ ) -> Result<(), QueueWriteError> {
+ span!(_guard, INFO, "Queue::write_texture");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let device = device_guard
+ .get_mut(queue_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let (texture_guard, _) = hub.textures.read(&mut token);
+ let (image_layers, image_range, image_offset) =
+ texture_copy_view_to_hal(destination, size, &*texture_guard)?;
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ let mut trace = trace.lock();
+ let data_path = trace.make_binary("bin", data);
+ trace.add(Action::WriteTexture {
+ to: destination.clone(),
+ data: data_path,
+ layout: data_layout.clone(),
+ size: *size,
+ });
+ }
+
+ if size.width == 0 || size.height == 0 || size.depth == 0 {
+ tracing::trace!("Ignoring write_texture of size 0");
+ return Ok(());
+ }
+
+ let texture_format = texture_guard.get(destination.texture).unwrap().format;
+ let bytes_per_block = conv::map_texture_format(texture_format, device.private_features)
+ .surface_desc()
+ .bits as u32
+ / BITS_PER_BYTE;
+ validate_linear_texture_data(
+ data_layout,
+ texture_format,
+ data.len() as wgt::BufferAddress,
+ CopySide::Source,
+ bytes_per_block as wgt::BufferAddress,
+ size,
+ )?;
+ let (block_width, block_height) = conv::texture_block_size(texture_format);
+ if !conv::is_valid_copy_dst_texture_format(texture_format) {
+ Err(TransferError::CopyToForbiddenTextureFormat(texture_format))?
+ }
+ let width_blocks = size.width / block_width;
+ let height_blocks = size.height / block_width;
+
+ let texel_rows_per_image = data_layout.rows_per_image;
+ let block_rows_per_image = data_layout.rows_per_image / block_height;
+
+ let bytes_per_row_alignment = get_lowest_common_denom(
+ device.hal_limits.optimal_buffer_copy_pitch_alignment as u32,
+ bytes_per_block,
+ );
+ let stage_bytes_per_row = align_to(bytes_per_block * width_blocks, bytes_per_row_alignment);
+
+ let block_rows_in_copy = (size.depth - 1) * block_rows_per_image + height_blocks;
+ let stage_size = stage_bytes_per_row as u64 * block_rows_in_copy as u64;
+ let mut stage = device.prepare_stage(stage_size)?;
+
+ let mut trackers = device.trackers.lock();
+ let (dst, transition) = trackers
+ .textures
+ .use_replace(
+ &*texture_guard,
+ destination.texture,
+ image_range,
+ TextureUse::COPY_DST,
+ )
+ .unwrap();
+ let &(ref dst_raw, _) = dst
+ .raw
+ .as_ref()
+ .ok_or(TransferError::InvalidTexture(destination.texture))?;
+
+ if !dst.usage.contains(wgt::TextureUsage::COPY_DST) {
+ Err(TransferError::MissingCopyDstUsageFlag(
+ None,
+ Some(destination.texture),
+ ))?
+ }
+ validate_texture_copy_range(
+ destination,
+ dst.format,
+ dst.kind,
+ CopySide::Destination,
+ size,
+ )?;
+ dst.life_guard.use_at(device.active_submission_index + 1);
+
+ let ptr = stage.memory.map(&device.raw, 0, stage_size)?;
+ unsafe {
+ //TODO: https://github.com/zakarumych/gpu-alloc/issues/13
+ if stage_bytes_per_row == data_layout.bytes_per_row {
+ // Fast path if the data isalready being aligned optimally.
+ ptr::copy_nonoverlapping(data.as_ptr(), ptr.as_ptr(), stage_size as usize);
+ } else {
+ // Copy row by row into the optimal alignment.
+ let copy_bytes_per_row =
+ stage_bytes_per_row.min(data_layout.bytes_per_row) as usize;
+ for layer in 0..size.depth {
+ let rows_offset = layer * block_rows_per_image;
+ for row in 0..height_blocks {
+ ptr::copy_nonoverlapping(
+ data.as_ptr().offset(
+ (rows_offset + row) as isize * data_layout.bytes_per_row as isize,
+ ),
+ ptr.as_ptr().offset(
+ (rows_offset + row) as isize * stage_bytes_per_row as isize,
+ ),
+ copy_bytes_per_row,
+ );
+ }
+ }
+ }
+ }
+ stage.memory.unmap(&device.raw);
+ if !stage.memory.is_coherent() {
+ stage.memory.flush_range(&device.raw, 0, None)?;
+ }
+
+ let region = hal::command::BufferImageCopy {
+ buffer_offset: 0,
+ buffer_width: (stage_bytes_per_row / bytes_per_block) * block_width,
+ buffer_height: texel_rows_per_image,
+ image_layers,
+ image_offset,
+ image_extent: conv::map_extent(size, dst.dimension),
+ };
+ unsafe {
+ stage.cmdbuf.pipeline_barrier(
+ super::all_image_stages() | hal::pso::PipelineStage::HOST
+ ..hal::pso::PipelineStage::TRANSFER,
+ hal::memory::Dependencies::empty(),
+ iter::once(hal::memory::Barrier::Buffer {
+ states: hal::buffer::Access::HOST_WRITE..hal::buffer::Access::TRANSFER_READ,
+ target: &stage.buffer,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ })
+ .chain(transition.map(|pending| pending.into_hal(dst))),
+ );
+ stage.cmdbuf.copy_buffer_to_image(
+ &stage.buffer,
+ dst_raw,
+ hal::image::Layout::TransferDstOptimal,
+ iter::once(region),
+ );
+ }
+
+ device.pending_writes.consume(stage);
+ device
+ .pending_writes
+ .dst_textures
+ .insert(destination.texture);
+
+ Ok(())
+ }
+
+ pub fn queue_submit<B: GfxBackend>(
+ &self,
+ queue_id: id::QueueId,
+ command_buffer_ids: &[id::CommandBufferId],
+ ) -> Result<(), QueueSubmitError> {
+ span!(_guard, INFO, "Queue::submit");
+
+ let hub = B::hub(self);
+
+ let callbacks = {
+ let mut token = Token::root();
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let device = device_guard
+ .get_mut(queue_id)
+ .map_err(|_| DeviceError::Invalid)?;
+ let pending_write_command_buffer = device.pending_writes.finish();
+ device.temp_suspected.clear();
+ device.active_submission_index += 1;
+ let submit_index = device.active_submission_index;
+
+ let fence = {
+ let mut signal_swapchain_semaphores = SmallVec::<[_; 1]>::new();
+ let (mut swap_chain_guard, mut token) = hub.swap_chains.write(&mut token);
+ let (mut command_buffer_guard, mut token) = hub.command_buffers.write(&mut token);
+
+ {
+ let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
+ let (compute_pipe_guard, mut token) = hub.compute_pipelines.read(&mut token);
+ let (render_pipe_guard, mut token) = hub.render_pipelines.read(&mut token);
+ let (mut buffer_guard, mut token) = hub.buffers.write(&mut token);
+ let (texture_guard, mut token) = hub.textures.read(&mut token);
+ let (texture_view_guard, mut token) = hub.texture_views.read(&mut token);
+ let (sampler_guard, _) = hub.samplers.read(&mut token);
+
+ //Note: locking the trackers has to be done after the storages
+ let mut trackers = device.trackers.lock();
+
+ //TODO: if multiple command buffers are submitted, we can re-use the last
+ // native command buffer of the previous chain instead of always creating
+ // a temporary one, since the chains are not finished.
+
+ // finish all the command buffers first
+ for &cmb_id in command_buffer_ids {
+ let cmdbuf = command_buffer_guard
+ .get_mut(cmb_id)
+ .map_err(|_| QueueSubmitError::InvalidCommandBuffer(cmb_id))?;
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(Action::Submit(
+ submit_index,
+ cmdbuf.commands.take().unwrap(),
+ ));
+ }
+
+ if let Some((sc_id, fbo)) = cmdbuf.used_swap_chain.take() {
+ let sc = &mut swap_chain_guard[sc_id.value];
+ sc.active_submission_index = submit_index;
+ if sc.acquired_view_id.is_none() {
+ return Err(QueueSubmitError::SwapChainOutputDropped);
+ }
+ // For each swapchain, we only want to have at most 1 signaled semaphore.
+ if sc.acquired_framebuffers.is_empty() {
+ // Only add a signal if this is the first time for this swapchain
+ // to be used in the submission.
+ signal_swapchain_semaphores.push(sc_id.value);
+ }
+ sc.acquired_framebuffers.push(fbo);
+ }
+
+ // optimize the tracked states
+ cmdbuf.trackers.optimize();
+
+ // update submission IDs
+ for id in cmdbuf.trackers.buffers.used() {
+ let buffer = &mut buffer_guard[id];
+ if buffer.raw.is_none() {
+ return Err(QueueSubmitError::DestroyedBuffer(id.0))?;
+ }
+ if !buffer.life_guard.use_at(submit_index) {
+ if let BufferMapState::Active { .. } = buffer.map_state {
+ tracing::warn!("Dropped buffer has a pending mapping.");
+ super::unmap_buffer(&device.raw, buffer)?;
+ }
+ device.temp_suspected.buffers.push(id);
+ } else {
+ match buffer.map_state {
+ BufferMapState::Idle => (),
+ _ => panic!("Buffer {:?} is still mapped", id),
+ }
+ }
+ }
+ for id in cmdbuf.trackers.textures.used() {
+ let texture = &texture_guard[id];
+ if texture.raw.is_none() {
+ return Err(QueueSubmitError::DestroyedTexture(id.0))?;
+ }
+ if !texture.life_guard.use_at(submit_index) {
+ device.temp_suspected.textures.push(id);
+ }
+ }
+ for id in cmdbuf.trackers.views.used() {
+ if !texture_view_guard[id].life_guard.use_at(submit_index) {
+ device.temp_suspected.texture_views.push(id);
+ }
+ }
+ for id in cmdbuf.trackers.bind_groups.used() {
+ if !bind_group_guard[id].life_guard.use_at(submit_index) {
+ device.temp_suspected.bind_groups.push(id);
+ }
+ }
+ for id in cmdbuf.trackers.samplers.used() {
+ if !sampler_guard[id].life_guard.use_at(submit_index) {
+ device.temp_suspected.samplers.push(id);
+ }
+ }
+ for id in cmdbuf.trackers.compute_pipes.used() {
+ if !compute_pipe_guard[id].life_guard.use_at(submit_index) {
+ device.temp_suspected.compute_pipelines.push(id);
+ }
+ }
+ for id in cmdbuf.trackers.render_pipes.used() {
+ if !render_pipe_guard[id].life_guard.use_at(submit_index) {
+ device.temp_suspected.render_pipelines.push(id);
+ }
+ }
+
+ // execute resource transitions
+ let mut transit = device.cmd_allocator.extend(cmdbuf);
+ unsafe {
+ // the last buffer was open, closing now
+ cmdbuf.raw.last_mut().unwrap().finish();
+ transit
+ .begin_primary(hal::command::CommandBufferFlags::ONE_TIME_SUBMIT);
+ }
+ tracing::trace!("Stitching command buffer {:?} before submission", cmb_id);
+ CommandBuffer::insert_barriers(
+ &mut transit,
+ &mut *trackers,
+ &cmdbuf.trackers,
+ &*buffer_guard,
+ &*texture_guard,
+ );
+ unsafe {
+ transit.finish();
+ }
+ cmdbuf.raw.insert(0, transit);
+ }
+
+ tracing::trace!("Device after submission {}: {:#?}", submit_index, trackers);
+ }
+
+ // now prepare the GPU submission
+ let fence = device
+ .raw
+ .create_fence(false)
+ .or(Err(DeviceError::OutOfMemory))?;
+ let submission = hal::queue::Submission {
+ command_buffers: pending_write_command_buffer.as_ref().into_iter().chain(
+ command_buffer_ids
+ .iter()
+ .flat_map(|&cmb_id| &command_buffer_guard.get(cmb_id).unwrap().raw),
+ ),
+ wait_semaphores: Vec::new(),
+ signal_semaphores: signal_swapchain_semaphores
+ .into_iter()
+ .map(|sc_id| &swap_chain_guard[sc_id].semaphore),
+ };
+
+ unsafe {
+ device.queue_group.queues[0].submit(submission, Some(&fence));
+ }
+ fence
+ };
+
+ if let Some(comb_raw) = pending_write_command_buffer {
+ device
+ .cmd_allocator
+ .after_submit_internal(comb_raw, submit_index);
+ }
+
+ let callbacks = match device.maintain(&hub, false, &mut token) {
+ Ok(callbacks) => callbacks,
+ Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)),
+ Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu),
+ };
+ super::Device::lock_life_internal(&device.life_tracker, &mut token).track_submission(
+ submit_index,
+ fence,
+ &device.temp_suspected,
+ device.pending_writes.temp_resources.drain(..),
+ );
+
+ // finally, return the command buffers to the allocator
+ for &cmb_id in command_buffer_ids {
+ if let (Some(cmd_buf), _) = hub.command_buffers.unregister(cmb_id, &mut token) {
+ device.cmd_allocator.after_submit(cmd_buf, submit_index);
+ }
+ }
+
+ callbacks
+ };
+
+ super::fire_map_callbacks(callbacks);
+
+ Ok(())
+ }
+}
+
+fn get_lowest_common_denom(a: u32, b: u32) -> u32 {
+ let gcd = if a >= b {
+ get_greatest_common_divisor(a, b)
+ } else {
+ get_greatest_common_divisor(b, a)
+ };
+ a * b / gcd
+}
+
+fn get_greatest_common_divisor(mut a: u32, mut b: u32) -> u32 {
+ assert!(a >= b);
+ loop {
+ let c = a % b;
+ if c == 0 {
+ return b;
+ } else {
+ a = b;
+ b = c;
+ }
+ }
+}
+
+fn align_to(value: u32, alignment: u32) -> u32 {
+ match value % alignment {
+ 0 => value,
+ other => value - other + alignment,
+ }
+}
+
+#[test]
+fn test_lcd() {
+ assert_eq!(get_lowest_common_denom(2, 2), 2);
+ assert_eq!(get_lowest_common_denom(2, 3), 6);
+ assert_eq!(get_lowest_common_denom(6, 4), 12);
+}
+
+#[test]
+fn test_gcd() {
+ assert_eq!(get_greatest_common_divisor(5, 1), 1);
+ assert_eq!(get_greatest_common_divisor(4, 2), 2);
+ assert_eq!(get_greatest_common_divisor(6, 4), 2);
+ assert_eq!(get_greatest_common_divisor(7, 7), 7);
+}
diff --git a/gfx/wgpu/wgpu-core/src/device/trace.rs b/gfx/wgpu/wgpu-core/src/device/trace.rs
new file mode 100644
index 0000000000..8fbd08526d
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/device/trace.rs
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::id;
+use std::ops::Range;
+#[cfg(feature = "trace")]
+use std::{borrow::Cow, io::Write as _};
+
+//TODO: consider a readable Id that doesn't include the backend
+
+type FileName = String;
+
+pub const FILE_NAME: &str = "trace.ron";
+
+#[cfg(feature = "trace")]
+pub(crate) fn new_render_bundle_encoder_descriptor<'a>(
+ label: Option<&'a str>,
+ context: &'a super::RenderPassContext,
+) -> crate::command::RenderBundleEncoderDescriptor<'a> {
+ crate::command::RenderBundleEncoderDescriptor {
+ label: label.map(Cow::Borrowed),
+ color_formats: Cow::Borrowed(&context.attachments.colors),
+ depth_stencil_format: context.attachments.depth_stencil,
+ sample_count: context.sample_count as u32,
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum Action<'a> {
+ Init {
+ desc: crate::device::DeviceDescriptor<'a>,
+ backend: wgt::Backend,
+ },
+ CreateBuffer(id::BufferId, crate::resource::BufferDescriptor<'a>),
+ FreeBuffer(id::BufferId),
+ DestroyBuffer(id::BufferId),
+ CreateTexture(id::TextureId, crate::resource::TextureDescriptor<'a>),
+ FreeTexture(id::TextureId),
+ DestroyTexture(id::TextureId),
+ CreateTextureView {
+ id: id::TextureViewId,
+ parent_id: id::TextureId,
+ desc: crate::resource::TextureViewDescriptor<'a>,
+ },
+ DestroyTextureView(id::TextureViewId),
+ CreateSampler(id::SamplerId, crate::resource::SamplerDescriptor<'a>),
+ DestroySampler(id::SamplerId),
+ CreateSwapChain(id::SwapChainId, wgt::SwapChainDescriptor),
+ GetSwapChainTexture {
+ id: Option<id::TextureViewId>,
+ parent_id: id::SwapChainId,
+ },
+ PresentSwapChain(id::SwapChainId),
+ CreateBindGroupLayout(
+ id::BindGroupLayoutId,
+ crate::binding_model::BindGroupLayoutDescriptor<'a>,
+ ),
+ DestroyBindGroupLayout(id::BindGroupLayoutId),
+ CreatePipelineLayout(
+ id::PipelineLayoutId,
+ crate::binding_model::PipelineLayoutDescriptor<'a>,
+ ),
+ DestroyPipelineLayout(id::PipelineLayoutId),
+ CreateBindGroup(
+ id::BindGroupId,
+ crate::binding_model::BindGroupDescriptor<'a>,
+ ),
+ DestroyBindGroup(id::BindGroupId),
+ CreateShaderModule {
+ id: id::ShaderModuleId,
+ label: crate::Label<'a>,
+ data: FileName,
+ },
+ DestroyShaderModule(id::ShaderModuleId),
+ CreateComputePipeline(
+ id::ComputePipelineId,
+ crate::pipeline::ComputePipelineDescriptor<'a>,
+ ),
+ DestroyComputePipeline(id::ComputePipelineId),
+ CreateRenderPipeline(
+ id::RenderPipelineId,
+ crate::pipeline::RenderPipelineDescriptor<'a>,
+ ),
+ DestroyRenderPipeline(id::RenderPipelineId),
+ CreateRenderBundle {
+ id: id::RenderBundleId,
+ desc: crate::command::RenderBundleEncoderDescriptor<'a>,
+ base: crate::command::BasePass<crate::command::RenderCommand>,
+ },
+ DestroyRenderBundle(id::RenderBundleId),
+ WriteBuffer {
+ id: id::BufferId,
+ data: FileName,
+ range: Range<wgt::BufferAddress>,
+ queued: bool,
+ },
+ WriteTexture {
+ to: crate::command::TextureCopyView,
+ data: FileName,
+ layout: wgt::TextureDataLayout,
+ size: wgt::Extent3d,
+ },
+ Submit(crate::SubmissionIndex, Vec<Command>),
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum Command {
+ CopyBufferToBuffer {
+ src: id::BufferId,
+ src_offset: wgt::BufferAddress,
+ dst: id::BufferId,
+ dst_offset: wgt::BufferAddress,
+ size: wgt::BufferAddress,
+ },
+ CopyBufferToTexture {
+ src: crate::command::BufferCopyView,
+ dst: crate::command::TextureCopyView,
+ size: wgt::Extent3d,
+ },
+ CopyTextureToBuffer {
+ src: crate::command::TextureCopyView,
+ dst: crate::command::BufferCopyView,
+ size: wgt::Extent3d,
+ },
+ CopyTextureToTexture {
+ src: crate::command::TextureCopyView,
+ dst: crate::command::TextureCopyView,
+ size: wgt::Extent3d,
+ },
+ RunComputePass {
+ base: crate::command::BasePass<crate::command::ComputeCommand>,
+ },
+ RunRenderPass {
+ base: crate::command::BasePass<crate::command::RenderCommand>,
+ target_colors: Vec<crate::command::ColorAttachmentDescriptor>,
+ target_depth_stencil: Option<crate::command::DepthStencilAttachmentDescriptor>,
+ },
+}
+
+#[cfg(feature = "trace")]
+#[derive(Debug)]
+pub struct Trace {
+ path: std::path::PathBuf,
+ file: std::fs::File,
+ config: ron::ser::PrettyConfig,
+ binary_id: usize,
+}
+
+#[cfg(feature = "trace")]
+impl Trace {
+ pub fn new(path: &std::path::Path) -> Result<Self, std::io::Error> {
+ tracing::info!("Tracing into '{:?}'", path);
+ let mut file = std::fs::File::create(path.join(FILE_NAME))?;
+ file.write_all(b"[\n")?;
+ Ok(Self {
+ path: path.to_path_buf(),
+ file,
+ config: ron::ser::PrettyConfig::default(),
+ binary_id: 0,
+ })
+ }
+
+ pub fn make_binary(&mut self, kind: &str, data: &[u8]) -> String {
+ self.binary_id += 1;
+ let name = format!("data{}.{}", self.binary_id, kind);
+ let _ = std::fs::write(self.path.join(&name), data);
+ name
+ }
+
+ pub(crate) fn add(&mut self, action: Action) {
+ match ron::ser::to_string_pretty(&action, self.config.clone()) {
+ Ok(string) => {
+ let _ = writeln!(self.file, "{},", string);
+ }
+ Err(e) => {
+ tracing::warn!("RON serialization failure: {:?}", e);
+ }
+ }
+ }
+}
+
+#[cfg(feature = "trace")]
+impl Drop for Trace {
+ fn drop(&mut self) {
+ let _ = self.file.write_all(b"]");
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/hub.rs b/gfx/wgpu/wgpu-core/src/hub.rs
new file mode 100644
index 0000000000..33fa6e0966
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/hub.rs
@@ -0,0 +1,866 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ backend,
+ binding_model::{BindGroup, BindGroupLayout, PipelineLayout},
+ command::{CommandBuffer, RenderBundle},
+ device::Device,
+ id::{
+ AdapterId, BindGroupId, BindGroupLayoutId, BufferId, CommandBufferId, ComputePipelineId,
+ DeviceId, PipelineLayoutId, RenderBundleId, RenderPipelineId, SamplerId, ShaderModuleId,
+ SurfaceId, SwapChainId, TextureId, TextureViewId, TypedId, Valid,
+ },
+ instance::{Adapter, Instance, Surface},
+ pipeline::{ComputePipeline, RenderPipeline, ShaderModule},
+ resource::{Buffer, Sampler, Texture, TextureView},
+ span,
+ swap_chain::SwapChain,
+ Epoch, Index,
+};
+
+use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
+use wgt::Backend;
+
+#[cfg(debug_assertions)]
+use std::cell::Cell;
+use std::{fmt::Debug, marker::PhantomData, ops, thread};
+
+/// A simple structure to manage identities of objects.
+#[derive(Debug)]
+pub struct IdentityManager {
+ free: Vec<Index>,
+ epochs: Vec<Epoch>,
+}
+
+impl Default for IdentityManager {
+ fn default() -> Self {
+ Self {
+ free: Default::default(),
+ epochs: Default::default(),
+ }
+ }
+}
+
+impl IdentityManager {
+ pub fn from_index(min_index: u32) -> Self {
+ Self {
+ free: (0..min_index).collect(),
+ epochs: vec![1; min_index as usize],
+ }
+ }
+
+ pub fn alloc<I: TypedId>(&mut self, backend: Backend) -> I {
+ match self.free.pop() {
+ Some(index) => I::zip(index, self.epochs[index as usize], backend),
+ None => {
+ let epoch = 1;
+ let id = I::zip(self.epochs.len() as Index, epoch, backend);
+ self.epochs.push(epoch);
+ id
+ }
+ }
+ }
+
+ pub fn free<I: TypedId + Debug>(&mut self, id: I) {
+ let (index, epoch, _backend) = id.unzip();
+ // avoid doing this check in release
+ if cfg!(debug_assertions) {
+ assert!(!self.free.contains(&index));
+ }
+ let pe = &mut self.epochs[index as usize];
+ assert_eq!(*pe, epoch);
+ *pe += 1;
+ self.free.push(index);
+ }
+}
+
+#[derive(Debug)]
+enum Element<T> {
+ Vacant,
+ Occupied(T, Epoch),
+ Error(Epoch, String),
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct InvalidId;
+
+#[derive(Debug)]
+pub struct Storage<T, I: TypedId> {
+ map: Vec<Element<T>>,
+ kind: &'static str,
+ _phantom: PhantomData<I>,
+}
+
+impl<T, I: TypedId> ops::Index<Valid<I>> for Storage<T, I> {
+ type Output = T;
+ fn index(&self, id: Valid<I>) -> &T {
+ self.get(id.0).unwrap()
+ }
+}
+
+impl<T, I: TypedId> ops::IndexMut<Valid<I>> for Storage<T, I> {
+ fn index_mut(&mut self, id: Valid<I>) -> &mut T {
+ self.get_mut(id.0).unwrap()
+ }
+}
+
+impl<T, I: TypedId> Storage<T, I> {
+ pub(crate) fn contains(&self, id: I) -> bool {
+ let (index, epoch, _) = id.unzip();
+ match self.map[index as usize] {
+ Element::Vacant => false,
+ Element::Occupied(_, storage_epoch) | Element::Error(storage_epoch, ..) => {
+ epoch == storage_epoch
+ }
+ }
+ }
+
+ /// Get a reference to an item behind a potentially invalid ID.
+ /// Panics if there is an epoch mismatch, or the entry is empty.
+ pub(crate) fn get(&self, id: I) -> Result<&T, InvalidId> {
+ let (index, epoch, _) = id.unzip();
+ let (result, storage_epoch) = match self.map[index as usize] {
+ Element::Occupied(ref v, epoch) => (Ok(v), epoch),
+ Element::Vacant => panic!("{}[{}] does not exist", self.kind, index),
+ Element::Error(epoch, ..) => (Err(InvalidId), epoch),
+ };
+ assert_eq!(
+ epoch, storage_epoch,
+ "{}[{}] is no longer alive",
+ self.kind, index
+ );
+ result
+ }
+
+ /// Get a mutable reference to an item behind a potentially invalid ID.
+ /// Panics if there is an epoch mismatch, or the entry is empty.
+ pub(crate) fn get_mut(&mut self, id: I) -> Result<&mut T, InvalidId> {
+ let (index, epoch, _) = id.unzip();
+ let (result, storage_epoch) = match self.map[index as usize] {
+ Element::Occupied(ref mut v, epoch) => (Ok(v), epoch),
+ Element::Vacant => panic!("{}[{}] does not exist", self.kind, index),
+ Element::Error(epoch, ..) => (Err(InvalidId), epoch),
+ };
+ assert_eq!(
+ epoch, storage_epoch,
+ "{}[{}] is no longer alive",
+ self.kind, index
+ );
+ result
+ }
+
+ pub(crate) fn label_for_invalid_id(&self, id: I) -> &str {
+ let (index, _, _) = id.unzip();
+ match self.map[index as usize] {
+ Element::Error(_, ref label) => label,
+ _ => "",
+ }
+ }
+
+ fn insert_impl(&mut self, index: usize, element: Element<T>) {
+ if index >= self.map.len() {
+ self.map.resize_with(index + 1, || Element::Vacant);
+ }
+ match std::mem::replace(&mut self.map[index], element) {
+ Element::Vacant => {}
+ _ => panic!("Index {:?} is already occupied", index),
+ }
+ }
+
+ pub(crate) fn insert(&mut self, id: I, value: T) {
+ let (index, epoch, _) = id.unzip();
+ self.insert_impl(index as usize, Element::Occupied(value, epoch))
+ }
+
+ pub(crate) fn insert_error(&mut self, id: I, label: &str) {
+ let (index, epoch, _) = id.unzip();
+ self.insert_impl(index as usize, Element::Error(epoch, label.to_string()))
+ }
+
+ pub(crate) fn remove(&mut self, id: I) -> Option<T> {
+ let (index, epoch, _) = id.unzip();
+ match std::mem::replace(&mut self.map[index as usize], Element::Vacant) {
+ Element::Occupied(value, storage_epoch) => {
+ assert_eq!(epoch, storage_epoch);
+ Some(value)
+ }
+ Element::Error(..) => None,
+ Element::Vacant => panic!("Cannot remove a vacant resource"),
+ }
+ }
+
+ // Prevents panic on out of range access, allows Vacant elements.
+ pub(crate) fn try_remove(&mut self, id: I) -> Option<T> {
+ let (index, epoch, _) = id.unzip();
+ if index as usize >= self.map.len() {
+ None
+ } else if let Element::Occupied(value, storage_epoch) =
+ std::mem::replace(&mut self.map[index as usize], Element::Vacant)
+ {
+ assert_eq!(epoch, storage_epoch);
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn iter(&self, backend: Backend) -> impl Iterator<Item = (I, &T)> {
+ self.map
+ .iter()
+ .enumerate()
+ .filter_map(move |(index, x)| match *x {
+ Element::Occupied(ref value, storage_epoch) => {
+ Some((I::zip(index as Index, storage_epoch, backend), value))
+ }
+ _ => None,
+ })
+ .into_iter()
+ }
+}
+
+/// Type system for enforcing the lock order on shared HUB structures.
+/// If type A implements `Access<B>`, that means we are allowed to proceed
+/// with locking resource `B` after we lock `A`.
+///
+/// The implenentations basically describe the edges in a directed graph
+/// of lock transitions. As long as it doesn't have loops, we can have
+/// multiple concurrent paths on this graph (from multiple threads) without
+/// deadlocks, i.e. there is always a path whose next resource is not locked
+/// by some other path, at any time.
+pub trait Access<B> {}
+
+pub enum Root {}
+//TODO: establish an order instead of declaring all the pairs.
+impl Access<Instance> for Root {}
+impl Access<Surface> for Root {}
+impl Access<Surface> for Instance {}
+impl<B: hal::Backend> Access<Adapter<B>> for Root {}
+impl<B: hal::Backend> Access<Adapter<B>> for Surface {}
+impl<B: hal::Backend> Access<Device<B>> for Root {}
+impl<B: hal::Backend> Access<Device<B>> for Surface {}
+impl<B: hal::Backend> Access<Device<B>> for Adapter<B> {}
+impl<B: hal::Backend> Access<SwapChain<B>> for Root {}
+impl<B: hal::Backend> Access<SwapChain<B>> for Device<B> {}
+impl<B: hal::Backend> Access<PipelineLayout<B>> for Root {}
+impl<B: hal::Backend> Access<PipelineLayout<B>> for Device<B> {}
+impl<B: hal::Backend> Access<PipelineLayout<B>> for RenderBundle {}
+impl<B: hal::Backend> Access<BindGroupLayout<B>> for Root {}
+impl<B: hal::Backend> Access<BindGroupLayout<B>> for Device<B> {}
+impl<B: hal::Backend> Access<BindGroupLayout<B>> for PipelineLayout<B> {}
+impl<B: hal::Backend> Access<BindGroup<B>> for Root {}
+impl<B: hal::Backend> Access<BindGroup<B>> for Device<B> {}
+impl<B: hal::Backend> Access<BindGroup<B>> for BindGroupLayout<B> {}
+impl<B: hal::Backend> Access<BindGroup<B>> for PipelineLayout<B> {}
+impl<B: hal::Backend> Access<BindGroup<B>> for CommandBuffer<B> {}
+impl<B: hal::Backend> Access<CommandBuffer<B>> for Root {}
+impl<B: hal::Backend> Access<CommandBuffer<B>> for Device<B> {}
+impl<B: hal::Backend> Access<CommandBuffer<B>> for SwapChain<B> {}
+impl<B: hal::Backend> Access<RenderBundle> for Device<B> {}
+impl<B: hal::Backend> Access<RenderBundle> for CommandBuffer<B> {}
+impl<B: hal::Backend> Access<ComputePipeline<B>> for Device<B> {}
+impl<B: hal::Backend> Access<ComputePipeline<B>> for BindGroup<B> {}
+impl<B: hal::Backend> Access<RenderPipeline<B>> for Device<B> {}
+impl<B: hal::Backend> Access<RenderPipeline<B>> for BindGroup<B> {}
+impl<B: hal::Backend> Access<RenderPipeline<B>> for ComputePipeline<B> {}
+impl<B: hal::Backend> Access<ShaderModule<B>> for Device<B> {}
+impl<B: hal::Backend> Access<ShaderModule<B>> for BindGroupLayout<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for Root {}
+impl<B: hal::Backend> Access<Buffer<B>> for Device<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for BindGroupLayout<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for BindGroup<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for CommandBuffer<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for ComputePipeline<B> {}
+impl<B: hal::Backend> Access<Buffer<B>> for RenderPipeline<B> {}
+impl<B: hal::Backend> Access<Texture<B>> for Root {}
+impl<B: hal::Backend> Access<Texture<B>> for Device<B> {}
+impl<B: hal::Backend> Access<Texture<B>> for Buffer<B> {}
+impl<B: hal::Backend> Access<TextureView<B>> for Root {}
+impl<B: hal::Backend> Access<TextureView<B>> for SwapChain<B> {}
+impl<B: hal::Backend> Access<TextureView<B>> for Device<B> {}
+impl<B: hal::Backend> Access<TextureView<B>> for Texture<B> {}
+impl<B: hal::Backend> Access<Sampler<B>> for Root {}
+impl<B: hal::Backend> Access<Sampler<B>> for Device<B> {}
+impl<B: hal::Backend> Access<Sampler<B>> for TextureView<B> {}
+
+#[cfg(debug_assertions)]
+thread_local! {
+ static ACTIVE_TOKEN: Cell<u8> = Cell::new(0);
+}
+
+/// A permission token to lock resource `T` or anything after it,
+/// as defined by the `Access` implementations.
+///
+/// Note: there can only be one non-borrowed `Token` alive on a thread
+/// at a time, which is enforced by `ACTIVE_TOKEN`.
+pub struct Token<'a, T: 'a> {
+ level: PhantomData<&'a T>,
+}
+
+impl<'a, T> Token<'a, T> {
+ fn new() -> Self {
+ #[cfg(debug_assertions)]
+ ACTIVE_TOKEN.with(|active| {
+ let old = active.get();
+ assert_ne!(old, 0, "Root token was dropped");
+ active.set(old + 1);
+ });
+ Self { level: PhantomData }
+ }
+}
+
+impl Token<'static, Root> {
+ pub fn root() -> Self {
+ #[cfg(debug_assertions)]
+ ACTIVE_TOKEN.with(|active| {
+ assert_eq!(0, active.replace(1), "Root token is already active");
+ });
+
+ Self { level: PhantomData }
+ }
+}
+
+impl<'a, T> Drop for Token<'a, T> {
+ fn drop(&mut self) {
+ #[cfg(debug_assertions)]
+ ACTIVE_TOKEN.with(|active| {
+ let old = active.get();
+ active.set(old - 1);
+ });
+ }
+}
+
+pub trait IdentityHandler<I>: Debug {
+ type Input: Clone + Debug;
+ fn process(&self, id: Self::Input, backend: Backend) -> I;
+ fn free(&self, id: I);
+}
+
+impl<I: TypedId + Debug> IdentityHandler<I> for Mutex<IdentityManager> {
+ type Input = PhantomData<I>;
+ fn process(&self, _id: Self::Input, backend: Backend) -> I {
+ self.lock().alloc(backend)
+ }
+ fn free(&self, id: I) {
+ self.lock().free(id)
+ }
+}
+
+pub trait IdentityHandlerFactory<I> {
+ type Filter: IdentityHandler<I>;
+ fn spawn(&self, min_index: Index) -> Self::Filter;
+}
+
+#[derive(Debug)]
+pub struct IdentityManagerFactory;
+
+impl<I: TypedId + Debug> IdentityHandlerFactory<I> for IdentityManagerFactory {
+ type Filter = Mutex<IdentityManager>;
+ fn spawn(&self, min_index: Index) -> Self::Filter {
+ Mutex::new(IdentityManager::from_index(min_index))
+ }
+}
+
+pub trait GlobalIdentityHandlerFactory:
+ IdentityHandlerFactory<AdapterId>
+ + IdentityHandlerFactory<DeviceId>
+ + IdentityHandlerFactory<SwapChainId>
+ + IdentityHandlerFactory<PipelineLayoutId>
+ + IdentityHandlerFactory<ShaderModuleId>
+ + IdentityHandlerFactory<BindGroupLayoutId>
+ + IdentityHandlerFactory<BindGroupId>
+ + IdentityHandlerFactory<CommandBufferId>
+ + IdentityHandlerFactory<RenderBundleId>
+ + IdentityHandlerFactory<RenderPipelineId>
+ + IdentityHandlerFactory<ComputePipelineId>
+ + IdentityHandlerFactory<BufferId>
+ + IdentityHandlerFactory<TextureId>
+ + IdentityHandlerFactory<TextureViewId>
+ + IdentityHandlerFactory<SamplerId>
+ + IdentityHandlerFactory<SurfaceId>
+{
+}
+
+impl GlobalIdentityHandlerFactory for IdentityManagerFactory {}
+
+pub type Input<G, I> = <<G as IdentityHandlerFactory<I>>::Filter as IdentityHandler<I>>::Input;
+
+pub trait Resource {
+ const TYPE: &'static str;
+ fn life_guard(&self) -> &crate::LifeGuard;
+ fn label(&self) -> &str {
+ #[cfg(debug_assertions)]
+ return &self.life_guard().label;
+ #[cfg(not(debug_assertions))]
+ return "";
+ }
+}
+
+#[derive(Debug)]
+pub struct Registry<T: Resource, I: TypedId, F: IdentityHandlerFactory<I>> {
+ identity: F::Filter,
+ data: RwLock<Storage<T, I>>,
+ backend: Backend,
+}
+
+impl<T: Resource, I: TypedId, F: IdentityHandlerFactory<I>> Registry<T, I, F> {
+ fn new(backend: Backend, factory: &F) -> Self {
+ Self {
+ identity: factory.spawn(0),
+ data: RwLock::new(Storage {
+ map: Vec::new(),
+ kind: T::TYPE,
+ _phantom: PhantomData,
+ }),
+ backend,
+ }
+ }
+
+ fn without_backend(factory: &F, kind: &'static str) -> Self {
+ Self {
+ identity: factory.spawn(1),
+ data: RwLock::new(Storage {
+ map: Vec::new(),
+ kind,
+ _phantom: PhantomData,
+ }),
+ backend: Backend::Empty,
+ }
+ }
+}
+
+impl<T: Resource, I: TypedId + Copy, F: IdentityHandlerFactory<I>> Registry<T, I, F> {
+ pub fn register<A: Access<T>>(&self, id: I, value: T, _token: &mut Token<A>) {
+ debug_assert_eq!(id.unzip().2, self.backend);
+ self.data.write().insert(id, value);
+ }
+
+ pub fn read<'a, A: Access<T>>(
+ &'a self,
+ _token: &'a mut Token<A>,
+ ) -> (RwLockReadGuard<'a, Storage<T, I>>, Token<'a, T>) {
+ (self.data.read(), Token::new())
+ }
+
+ pub fn write<'a, A: Access<T>>(
+ &'a self,
+ _token: &'a mut Token<A>,
+ ) -> (RwLockWriteGuard<'a, Storage<T, I>>, Token<'a, T>) {
+ (self.data.write(), Token::new())
+ }
+
+ pub(crate) fn register_identity<A: Access<T>>(
+ &self,
+ id_in: <F::Filter as IdentityHandler<I>>::Input,
+ value: T,
+ token: &mut Token<A>,
+ ) -> Valid<I> {
+ let id = self.identity.process(id_in, self.backend);
+ self.register(id, value, token);
+ Valid(id)
+ }
+
+ pub(crate) fn register_identity_locked(
+ &self,
+ id_in: <F::Filter as IdentityHandler<I>>::Input,
+ value: T,
+ guard: &mut Storage<T, I>,
+ ) -> Valid<I> {
+ let id = self.identity.process(id_in, self.backend);
+ guard.insert(id, value);
+ Valid(id)
+ }
+
+ pub fn register_error<A: Access<T>>(
+ &self,
+ id_in: <F::Filter as IdentityHandler<I>>::Input,
+ label: &str,
+ _token: &mut Token<A>,
+ ) -> I {
+ let id = self.identity.process(id_in, self.backend);
+ debug_assert_eq!(id.unzip().2, self.backend);
+ self.data.write().insert_error(id, label);
+ id
+ }
+
+ pub fn unregister_locked(&self, id: I, guard: &mut Storage<T, I>) -> Option<T> {
+ let value = guard.remove(id);
+ //Note: careful about the order here!
+ self.identity.free(id);
+ //Returning None is legal if it's an error ID
+ value
+ }
+
+ pub fn unregister<'a, A: Access<T>>(
+ &self,
+ id: I,
+ _token: &'a mut Token<A>,
+ ) -> (Option<T>, Token<'a, T>) {
+ let value = self.data.write().remove(id);
+ //Note: careful about the order here!
+ self.identity.free(id);
+ //Returning None is legal if it's an error ID
+ (value, Token::new())
+ }
+
+ pub fn process_id(&self, id_in: <F::Filter as IdentityHandler<I>>::Input) -> I {
+ self.identity.process(id_in, self.backend)
+ }
+
+ pub fn free_id(&self, id: I) {
+ self.identity.free(id)
+ }
+
+ pub fn label_for_resource(&self, id: I) -> String {
+ let guard = self.data.read();
+
+ let type_name = guard.kind;
+ match guard.get(id) {
+ Ok(res) => {
+ let label = res.label();
+ if label.is_empty() {
+ format!("<{}-{:?}>", type_name, id.unzip())
+ } else {
+ label.to_string()
+ }
+ }
+ Err(_) => format!(
+ "<Invalid-{} label={}>",
+ type_name,
+ guard.label_for_invalid_id(id)
+ ),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Hub<B: hal::Backend, F: GlobalIdentityHandlerFactory> {
+ pub adapters: Registry<Adapter<B>, AdapterId, F>,
+ pub devices: Registry<Device<B>, DeviceId, F>,
+ pub swap_chains: Registry<SwapChain<B>, SwapChainId, F>,
+ pub pipeline_layouts: Registry<PipelineLayout<B>, PipelineLayoutId, F>,
+ pub shader_modules: Registry<ShaderModule<B>, ShaderModuleId, F>,
+ pub bind_group_layouts: Registry<BindGroupLayout<B>, BindGroupLayoutId, F>,
+ pub bind_groups: Registry<BindGroup<B>, BindGroupId, F>,
+ pub command_buffers: Registry<CommandBuffer<B>, CommandBufferId, F>,
+ pub render_bundles: Registry<RenderBundle, RenderBundleId, F>,
+ pub render_pipelines: Registry<RenderPipeline<B>, RenderPipelineId, F>,
+ pub compute_pipelines: Registry<ComputePipeline<B>, ComputePipelineId, F>,
+ pub buffers: Registry<Buffer<B>, BufferId, F>,
+ pub textures: Registry<Texture<B>, TextureId, F>,
+ pub texture_views: Registry<TextureView<B>, TextureViewId, F>,
+ pub samplers: Registry<Sampler<B>, SamplerId, F>,
+}
+
+impl<B: GfxBackend, F: GlobalIdentityHandlerFactory> Hub<B, F> {
+ fn new(factory: &F) -> Self {
+ Self {
+ adapters: Registry::new(B::VARIANT, factory),
+ devices: Registry::new(B::VARIANT, factory),
+ swap_chains: Registry::new(B::VARIANT, factory),
+ pipeline_layouts: Registry::new(B::VARIANT, factory),
+ shader_modules: Registry::new(B::VARIANT, factory),
+ bind_group_layouts: Registry::new(B::VARIANT, factory),
+ bind_groups: Registry::new(B::VARIANT, factory),
+ command_buffers: Registry::new(B::VARIANT, factory),
+ render_bundles: Registry::new(B::VARIANT, factory),
+ render_pipelines: Registry::new(B::VARIANT, factory),
+ compute_pipelines: Registry::new(B::VARIANT, factory),
+ buffers: Registry::new(B::VARIANT, factory),
+ textures: Registry::new(B::VARIANT, factory),
+ texture_views: Registry::new(B::VARIANT, factory),
+ samplers: Registry::new(B::VARIANT, factory),
+ }
+ }
+}
+
+impl<B: GfxBackend, F: GlobalIdentityHandlerFactory> Hub<B, F> {
+ fn clear(&self, surface_guard: &mut Storage<Surface, SurfaceId>) {
+ use crate::resource::TextureViewInner;
+ use hal::{device::Device as _, window::PresentationSurface as _};
+
+ let mut devices = self.devices.data.write();
+ for element in devices.map.iter_mut() {
+ if let Element::Occupied(device, _) = element {
+ device.prepare_to_die();
+ }
+ }
+
+ for element in self.samplers.data.write().map.drain(..) {
+ if let Element::Occupied(sampler, _) = element {
+ unsafe {
+ devices[sampler.device_id.value]
+ .raw
+ .destroy_sampler(sampler.raw);
+ }
+ }
+ }
+ {
+ let textures = self.textures.data.read();
+ for element in self.texture_views.data.write().map.drain(..) {
+ if let Element::Occupied(texture_view, _) = element {
+ match texture_view.inner {
+ TextureViewInner::Native { raw, source_id } => {
+ let device = &devices[textures[source_id.value].device_id.value];
+ unsafe {
+ device.raw.destroy_image_view(raw);
+ }
+ }
+ TextureViewInner::SwapChain { .. } => {} //TODO
+ }
+ }
+ }
+ }
+
+ for element in self.textures.data.write().map.drain(..) {
+ if let Element::Occupied(texture, _) = element {
+ devices[texture.device_id.value].destroy_texture(texture);
+ }
+ }
+ for element in self.buffers.data.write().map.drain(..) {
+ if let Element::Occupied(buffer, _) = element {
+ //TODO: unmap if needed
+ devices[buffer.device_id.value].destroy_buffer(buffer);
+ }
+ }
+ for element in self.command_buffers.data.write().map.drain(..) {
+ if let Element::Occupied(command_buffer, _) = element {
+ devices[command_buffer.device_id.value]
+ .cmd_allocator
+ .after_submit(command_buffer, 0);
+ }
+ }
+ for element in self.bind_groups.data.write().map.drain(..) {
+ if let Element::Occupied(bind_group, _) = element {
+ let device = &devices[bind_group.device_id.value];
+ device.destroy_bind_group(bind_group);
+ }
+ }
+
+ for element in self.shader_modules.data.write().map.drain(..) {
+ if let Element::Occupied(module, _) = element {
+ let device = &devices[module.device_id.value];
+ unsafe {
+ device.raw.destroy_shader_module(module.raw);
+ }
+ }
+ }
+ for element in self.bind_group_layouts.data.write().map.drain(..) {
+ if let Element::Occupied(bgl, _) = element {
+ let device = &devices[bgl.device_id.value];
+ unsafe {
+ device.raw.destroy_descriptor_set_layout(bgl.raw);
+ }
+ }
+ }
+ for element in self.pipeline_layouts.data.write().map.drain(..) {
+ if let Element::Occupied(pipeline_layout, _) = element {
+ let device = &devices[pipeline_layout.device_id.value];
+ unsafe {
+ device.raw.destroy_pipeline_layout(pipeline_layout.raw);
+ }
+ }
+ }
+ for element in self.compute_pipelines.data.write().map.drain(..) {
+ if let Element::Occupied(pipeline, _) = element {
+ let device = &devices[pipeline.device_id.value];
+ unsafe {
+ device.raw.destroy_compute_pipeline(pipeline.raw);
+ }
+ }
+ }
+ for element in self.render_pipelines.data.write().map.drain(..) {
+ if let Element::Occupied(pipeline, _) = element {
+ let device = &devices[pipeline.device_id.value];
+ unsafe {
+ device.raw.destroy_graphics_pipeline(pipeline.raw);
+ }
+ }
+ }
+
+ for (index, element) in self.swap_chains.data.write().map.drain(..).enumerate() {
+ if let Element::Occupied(swap_chain, epoch) = element {
+ let device = &devices[swap_chain.device_id.value];
+ unsafe {
+ device.raw.destroy_semaphore(swap_chain.semaphore);
+ }
+ let suf_id = TypedId::zip(index as Index, epoch, B::VARIANT);
+ //TODO: hold the surface alive by the swapchain
+ if surface_guard.contains(suf_id) {
+ let surface = surface_guard.get_mut(suf_id).unwrap();
+ let suf = B::get_surface_mut(surface);
+ unsafe {
+ suf.unconfigure_swapchain(&device.raw);
+ }
+ }
+ }
+ }
+
+ for element in devices.map.drain(..) {
+ if let Element::Occupied(device, _) = element {
+ device.dispose();
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Hubs<F: GlobalIdentityHandlerFactory> {
+ #[cfg(vulkan)]
+ vulkan: Hub<backend::Vulkan, F>,
+ #[cfg(metal)]
+ metal: Hub<backend::Metal, F>,
+ #[cfg(dx12)]
+ dx12: Hub<backend::Dx12, F>,
+ #[cfg(dx11)]
+ dx11: Hub<backend::Dx11, F>,
+ #[cfg(gl)]
+ gl: Hub<backend::Gl, F>,
+}
+
+impl<F: GlobalIdentityHandlerFactory> Hubs<F> {
+ fn new(factory: &F) -> Self {
+ Self {
+ #[cfg(vulkan)]
+ vulkan: Hub::new(factory),
+ #[cfg(metal)]
+ metal: Hub::new(factory),
+ #[cfg(dx12)]
+ dx12: Hub::new(factory),
+ #[cfg(dx11)]
+ dx11: Hub::new(factory),
+ #[cfg(gl)]
+ gl: Hub::new(factory),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Global<G: GlobalIdentityHandlerFactory> {
+ pub instance: Instance,
+ pub surfaces: Registry<Surface, SurfaceId, G>,
+ hubs: Hubs<G>,
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn new(name: &str, factory: G, backends: wgt::BackendBit) -> Self {
+ span!(_guard, INFO, "Global::new");
+ Self {
+ instance: Instance::new(name, 1, backends),
+ surfaces: Registry::without_backend(&factory, "Surface"),
+ hubs: Hubs::new(&factory),
+ }
+ }
+
+ pub fn clear_backend<B: GfxBackend>(&self, _dummy: ()) {
+ let mut surface_guard = self.surfaces.data.write();
+ let hub = B::hub(self);
+ hub.clear(&mut *surface_guard);
+ }
+}
+
+impl<G: GlobalIdentityHandlerFactory> Drop for Global<G> {
+ fn drop(&mut self) {
+ if !thread::panicking() {
+ tracing::info!("Dropping Global");
+ let mut surface_guard = self.surfaces.data.write();
+
+ // destroy hubs
+ #[cfg(vulkan)]
+ {
+ self.hubs.vulkan.clear(&mut *surface_guard);
+ }
+ #[cfg(metal)]
+ {
+ self.hubs.metal.clear(&mut *surface_guard);
+ }
+ #[cfg(dx12)]
+ {
+ self.hubs.dx12.clear(&mut *surface_guard);
+ }
+ #[cfg(dx11)]
+ {
+ self.hubs.dx11.clear(&mut *surface_guard);
+ }
+ #[cfg(gl)]
+ {
+ self.hubs.gl.clear(&mut *surface_guard);
+ }
+
+ // destroy surfaces
+ for element in surface_guard.map.drain(..) {
+ if let Element::Occupied(surface, _) = element {
+ self.instance.destroy_surface(surface);
+ }
+ }
+ }
+ }
+}
+
+pub trait GfxBackend: hal::Backend {
+ const VARIANT: Backend;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G>;
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface;
+}
+
+#[cfg(vulkan)]
+impl GfxBackend for backend::Vulkan {
+ const VARIANT: Backend = Backend::Vulkan;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> {
+ &global.hubs.vulkan
+ }
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface {
+ surface.vulkan.as_mut().unwrap()
+ }
+}
+
+#[cfg(metal)]
+impl GfxBackend for backend::Metal {
+ const VARIANT: Backend = Backend::Metal;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> {
+ &global.hubs.metal
+ }
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface {
+ surface.metal.as_mut().unwrap()
+ }
+}
+
+#[cfg(dx12)]
+impl GfxBackend for backend::Dx12 {
+ const VARIANT: Backend = Backend::Dx12;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> {
+ &global.hubs.dx12
+ }
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface {
+ surface.dx12.as_mut().unwrap()
+ }
+}
+
+#[cfg(dx11)]
+impl GfxBackend for backend::Dx11 {
+ const VARIANT: Backend = Backend::Dx11;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> {
+ &global.hubs.dx11
+ }
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface {
+ surface.dx11.as_mut().unwrap()
+ }
+}
+
+#[cfg(gl)]
+impl GfxBackend for backend::Gl {
+ const VARIANT: Backend = Backend::Gl;
+ fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> {
+ &global.hubs.gl
+ }
+ fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface {
+ surface.gl.as_mut().unwrap()
+ }
+}
+
+#[cfg(test)]
+fn _test_send_sync(global: &Global<IdentityManagerFactory>) {
+ fn test_internal<T: Send + Sync>(_: T) {}
+ test_internal(global)
+}
diff --git a/gfx/wgpu/wgpu-core/src/id.rs b/gfx/wgpu/wgpu-core/src/id.rs
new file mode 100644
index 0000000000..7a1201be2e
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/id.rs
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{Epoch, Index};
+use std::{cmp::Ordering, fmt, marker::PhantomData, num::NonZeroU64};
+use wgt::Backend;
+
+const BACKEND_BITS: usize = 3;
+const EPOCH_MASK: u32 = (1 << (32 - BACKEND_BITS)) - 1;
+type Dummy = crate::backend::Empty;
+
+#[repr(transparent)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize), serde(into = "SerialId"))]
+#[cfg_attr(
+ feature = "replay",
+ derive(serde::Deserialize),
+ serde(from = "SerialId")
+)]
+#[cfg_attr(
+ all(feature = "serde", not(feature = "trace")),
+ derive(serde::Serialize)
+)]
+#[cfg_attr(
+ all(feature = "serde", not(feature = "replay")),
+ derive(serde::Deserialize)
+)]
+pub struct Id<T>(NonZeroU64, PhantomData<T>);
+
+// This type represents Id in a more readable (and editable) way.
+#[allow(dead_code)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+enum SerialId {
+ // The only variant forces RON to not ignore "Id"
+ Id(Index, Epoch, Backend),
+}
+#[cfg(feature = "trace")]
+impl<T> From<Id<T>> for SerialId {
+ fn from(id: Id<T>) -> Self {
+ let (index, epoch, backend) = id.unzip();
+ Self::Id(index, epoch, backend)
+ }
+}
+#[cfg(feature = "replay")]
+impl<T> From<SerialId> for Id<T> {
+ fn from(id: SerialId) -> Self {
+ match id {
+ SerialId::Id(index, epoch, backend) => TypedId::zip(index, epoch, backend),
+ }
+ }
+}
+
+impl<T> Id<T> {
+ #[cfg(test)]
+ pub(crate) fn dummy() -> Valid<Self> {
+ Valid(Id(NonZeroU64::new(1).unwrap(), PhantomData))
+ }
+
+ pub fn backend(self) -> Backend {
+ match self.0.get() >> (64 - BACKEND_BITS) as u8 {
+ 0 => Backend::Empty,
+ 1 => Backend::Vulkan,
+ 2 => Backend::Metal,
+ 3 => Backend::Dx12,
+ 4 => Backend::Dx11,
+ 5 => Backend::Gl,
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<T> Copy for Id<T> {}
+
+impl<T> Clone for Id<T> {
+ fn clone(&self) -> Self {
+ Self(self.0, PhantomData)
+ }
+}
+
+impl<T> fmt::Debug for Id<T> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.unzip().fmt(formatter)
+ }
+}
+
+impl<T> std::hash::Hash for Id<T> {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ }
+}
+
+impl<T> PartialEq for Id<T> {
+ fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl<T> Eq for Id<T> {}
+
+impl<T> PartialOrd for Id<T> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+
+impl<T> Ord for Id<T> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.0.cmp(&other.0)
+ }
+}
+
+/// An internal ID that has been checked to point to
+/// a valid object in the storages.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub(crate) struct Valid<I>(pub I);
+
+pub trait TypedId {
+ fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self;
+ fn unzip(self) -> (Index, Epoch, Backend);
+}
+
+impl<T> TypedId for Id<T> {
+ fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self {
+ assert_eq!(0, epoch >> (32 - BACKEND_BITS));
+ let v = index as u64 | ((epoch as u64) << 32) | ((backend as u64) << (64 - BACKEND_BITS));
+ Id(NonZeroU64::new(v).unwrap(), PhantomData)
+ }
+
+ fn unzip(self) -> (Index, Epoch, Backend) {
+ (
+ self.0.get() as u32,
+ (self.0.get() >> 32) as u32 & EPOCH_MASK,
+ self.backend(),
+ )
+ }
+}
+
+pub type AdapterId = Id<crate::instance::Adapter<Dummy>>;
+pub type SurfaceId = Id<crate::instance::Surface>;
+// Device
+pub type DeviceId = Id<crate::device::Device<Dummy>>;
+pub type QueueId = DeviceId;
+// Resource
+pub type BufferId = Id<crate::resource::Buffer<Dummy>>;
+pub type TextureViewId = Id<crate::resource::TextureView<Dummy>>;
+pub type TextureId = Id<crate::resource::Texture<Dummy>>;
+pub type SamplerId = Id<crate::resource::Sampler<Dummy>>;
+// Binding model
+pub type BindGroupLayoutId = Id<crate::binding_model::BindGroupLayout<Dummy>>;
+pub type PipelineLayoutId = Id<crate::binding_model::PipelineLayout<Dummy>>;
+pub type BindGroupId = Id<crate::binding_model::BindGroup<Dummy>>;
+// Pipeline
+pub type ShaderModuleId = Id<crate::pipeline::ShaderModule<Dummy>>;
+pub type RenderPipelineId = Id<crate::pipeline::RenderPipeline<Dummy>>;
+pub type ComputePipelineId = Id<crate::pipeline::ComputePipeline<Dummy>>;
+// Command
+pub type CommandEncoderId = CommandBufferId;
+pub type CommandBufferId = Id<crate::command::CommandBuffer<Dummy>>;
+pub type RenderPassEncoderId = *mut crate::command::RenderPass;
+pub type ComputePassEncoderId = *mut crate::command::ComputePass;
+pub type RenderBundleEncoderId = *mut crate::command::RenderBundleEncoder;
+pub type RenderBundleId = Id<crate::command::RenderBundle>;
+// Swap chain
+pub type SwapChainId = Id<crate::swap_chain::SwapChain<Dummy>>;
+
+impl SurfaceId {
+ pub(crate) fn to_swap_chain_id(self, backend: Backend) -> SwapChainId {
+ let (index, epoch, _) = self.unzip();
+ Id::zip(index, epoch, backend)
+ }
+}
+impl SwapChainId {
+ pub fn to_surface_id(self) -> SurfaceId {
+ let (index, epoch, _) = self.unzip();
+ Id::zip(index, epoch, Backend::Empty)
+ }
+}
+
+#[test]
+fn test_id_backend() {
+ for &b in &[
+ Backend::Empty,
+ Backend::Vulkan,
+ Backend::Metal,
+ Backend::Dx12,
+ Backend::Dx11,
+ Backend::Gl,
+ ] {
+ let id: Id<()> = Id::zip(1, 0, b);
+ assert_eq!(id.backend(), b);
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/instance.rs b/gfx/wgpu/wgpu-core/src/instance.rs
new file mode 100644
index 0000000000..8f156c9d3f
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/instance.rs
@@ -0,0 +1,840 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ backend,
+ device::{Device, DeviceDescriptor},
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Input, Token},
+ id::{AdapterId, DeviceId, SurfaceId, Valid},
+ span, LabelHelpers, LifeGuard, PrivateFeatures, Stored, MAX_BIND_GROUPS,
+};
+
+use wgt::{Backend, BackendBit, PowerPreference, BIND_BUFFER_ALIGNMENT};
+
+use hal::{
+ adapter::{AdapterInfo as HalAdapterInfo, DeviceType as HalDeviceType, PhysicalDevice as _},
+ queue::QueueFamily as _,
+ window::Surface as _,
+ Instance as _,
+};
+use thiserror::Error;
+
+/// Size that is guaranteed to be available in push constants.
+///
+/// This is needed because non-vulkan backends might not
+/// provide a push-constant size limit.
+const MIN_PUSH_CONSTANT_SIZE: u32 = 128;
+
+pub type RequestAdapterOptions = wgt::RequestAdapterOptions<SurfaceId>;
+
+#[derive(Debug)]
+pub struct Instance {
+ #[cfg(vulkan)]
+ pub vulkan: Option<gfx_backend_vulkan::Instance>,
+ #[cfg(metal)]
+ pub metal: Option<gfx_backend_metal::Instance>,
+ #[cfg(dx12)]
+ pub dx12: Option<gfx_backend_dx12::Instance>,
+ #[cfg(dx11)]
+ pub dx11: Option<gfx_backend_dx11::Instance>,
+ #[cfg(gl)]
+ pub gl: Option<gfx_backend_gl::Instance>,
+}
+
+impl Instance {
+ pub fn new(name: &str, version: u32, backends: BackendBit) -> Self {
+ backends_map! {
+ let map = |(backend, backend_create)| {
+ if backends.contains(backend.into()) {
+ backend_create(name, version).ok()
+ } else {
+ None
+ }
+ };
+ Self {
+ #[cfg(vulkan)]
+ vulkan: map((Backend::Vulkan, gfx_backend_vulkan::Instance::create)),
+ #[cfg(metal)]
+ metal: map((Backend::Metal, gfx_backend_metal::Instance::create)),
+ #[cfg(dx12)]
+ dx12: map((Backend::Dx12, gfx_backend_dx12::Instance::create)),
+ #[cfg(dx11)]
+ dx11: map((Backend::Dx11, gfx_backend_dx11::Instance::create)),
+ #[cfg(gl)]
+ gl: map((Backend::Gl, gfx_backend_gl::Instance::create)),
+ }
+ }
+ }
+
+ pub(crate) fn destroy_surface(&self, surface: Surface) {
+ backends_map! {
+ let map = |(surface_backend, self_backend)| {
+ unsafe {
+ if let Some(suf) = surface_backend {
+ self_backend.as_ref().unwrap().destroy_surface(suf);
+ }
+ }
+ };
+
+ #[cfg(vulkan)]
+ map((surface.vulkan, &self.vulkan)),
+ #[cfg(metal)]
+ map((surface.metal, &self.metal)),
+ #[cfg(dx12)]
+ map((surface.dx12, &self.dx12)),
+ #[cfg(dx11)]
+ map((surface.dx11, &self.dx11)),
+ #[cfg(gl)]
+ map((surface.gl, &self.gl)),
+ }
+ }
+}
+
+type GfxSurface<B> = <B as hal::Backend>::Surface;
+
+#[derive(Debug)]
+pub struct Surface {
+ #[cfg(vulkan)]
+ pub vulkan: Option<GfxSurface<backend::Vulkan>>,
+ #[cfg(metal)]
+ pub metal: Option<GfxSurface<backend::Metal>>,
+ #[cfg(dx12)]
+ pub dx12: Option<GfxSurface<backend::Dx12>>,
+ #[cfg(dx11)]
+ pub dx11: Option<GfxSurface<backend::Dx11>>,
+ #[cfg(gl)]
+ pub gl: Option<GfxSurface<backend::Gl>>,
+}
+
+impl crate::hub::Resource for Surface {
+ const TYPE: &'static str = "Surface";
+
+ fn life_guard(&self) -> &LifeGuard {
+ unreachable!()
+ }
+
+ fn label(&self) -> &str {
+ "<Surface>"
+ }
+}
+
+#[derive(Debug)]
+pub struct Adapter<B: hal::Backend> {
+ pub(crate) raw: hal::adapter::Adapter<B>,
+ features: wgt::Features,
+ limits: wgt::Limits,
+ life_guard: LifeGuard,
+}
+
+impl<B: GfxBackend> Adapter<B> {
+ fn new(raw: hal::adapter::Adapter<B>) -> Self {
+ span!(_guard, INFO, "Adapter::new");
+
+ let adapter_features = raw.physical_device.features();
+
+ let mut features = wgt::Features::default()
+ | wgt::Features::MAPPABLE_PRIMARY_BUFFERS
+ | wgt::Features::PUSH_CONSTANTS;
+ features.set(
+ wgt::Features::DEPTH_CLAMPING,
+ adapter_features.contains(hal::Features::DEPTH_CLAMP),
+ );
+ features.set(
+ wgt::Features::TEXTURE_COMPRESSION_BC,
+ adapter_features.contains(hal::Features::FORMAT_BC),
+ );
+ features.set(
+ wgt::Features::SAMPLED_TEXTURE_BINDING_ARRAY,
+ adapter_features.contains(hal::Features::TEXTURE_DESCRIPTOR_ARRAY),
+ );
+ features.set(
+ wgt::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING,
+ adapter_features.contains(hal::Features::SHADER_SAMPLED_IMAGE_ARRAY_DYNAMIC_INDEXING),
+ );
+ features.set(
+ wgt::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING,
+ adapter_features.contains(hal::Features::SAMPLED_TEXTURE_DESCRIPTOR_INDEXING),
+ );
+ features.set(
+ wgt::Features::UNSIZED_BINDING_ARRAY,
+ adapter_features.contains(hal::Features::UNSIZED_DESCRIPTOR_ARRAY),
+ );
+ features.set(
+ wgt::Features::MULTI_DRAW_INDIRECT,
+ adapter_features.contains(hal::Features::MULTI_DRAW_INDIRECT),
+ );
+ features.set(
+ wgt::Features::MULTI_DRAW_INDIRECT_COUNT,
+ adapter_features.contains(hal::Features::DRAW_INDIRECT_COUNT),
+ );
+ features.set(
+ wgt::Features::NON_FILL_POLYGON_MODE,
+ adapter_features.contains(hal::Features::NON_FILL_POLYGON_MODE),
+ );
+ #[cfg(not(target_os = "ios"))]
+ //TODO: https://github.com/gfx-rs/gfx/issues/3346
+ features.set(wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER, true);
+
+ let adapter_limits = raw.physical_device.limits();
+
+ let default_limits = wgt::Limits::default();
+
+ // All these casts to u32 are safe as the underlying vulkan types are u32s.
+ // If another backend provides larger limits than u32, we need to clamp them to u32::MAX.
+ // TODO: fix all gfx-hal backends to produce limits we care about, and remove .max
+ let limits = wgt::Limits {
+ max_bind_groups: (adapter_limits.max_bound_descriptor_sets as u32)
+ .min(MAX_BIND_GROUPS as u32)
+ .max(default_limits.max_bind_groups),
+ max_dynamic_uniform_buffers_per_pipeline_layout: (adapter_limits
+ .max_descriptor_set_uniform_buffers_dynamic
+ as u32)
+ .max(default_limits.max_dynamic_uniform_buffers_per_pipeline_layout),
+ max_dynamic_storage_buffers_per_pipeline_layout: (adapter_limits
+ .max_descriptor_set_storage_buffers_dynamic
+ as u32)
+ .max(default_limits.max_dynamic_storage_buffers_per_pipeline_layout),
+ max_sampled_textures_per_shader_stage: (adapter_limits
+ .max_per_stage_descriptor_sampled_images
+ as u32)
+ .max(default_limits.max_sampled_textures_per_shader_stage),
+ max_samplers_per_shader_stage: (adapter_limits.max_per_stage_descriptor_samplers
+ as u32)
+ .max(default_limits.max_samplers_per_shader_stage),
+ max_storage_buffers_per_shader_stage: (adapter_limits
+ .max_per_stage_descriptor_storage_buffers
+ as u32)
+ .max(default_limits.max_storage_buffers_per_shader_stage),
+ max_storage_textures_per_shader_stage: (adapter_limits
+ .max_per_stage_descriptor_storage_images
+ as u32)
+ .max(default_limits.max_storage_textures_per_shader_stage),
+ max_uniform_buffers_per_shader_stage: (adapter_limits
+ .max_per_stage_descriptor_uniform_buffers
+ as u32)
+ .max(default_limits.max_uniform_buffers_per_shader_stage),
+ max_uniform_buffer_binding_size: (adapter_limits.max_uniform_buffer_range as u32)
+ .max(default_limits.max_uniform_buffer_binding_size),
+ max_push_constant_size: (adapter_limits.max_push_constants_size as u32)
+ .max(MIN_PUSH_CONSTANT_SIZE), // As an extension, the default is always 0, so define a separate minimum.
+ };
+
+ Self {
+ raw,
+ features,
+ limits,
+ life_guard: LifeGuard::new("<Adapter>"),
+ }
+ }
+
+ fn create_device(
+ &self,
+ self_id: AdapterId,
+ desc: &DeviceDescriptor,
+ trace_path: Option<&std::path::Path>,
+ ) -> Result<Device<B>, RequestDeviceError> {
+ // Verify all features were exposed by the adapter
+ if !self.features.contains(desc.features) {
+ return Err(RequestDeviceError::UnsupportedFeature(
+ desc.features - self.features,
+ ));
+ }
+
+ // Verify feature preconditions
+ if desc
+ .features
+ .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS)
+ && self.raw.info.device_type == hal::adapter::DeviceType::DiscreteGpu
+ {
+ tracing::warn!("Feature MAPPABLE_PRIMARY_BUFFERS enabled on a discrete gpu. This is a massive performance footgun and likely not what you wanted");
+ }
+
+ let phd = &self.raw.physical_device;
+ let available_features = phd.features();
+
+ // Check features that are always needed
+ let wishful_features = hal::Features::ROBUST_BUFFER_ACCESS
+ | hal::Features::VERTEX_STORES_AND_ATOMICS
+ | hal::Features::FRAGMENT_STORES_AND_ATOMICS
+ | hal::Features::NDC_Y_UP
+ | hal::Features::INDEPENDENT_BLENDING
+ | hal::Features::SAMPLER_ANISOTROPY
+ | hal::Features::IMAGE_CUBE_ARRAY;
+ let mut enabled_features = available_features & wishful_features;
+ if enabled_features != wishful_features {
+ tracing::warn!(
+ "Missing internal features: {:?}",
+ wishful_features - enabled_features
+ );
+ }
+
+ // Features
+ enabled_features.set(
+ hal::Features::TEXTURE_DESCRIPTOR_ARRAY,
+ desc.features
+ .contains(wgt::Features::SAMPLED_TEXTURE_BINDING_ARRAY),
+ );
+ enabled_features.set(
+ hal::Features::SHADER_SAMPLED_IMAGE_ARRAY_DYNAMIC_INDEXING,
+ desc.features
+ .contains(wgt::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING),
+ );
+ enabled_features.set(
+ hal::Features::SAMPLED_TEXTURE_DESCRIPTOR_INDEXING,
+ desc.features
+ .contains(wgt::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING),
+ );
+ enabled_features.set(
+ hal::Features::UNSIZED_DESCRIPTOR_ARRAY,
+ desc.features.contains(wgt::Features::UNSIZED_BINDING_ARRAY),
+ );
+ enabled_features.set(
+ hal::Features::MULTI_DRAW_INDIRECT,
+ desc.features.contains(wgt::Features::MULTI_DRAW_INDIRECT),
+ );
+ enabled_features.set(
+ hal::Features::DRAW_INDIRECT_COUNT,
+ desc.features
+ .contains(wgt::Features::MULTI_DRAW_INDIRECT_COUNT),
+ );
+ enabled_features.set(
+ hal::Features::NON_FILL_POLYGON_MODE,
+ desc.features.contains(wgt::Features::NON_FILL_POLYGON_MODE),
+ );
+
+ let family = self
+ .raw
+ .queue_families
+ .iter()
+ .find(|family| family.queue_type().supports_graphics())
+ .ok_or(RequestDeviceError::NoGraphicsQueue)?;
+ let mut gpu =
+ unsafe { phd.open(&[(family, &[1.0])], enabled_features) }.map_err(|err| {
+ use hal::device::CreationError::*;
+ match err {
+ DeviceLost => RequestDeviceError::DeviceLost,
+ InitializationFailed => RequestDeviceError::Internal,
+ OutOfMemory(_) => RequestDeviceError::OutOfMemory,
+ _ => panic!("failed to create `gfx-hal` device: {}", err),
+ }
+ })?;
+
+ if let Some(_) = desc.label {
+ //TODO
+ }
+
+ let limits = phd.limits();
+ assert_eq!(
+ 0,
+ BIND_BUFFER_ALIGNMENT % limits.min_storage_buffer_offset_alignment,
+ "Adapter storage buffer offset alignment not compatible with WGPU"
+ );
+ assert_eq!(
+ 0,
+ BIND_BUFFER_ALIGNMENT % limits.min_uniform_buffer_offset_alignment,
+ "Adapter uniform buffer offset alignment not compatible with WGPU"
+ );
+ if self.limits < desc.limits {
+ return Err(RequestDeviceError::LimitsExceeded);
+ }
+
+ let mem_props = phd.memory_properties();
+ if !desc.shader_validation {
+ tracing::warn!("Shader validation is disabled");
+ }
+ let private_features = PrivateFeatures {
+ shader_validation: desc.shader_validation,
+ anisotropic_filtering: enabled_features.contains(hal::Features::SAMPLER_ANISOTROPY),
+ texture_d24: phd
+ .format_properties(Some(hal::format::Format::X8D24Unorm))
+ .optimal_tiling
+ .contains(hal::format::ImageFeature::DEPTH_STENCIL_ATTACHMENT),
+ texture_d24_s8: phd
+ .format_properties(Some(hal::format::Format::D24UnormS8Uint))
+ .optimal_tiling
+ .contains(hal::format::ImageFeature::DEPTH_STENCIL_ATTACHMENT),
+ };
+
+ Device::new(
+ gpu.device,
+ Stored {
+ value: Valid(self_id),
+ ref_count: self.life_guard.add_ref(),
+ },
+ gpu.queue_groups.swap_remove(0),
+ mem_props,
+ limits,
+ private_features,
+ desc,
+ trace_path,
+ )
+ .or(Err(RequestDeviceError::OutOfMemory))
+ }
+}
+
+impl<B: hal::Backend> crate::hub::Resource for Adapter<B> {
+ const TYPE: &'static str = "Adapter";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+/// Metadata about a backend adapter.
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct AdapterInfo {
+ /// Adapter name
+ pub name: String,
+ /// Vendor PCI id of the adapter
+ pub vendor: usize,
+ /// PCI id of the adapter
+ pub device: usize,
+ /// Type of device
+ pub device_type: DeviceType,
+ /// Backend used for device
+ pub backend: Backend,
+}
+
+impl AdapterInfo {
+ fn from_gfx(adapter_info: HalAdapterInfo, backend: Backend) -> Self {
+ let HalAdapterInfo {
+ name,
+ vendor,
+ device,
+ device_type,
+ } = adapter_info;
+
+ Self {
+ name,
+ vendor,
+ device,
+ device_type: device_type.into(),
+ backend,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+/// Error when requesting a device from the adaptor
+pub enum RequestDeviceError {
+ #[error("parent adapter is invalid")]
+ InvalidAdapter,
+ #[error("connection to device was lost during initialization")]
+ DeviceLost,
+ #[error("device initialization failed due to implementation specific errors")]
+ Internal,
+ #[error("some of the requested device limits are not supported")]
+ LimitsExceeded,
+ #[error("device has no queue supporting graphics")]
+ NoGraphicsQueue,
+ #[error("not enough memory left")]
+ OutOfMemory,
+ #[error("unsupported features were requested: {0:?}")]
+ UnsupportedFeature(wgt::Features),
+}
+
+/// Supported physical device types.
+#[repr(u8)]
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum DeviceType {
+ /// Other.
+ Other,
+ /// Integrated GPU with shared CPU/GPU memory.
+ IntegratedGpu,
+ /// Discrete GPU with separate CPU/GPU memory.
+ DiscreteGpu,
+ /// Virtual / Hosted.
+ VirtualGpu,
+ /// Cpu / Software Rendering.
+ Cpu,
+}
+
+impl From<HalDeviceType> for DeviceType {
+ fn from(device_type: HalDeviceType) -> Self {
+ match device_type {
+ HalDeviceType::Other => Self::Other,
+ HalDeviceType::IntegratedGpu => Self::IntegratedGpu,
+ HalDeviceType::DiscreteGpu => Self::DiscreteGpu,
+ HalDeviceType::VirtualGpu => Self::VirtualGpu,
+ HalDeviceType::Cpu => Self::Cpu,
+ }
+ }
+}
+
+pub enum AdapterInputs<'a, I> {
+ IdSet(&'a [I], fn(&I) -> Backend),
+ Mask(BackendBit, fn(Backend) -> I),
+}
+
+impl<I: Clone> AdapterInputs<'_, I> {
+ fn find(&self, b: Backend) -> Option<I> {
+ match *self {
+ Self::IdSet(ids, ref fun) => ids.iter().find(|id| fun(id) == b).cloned(),
+ Self::Mask(bits, ref fun) => {
+ if bits.contains(b.into()) {
+ Some(fun(b))
+ } else {
+ None
+ }
+ }
+ }
+ }
+}
+
+#[error("adapter is invalid")]
+#[derive(Clone, Debug, Error)]
+pub struct InvalidAdapter;
+
+#[derive(Clone, Debug, Error)]
+pub enum RequestAdapterError {
+ #[error("no suitable adapter found")]
+ NotFound,
+ #[error("surface {0:?} is invalid")]
+ InvalidSurface(SurfaceId),
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ #[cfg(feature = "raw-window-handle")]
+ pub fn instance_create_surface(
+ &self,
+ handle: &impl raw_window_handle::HasRawWindowHandle,
+ id_in: Input<G, SurfaceId>,
+ ) -> SurfaceId {
+ span!(_guard, INFO, "Instance::create_surface");
+
+ let surface = unsafe {
+ backends_map! {
+ let map = |inst| {
+ inst
+ .as_ref()
+ .and_then(|inst| inst.create_surface(handle).map_err(|e| {
+ tracing::warn!("Error: {:?}", e);
+ }).ok())
+ };
+
+ Surface {
+ #[cfg(vulkan)]
+ vulkan: map(&self.instance.vulkan),
+ #[cfg(metal)]
+ metal: map(&self.instance.metal),
+ #[cfg(dx12)]
+ dx12: map(&self.instance.dx12),
+ #[cfg(dx11)]
+ dx11: map(&self.instance.dx11),
+ #[cfg(gl)]
+ gl: map(&self.instance.gl),
+ }
+ }
+ };
+
+ let mut token = Token::root();
+ let id = self.surfaces.register_identity(id_in, surface, &mut token);
+ id.0
+ }
+
+ pub fn surface_drop(&self, id: SurfaceId) {
+ span!(_guard, INFO, "Surface::drop");
+ let mut token = Token::root();
+ let (surface, _) = self.surfaces.unregister(id, &mut token);
+ self.instance.destroy_surface(surface.unwrap());
+ }
+
+ pub fn enumerate_adapters(&self, inputs: AdapterInputs<Input<G, AdapterId>>) -> Vec<AdapterId> {
+ span!(_guard, INFO, "Instance::enumerate_adapters");
+
+ let instance = &self.instance;
+ let mut token = Token::root();
+ let mut adapters = Vec::new();
+
+ backends_map! {
+ let map = |(instance_field, backend, backend_info, backend_hub)| {
+ if let Some(inst) = instance_field {
+ let hub = backend_hub(self);
+ if let Some(id_backend) = inputs.find(backend) {
+ for raw in inst.enumerate_adapters() {
+ let adapter = Adapter::new(raw);
+ tracing::info!("Adapter {} {:?}", backend_info, adapter.raw.info);
+ let id = hub.adapters.register_identity(
+ id_backend.clone(),
+ adapter,
+ &mut token,
+ );
+ adapters.push(id.0);
+ }
+ }
+ }
+ };
+
+ #[cfg(vulkan)]
+ map((&instance.vulkan, Backend::Vulkan, "Vulkan", backend::Vulkan::hub)),
+ #[cfg(metal)]
+ map((&instance.metal, Backend::Metal, "Metal", backend::Metal::hub)),
+ #[cfg(dx12)]
+ map((&instance.dx12, Backend::Dx12, "Dx12", backend::Dx12::hub)),
+ #[cfg(dx11)]
+ map((&instance.dx11, Backend::Dx11, "Dx11", backend::Dx11::hub)),
+ #[cfg(gl)]
+ map((&instance.gl, Backend::Gl, "GL", backend::Gl::hub)),
+ }
+
+ adapters
+ }
+
+ pub fn request_adapter(
+ &self,
+ desc: &RequestAdapterOptions,
+ inputs: AdapterInputs<Input<G, AdapterId>>,
+ ) -> Result<AdapterId, RequestAdapterError> {
+ span!(_guard, INFO, "Instance::pick_adapter");
+
+ let instance = &self.instance;
+ let mut token = Token::root();
+ let (surface_guard, mut token) = self.surfaces.read(&mut token);
+ let compatible_surface = desc
+ .compatible_surface
+ .map(|id| {
+ surface_guard
+ .get(id)
+ .map_err(|_| RequestAdapterError::InvalidSurface(id))
+ })
+ .transpose()?;
+ let mut device_types = Vec::new();
+
+ let mut id_vulkan = inputs.find(Backend::Vulkan);
+ let mut id_metal = inputs.find(Backend::Metal);
+ let mut id_dx12 = inputs.find(Backend::Dx12);
+ let mut id_dx11 = inputs.find(Backend::Dx11);
+ let mut id_gl = inputs.find(Backend::Gl);
+
+ backends_map! {
+ let map = |(instance_backend, id_backend, surface_backend)| {
+ match instance_backend {
+ Some(ref inst) if id_backend.is_some() => {
+ let mut adapters = inst.enumerate_adapters();
+ if let Some(surface_backend) = compatible_surface.and_then(surface_backend) {
+ adapters.retain(|a| {
+ a.queue_families
+ .iter()
+ .find(|qf| qf.queue_type().supports_graphics())
+ .map_or(false, |qf| surface_backend.supports_queue_family(qf))
+ });
+ }
+ device_types.extend(adapters.iter().map(|ad| ad.info.device_type.clone()));
+ adapters
+ }
+ _ => Vec::new(),
+ }
+ };
+
+ // NB: The internal function definitions are a workaround for Rust
+ // being weird with lifetimes for closure literals...
+ #[cfg(vulkan)]
+ let adapters_vk = map((&instance.vulkan, &id_vulkan, {
+ fn surface_vulkan(surf: &Surface) -> Option<&GfxSurface<backend::Vulkan>> {
+ surf.vulkan.as_ref()
+ }
+ surface_vulkan
+ }));
+ #[cfg(metal)]
+ let adapters_mtl = map((&instance.metal, &id_metal, {
+ fn surface_metal(surf: &Surface) -> Option<&GfxSurface<backend::Metal>> {
+ surf.metal.as_ref()
+ }
+ surface_metal
+ }));
+ #[cfg(dx12)]
+ let adapters_dx12 = map((&instance.dx12, &id_dx12, {
+ fn surface_dx12(surf: &Surface) -> Option<&GfxSurface<backend::Dx12>> {
+ surf.dx12.as_ref()
+ }
+ surface_dx12
+ }));
+ #[cfg(dx11)]
+ let adapters_dx11 = map((&instance.dx11, &id_dx11, {
+ fn surface_dx11(surf: &Surface) -> Option<&GfxSurface<backend::Dx11>> {
+ surf.dx11.as_ref()
+ }
+ surface_dx11
+ }));
+ #[cfg(gl)]
+ let adapters_gl = map((&instance.gl, &id_gl, {
+ fn surface_gl(surf: &Surface) -> Option<&GfxSurface<backend::Gl>> {
+ surf.gl.as_ref()
+ }
+ surface_gl
+ }));
+ }
+
+ if device_types.is_empty() {
+ return Err(RequestAdapterError::NotFound);
+ }
+
+ let (mut integrated, mut discrete, mut virt, mut other) = (None, None, None, None);
+
+ for (i, ty) in device_types.into_iter().enumerate() {
+ match ty {
+ hal::adapter::DeviceType::IntegratedGpu => {
+ integrated = integrated.or(Some(i));
+ }
+ hal::adapter::DeviceType::DiscreteGpu => {
+ discrete = discrete.or(Some(i));
+ }
+ hal::adapter::DeviceType::VirtualGpu => {
+ virt = virt.or(Some(i));
+ }
+ _ => {
+ other = other.or(Some(i));
+ }
+ }
+ }
+
+ let preferred_gpu = match desc.power_preference {
+ PowerPreference::LowPower => integrated.or(other).or(discrete).or(virt),
+ PowerPreference::HighPerformance => discrete.or(other).or(integrated).or(virt),
+ };
+
+ let mut selected = preferred_gpu.unwrap_or(0);
+
+ backends_map! {
+ let map = |(info_adapter, id_backend, mut adapters_backend, backend_hub)| {
+ if selected < adapters_backend.len() {
+ let adapter = Adapter::new(adapters_backend.swap_remove(selected));
+ tracing::info!("Adapter {} {:?}", info_adapter, adapter.raw.info);
+ let id = backend_hub(self).adapters.register_identity(
+ id_backend.take().unwrap(),
+ adapter,
+ &mut token,
+ );
+ return Ok(id.0);
+ }
+ selected -= adapters_backend.len();
+ };
+
+ #[cfg(vulkan)]
+ map(("Vulkan", &mut id_vulkan, adapters_vk, backend::Vulkan::hub)),
+ #[cfg(metal)]
+ map(("Metal", &mut id_metal, adapters_mtl, backend::Metal::hub)),
+ #[cfg(dx12)]
+ map(("Dx12", &mut id_dx12, adapters_dx12, backend::Dx12::hub)),
+ #[cfg(dx11)]
+ map(("Dx11", &mut id_dx11, adapters_dx11, backend::Dx11::hub)),
+ #[cfg(gl)]
+ map(("GL", &mut id_dx11, adapters_gl, backend::Gl::hub)),
+ }
+
+ let _ = (
+ selected,
+ id_vulkan.take(),
+ id_metal.take(),
+ id_dx12.take(),
+ id_dx11.take(),
+ id_gl.take(),
+ );
+ tracing::warn!("Some adapters are present, but enumerating them failed!");
+ Err(RequestAdapterError::NotFound)
+ }
+
+ pub fn adapter_get_info<B: GfxBackend>(
+ &self,
+ adapter_id: AdapterId,
+ ) -> Result<AdapterInfo, InvalidAdapter> {
+ span!(_guard, INFO, "Adapter::get_info");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (adapter_guard, _) = hub.adapters.read(&mut token);
+ adapter_guard
+ .get(adapter_id)
+ .map(|adapter| AdapterInfo::from_gfx(adapter.raw.info.clone(), adapter_id.backend()))
+ .map_err(|_| InvalidAdapter)
+ }
+
+ pub fn adapter_features<B: GfxBackend>(
+ &self,
+ adapter_id: AdapterId,
+ ) -> Result<wgt::Features, InvalidAdapter> {
+ span!(_guard, INFO, "Adapter::features");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (adapter_guard, _) = hub.adapters.read(&mut token);
+ adapter_guard
+ .get(adapter_id)
+ .map(|adapter| adapter.features)
+ .map_err(|_| InvalidAdapter)
+ }
+
+ pub fn adapter_limits<B: GfxBackend>(
+ &self,
+ adapter_id: AdapterId,
+ ) -> Result<wgt::Limits, InvalidAdapter> {
+ span!(_guard, INFO, "Adapter::limits");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (adapter_guard, _) = hub.adapters.read(&mut token);
+ adapter_guard
+ .get(adapter_id)
+ .map(|adapter| adapter.limits.clone())
+ .map_err(|_| InvalidAdapter)
+ }
+
+ pub fn adapter_drop<B: GfxBackend>(&self, adapter_id: AdapterId) {
+ span!(_guard, INFO, "Adapter::drop");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+ let (mut adapter_guard, _) = hub.adapters.write(&mut token);
+
+ match adapter_guard.get_mut(adapter_id) {
+ Ok(adapter) => {
+ if adapter.life_guard.ref_count.take().unwrap().load() == 1 {
+ hub.adapters
+ .unregister_locked(adapter_id, &mut *adapter_guard);
+ }
+ }
+ Err(_) => {
+ hub.adapters.free_id(adapter_id);
+ }
+ }
+ }
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn adapter_request_device<B: GfxBackend>(
+ &self,
+ adapter_id: AdapterId,
+ desc: &DeviceDescriptor,
+ trace_path: Option<&std::path::Path>,
+ id_in: Input<G, DeviceId>,
+ ) -> (DeviceId, Option<RequestDeviceError>) {
+ span!(_guard, INFO, "Adapter::request_device");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let error = loop {
+ let (adapter_guard, mut token) = hub.adapters.read(&mut token);
+ let adapter = match adapter_guard.get(adapter_id) {
+ Ok(adapter) => adapter,
+ Err(_) => break RequestDeviceError::InvalidAdapter,
+ };
+ let device = match adapter.create_device(adapter_id, desc, trace_path) {
+ Ok(device) => device,
+ Err(e) => break e,
+ };
+ let id = hub.devices.register_identity(id_in, device, &mut token);
+ return (id.0, None);
+ };
+
+ let id = hub
+ .devices
+ .register_error(id_in, desc.label.borrow_or_default(), &mut token);
+ (id, Some(error))
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/lib.rs b/gfx/wgpu/wgpu-core/src/lib.rs
new file mode 100644
index 0000000000..68bb1738ab
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/lib.rs
@@ -0,0 +1,271 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![warn(
+ trivial_casts,
+ trivial_numeric_casts,
+ unused_extern_crates,
+ unused_qualifications
+)]
+// We use loops for getting early-out of scope without closures.
+#![allow(clippy::never_loop)]
+
+#[macro_use]
+mod macros;
+
+pub mod backend {
+ pub use gfx_backend_empty::Backend as Empty;
+
+ #[cfg(dx11)]
+ pub use gfx_backend_dx11::Backend as Dx11;
+ #[cfg(dx12)]
+ pub use gfx_backend_dx12::Backend as Dx12;
+ #[cfg(gl)]
+ pub use gfx_backend_gl::Backend as Gl;
+ #[cfg(metal)]
+ pub use gfx_backend_metal::Backend as Metal;
+ #[cfg(vulkan)]
+ pub use gfx_backend_vulkan::Backend as Vulkan;
+}
+
+pub mod binding_model;
+pub mod command;
+mod conv;
+pub mod device;
+pub mod hub;
+pub mod id;
+pub mod instance;
+pub mod pipeline;
+pub mod resource;
+pub mod swap_chain;
+mod track;
+mod validation;
+
+#[cfg(test)]
+use loom::sync::atomic;
+#[cfg(not(test))]
+use std::sync::atomic;
+
+use atomic::{AtomicUsize, Ordering};
+
+use std::{borrow::Cow, os::raw::c_char, ptr};
+
+pub const MAX_BIND_GROUPS: usize = 8;
+
+type SubmissionIndex = usize;
+type Index = u32;
+type Epoch = u32;
+
+pub type RawString = *const c_char;
+pub type Label<'a> = Option<Cow<'a, str>>;
+
+trait LabelHelpers<'a> {
+ fn to_string_or_default(&'a self) -> String;
+ fn borrow_or_default(&'a self) -> &'a str;
+}
+impl<'a> LabelHelpers<'a> for Label<'a> {
+ fn borrow_or_default(&'a self) -> &'a str {
+ self.as_ref().map(|cow| cow.as_ref()).unwrap_or("")
+ }
+ fn to_string_or_default(&'a self) -> String {
+ self.as_ref()
+ .map(|cow| cow.as_ref())
+ .unwrap_or("")
+ .to_string()
+ }
+}
+
+/// Reference count object that is 1:1 with each reference.
+#[derive(Debug)]
+struct RefCount(ptr::NonNull<AtomicUsize>);
+
+unsafe impl Send for RefCount {}
+unsafe impl Sync for RefCount {}
+
+impl RefCount {
+ const MAX: usize = 1 << 24;
+
+ fn load(&self) -> usize {
+ unsafe { self.0.as_ref() }.load(Ordering::Acquire)
+ }
+
+ /// This works like `std::mem::drop`, except that it returns a boolean which is true if and only
+ /// if we deallocated the underlying memory, i.e. if this was the last clone of this `RefCount`
+ /// to be dropped. This is useful for loom testing because it allows us to verify that we
+ /// deallocated the underlying memory exactly once.
+ #[cfg(test)]
+ fn rich_drop_outer(self) -> bool {
+ unsafe { std::mem::ManuallyDrop::new(self).rich_drop_inner() }
+ }
+
+ /// This function exists to allow `Self::rich_drop_outer` and `Drop::drop` to share the same
+ /// logic. To use this safely from outside of `Drop::drop`, the calling function must move
+ /// `Self` into a `ManuallyDrop`.
+ unsafe fn rich_drop_inner(&mut self) -> bool {
+ if self.0.as_ref().fetch_sub(1, Ordering::AcqRel) == 1 {
+ let _ = Box::from_raw(self.0.as_ptr());
+ true
+ } else {
+ false
+ }
+ }
+}
+
+impl Clone for RefCount {
+ fn clone(&self) -> Self {
+ let old_size = unsafe { self.0.as_ref() }.fetch_add(1, Ordering::AcqRel);
+ assert!(old_size < Self::MAX);
+ Self(self.0)
+ }
+}
+
+impl Drop for RefCount {
+ fn drop(&mut self) {
+ unsafe {
+ self.rich_drop_inner();
+ }
+ }
+}
+
+#[cfg(test)]
+#[test]
+fn loom() {
+ loom::model(move || {
+ let bx = Box::new(AtomicUsize::new(1));
+ let ref_count_main = ptr::NonNull::new(Box::into_raw(bx)).map(RefCount).unwrap();
+ let ref_count_spawned = ref_count_main.clone();
+
+ let join_handle = loom::thread::spawn(move || {
+ let _ = ref_count_spawned.clone();
+ ref_count_spawned.rich_drop_outer()
+ });
+
+ let dropped_in_main = ref_count_main.rich_drop_outer();
+ let dropped_in_spawned = join_handle.join().unwrap();
+ assert_ne!(
+ dropped_in_main, dropped_in_spawned,
+ "must drop exactly once"
+ );
+ });
+}
+
+/// Reference count object that tracks multiple references.
+/// Unlike `RefCount`, it's manually inc()/dec() called.
+#[derive(Debug)]
+struct MultiRefCount(ptr::NonNull<AtomicUsize>);
+
+unsafe impl Send for MultiRefCount {}
+unsafe impl Sync for MultiRefCount {}
+
+impl MultiRefCount {
+ fn new() -> Self {
+ let bx = Box::new(AtomicUsize::new(1));
+ let ptr = Box::into_raw(bx);
+ Self(unsafe { ptr::NonNull::new_unchecked(ptr) })
+ }
+
+ fn inc(&self) {
+ unsafe { self.0.as_ref() }.fetch_add(1, Ordering::AcqRel);
+ }
+
+ fn dec_and_check_empty(&self) -> bool {
+ unsafe { self.0.as_ref() }.fetch_sub(1, Ordering::AcqRel) == 1
+ }
+}
+
+impl Drop for MultiRefCount {
+ fn drop(&mut self) {
+ let _ = unsafe { Box::from_raw(self.0.as_ptr()) };
+ }
+}
+
+#[derive(Debug)]
+pub struct LifeGuard {
+ ref_count: Option<RefCount>,
+ submission_index: AtomicUsize,
+ #[cfg(debug_assertions)]
+ pub(crate) label: String,
+}
+
+impl LifeGuard {
+ #[allow(unused_variables)]
+ fn new(label: &str) -> Self {
+ let bx = Box::new(AtomicUsize::new(1));
+ Self {
+ ref_count: ptr::NonNull::new(Box::into_raw(bx)).map(RefCount),
+ submission_index: AtomicUsize::new(0),
+ #[cfg(debug_assertions)]
+ label: label.to_string(),
+ }
+ }
+
+ fn add_ref(&self) -> RefCount {
+ self.ref_count.clone().unwrap()
+ }
+
+ /// Returns `true` if the resource is still needed by the user.
+ fn use_at(&self, submit_index: SubmissionIndex) -> bool {
+ self.submission_index.store(submit_index, Ordering::Release);
+ self.ref_count.is_some()
+ }
+}
+
+#[derive(Clone, Debug)]
+struct Stored<T> {
+ value: id::Valid<T>,
+ ref_count: RefCount,
+}
+
+#[derive(Clone, Copy, Debug)]
+struct PrivateFeatures {
+ shader_validation: bool,
+ anisotropic_filtering: bool,
+ texture_d24: bool,
+ texture_d24_s8: bool,
+}
+
+#[macro_export]
+macro_rules! gfx_select {
+ ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => {
+ // Note: For some reason the cfg aliases defined in build.rs don't succesfully apply in this
+ // macro so we must specify their equivalents manually
+ match $id.backend() {
+ #[cfg(any(not(any(target_os = "ios", target_os = "macos")), feature = "gfx-backend-vulkan"))]
+ wgt::Backend::Vulkan => $global.$method::<$crate::backend::Vulkan>( $($param),* ),
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ wgt::Backend::Metal => $global.$method::<$crate::backend::Metal>( $($param),* ),
+ #[cfg(windows)]
+ wgt::Backend::Dx12 => $global.$method::<$crate::backend::Dx12>( $($param),* ),
+ #[cfg(windows)]
+ wgt::Backend::Dx11 => $global.$method::<$crate::backend::Dx11>( $($param),* ),
+ //#[cfg(all(unix, not(any(target_os = "ios", target_os = "macos"))))]
+ //wgt::Backend::Gl => $global.$method::<$crate::backend::Gl>( $($param),+ ),
+ other => panic!("Unexpected backend {:?}", other),
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! span {
+ ($guard_name:tt, $level:ident, $name:expr, $($fields:tt)*) => {
+ let span = tracing::span!(tracing::Level::$level, $name, $($fields)*);
+ let $guard_name = span.enter();
+ };
+ ($guard_name:tt, $level:ident, $name:expr) => {
+ let span = tracing::span!(tracing::Level::$level, $name);
+ let $guard_name = span.enter();
+ };
+}
+
+/// Fast hash map used internally.
+type FastHashMap<K, V> =
+ std::collections::HashMap<K, V, std::hash::BuildHasherDefault<fxhash::FxHasher>>;
+/// Fast hash set used internally.
+type FastHashSet<K> = std::collections::HashSet<K, std::hash::BuildHasherDefault<fxhash::FxHasher>>;
+
+#[test]
+fn test_default_limits() {
+ let limits = wgt::Limits::default();
+ assert!(limits.max_bind_groups <= MAX_BIND_GROUPS as u32);
+}
diff --git a/gfx/wgpu/wgpu-core/src/macros.rs b/gfx/wgpu/wgpu-core/src/macros.rs
new file mode 100644
index 0000000000..47485e6d42
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/macros.rs
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+macro_rules! backends_map {
+ // one let statement per backend with mapped data
+ (
+ let map = |$backend:pat| $map:block;
+ $(
+ #[cfg($backend_cfg:meta)] let $pat:pat = map($expr:expr);
+ )*
+ ) => {
+ $(
+ #[cfg($backend_cfg)]
+ let $pat = {
+ let $backend = $expr;
+ $map
+ };
+ )*
+ };
+
+ // one block statement per backend with mapped data
+ (
+ let map = |$backend:pat| $map:block;
+ $(
+ #[cfg($backend_cfg:meta)] map($expr:expr),
+ )*
+ ) => {
+ $(
+ #[cfg($backend_cfg)]
+ {
+ let $backend = $expr;
+ $map
+ }
+ )*
+ };
+
+ // a struct constructor with one field per backend with mapped data
+ (
+ let map = |$backend:pat| $map:block;
+ $Struct:ident {
+ $(
+ #[cfg($backend_cfg:meta)] $ident:ident : map($expr:expr),
+ )*
+ }
+ ) => {
+ $Struct {
+ $(
+ #[cfg($backend_cfg)]
+ $ident: {
+ let $backend = $expr;
+ $map
+ },
+ )*
+ }
+ };
+}
+
+#[test]
+fn test_backend_macro() {
+ struct Foo {
+ #[cfg(any(
+ windows,
+ all(unix, not(any(target_os = "ios", target_os = "macos"))),
+ feature = "gfx-backend-vulkan",
+ ))]
+ vulkan: u32,
+
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ metal: u32,
+
+ #[cfg(windows)]
+ dx12: u32,
+
+ #[cfg(windows)]
+ dx11: u32,
+ }
+
+ // test struct construction
+ let test_foo: Foo = backends_map! {
+ let map = |init| { init - 100 };
+ Foo {
+ #[cfg(vulkan)] vulkan: map(101),
+ #[cfg(metal)] metal: map(102),
+ #[cfg(dx12)] dx12: map(103),
+ #[cfg(dx11)] dx11: map(104),
+ }
+ };
+
+ let mut vec = Vec::new();
+
+ // test basic statement-per-backend
+ backends_map! {
+ let map = |(id, chr)| {
+ vec.push((id, chr));
+ };
+
+ #[cfg(vulkan)]
+ map((test_foo.vulkan, 'a')),
+
+ #[cfg(metal)]
+ map((test_foo.metal, 'b')),
+
+ #[cfg(dx12)]
+ map((test_foo.dx12, 'c')),
+
+ #[cfg(dx11)]
+ map((test_foo.dx11, 'd')),
+ }
+
+ #[cfg(any(
+ windows,
+ all(unix, not(any(target_os = "ios", target_os = "macos"))),
+ feature = "gfx-backend-vulkan",
+ ))]
+ assert!(vec.contains(&(1, 'a')));
+
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ assert!(vec.contains(&(2, 'b')));
+
+ #[cfg(windows)]
+ assert!(vec.contains(&(3, 'c')));
+
+ #[cfg(windows)]
+ assert!(vec.contains(&(4, 'd')));
+
+ // test complex statement-per-backend
+ backends_map! {
+ let map = |(id, pred, code)| {
+ if pred(id) {
+ code();
+ }
+ };
+
+ #[cfg(vulkan)]
+ map((test_foo.vulkan, |v| v == 1, || println!("vulkan"))),
+
+ #[cfg(metal)]
+ map((test_foo.metal, |v| v == 2, || println!("metal"))),
+
+ #[cfg(dx12)]
+ map((test_foo.dx12, |v| v == 3, || println!("dx12"))),
+
+ #[cfg(dx11)]
+ map((test_foo.dx11, |v| v == 4, || println!("dx11"))),
+ }
+
+ // test struct construction 2
+ let test_foo_2: Foo = Foo {
+ #[cfg(vulkan)]
+ vulkan: 1,
+
+ #[cfg(metal)]
+ metal: 2,
+
+ #[cfg(dx12)]
+ dx12: 3,
+
+ #[cfg(dx11)]
+ dx11: 4,
+ };
+
+ #[cfg(vulkan)]
+ let var_vulkan = test_foo_2.vulkan;
+
+ #[cfg(metal)]
+ let var_metal = test_foo_2.metal;
+
+ #[cfg(dx12)]
+ let var_dx12 = test_foo_2.dx12;
+
+ #[cfg(dx11)]
+ let var_dx11 = test_foo_2.dx11;
+
+ backends_map! {
+ let map = |(id, chr, var)| { (chr, id, var) };
+
+ #[cfg(vulkan)]
+ let var_vulkan = map((test_foo_2.vulkan, 'a', var_vulkan));
+
+ #[cfg(metal)]
+ let var_metal = map((test_foo_2.metal, 'b', var_metal));
+
+ #[cfg(dx12)]
+ let var_dx12 = map((test_foo_2.dx12, 'c', var_dx12));
+
+ #[cfg(dx11)]
+ let var_dx11 = map((test_foo_2.dx11, 'd', var_dx11));
+ }
+
+ #[cfg(vulkan)]
+ {
+ println!("backend int: {:?}", var_vulkan);
+ }
+
+ #[cfg(metal)]
+ {
+ println!("backend int: {:?}", var_metal);
+ }
+
+ #[cfg(dx12)]
+ {
+ println!("backend int: {:?}", var_dx12);
+ }
+
+ #[cfg(dx11)]
+ {
+ println!("backend int: {:?}", var_dx11);
+ }
+
+ #[cfg(any(
+ windows,
+ all(unix, not(any(target_os = "ios", target_os = "macos"))),
+ feature = "gfx-backend-vulkan",
+ ))]
+ let _ = var_vulkan;
+
+ #[cfg(any(target_os = "ios", target_os = "macos"))]
+ let _ = var_metal;
+
+ #[cfg(windows)]
+ let _ = var_dx12;
+
+ #[cfg(windows)]
+ let _ = var_dx11;
+}
diff --git a/gfx/wgpu/wgpu-core/src/pipeline.rs b/gfx/wgpu/wgpu-core/src/pipeline.rs
new file mode 100644
index 0000000000..c4ca0e1407
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/pipeline.rs
@@ -0,0 +1,254 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ binding_model::{CreateBindGroupLayoutError, CreatePipelineLayoutError},
+ device::{DeviceError, RenderPassContext},
+ hub::Resource,
+ id::{DeviceId, PipelineLayoutId, ShaderModuleId},
+ validation::StageError,
+ Label, LifeGuard, Stored,
+};
+use std::borrow::Cow;
+use thiserror::Error;
+use wgt::{BufferAddress, IndexFormat, InputStepMode};
+
+#[derive(Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub enum ShaderModuleSource<'a> {
+ SpirV(Cow<'a, [u32]>),
+ Wgsl(Cow<'a, str>),
+ // Unable to serialize with `naga::Module` in here:
+ // requires naga serialization feature.
+ //Naga(naga::Module),
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct ShaderModuleDescriptor<'a> {
+ pub label: Label<'a>,
+ pub source: ShaderModuleSource<'a>,
+}
+
+#[derive(Debug)]
+pub struct ShaderModule<B: hal::Backend> {
+ pub(crate) raw: B::ShaderModule,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) module: Option<naga::Module>,
+ #[cfg(debug_assertions)]
+ pub(crate) label: String,
+}
+
+impl<B: hal::Backend> Resource for ShaderModule<B> {
+ const TYPE: &'static str = "ShaderModule";
+
+ fn life_guard(&self) -> &LifeGuard {
+ unreachable!()
+ }
+
+ fn label(&self) -> &str {
+ #[cfg(debug_assertions)]
+ return &self.label;
+ #[cfg(not(debug_assertions))]
+ return "";
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateShaderModuleError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error(transparent)]
+ Validation(#[from] naga::proc::ValidationError),
+}
+
+/// Describes a programmable pipeline stage.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct ProgrammableStageDescriptor<'a> {
+ /// The compiled shader module for this stage.
+ pub module: ShaderModuleId,
+ /// The name of the entry point in the compiled shader. There must be a function that returns
+ /// void with this name in the shader.
+ pub entry_point: Cow<'a, str>,
+}
+
+/// Number of implicit bind groups derived at pipeline creation.
+pub type ImplicitBindGroupCount = u8;
+
+#[derive(Clone, Debug, Error)]
+pub enum ImplicitLayoutError {
+ #[error("missing IDs for deriving {0} bind groups")]
+ MissingIds(ImplicitBindGroupCount),
+ #[error("unable to reflect the shader {0:?} interface")]
+ ReflectionError(wgt::ShaderStage),
+ #[error(transparent)]
+ BindGroup(#[from] CreateBindGroupLayoutError),
+ #[error(transparent)]
+ Pipeline(#[from] CreatePipelineLayoutError),
+}
+
+/// Describes a compute pipeline.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct ComputePipelineDescriptor<'a> {
+ pub label: Label<'a>,
+ /// The layout of bind groups for this pipeline.
+ pub layout: Option<PipelineLayoutId>,
+ /// The compiled compute stage and its entry point.
+ pub compute_stage: ProgrammableStageDescriptor<'a>,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateComputePipelineError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("pipeline layout is invalid")]
+ InvalidLayout,
+ #[error("unable to derive an implicit layout")]
+ Implicit(#[from] ImplicitLayoutError),
+ #[error(transparent)]
+ Stage(StageError),
+}
+
+#[derive(Debug)]
+pub struct ComputePipeline<B: hal::Backend> {
+ pub(crate) raw: B::ComputePipeline,
+ pub(crate) layout_id: Stored<PipelineLayoutId>,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) life_guard: LifeGuard,
+}
+
+impl<B: hal::Backend> Resource for ComputePipeline<B> {
+ const TYPE: &'static str = "ComputePipeline";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+/// Describes how the vertex buffer is interpreted.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct VertexBufferDescriptor<'a> {
+ /// The stride, in bytes, between elements of this buffer.
+ pub stride: BufferAddress,
+ /// How often this vertex buffer is "stepped" forward.
+ pub step_mode: InputStepMode,
+ /// The list of attributes which comprise a single vertex.
+ pub attributes: Cow<'a, [wgt::VertexAttributeDescriptor]>,
+}
+
+/// Describes vertex input state for a render pipeline.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct VertexStateDescriptor<'a> {
+ /// The format of any index buffers used with this pipeline.
+ pub index_format: IndexFormat,
+ /// The format of any vertex buffers used with this pipeline.
+ pub vertex_buffers: Cow<'a, [VertexBufferDescriptor<'a>]>,
+}
+
+/// Describes a render (graphics) pipeline.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct RenderPipelineDescriptor<'a> {
+ pub label: Label<'a>,
+ /// The layout of bind groups for this pipeline.
+ pub layout: Option<PipelineLayoutId>,
+ /// The compiled vertex stage and its entry point.
+ pub vertex_stage: ProgrammableStageDescriptor<'a>,
+ /// The compiled fragment stage and its entry point, if any.
+ pub fragment_stage: Option<ProgrammableStageDescriptor<'a>>,
+ /// The rasterization process for this pipeline.
+ pub rasterization_state: Option<wgt::RasterizationStateDescriptor>,
+ /// The primitive topology used to interpret vertices.
+ pub primitive_topology: wgt::PrimitiveTopology,
+ /// The effect of draw calls on the color aspect of the output target.
+ pub color_states: Cow<'a, [wgt::ColorStateDescriptor]>,
+ /// The effect of draw calls on the depth and stencil aspects of the output target, if any.
+ pub depth_stencil_state: Option<wgt::DepthStencilStateDescriptor>,
+ /// The vertex input state for this pipeline.
+ pub vertex_state: VertexStateDescriptor<'a>,
+ /// The number of samples calculated per pixel (for MSAA). For non-multisampled textures,
+ /// this should be `1`
+ pub sample_count: u32,
+ /// Bitmask that restricts the samples of a pixel modified by this pipeline. All samples
+ /// can be enabled using the value `!0`
+ pub sample_mask: u32,
+ /// When enabled, produces another sample mask per pixel based on the alpha output value, that
+ /// is ANDed with the sample_mask and the primitive coverage to restrict the set of samples
+ /// affected by a primitive.
+ ///
+ /// The implicit mask produced for alpha of zero is guaranteed to be zero, and for alpha of one
+ /// is guaranteed to be all 1-s.
+ pub alpha_to_coverage_enabled: bool,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateRenderPipelineError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("pipelie layout is invalid")]
+ InvalidLayout,
+ #[error("unable to derive an implicit layout")]
+ Implicit(#[from] ImplicitLayoutError),
+ #[error("missing output at index {index}")]
+ MissingOutput { index: u8 },
+ #[error("incompatible output format at index {index}")]
+ IncompatibleOutputFormat { index: u8 },
+ #[error("invalid sample count {0}")]
+ InvalidSampleCount(u32),
+ #[error("vertex buffer {index} stride {stride} does not respect `VERTEX_STRIDE_ALIGNMENT`")]
+ UnalignedVertexStride { index: u32, stride: BufferAddress },
+ #[error("vertex attribute at location {location} has invalid offset {offset}")]
+ InvalidVertexAttributeOffset {
+ location: wgt::ShaderLocation,
+ offset: BufferAddress,
+ },
+ #[error("missing required device features {0:?}")]
+ MissingFeature(wgt::Features),
+ #[error("error in stage {flag:?}")]
+ Stage {
+ flag: wgt::ShaderStage,
+ #[source]
+ error: StageError,
+ },
+}
+
+bitflags::bitflags! {
+ #[repr(transparent)]
+ pub struct PipelineFlags: u32 {
+ const BLEND_COLOR = 1;
+ const STENCIL_REFERENCE = 2;
+ const WRITES_DEPTH_STENCIL = 4;
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderPipeline<B: hal::Backend> {
+ pub(crate) raw: B::GraphicsPipeline,
+ pub(crate) layout_id: Stored<PipelineLayoutId>,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) pass_context: RenderPassContext,
+ pub(crate) flags: PipelineFlags,
+ pub(crate) index_format: IndexFormat,
+ pub(crate) vertex_strides: Vec<(BufferAddress, InputStepMode)>,
+ pub(crate) life_guard: LifeGuard,
+}
+
+impl<B: hal::Backend> Resource for RenderPipeline<B> {
+ const TYPE: &'static str = "RenderPipeline";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/resource.rs b/gfx/wgpu/wgpu-core/src/resource.rs
new file mode 100644
index 0000000000..16319dd27e
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/resource.rs
@@ -0,0 +1,447 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+ device::{alloc::MemoryBlock, DeviceError, HostMap},
+ hub::Resource,
+ id::{DeviceId, SwapChainId, TextureId},
+ track::{TextureSelector, DUMMY_SELECTOR},
+ validation::MissingBufferUsageError,
+ Label, LifeGuard, RefCount, Stored,
+};
+
+use thiserror::Error;
+
+use std::{
+ borrow::Borrow,
+ num::{NonZeroU32, NonZeroU8},
+ ops::Range,
+ ptr::NonNull,
+};
+
+bitflags::bitflags! {
+ /// The internal enum mirrored from `BufferUsage`. The values don't have to match!
+ pub struct BufferUse: u32 {
+ const EMPTY = 0;
+ const MAP_READ = 1;
+ const MAP_WRITE = 2;
+ const COPY_SRC = 4;
+ const COPY_DST = 8;
+ const INDEX = 16;
+ const VERTEX = 32;
+ const UNIFORM = 64;
+ const STORAGE_LOAD = 128;
+ const STORAGE_STORE = 256;
+ const INDIRECT = 512;
+ /// The combination of all read-only usages.
+ const READ_ALL = Self::MAP_READ.bits | Self::COPY_SRC.bits |
+ Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits |
+ Self::STORAGE_LOAD.bits | Self::INDIRECT.bits;
+ /// The combination of all write-only and read-write usages.
+ const WRITE_ALL = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE_STORE.bits;
+ /// The combination of all usages that the are guaranteed to be be ordered by the hardware.
+ /// If a usage is not ordered, then even if it doesn't change between draw calls, there
+ /// still need to be pipeline barriers inserted for synchronization.
+ const ORDERED = Self::READ_ALL.bits | Self::MAP_WRITE.bits | Self::COPY_DST.bits;
+ }
+}
+
+bitflags::bitflags! {
+ /// The internal enum mirrored from `TextureUsage`. The values don't have to match!
+ pub struct TextureUse: u32 {
+ const EMPTY = 0;
+ const COPY_SRC = 1;
+ const COPY_DST = 2;
+ const SAMPLED = 4;
+ const ATTACHMENT_READ = 8;
+ const ATTACHMENT_WRITE = 16;
+ const STORAGE_LOAD = 32;
+ const STORAGE_STORE = 48;
+ /// The combination of all read-only usages.
+ const READ_ALL = Self::COPY_SRC.bits | Self::SAMPLED.bits | Self::ATTACHMENT_READ.bits | Self::STORAGE_LOAD.bits;
+ /// The combination of all write-only and read-write usages.
+ const WRITE_ALL = Self::COPY_DST.bits | Self::ATTACHMENT_WRITE.bits | Self::STORAGE_STORE.bits;
+ /// The combination of all usages that the are guaranteed to be be ordered by the hardware.
+ /// If a usage is not ordered, then even if it doesn't change between draw calls, there
+ /// still need to be pipeline barriers inserted for synchronization.
+ const ORDERED = Self::READ_ALL.bits | Self::COPY_DST.bits | Self::ATTACHMENT_WRITE.bits;
+ const UNINITIALIZED = 0xFFFF;
+ }
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub enum BufferMapAsyncStatus {
+ Success,
+ Error,
+ Unknown,
+ ContextLost,
+}
+
+#[derive(Debug)]
+pub(crate) enum BufferMapState<B: hal::Backend> {
+ /// Mapped at creation.
+ Init {
+ ptr: NonNull<u8>,
+ stage_buffer: B::Buffer,
+ stage_memory: MemoryBlock<B>,
+ needs_flush: bool,
+ },
+ /// Waiting for GPU to be done before mapping
+ Waiting(BufferPendingMapping),
+ /// Mapped
+ Active {
+ ptr: NonNull<u8>,
+ sub_range: hal::buffer::SubRange,
+ host: HostMap,
+ },
+ /// Not mapped
+ Idle,
+}
+
+unsafe impl<B: hal::Backend> Send for BufferMapState<B> {}
+unsafe impl<B: hal::Backend> Sync for BufferMapState<B> {}
+
+pub type BufferMapCallback = unsafe extern "C" fn(status: BufferMapAsyncStatus, userdata: *mut u8);
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct BufferMapOperation {
+ pub host: HostMap,
+ pub callback: BufferMapCallback,
+ pub user_data: *mut u8,
+}
+
+//TODO: clarify if/why this is needed here
+unsafe impl Send for BufferMapOperation {}
+unsafe impl Sync for BufferMapOperation {}
+
+impl BufferMapOperation {
+ pub(crate) fn call_error(self) {
+ tracing::error!("wgpu_buffer_map_async failed: buffer mapping is pending");
+ unsafe {
+ (self.callback)(BufferMapAsyncStatus::Error, self.user_data);
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum BufferAccessError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("buffer is invalid")]
+ Invalid,
+ #[error("buffer is destroyed")]
+ Destroyed,
+ #[error("buffer is already mapped")]
+ AlreadyMapped,
+ #[error(transparent)]
+ MissingBufferUsage(#[from] MissingBufferUsageError),
+ #[error("buffer is not mapped")]
+ NotMapped,
+ #[error("buffer map range does not respect `COPY_BUFFER_ALIGNMENT`")]
+ UnalignedRange,
+}
+
+#[derive(Debug)]
+pub(crate) struct BufferPendingMapping {
+ pub range: Range<wgt::BufferAddress>,
+ pub op: BufferMapOperation,
+ // hold the parent alive while the mapping is active
+ pub parent_ref_count: RefCount,
+}
+
+pub type BufferDescriptor<'a> = wgt::BufferDescriptor<Label<'a>>;
+
+#[derive(Debug)]
+pub struct Buffer<B: hal::Backend> {
+ pub(crate) raw: Option<(B::Buffer, MemoryBlock<B>)>,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) usage: wgt::BufferUsage,
+ pub(crate) size: wgt::BufferAddress,
+ pub(crate) full_range: (),
+ pub(crate) sync_mapped_writes: Option<hal::memory::Segment>,
+ pub(crate) life_guard: LifeGuard,
+ pub(crate) map_state: BufferMapState<B>,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateBufferError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("failed to map buffer while creating: {0}")]
+ AccessError(#[from] BufferAccessError),
+ #[error("buffers that are mapped at creation have to be aligned to `COPY_BUFFER_ALIGNMENT`")]
+ UnalignedSize,
+ #[error("`MAP` usage can only be combined with the opposite `COPY`, requested {0:?}")]
+ UsageMismatch(wgt::BufferUsage),
+}
+
+impl<B: hal::Backend> Resource for Buffer<B> {
+ const TYPE: &'static str = "Buffer";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+impl<B: hal::Backend> Borrow<()> for Buffer<B> {
+ fn borrow(&self) -> &() {
+ &DUMMY_SELECTOR
+ }
+}
+
+pub type TextureDescriptor<'a> = wgt::TextureDescriptor<Label<'a>>;
+
+#[derive(Debug)]
+pub struct Texture<B: hal::Backend> {
+ pub(crate) raw: Option<(B::Image, MemoryBlock<B>)>,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) usage: wgt::TextureUsage,
+ pub(crate) aspects: hal::format::Aspects,
+ pub(crate) dimension: wgt::TextureDimension,
+ pub(crate) kind: hal::image::Kind,
+ pub(crate) format: wgt::TextureFormat,
+ pub(crate) full_range: TextureSelector,
+ pub(crate) life_guard: LifeGuard,
+}
+
+#[derive(Clone, Debug)]
+pub enum TextureErrorDimension {
+ X,
+ Y,
+ Z,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum TextureDimensionError {
+ #[error("Dimension {0:?} is zero")]
+ Zero(TextureErrorDimension),
+ #[error("1D textures must have height set to 1")]
+ InvalidHeight,
+ #[error("sample count {0} is invalid")]
+ InvalidSampleCount(u32),
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateTextureError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("D24Plus textures cannot be copied")]
+ CannotCopyD24Plus,
+ #[error(transparent)]
+ InvalidDimension(#[from] TextureDimensionError),
+ #[error("texture descriptor mip level count ({0}) is invalid")]
+ InvalidMipLevelCount(u32),
+ #[error("Feature {0:?} must be enabled to create a texture of type {1:?}")]
+ MissingFeature(wgt::Features, wgt::TextureFormat),
+}
+
+impl<B: hal::Backend> Resource for Texture<B> {
+ const TYPE: &'static str = "Texture";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+impl<B: hal::Backend> Borrow<TextureSelector> for Texture<B> {
+ fn borrow(&self) -> &TextureSelector {
+ &self.full_range
+ }
+}
+
+/// Describes a [`TextureView`].
+#[derive(Clone, Debug, Default, PartialEq)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize), serde(default))]
+pub struct TextureViewDescriptor<'a> {
+ /// Debug label of the texture view. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// Format of the texture view, or `None` for the same format as the texture itself.
+ /// At this time, it must be the same the underlying format of the texture.
+ pub format: Option<wgt::TextureFormat>,
+ /// The dimension of the texture view. For 1D textures, this must be `1D`. For 2D textures it must be one of
+ /// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `3D`
+ pub dimension: Option<wgt::TextureViewDimension>,
+ /// Aspect of the texture. Color textures must be [`TextureAspect::All`].
+ pub aspect: wgt::TextureAspect,
+ /// Base mip level.
+ pub base_mip_level: u32,
+ /// Mip level count.
+ /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count.
+ /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total.
+ pub level_count: Option<NonZeroU32>,
+ /// Base array layer.
+ pub base_array_layer: u32,
+ /// Layer count.
+ /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count.
+ /// If `None`, considered to include the rest of the array layers, but at least 1 in total.
+ pub array_layer_count: Option<NonZeroU32>,
+}
+
+#[derive(Debug)]
+pub(crate) enum TextureViewInner<B: hal::Backend> {
+ Native {
+ raw: B::ImageView,
+ source_id: Stored<TextureId>,
+ },
+ SwapChain {
+ image: <B::Surface as hal::window::PresentationSurface<B>>::SwapchainImage,
+ source_id: Stored<SwapChainId>,
+ },
+}
+
+#[derive(Debug)]
+pub struct TextureView<B: hal::Backend> {
+ pub(crate) inner: TextureViewInner<B>,
+ //TODO: store device_id for quick access?
+ pub(crate) aspects: hal::format::Aspects,
+ pub(crate) format: wgt::TextureFormat,
+ pub(crate) extent: hal::image::Extent,
+ pub(crate) samples: hal::image::NumSamples,
+ pub(crate) selector: TextureSelector,
+ pub(crate) life_guard: LifeGuard,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateTextureViewError {
+ #[error("parent texture is invalid or destroyed")]
+ InvalidTexture,
+ #[error("not enough memory left")]
+ OutOfMemory,
+ #[error("Invalid texture view dimension `{view:?}` with texture of dimension `{image:?}`")]
+ InvalidTextureViewDimension {
+ view: wgt::TextureViewDimension,
+ image: wgt::TextureDimension,
+ },
+ #[error("Invalid texture depth `{depth}` for texture view of dimension `Cubemap`. Cubemap views must use images of size 6.")]
+ InvalidCubemapTextureDepth { depth: u16 },
+ #[error("Invalid texture depth `{depth}` for texture view of dimension `CubemapArray`. Cubemap views must use images with sizes which are a multiple of 6.")]
+ InvalidCubemapArrayTextureDepth { depth: u16 },
+ #[error(
+ "TextureView mip level count + base mip level {requested} must be <= Texture mip level count {total}"
+ )]
+ TooManyMipLevels { requested: u32, total: u8 },
+ #[error("TextureView array layer count + base array layer {requested} must be <= Texture depth/array layer count {total}")]
+ TooManyArrayLayers { requested: u32, total: u16 },
+ #[error("Requested array layer count {requested} is not valid for the target view dimension {dim:?}")]
+ InvalidArrayLayerCount {
+ requested: u32,
+ dim: wgt::TextureViewDimension,
+ },
+ #[error("Aspect {requested:?} is not in the source texture ({total:?})")]
+ InvalidAspect {
+ requested: hal::format::Aspects,
+ total: hal::format::Aspects,
+ },
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum TextureViewDestroyError {
+ #[error("cannot destroy swap chain image")]
+ SwapChainImage,
+}
+
+impl<B: hal::Backend> Resource for TextureView<B> {
+ const TYPE: &'static str = "TextureView";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+impl<B: hal::Backend> Borrow<()> for TextureView<B> {
+ fn borrow(&self) -> &() {
+ &DUMMY_SELECTOR
+ }
+}
+
+/// Describes a [`Sampler`]
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "trace", derive(serde::Serialize))]
+#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
+pub struct SamplerDescriptor<'a> {
+ /// Debug label of the sampler. This will show up in graphics debuggers for easy identification.
+ pub label: Label<'a>,
+ /// How to deal with out of bounds accesses in the u (i.e. x) direction
+ pub address_modes: [wgt::AddressMode; 3],
+ /// How to filter the texture when it needs to be magnified (made larger)
+ pub mag_filter: wgt::FilterMode,
+ /// How to filter the texture when it needs to be minified (made smaller)
+ pub min_filter: wgt::FilterMode,
+ /// How to filter between mip map levels
+ pub mipmap_filter: wgt::FilterMode,
+ /// Minimum level of detail (i.e. mip level) to use
+ pub lod_min_clamp: f32,
+ /// Maximum level of detail (i.e. mip level) to use
+ pub lod_max_clamp: f32,
+ /// If this is enabled, this is a comparison sampler using the given comparison function.
+ pub compare: Option<wgt::CompareFunction>,
+ /// Valid values: 1, 2, 4, 8, and 16.
+ pub anisotropy_clamp: Option<NonZeroU8>,
+ /// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
+ pub border_color: Option<wgt::SamplerBorderColor>,
+}
+
+impl Default for SamplerDescriptor<'_> {
+ fn default() -> Self {
+ Self {
+ label: None,
+ address_modes: Default::default(),
+ mag_filter: Default::default(),
+ min_filter: Default::default(),
+ mipmap_filter: Default::default(),
+ lod_min_clamp: 0.0,
+ lod_max_clamp: std::f32::MAX,
+ compare: None,
+ anisotropy_clamp: None,
+ border_color: None,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Sampler<B: hal::Backend> {
+ pub(crate) raw: B::Sampler,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) life_guard: LifeGuard,
+ /// `true` if this is a comparison sampler
+ pub(crate) comparison: bool,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateSamplerError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("invalid anisotropic clamp {0}, must be one of 1, 2, 4, 8 or 16")]
+ InvalidClamp(u8),
+ #[error("cannot create any more samplers")]
+ TooManyObjects,
+ /// AddressMode::ClampToBorder requires feature ADDRESS_MODE_CLAMP_TO_BORDER
+ #[error("Feature {0:?} must be enabled")]
+ MissingFeature(wgt::Features),
+}
+
+impl<B: hal::Backend> Resource for Sampler<B> {
+ const TYPE: &'static str = "Sampler";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+impl<B: hal::Backend> Borrow<()> for Sampler<B> {
+ fn borrow(&self) -> &() {
+ &DUMMY_SELECTOR
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum DestroyError {
+ #[error("resource is invalid")]
+ Invalid,
+ #[error("resource is already destroyed")]
+ AlreadyDestroyed,
+}
diff --git a/gfx/wgpu/wgpu-core/src/swap_chain.rs b/gfx/wgpu/wgpu-core/src/swap_chain.rs
new file mode 100644
index 0000000000..22c65495bc
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/swap_chain.rs
@@ -0,0 +1,294 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*! Swap chain management.
+
+ ## Lifecycle
+
+ At the low level, the swap chain is using the new simplified model of gfx-rs.
+
+ A swap chain is a separate object that is backend-dependent but shares the index with
+ the parent surface, which is backend-independent. This ensures a 1:1 correspondence
+ between them.
+
+ `get_next_image()` requests a new image from the surface. It becomes a part of
+ `TextureViewInner::SwapChain` of the resulted view. The view is registered in the HUB
+ but not in the device tracker.
+
+ The only operation allowed on the view is to be either a color or a resolve attachment.
+ It can only be used in one command buffer, which needs to be submitted before presenting.
+ Command buffer tracker knows about the view, but only for the duration of recording.
+ The view ID is erased from it at the end, so that it's not merged into the device tracker.
+
+ When a swapchain view is used in `begin_render_pass()`, we assume the start and end image
+ layouts purely based on whether or not this view was used in this command buffer before.
+ It always starts with `Uninitialized` and ends with `Present`, so that no barriers are
+ needed when we need to actually present it.
+
+ In `queue_submit()` we make sure to signal the semaphore whenever we render to a swap
+ chain view.
+
+ In `present()` we return the swap chain image back and wait on the semaphore.
+!*/
+
+#[cfg(feature = "trace")]
+use crate::device::trace::Action;
+use crate::{
+ conv,
+ device::DeviceError,
+ hub::{GfxBackend, Global, GlobalIdentityHandlerFactory, Input, Token},
+ id::{DeviceId, SwapChainId, TextureViewId, Valid},
+ resource, span,
+ track::TextureSelector,
+ LifeGuard, PrivateFeatures, Stored, SubmissionIndex,
+};
+
+use hal::{self, device::Device as _, queue::CommandQueue as _, window::PresentationSurface as _};
+use thiserror::Error;
+use wgt::{SwapChainDescriptor, SwapChainStatus};
+
+const FRAME_TIMEOUT_MS: u64 = 1000;
+pub const DESIRED_NUM_FRAMES: u32 = 3;
+
+#[derive(Debug)]
+pub struct SwapChain<B: hal::Backend> {
+ pub(crate) life_guard: LifeGuard,
+ pub(crate) device_id: Stored<DeviceId>,
+ pub(crate) desc: SwapChainDescriptor,
+ pub(crate) num_frames: hal::window::SwapImageIndex,
+ pub(crate) semaphore: B::Semaphore,
+ pub(crate) acquired_view_id: Option<Stored<TextureViewId>>,
+ pub(crate) acquired_framebuffers: Vec<B::Framebuffer>,
+ pub(crate) active_submission_index: SubmissionIndex,
+}
+
+impl<B: hal::Backend> crate::hub::Resource for SwapChain<B> {
+ const TYPE: &'static str = "SwapChain";
+
+ fn life_guard(&self) -> &LifeGuard {
+ &self.life_guard
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum SwapChainError {
+ #[error("swap chain is invalid")]
+ Invalid,
+ #[error("parent surface is invalid")]
+ InvalidSurface,
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("swap chain image is already acquired")]
+ AlreadyAcquired,
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum CreateSwapChainError {
+ #[error(transparent)]
+ Device(#[from] DeviceError),
+ #[error("invalid surface")]
+ InvalidSurface,
+ #[error("`SwapChainOutput` must be dropped before a new `SwapChain` is made")]
+ SwapChainOutputExists,
+ #[error("surface does not support the adapter's queue family")]
+ UnsupportedQueueFamily,
+ #[error("requested format {requested:?} is not in list of supported formats: {available:?}")]
+ UnsupportedFormat {
+ requested: hal::format::Format,
+ available: Vec<hal::format::Format>,
+ },
+}
+
+pub(crate) fn swap_chain_descriptor_to_hal(
+ desc: &SwapChainDescriptor,
+ num_frames: u32,
+ private_features: PrivateFeatures,
+) -> hal::window::SwapchainConfig {
+ let mut config = hal::window::SwapchainConfig::new(
+ desc.width,
+ desc.height,
+ conv::map_texture_format(desc.format, private_features),
+ num_frames,
+ );
+ //TODO: check for supported
+ config.image_usage = conv::map_texture_usage(desc.usage, hal::format::Aspects::COLOR);
+ config.composite_alpha_mode = hal::window::CompositeAlphaMode::OPAQUE;
+ config.present_mode = match desc.present_mode {
+ wgt::PresentMode::Immediate => hal::window::PresentMode::IMMEDIATE,
+ wgt::PresentMode::Mailbox => hal::window::PresentMode::MAILBOX,
+ wgt::PresentMode::Fifo => hal::window::PresentMode::FIFO,
+ };
+ config
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct SwapChainOutput {
+ pub status: SwapChainStatus,
+ pub view_id: Option<TextureViewId>,
+}
+
+impl<G: GlobalIdentityHandlerFactory> Global<G> {
+ pub fn swap_chain_get_current_texture_view<B: GfxBackend>(
+ &self,
+ swap_chain_id: SwapChainId,
+ view_id_in: Input<G, TextureViewId>,
+ ) -> Result<SwapChainOutput, SwapChainError> {
+ span!(_guard, INFO, "SwapChain::get_next_texture");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut surface_guard, mut token) = self.surfaces.write(&mut token);
+ let surface = surface_guard
+ .get_mut(swap_chain_id.to_surface_id())
+ .map_err(|_| SwapChainError::InvalidSurface)?;
+ let (device_guard, mut token) = hub.devices.read(&mut token);
+ let (mut swap_chain_guard, mut token) = hub.swap_chains.write(&mut token);
+ let sc = swap_chain_guard
+ .get_mut(swap_chain_id)
+ .map_err(|_| SwapChainError::Invalid)?;
+ #[cfg_attr(not(feature = "trace"), allow(unused_variables))]
+ let device = &device_guard[sc.device_id.value];
+
+ let suf = B::get_surface_mut(surface);
+ let (image, status) = match unsafe { suf.acquire_image(FRAME_TIMEOUT_MS * 1_000_000) } {
+ Ok((surface_image, None)) => (Some(surface_image), SwapChainStatus::Good),
+ Ok((surface_image, Some(_))) => (Some(surface_image), SwapChainStatus::Suboptimal),
+ Err(err) => (
+ None,
+ match err {
+ hal::window::AcquireError::OutOfMemory(_) => Err(DeviceError::OutOfMemory)?,
+ hal::window::AcquireError::NotReady => unreachable!(), // we always set a timeout
+ hal::window::AcquireError::Timeout => SwapChainStatus::Timeout,
+ hal::window::AcquireError::OutOfDate => SwapChainStatus::Outdated,
+ hal::window::AcquireError::SurfaceLost(_) => SwapChainStatus::Lost,
+ hal::window::AcquireError::DeviceLost(_) => Err(DeviceError::Lost)?,
+ },
+ ),
+ };
+
+ let view_id = match image {
+ Some(image) => {
+ let view = resource::TextureView {
+ inner: resource::TextureViewInner::SwapChain {
+ image,
+ source_id: Stored {
+ value: Valid(swap_chain_id),
+ ref_count: sc.life_guard.add_ref(),
+ },
+ },
+ aspects: hal::format::Aspects::COLOR,
+ format: sc.desc.format,
+ extent: hal::image::Extent {
+ width: sc.desc.width,
+ height: sc.desc.height,
+ depth: 1,
+ },
+ samples: 1,
+ selector: TextureSelector {
+ layers: 0..1,
+ levels: 0..1,
+ },
+ life_guard: LifeGuard::new("<SwapChain View>"),
+ };
+
+ let ref_count = view.life_guard.add_ref();
+ let id = hub
+ .texture_views
+ .register_identity(view_id_in, view, &mut token);
+
+ if sc.acquired_view_id.is_some() {
+ return Err(SwapChainError::AlreadyAcquired);
+ }
+
+ sc.acquired_view_id = Some(Stored {
+ value: id,
+ ref_count,
+ });
+
+ Some(id.0)
+ }
+ None => None,
+ };
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(Action::GetSwapChainTexture {
+ id: view_id,
+ parent_id: swap_chain_id,
+ });
+ }
+
+ Ok(SwapChainOutput { status, view_id })
+ }
+
+ pub fn swap_chain_present<B: GfxBackend>(
+ &self,
+ swap_chain_id: SwapChainId,
+ ) -> Result<SwapChainStatus, SwapChainError> {
+ span!(_guard, INFO, "SwapChain::present");
+
+ let hub = B::hub(self);
+ let mut token = Token::root();
+
+ let (mut surface_guard, mut token) = self.surfaces.write(&mut token);
+ let surface = surface_guard
+ .get_mut(swap_chain_id.to_surface_id())
+ .map_err(|_| SwapChainError::InvalidSurface)?;
+ let (mut device_guard, mut token) = hub.devices.write(&mut token);
+ let (mut swap_chain_guard, mut token) = hub.swap_chains.write(&mut token);
+ let sc = swap_chain_guard
+ .get_mut(swap_chain_id)
+ .map_err(|_| SwapChainError::Invalid)?;
+ let device = &mut device_guard[sc.device_id.value];
+
+ #[cfg(feature = "trace")]
+ if let Some(ref trace) = device.trace {
+ trace.lock().add(Action::PresentSwapChain(swap_chain_id));
+ }
+
+ let view_id = sc
+ .acquired_view_id
+ .take()
+ .ok_or(SwapChainError::AlreadyAcquired)?;
+ let (view_maybe, _) = hub.texture_views.unregister(view_id.value.0, &mut token);
+ let view = view_maybe.ok_or(SwapChainError::Invalid)?;
+ let image = match view.inner {
+ resource::TextureViewInner::Native { .. } => unreachable!(),
+ resource::TextureViewInner::SwapChain { image, .. } => image,
+ };
+
+ let sem = if sc.active_submission_index > device.last_completed_submission_index() {
+ Some(&sc.semaphore)
+ } else {
+ None
+ };
+ let queue = &mut device.queue_group.queues[0];
+ let result = unsafe { queue.present(B::get_surface_mut(surface), image, sem) };
+
+ tracing::debug!(trace = true, "Presented. End of Frame");
+
+ for fbo in sc.acquired_framebuffers.drain(..) {
+ unsafe {
+ device.raw.destroy_framebuffer(fbo);
+ }
+ }
+
+ match result {
+ Ok(None) => Ok(SwapChainStatus::Good),
+ Ok(Some(_)) => Ok(SwapChainStatus::Suboptimal),
+ Err(err) => match err {
+ hal::window::PresentError::OutOfMemory(_) => {
+ Err(SwapChainError::Device(DeviceError::OutOfMemory))
+ }
+ hal::window::PresentError::OutOfDate => Ok(SwapChainStatus::Outdated),
+ hal::window::PresentError::SurfaceLost(_) => Ok(SwapChainStatus::Lost),
+ hal::window::PresentError::DeviceLost(_) => {
+ Err(SwapChainError::Device(DeviceError::Lost))
+ }
+ },
+ }
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/buffer.rs b/gfx/wgpu/wgpu-core/src/track/buffer.rs
new file mode 100644
index 0000000000..e4999a9ae4
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/buffer.rs
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{PendingTransition, ResourceState, Unit};
+use crate::{
+ id::{BufferId, Valid},
+ resource::BufferUse,
+};
+
+//TODO: store `hal::buffer::State` here to avoid extra conversions
+pub(crate) type BufferState = Unit<BufferUse>;
+
+impl PendingTransition<BufferState> {
+ fn collapse(self) -> Result<BufferUse, Self> {
+ if self.usage.start.is_empty()
+ || self.usage.start == self.usage.end
+ || !BufferUse::WRITE_ALL.intersects(self.usage.start | self.usage.end)
+ {
+ Ok(self.usage.start | self.usage.end)
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl Default for BufferState {
+ fn default() -> Self {
+ Self {
+ first: None,
+ last: BufferUse::empty(),
+ }
+ }
+}
+
+impl BufferState {
+ pub fn with_usage(usage: BufferUse) -> Self {
+ Unit::new(usage)
+ }
+}
+
+impl ResourceState for BufferState {
+ type Id = BufferId;
+ type Selector = ();
+ type Usage = BufferUse;
+
+ fn query(&self, _selector: Self::Selector) -> Option<Self::Usage> {
+ Some(self.last)
+ }
+
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ usage: Self::Usage,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let old = self.last;
+ if old != usage || !BufferUse::ORDERED.contains(usage) {
+ let pending = PendingTransition {
+ id,
+ selector: (),
+ usage: old..usage,
+ };
+ *self = match output {
+ None => {
+ assert_eq!(
+ self.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(transitions) => {
+ transitions.push(pending);
+ Unit {
+ first: self.first.or(Some(old)),
+ last: usage,
+ }
+ }
+ };
+ }
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ match self.first {
+ Some(old) if old != usage => Err(PendingTransition {
+ id,
+ selector: (),
+ usage: old..usage,
+ }),
+ _ => {
+ self.first = Some(usage);
+ Ok(())
+ }
+ }
+ }
+
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let old = self.last;
+ let new = other.port();
+ if old == new && BufferUse::ORDERED.contains(new) {
+ if output.is_some() && self.first.is_none() {
+ self.first = Some(old);
+ }
+ } else {
+ let pending = PendingTransition {
+ id,
+ selector: (),
+ usage: old..new,
+ };
+ *self = match output {
+ None => {
+ assert_eq!(
+ self.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(transitions) => {
+ transitions.push(pending);
+ Unit {
+ first: self.first.or(Some(old)),
+ last: other.last,
+ }
+ }
+ };
+ }
+ Ok(())
+ }
+
+ fn optimize(&mut self) {}
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::id::Id;
+
+ #[test]
+ fn change_extend() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::INDEX,
+ };
+ let id = Id::dummy();
+ assert_eq!(
+ bs.change(id, (), BufferUse::STORAGE_STORE, None),
+ Err(PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::INDEX..BufferUse::STORAGE_STORE,
+ }),
+ );
+ bs.change(id, (), BufferUse::VERTEX, None).unwrap();
+ bs.change(id, (), BufferUse::INDEX, None).unwrap();
+ assert_eq!(bs, Unit::new(BufferUse::VERTEX | BufferUse::INDEX));
+ }
+
+ #[test]
+ fn change_replace() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::STORAGE_STORE,
+ };
+ let id = Id::dummy();
+ let mut list = Vec::new();
+ bs.change(id, (), BufferUse::VERTEX, Some(&mut list))
+ .unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::STORAGE_STORE..BufferUse::VERTEX,
+ }],
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::STORAGE_STORE),
+ last: BufferUse::VERTEX,
+ }
+ );
+
+ list.clear();
+ bs.change(id, (), BufferUse::STORAGE_STORE, Some(&mut list))
+ .unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::VERTEX..BufferUse::STORAGE_STORE,
+ }],
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::STORAGE_STORE),
+ last: BufferUse::STORAGE_STORE,
+ }
+ );
+ }
+
+ #[test]
+ fn prepend() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::VERTEX,
+ };
+ let id = Id::dummy();
+ bs.prepend(id, (), BufferUse::INDEX).unwrap();
+ bs.prepend(id, (), BufferUse::INDEX).unwrap();
+ assert_eq!(
+ bs.prepend(id, (), BufferUse::STORAGE_LOAD),
+ Err(PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::INDEX..BufferUse::STORAGE_LOAD,
+ })
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::INDEX),
+ last: BufferUse::VERTEX,
+ }
+ );
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/mod.rs b/gfx/wgpu/wgpu-core/src/track/mod.rs
new file mode 100644
index 0000000000..6d7e908ef6
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/mod.rs
@@ -0,0 +1,593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod buffer;
+mod range;
+mod texture;
+
+use crate::{
+ conv, hub,
+ id::{self, TypedId, Valid},
+ resource, Epoch, FastHashMap, Index, RefCount,
+};
+
+use std::{collections::hash_map::Entry, fmt, marker::PhantomData, ops, vec::Drain};
+use thiserror::Error;
+
+pub(crate) use buffer::BufferState;
+pub(crate) use texture::{TextureSelector, TextureState};
+
+/// A single unit of state tracking. It keeps an initial
+/// usage as well as the last/current one, similar to `Range`.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Unit<U> {
+ first: Option<U>,
+ last: U,
+}
+
+impl<U: Copy> Unit<U> {
+ /// Create a new unit from a given usage.
+ fn new(usage: U) -> Self {
+ Self {
+ first: None,
+ last: usage,
+ }
+ }
+
+ /// Return a usage to link to.
+ fn port(&self) -> U {
+ self.first.unwrap_or(self.last)
+ }
+}
+
+/// The main trait that abstracts away the tracking logic of
+/// a particular resource type, like a buffer or a texture.
+pub(crate) trait ResourceState: Clone + Default {
+ /// Corresponding `HUB` identifier.
+ type Id: Copy + fmt::Debug + TypedId;
+ /// A type specifying the sub-resources.
+ type Selector: fmt::Debug;
+ /// Usage type for a `Unit` of a sub-resource.
+ type Usage: fmt::Debug;
+
+ /// Check if all the selected sub-resources have the same
+ /// usage, and return it.
+ ///
+ /// Returns `None` if no sub-resources
+ /// are intersecting with the selector, or their usage
+ /// isn't consistent.
+ fn query(&self, selector: Self::Selector) -> Option<Self::Usage>;
+
+ /// Change the last usage of the selected sub-resources.
+ ///
+ /// If `output` is specified, it's filled with the
+ /// `PendingTransition` objects corresponding to smaller
+ /// sub-resource transitions. The old usage is replaced by
+ /// the new one.
+ ///
+ /// If `output` is `None`, the old usage is extended with
+ /// the new usage. The error is returned if it's not possible,
+ /// specifying the conflicting transition. Extension can only
+ /// be done for read-only usages.
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Sets up the first usage of the selected sub-resources.
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Merge the state of this resource tracked by a different instance
+ /// with the current one.
+ ///
+ /// Same rules for `output` apply as with `change()`: last usage state
+ /// is either replaced (when `output` is provided) with a
+ /// `PendingTransition` pushed to this vector, or extended with the
+ /// other read-only usage, unless there is a usage conflict, and
+ /// the error is generated (returning the conflict).
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Try to optimize the internal representation.
+ fn optimize(&mut self);
+}
+
+/// Structure wrapping the abstract tracking state with the relevant resource
+/// data, such as the reference count and the epoch.
+#[derive(Clone)]
+struct Resource<S> {
+ ref_count: RefCount,
+ state: S,
+ epoch: Epoch,
+}
+
+/// A structure containing all the information about a particular resource
+/// transition. User code should be able to generate a pipeline barrier
+/// based on the contents.
+#[derive(Debug, PartialEq)]
+pub(crate) struct PendingTransition<S: ResourceState> {
+ pub id: Valid<S::Id>,
+ pub selector: S::Selector,
+ pub usage: ops::Range<S::Usage>,
+}
+
+impl PendingTransition<BufferState> {
+ /// Produce the gfx-hal barrier corresponding to the transition.
+ pub fn into_hal<'a, B: hal::Backend>(
+ self,
+ buf: &'a resource::Buffer<B>,
+ ) -> hal::memory::Barrier<'a, B> {
+ tracing::trace!("\tbuffer -> {:?}", self);
+ let &(ref target, _) = buf.raw.as_ref().expect("Buffer is destroyed");
+ hal::memory::Barrier::Buffer {
+ states: conv::map_buffer_state(self.usage.start)
+ ..conv::map_buffer_state(self.usage.end),
+ target,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ }
+ }
+}
+
+impl PendingTransition<TextureState> {
+ /// Produce the gfx-hal barrier corresponding to the transition.
+ pub fn into_hal<'a, B: hal::Backend>(
+ self,
+ tex: &'a resource::Texture<B>,
+ ) -> hal::memory::Barrier<'a, B> {
+ tracing::trace!("\ttexture -> {:?}", self);
+ let &(ref target, _) = tex.raw.as_ref().expect("Texture is destroyed");
+ let aspects = tex.aspects;
+ hal::memory::Barrier::Image {
+ states: conv::map_texture_state(self.usage.start, aspects)
+ ..conv::map_texture_state(self.usage.end, aspects),
+ target,
+ range: hal::image::SubresourceRange {
+ aspects,
+ level_start: self.selector.levels.start,
+ level_count: Some(self.selector.levels.end - self.selector.levels.start),
+ layer_start: self.selector.layers.start,
+ layer_count: Some(self.selector.layers.end - self.selector.layers.start),
+ },
+ families: None,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum UseExtendError<U: fmt::Debug> {
+ #[error("resource is invalid")]
+ InvalidResource,
+ #[error("total usage {0:?} is not valid")]
+ Conflict(U),
+}
+
+/// A tracker for all resources of a given type.
+pub(crate) struct ResourceTracker<S: ResourceState> {
+ /// An association of known resource indices with their tracked states.
+ map: FastHashMap<Index, Resource<S>>,
+ /// Temporary storage for collecting transitions.
+ temp: Vec<PendingTransition<S>>,
+ /// The backend variant for all the tracked resources.
+ backend: wgt::Backend,
+}
+
+impl<S: ResourceState + fmt::Debug> fmt::Debug for ResourceTracker<S> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.map
+ .iter()
+ .map(|(&index, res)| ((index, res.epoch), &res.state))
+ .collect::<FastHashMap<_, _>>()
+ .fmt(formatter)
+ }
+}
+
+impl<S: ResourceState> ResourceTracker<S> {
+ /// Create a new empty tracker.
+ pub fn new(backend: wgt::Backend) -> Self {
+ Self {
+ map: FastHashMap::default(),
+ temp: Vec::new(),
+ backend,
+ }
+ }
+
+ /// Remove an id from the tracked map.
+ pub(crate) fn remove(&mut self, id: Valid<S::Id>) -> bool {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.remove(&index) {
+ Some(resource) => {
+ assert_eq!(resource.epoch, epoch);
+ true
+ }
+ None => false,
+ }
+ }
+
+ /// Removes the resource from the tracker if we are holding the last reference.
+ pub(crate) fn remove_abandoned(&mut self, id: Valid<S::Id>) -> bool {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.entry(index) {
+ Entry::Occupied(e) => {
+ if e.get().ref_count.load() == 1 {
+ let res = e.remove();
+ assert_eq!(res.epoch, epoch);
+ true
+ } else {
+ false
+ }
+ }
+ _ => false,
+ }
+ }
+
+ /// Try to optimize the internal representation.
+ pub(crate) fn optimize(&mut self) {
+ for resource in self.map.values_mut() {
+ resource.state.optimize();
+ }
+ }
+
+ /// Return an iterator over used resources keys.
+ pub fn used<'a>(&'a self) -> impl 'a + Iterator<Item = Valid<S::Id>> {
+ let backend = self.backend;
+ self.map
+ .iter()
+ .map(move |(&index, resource)| Valid(S::Id::zip(index, resource.epoch, backend)))
+ }
+
+ /// Clear the tracked contents.
+ fn clear(&mut self) {
+ self.map.clear();
+ }
+
+ /// Initialize a resource to be used.
+ ///
+ /// Returns false if the resource is already registered.
+ pub(crate) fn init(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: RefCount,
+ state: S,
+ ) -> Result<(), &S> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(Resource {
+ ref_count,
+ state,
+ epoch,
+ });
+ Ok(())
+ }
+ Entry::Occupied(e) => Err(&e.into_mut().state),
+ }
+ }
+
+ /// Query the usage of a resource selector.
+ ///
+ /// Returns `Some(Usage)` only if this usage is consistent
+ /// across the given selector.
+ pub fn query(&self, id: Valid<S::Id>, selector: S::Selector) -> Option<S::Usage> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ let res = self.map.get(&index)?;
+ assert_eq!(res.epoch, epoch);
+ res.state.query(selector)
+ }
+
+ /// Make sure that a resource is tracked, and return a mutable
+ /// reference to it.
+ fn get_or_insert<'a>(
+ self_backend: wgt::Backend,
+ map: &'a mut FastHashMap<Index, Resource<S>>,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ ) -> &'a mut Resource<S> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(self_backend, backend);
+ match map.entry(index) {
+ Entry::Vacant(e) => e.insert(Resource {
+ ref_count: ref_count.clone(),
+ state: S::default(),
+ epoch,
+ }),
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, epoch);
+ e.into_mut()
+ }
+ }
+ }
+
+ /// Extend the usage of a specified resource.
+ ///
+ /// Returns conflicting transition as an error.
+ pub(crate) fn change_extend(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(), PendingTransition<S>> {
+ Self::get_or_insert(self.backend, &mut self.map, id, ref_count)
+ .state
+ .change(id, selector, usage, None)
+ }
+
+ /// Replace the usage of a specified resource.
+ pub(crate) fn change_replace(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Drain<PendingTransition<S>> {
+ let res = Self::get_or_insert(self.backend, &mut self.map, id, ref_count);
+ res.state
+ .change(id, selector, usage, Some(&mut self.temp))
+ .ok(); //TODO: unwrap?
+ self.temp.drain(..)
+ }
+
+ /// Turn the tracking from the "expand" mode into the "replace" one,
+ /// installing the selected usage as the "first".
+ /// This is a special operation only used by the render pass attachments.
+ pub(crate) fn prepend(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(), PendingTransition<S>> {
+ Self::get_or_insert(self.backend, &mut self.map, id, ref_count)
+ .state
+ .prepend(id, selector, usage)
+ }
+
+ /// Merge another tracker into `self` by extending the current states
+ /// without any transitions.
+ pub(crate) fn merge_extend(&mut self, other: &Self) -> Result<(), PendingTransition<S>> {
+ debug_assert_eq!(self.backend, other.backend);
+ for (&index, new) in other.map.iter() {
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(new.clone());
+ }
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, new.epoch);
+ let id = Valid(S::Id::zip(index, new.epoch, self.backend));
+ e.into_mut().state.merge(id, &new.state, None)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Merge another tracker, adding it's transitions to `self`.
+ /// Transitions the current usage to the new one.
+ pub(crate) fn merge_replace<'a>(&'a mut self, other: &'a Self) -> Drain<PendingTransition<S>> {
+ for (&index, new) in other.map.iter() {
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(new.clone());
+ }
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, new.epoch);
+ let id = Valid(S::Id::zip(index, new.epoch, self.backend));
+ e.into_mut()
+ .state
+ .merge(id, &new.state, Some(&mut self.temp))
+ .ok(); //TODO: unwrap?
+ }
+ }
+ }
+ self.temp.drain(..)
+ }
+
+ /// Use a given resource provided by an `Id` with the specified usage.
+ /// Combines storage access by 'Id' with the transition that extends
+ /// the last read-only usage, if possible.
+ ///
+ /// Returns the old usage as an error if there is a conflict.
+ pub(crate) fn use_extend<'a, T: 'a + hub::Resource>(
+ &mut self,
+ storage: &'a hub::Storage<T, S::Id>,
+ id: S::Id,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<&'a T, UseExtendError<S::Usage>> {
+ let item = storage
+ .get(id)
+ .map_err(|_| UseExtendError::InvalidResource)?;
+ self.change_extend(
+ Valid(id),
+ item.life_guard().ref_count.as_ref().unwrap(),
+ selector,
+ usage,
+ )
+ .map(|()| item)
+ .map_err(|pending| UseExtendError::Conflict(pending.usage.end))
+ }
+
+ /// Use a given resource provided by an `Id` with the specified usage.
+ /// Combines storage access by 'Id' with the transition that replaces
+ /// the last usage with a new one, returning an iterator over these
+ /// transitions.
+ pub(crate) fn use_replace<'a, T: 'a + hub::Resource>(
+ &mut self,
+ storage: &'a hub::Storage<T, S::Id>,
+ id: S::Id,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(&'a T, Drain<PendingTransition<S>>), S::Id> {
+ let item = storage.get(id).map_err(|_| id)?;
+ let drain = self.change_replace(
+ Valid(id),
+ item.life_guard().ref_count.as_ref().unwrap(),
+ selector,
+ usage,
+ );
+ Ok((item, drain))
+ }
+}
+
+impl<I: Copy + fmt::Debug + TypedId> ResourceState for PhantomData<I> {
+ type Id = I;
+ type Selector = ();
+ type Usage = ();
+
+ fn query(&self, _selector: Self::Selector) -> Option<Self::Usage> {
+ Some(())
+ }
+
+ fn change(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ _usage: Self::Usage,
+ _output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ _usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn merge(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _other: &Self,
+ _output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn optimize(&mut self) {}
+}
+
+pub const DUMMY_SELECTOR: () = ();
+
+#[derive(Clone, Debug, Error)]
+pub enum UsageConflict {
+ #[error(
+ "Attempted to use buffer {id:?} as a combination of {combined_use:?} within a usage scope."
+ )]
+ Buffer {
+ id: id::BufferId,
+ combined_use: resource::BufferUse,
+ },
+ #[error("Attempted to use texture {id:?} mips {mip_levels:?} layers {array_layers:?} as a combination of {combined_use:?} within a usage scope.")]
+ Texture {
+ id: id::TextureId,
+ mip_levels: ops::Range<u32>,
+ array_layers: ops::Range<u32>,
+ combined_use: resource::TextureUse,
+ },
+}
+
+/// A set of trackers for all relevant resources.
+#[derive(Debug)]
+pub(crate) struct TrackerSet {
+ pub buffers: ResourceTracker<BufferState>,
+ pub textures: ResourceTracker<TextureState>,
+ pub views: ResourceTracker<PhantomData<id::TextureViewId>>,
+ pub bind_groups: ResourceTracker<PhantomData<id::BindGroupId>>,
+ pub samplers: ResourceTracker<PhantomData<id::SamplerId>>,
+ pub compute_pipes: ResourceTracker<PhantomData<id::ComputePipelineId>>,
+ pub render_pipes: ResourceTracker<PhantomData<id::RenderPipelineId>>,
+ pub bundles: ResourceTracker<PhantomData<id::RenderBundleId>>,
+}
+
+impl TrackerSet {
+ /// Create an empty set.
+ pub fn new(backend: wgt::Backend) -> Self {
+ Self {
+ buffers: ResourceTracker::new(backend),
+ textures: ResourceTracker::new(backend),
+ views: ResourceTracker::new(backend),
+ bind_groups: ResourceTracker::new(backend),
+ samplers: ResourceTracker::new(backend),
+ compute_pipes: ResourceTracker::new(backend),
+ render_pipes: ResourceTracker::new(backend),
+ bundles: ResourceTracker::new(backend),
+ }
+ }
+
+ /// Clear all the trackers.
+ pub fn clear(&mut self) {
+ self.buffers.clear();
+ self.textures.clear();
+ self.views.clear();
+ self.bind_groups.clear();
+ self.samplers.clear();
+ self.compute_pipes.clear();
+ self.render_pipes.clear();
+ self.bundles.clear();
+ }
+
+ /// Try to optimize the tracking representation.
+ pub fn optimize(&mut self) {
+ self.buffers.optimize();
+ self.textures.optimize();
+ self.views.optimize();
+ self.bind_groups.optimize();
+ self.samplers.optimize();
+ self.compute_pipes.optimize();
+ self.render_pipes.optimize();
+ self.bundles.optimize();
+ }
+
+ /// Merge all the trackers of another instance by extending
+ /// the usage. Panics on a conflict.
+ pub fn merge_extend(&mut self, other: &Self) -> Result<(), UsageConflict> {
+ self.buffers
+ .merge_extend(&other.buffers)
+ .map_err(|e| UsageConflict::Buffer {
+ id: e.id.0,
+ combined_use: e.usage.end,
+ })?;
+ self.textures
+ .merge_extend(&other.textures)
+ .map_err(|e| UsageConflict::Texture {
+ id: e.id.0,
+ mip_levels: e.selector.levels.start as u32..e.selector.levels.end as u32,
+ array_layers: e.selector.layers.start as u32..e.selector.layers.end as u32,
+ combined_use: e.usage.end,
+ })?;
+ self.views.merge_extend(&other.views).unwrap();
+ self.bind_groups.merge_extend(&other.bind_groups).unwrap();
+ self.samplers.merge_extend(&other.samplers).unwrap();
+ self.compute_pipes
+ .merge_extend(&other.compute_pipes)
+ .unwrap();
+ self.render_pipes.merge_extend(&other.render_pipes).unwrap();
+ self.bundles.merge_extend(&other.bundles).unwrap();
+ Ok(())
+ }
+
+ pub fn backend(&self) -> wgt::Backend {
+ self.buffers.backend
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/range.rs b/gfx/wgpu/wgpu-core/src/track/range.rs
new file mode 100644
index 0000000000..458861e1a9
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/range.rs
@@ -0,0 +1,399 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use smallvec::SmallVec;
+
+use std::{cmp::Ordering, fmt::Debug, iter, ops::Range, slice::Iter};
+
+/// Structure that keeps track of a I -> T mapping,
+/// optimized for a case where keys of the same values
+/// are often grouped together linearly.
+#[derive(Clone, Debug, PartialEq)]
+pub struct RangedStates<I, T> {
+ /// List of ranges, each associated with a singe value.
+ /// Ranges of keys have to be non-intersecting and ordered.
+ ranges: SmallVec<[(Range<I>, T); 1]>,
+}
+
+impl<I: Copy + PartialOrd, T: Copy + PartialEq> RangedStates<I, T> {
+ pub fn empty() -> Self {
+ Self {
+ ranges: SmallVec::new(),
+ }
+ }
+
+ pub fn from_range(range: Range<I>, value: T) -> Self {
+ Self {
+ ranges: iter::once((range, value)).collect(),
+ }
+ }
+
+ /// Construct a new instance from a slice of ranges.
+ #[cfg(test)]
+ pub fn from_slice(values: &[(Range<I>, T)]) -> Self {
+ Self {
+ ranges: values.iter().cloned().collect(),
+ }
+ }
+
+ /// Clear all the ranges.
+ pub fn clear(&mut self) {
+ self.ranges.clear();
+ }
+
+ /// Append a range.
+ ///
+ /// Assumes that the object is being constructed from a set of
+ /// ranges, and they are given in the ascending order of their keys.
+ pub fn append(&mut self, index: Range<I>, value: T) {
+ if let Some(last) = self.ranges.last() {
+ debug_assert!(last.0.end <= index.start);
+ }
+ self.ranges.push((index, value));
+ }
+
+ /// Check that all the ranges are non-intersecting and ordered.
+ /// Panics otherwise.
+ #[cfg(test)]
+ fn check_sanity(&self) {
+ for a in self.ranges.iter() {
+ assert!(a.0.start < a.0.end);
+ }
+ for (a, b) in self.ranges.iter().zip(self.ranges[1..].iter()) {
+ assert!(a.0.end <= b.0.start);
+ }
+ }
+
+ /// Merge the neighboring ranges together, where possible.
+ pub fn coalesce(&mut self) {
+ let mut num_removed = 0;
+ let mut iter = self.ranges.iter_mut();
+ let mut cur = match iter.next() {
+ Some(elem) => elem,
+ None => return,
+ };
+ for next in iter {
+ if cur.0.end == next.0.start && cur.1 == next.1 {
+ num_removed += 1;
+ cur.0.end = next.0.end;
+ next.0.end = next.0.start;
+ } else {
+ cur = next;
+ }
+ }
+ if num_removed != 0 {
+ self.ranges.retain(|pair| pair.0.start != pair.0.end);
+ }
+ }
+
+ /// Check if all intersecting ranges have the same value, which is returned.
+ ///
+ /// Returns `None` if no intersections are detected.
+ /// Returns `Some(Err)` if the intersected values are inconsistent.
+ pub fn query<U: PartialEq>(
+ &self,
+ index: &Range<I>,
+ fun: impl Fn(&T) -> U,
+ ) -> Option<Result<U, ()>> {
+ let mut result = None;
+ for &(ref range, ref value) in self.ranges.iter() {
+ if range.end > index.start && range.start < index.end {
+ let old = result.replace(fun(value));
+ if old.is_some() && old != result {
+ return Some(Err(()));
+ }
+ }
+ }
+ result.map(Ok)
+ }
+
+ /// Split the storage ranges in such a way that there is a linear subset of
+ /// them occupying exactly `index` range, which is returned mutably.
+ ///
+ /// Gaps in the ranges are filled with `default` value.
+ pub fn isolate(&mut self, index: &Range<I>, default: T) -> &mut [(Range<I>, T)] {
+ //TODO: implement this in 2 passes:
+ // 1. scan the ranges to figure out how many extra ones need to be inserted
+ // 2. go through the ranges by moving them them to the right and inserting the missing ones
+
+ let mut start_pos = match self.ranges.iter().position(|pair| pair.0.end > index.start) {
+ Some(pos) => pos,
+ None => {
+ let pos = self.ranges.len();
+ self.ranges.push((index.clone(), default));
+ return &mut self.ranges[pos..];
+ }
+ };
+
+ {
+ let (range, value) = self.ranges[start_pos].clone();
+ if range.start < index.start {
+ self.ranges[start_pos].0.start = index.start;
+ self.ranges
+ .insert(start_pos, (range.start..index.start, value));
+ start_pos += 1;
+ }
+ }
+ let mut pos = start_pos;
+ let mut range_pos = index.start;
+ loop {
+ let (range, value) = self.ranges[pos].clone();
+ if range.start >= index.end {
+ self.ranges.insert(pos, (range_pos..index.end, default));
+ pos += 1;
+ break;
+ }
+ if range.start > range_pos {
+ self.ranges.insert(pos, (range_pos..range.start, default));
+ pos += 1;
+ range_pos = range.start;
+ }
+ if range.end >= index.end {
+ if range.end != index.end {
+ self.ranges[pos].0.start = index.end;
+ self.ranges.insert(pos, (range_pos..index.end, value));
+ }
+ pos += 1;
+ break;
+ }
+ pos += 1;
+ range_pos = range.end;
+ if pos == self.ranges.len() {
+ self.ranges.push((range_pos..index.end, default));
+ pos += 1;
+ break;
+ }
+ }
+
+ &mut self.ranges[start_pos..pos]
+ }
+
+ /// Helper method for isolation that checks the sanity of the results.
+ #[cfg(test)]
+ pub fn sanely_isolated(&self, index: Range<I>, default: T) -> Vec<(Range<I>, T)> {
+ let mut clone = self.clone();
+ let result = clone.isolate(&index, default).to_vec();
+ clone.check_sanity();
+ result
+ }
+
+ /// Produce an iterator that merges two instances together.
+ ///
+ /// Each range in the returned iterator is a subset of a range in either
+ /// `self` or `other`, and the value returned as a `Range` from `self` to `other`.
+ pub fn merge<'a>(&'a self, other: &'a Self, base: I) -> Merge<'a, I, T> {
+ Merge {
+ base,
+ sa: self.ranges.iter().peekable(),
+ sb: other.ranges.iter().peekable(),
+ }
+ }
+}
+
+/// A custom iterator that goes through two `RangedStates` and process a merge.
+#[derive(Debug)]
+pub struct Merge<'a, I, T> {
+ base: I,
+ sa: iter::Peekable<Iter<'a, (Range<I>, T)>>,
+ sb: iter::Peekable<Iter<'a, (Range<I>, T)>>,
+}
+
+impl<'a, I: Copy + Debug + Ord, T: Copy + Debug> Iterator for Merge<'a, I, T> {
+ type Item = (Range<I>, Range<Option<T>>);
+ fn next(&mut self) -> Option<Self::Item> {
+ match (self.sa.peek(), self.sb.peek()) {
+ // we have both streams
+ (Some(&(ref ra, va)), Some(&(ref rb, vb))) => {
+ let (range, usage) = if ra.start < self.base {
+ // in the middle of the left stream
+ if self.base == rb.start {
+ // right stream is starting
+ debug_assert!(self.base < ra.end);
+ (self.base..ra.end.min(rb.end), Some(*va)..Some(*vb))
+ } else {
+ // right hasn't started yet
+ debug_assert!(self.base < rb.start);
+ (self.base..rb.start, Some(*va)..None)
+ }
+ } else if rb.start < self.base {
+ // in the middle of the right stream
+ if self.base == ra.start {
+ // left stream is starting
+ debug_assert!(self.base < rb.end);
+ (self.base..ra.end.min(rb.end), Some(*va)..Some(*vb))
+ } else {
+ // left hasn't started yet
+ debug_assert!(self.base < ra.start);
+ (self.base..ra.start, None..Some(*vb))
+ }
+ } else {
+ // no active streams
+ match ra.start.cmp(&rb.start) {
+ // both are starting
+ Ordering::Equal => (ra.start..ra.end.min(rb.end), Some(*va)..Some(*vb)),
+ // only left is starting
+ Ordering::Less => (ra.start..rb.start.min(ra.end), Some(*va)..None),
+ // only right is starting
+ Ordering::Greater => (rb.start..ra.start.min(rb.end), None..Some(*vb)),
+ }
+ };
+ self.base = range.end;
+ if ra.end == range.end {
+ let _ = self.sa.next();
+ }
+ if rb.end == range.end {
+ let _ = self.sb.next();
+ }
+ Some((range, usage))
+ }
+ // only right stream
+ (None, Some(&(ref rb, vb))) => {
+ let range = self.base.max(rb.start)..rb.end;
+ self.base = rb.end;
+ let _ = self.sb.next();
+ Some((range, None..Some(*vb)))
+ }
+ // only left stream
+ (Some(&(ref ra, va)), None) => {
+ let range = self.base.max(ra.start)..ra.end;
+ self.base = ra.end;
+ let _ = self.sa.next();
+ Some((range, Some(*va)..None))
+ }
+ // done
+ (None, None) => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ //TODO: randomized/fuzzy testing
+ use super::RangedStates;
+ use std::{fmt::Debug, ops::Range};
+
+ fn easy_merge<T: PartialEq + Copy + Debug>(
+ ra: &[(Range<usize>, T)],
+ rb: &[(Range<usize>, T)],
+ ) -> Vec<(Range<usize>, Range<Option<T>>)> {
+ RangedStates::from_slice(ra)
+ .merge(&RangedStates::from_slice(rb), 0)
+ .collect()
+ }
+
+ #[test]
+ fn sane_good() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ #[should_panic]
+ fn sane_empty() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (5..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ #[should_panic]
+ fn sane_intersect() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (3..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ fn coalesce() {
+ let mut rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9), (5..7, 1), (8..9, 1)]);
+ rs.coalesce();
+ rs.check_sanity();
+ assert_eq!(rs.ranges.as_slice(), &[(1..5, 9), (5..7, 1), (8..9, 1),]);
+ }
+
+ #[test]
+ fn query() {
+ let rs = RangedStates::from_slice(&[(1..4, 1u8), (5..7, 2)]);
+ assert_eq!(rs.query(&(0..1), |v| *v), None);
+ assert_eq!(rs.query(&(1..3), |v| *v), Some(Ok(1)));
+ assert_eq!(rs.query(&(1..6), |v| *v), Some(Err(())));
+ }
+
+ #[test]
+ fn isolate() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9), (5..7, 1), (8..9, 1)]);
+ assert_eq!(&rs.sanely_isolated(4..5, 0), &[(4..5, 9u8),]);
+ assert_eq!(
+ &rs.sanely_isolated(0..6, 0),
+ &[(0..1, 0), (1..4, 9u8), (4..5, 9), (5..6, 1),]
+ );
+ assert_eq!(&rs.sanely_isolated(8..10, 1), &[(8..9, 1), (9..10, 1),]);
+ assert_eq!(
+ &rs.sanely_isolated(6..9, 0),
+ &[(6..7, 1), (7..8, 0), (8..9, 1),]
+ );
+ }
+
+ #[test]
+ fn merge_same() {
+ assert_eq!(
+ &easy_merge(&[(1..4, 0u8),], &[(1..4, 2u8),],),
+ &[(1..4, Some(0)..Some(2)),]
+ );
+ }
+
+ #[test]
+ fn merge_empty() {
+ assert_eq!(
+ &easy_merge(&[(1..2, 0u8),], &[],),
+ &[(1..2, Some(0)..None),]
+ );
+ assert_eq!(
+ &easy_merge(&[], &[(3..4, 1u8),],),
+ &[(3..4, None..Some(1)),]
+ );
+ }
+
+ #[test]
+ fn merge_separate() {
+ assert_eq!(
+ &easy_merge(&[(1..2, 0u8), (5..6, 1u8),], &[(2..4, 2u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, None..Some(2)),
+ (5..6, Some(1)..None),
+ ]
+ );
+ }
+
+ #[test]
+ fn merge_subset() {
+ assert_eq!(
+ &easy_merge(&[(1..6, 0u8),], &[(2..4, 2u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, Some(0)..Some(2)),
+ (4..6, Some(0)..None),
+ ]
+ );
+ assert_eq!(
+ &easy_merge(&[(2..4, 0u8),], &[(1..4, 2u8),],),
+ &[(1..2, None..Some(2)), (2..4, Some(0)..Some(2)),]
+ );
+ }
+
+ #[test]
+ fn merge_all() {
+ assert_eq!(
+ &easy_merge(&[(1..4, 0u8), (5..8, 1u8),], &[(2..6, 2u8), (7..9, 3u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, Some(0)..Some(2)),
+ (4..5, None..Some(2)),
+ (5..6, Some(1)..Some(2)),
+ (6..7, Some(1)..None),
+ (7..8, Some(1)..Some(3)),
+ (8..9, None..Some(3)),
+ ]
+ );
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/texture.rs b/gfx/wgpu/wgpu-core/src/track/texture.rs
new file mode 100644
index 0000000000..6d1d4a5935
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/texture.rs
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{range::RangedStates, PendingTransition, ResourceState, Unit};
+use crate::{
+ device::MAX_MIP_LEVELS,
+ id::{TextureId, Valid},
+ resource::TextureUse,
+};
+
+use arrayvec::ArrayVec;
+
+use std::{iter, ops::Range};
+
+//TODO: store `hal::image::State` here to avoid extra conversions
+type PlaneStates = RangedStates<hal::image::Layer, Unit<TextureUse>>;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TextureSelector {
+ //pub aspects: hal::format::Aspects,
+ pub levels: Range<hal::image::Level>,
+ pub layers: Range<hal::image::Layer>,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub(crate) struct TextureState {
+ mips: ArrayVec<[PlaneStates; MAX_MIP_LEVELS as usize]>,
+ /// True if we have the information about all the subresources here
+ full: bool,
+}
+
+impl PendingTransition<TextureState> {
+ fn collapse(self) -> Result<TextureUse, Self> {
+ if self.usage.start.is_empty()
+ || self.usage.start == self.usage.end
+ || !TextureUse::WRITE_ALL.intersects(self.usage.start | self.usage.end)
+ {
+ Ok(self.usage.start | self.usage.end)
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl TextureState {
+ pub fn new(mip_level_count: hal::image::Level, array_layer_count: hal::image::Layer) -> Self {
+ Self {
+ mips: iter::repeat_with(|| {
+ PlaneStates::from_range(0..array_layer_count, Unit::new(TextureUse::UNINITIALIZED))
+ })
+ .take(mip_level_count as usize)
+ .collect(),
+ full: true,
+ }
+ }
+}
+
+impl ResourceState for TextureState {
+ type Id = TextureId;
+ type Selector = TextureSelector;
+ type Usage = TextureUse;
+
+ fn query(&self, selector: Self::Selector) -> Option<Self::Usage> {
+ let mut result = None;
+ // Note: we only consider the subresources tracked by `self`.
+ // If some are not known to `self`, it means the can assume the
+ // initial state to whatever we need, which we can always make
+ // to be the same as the query result for the known subresources.
+ let num_levels = self.mips.len();
+ if self.full {
+ assert!(num_levels >= selector.levels.end as usize);
+ }
+ let mip_start = num_levels.min(selector.levels.start as usize);
+ let mip_end = num_levels.min(selector.levels.end as usize);
+ for mip in self.mips[mip_start..mip_end].iter() {
+ match mip.query(&selector.layers, |unit| unit.last) {
+ None => {}
+ Some(Ok(usage)) if result == Some(usage) => {}
+ Some(Ok(usage)) if result.is_none() => {
+ result = Some(usage);
+ }
+ Some(Ok(_)) | Some(Err(())) => return None,
+ }
+ }
+ result
+ }
+
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ mut output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ if self.full {
+ assert!(self.mips.len() >= selector.levels.end as usize);
+ } else {
+ while self.mips.len() < selector.levels.end as usize {
+ self.mips.push(PlaneStates::empty());
+ }
+ }
+ for (mip_id, mip) in self.mips[selector.levels.start as usize..selector.levels.end as usize]
+ .iter_mut()
+ .enumerate()
+ {
+ let level = selector.levels.start + mip_id as hal::image::Level;
+ let layers = mip.isolate(&selector.layers, Unit::new(usage));
+ for &mut (ref range, ref mut unit) in layers {
+ if unit.last == usage && TextureUse::ORDERED.contains(usage) {
+ continue;
+ }
+ // TODO: Can't satisfy clippy here unless we modify
+ // `TextureSelector` to use `std::ops::RangeBounds`.
+ #[allow(clippy::range_plus_one)]
+ let pending = PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: range.clone(),
+ },
+ usage: unit.last..usage,
+ };
+
+ *unit = match output {
+ None => {
+ assert_eq!(
+ unit.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(ref mut out) => {
+ out.push(pending);
+ Unit {
+ first: unit.first.or(Some(unit.last)),
+ last: usage,
+ }
+ }
+ };
+ }
+ }
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ assert!(self.mips.len() >= selector.levels.end as usize);
+ for (mip_id, mip) in self.mips[selector.levels.start as usize..selector.levels.end as usize]
+ .iter_mut()
+ .enumerate()
+ {
+ let level = selector.levels.start + mip_id as hal::image::Level;
+ let layers = mip.isolate(&selector.layers, Unit::new(usage));
+ for &mut (ref range, ref mut unit) in layers {
+ match unit.first {
+ Some(old) if old != usage => {
+ return Err(PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: range.clone(),
+ },
+ usage: old..usage,
+ });
+ }
+ _ => {
+ unit.first = Some(usage);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ mut output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let mut temp = Vec::new();
+ if self.full {
+ assert!(self.mips.len() >= other.mips.len());
+ } else {
+ while self.mips.len() < other.mips.len() {
+ self.mips.push(PlaneStates::empty());
+ }
+ }
+
+ for (mip_id, (mip_self, mip_other)) in self.mips.iter_mut().zip(&other.mips).enumerate() {
+ let level = mip_id as hal::image::Level;
+ temp.extend(mip_self.merge(mip_other, 0));
+ mip_self.clear();
+
+ for (layers, states) in temp.drain(..) {
+ let unit = match states {
+ Range {
+ start: None,
+ end: None,
+ } => unreachable!(),
+ Range {
+ start: Some(start),
+ end: None,
+ } => start,
+ Range {
+ start: None,
+ end: Some(end),
+ } => end,
+ Range {
+ start: Some(start),
+ end: Some(end),
+ } => {
+ let to_usage = end.port();
+ if start.last == to_usage && TextureUse::ORDERED.contains(to_usage) {
+ Unit {
+ first: match output {
+ None => start.first,
+ Some(_) => start.first.or(Some(start.last)),
+ },
+ last: end.last,
+ }
+ } else {
+ // TODO: Can't satisfy clippy here unless we modify
+ // `TextureSelector` to use `std::ops::RangeBounds`.
+ #[allow(clippy::range_plus_one)]
+ let pending = PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: layers.clone(),
+ },
+ usage: start.last..to_usage,
+ };
+
+ match output {
+ None => {
+ assert_eq!(
+ start.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(ref mut out) => {
+ out.push(pending);
+ Unit {
+ // this has to leave a valid `first` state
+ first: start.first.or(Some(start.last)),
+ last: end.last,
+ }
+ }
+ }
+ }
+ }
+ };
+ mip_self.append(layers, unit);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn optimize(&mut self) {
+ for mip in self.mips.iter_mut() {
+ mip.coalesce();
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ //TODO: change() tests
+ use super::*;
+ use crate::id::Id;
+
+ #[test]
+ fn query() {
+ let mut ts = TextureState::default();
+ ts.mips.push(PlaneStates::empty());
+ ts.mips.push(PlaneStates::from_slice(&[
+ (1..3, Unit::new(TextureUse::SAMPLED)),
+ (3..5, Unit::new(TextureUse::SAMPLED)),
+ (5..6, Unit::new(TextureUse::STORAGE_LOAD)),
+ ]));
+
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 2..5,
+ }),
+ // level 1 matches
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 0..2,
+ layers: 2..5,
+ }),
+ // level 0 is empty, level 1 matches
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 1..5,
+ }),
+ // level 1 matches with gaps
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 4..6,
+ }),
+ // level 1 doesn't match
+ None,
+ );
+ }
+
+ #[test]
+ fn merge() {
+ let id = Id::dummy();
+ let mut ts1 = TextureState::default();
+ ts1.mips.push(PlaneStates::from_slice(&[(
+ 1..3,
+ Unit::new(TextureUse::SAMPLED),
+ )]));
+ let mut ts2 = TextureState::default();
+ assert_eq!(
+ ts1.merge(id, &ts2, None),
+ Ok(()),
+ "failed to merge with an empty"
+ );
+
+ ts2.mips.push(PlaneStates::from_slice(&[(
+ 1..2,
+ Unit::new(TextureUse::COPY_SRC),
+ )]));
+ assert_eq!(
+ ts1.merge(Id::dummy(), &ts2, None),
+ Ok(()),
+ "failed to extend a compatible state"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(1..2), |&v| v),
+ Some(Ok(Unit {
+ first: None,
+ last: TextureUse::SAMPLED | TextureUse::COPY_SRC,
+ })),
+ "wrong extension result"
+ );
+
+ ts2.mips[0] = PlaneStates::from_slice(&[(1..2, Unit::new(TextureUse::COPY_DST))]);
+ assert_eq!(
+ ts1.clone().merge(Id::dummy(), &ts2, None),
+ Err(PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 1..2,
+ },
+ usage: TextureUse::SAMPLED | TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ }),
+ "wrong error on extending with incompatible state"
+ );
+
+ let mut list = Vec::new();
+ ts2.mips[0] = PlaneStates::from_slice(&[
+ (1..2, Unit::new(TextureUse::COPY_DST)),
+ (
+ 2..3,
+ Unit {
+ first: Some(TextureUse::COPY_SRC),
+ last: TextureUse::ATTACHMENT_WRITE,
+ },
+ ),
+ ]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(
+ &list,
+ &[
+ PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 1..2,
+ },
+ usage: TextureUse::SAMPLED | TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ },
+ PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 2..3,
+ },
+ // the transition links the end of the base rage (..SAMPLED)
+ // with the start of the next range (COPY_SRC..)
+ usage: TextureUse::SAMPLED..TextureUse::COPY_SRC,
+ },
+ ],
+ "replacing produced wrong transitions"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(1..2), |&v| v),
+ Some(Ok(Unit {
+ first: Some(TextureUse::SAMPLED | TextureUse::COPY_SRC),
+ last: TextureUse::COPY_DST,
+ })),
+ "wrong final layer 1 state"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(2..3), |&v| v),
+ Some(Ok(Unit {
+ first: Some(TextureUse::SAMPLED),
+ last: TextureUse::ATTACHMENT_WRITE,
+ })),
+ "wrong final layer 2 state"
+ );
+
+ list.clear();
+ ts2.mips[0] = PlaneStates::from_slice(&[(
+ 2..3,
+ Unit {
+ first: Some(TextureUse::ATTACHMENT_WRITE),
+ last: TextureUse::COPY_SRC,
+ },
+ )]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(&list, &[], "unexpected replacing transition");
+
+ list.clear();
+ ts2.mips[0] = PlaneStates::from_slice(&[(
+ 2..3,
+ Unit {
+ first: Some(TextureUse::COPY_DST),
+ last: TextureUse::COPY_DST,
+ },
+ )]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 2..3,
+ },
+ usage: TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ },],
+ "invalid replacing transition"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(2..3), |&v| v),
+ Some(Ok(Unit {
+ // the initial state here is never expected to change
+ first: Some(TextureUse::SAMPLED),
+ last: TextureUse::COPY_DST,
+ })),
+ "wrong final layer 2 state"
+ );
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/validation.rs b/gfx/wgpu/wgpu-core/src/validation.rs
new file mode 100644
index 0000000000..6cfe12d53f
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/validation.rs
@@ -0,0 +1,966 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{binding_model::BindEntryMap, FastHashMap, MAX_BIND_GROUPS};
+use arrayvec::ArrayVec;
+use std::collections::hash_map::Entry;
+use thiserror::Error;
+use wgt::{BindGroupLayoutEntry, BindingType};
+
+#[derive(Clone, Debug, Error)]
+#[error("buffer usage is {actual:?} which does not contain required usage {expected:?}")]
+pub struct MissingBufferUsageError {
+ pub(crate) actual: wgt::BufferUsage,
+ pub(crate) expected: wgt::BufferUsage,
+}
+
+/// Checks that the given buffer usage contains the required buffer usage,
+/// returns an error otherwise.
+pub fn check_buffer_usage(
+ actual: wgt::BufferUsage,
+ expected: wgt::BufferUsage,
+) -> Result<(), MissingBufferUsageError> {
+ if !actual.contains(expected) {
+ Err(MissingBufferUsageError { actual, expected })
+ } else {
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+#[error("texture usage is {actual:?} which does not contain required usage {expected:?}")]
+pub struct MissingTextureUsageError {
+ pub(crate) actual: wgt::TextureUsage,
+ pub(crate) expected: wgt::TextureUsage,
+}
+
+/// Checks that the given texture usage contains the required texture usage,
+/// returns an error otherwise.
+pub fn check_texture_usage(
+ actual: wgt::TextureUsage,
+ expected: wgt::TextureUsage,
+) -> Result<(), MissingTextureUsageError> {
+ if !actual.contains(expected) {
+ Err(MissingTextureUsageError { actual, expected })
+ } else {
+ Ok(())
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum BindingError {
+ #[error("binding is missing from the pipeline layout")]
+ Missing,
+ #[error("visibility flags don't include the shader stage")]
+ Invisible,
+ #[error("load/store access flags {0:?} don't match the shader")]
+ WrongUsage(naga::GlobalUse),
+ #[error("type on the shader side does not match the pipeline binding")]
+ WrongType,
+ #[error("buffer structure size {0}, added to one element of an unbound array, if it's the last field, ended up greater than the given `min_binding_size`")]
+ WrongBufferSize(wgt::BufferAddress),
+ #[error("view dimension {dim:?} (is array: {is_array}) doesn't match the shader")]
+ WrongTextureViewDimension {
+ dim: naga::ImageDimension,
+ is_array: bool,
+ },
+ #[error("texture class {binding:?} doesn't match the shader {shader:?}")]
+ WrongTextureClass {
+ binding: naga::ImageClass,
+ shader: naga::ImageClass,
+ },
+ #[error("comparison flag doesn't match the shader")]
+ WrongSamplerComparison,
+ #[error("derived bind group layout type is not consistent between stages")]
+ InconsistentlyDerivedType,
+ #[error("texture format {0:?} is not supported for storage use")]
+ BadStorageFormat(wgt::TextureFormat),
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum InputError {
+ #[error("input is not provided by the earlier stage in the pipeline")]
+ Missing,
+ #[error("input type is not compatible with the provided")]
+ WrongType,
+}
+
+/// Errors produced when validating a programmable stage of a pipeline.
+#[derive(Clone, Debug, Error)]
+pub enum StageError {
+ #[error("shader module is invalid")]
+ InvalidModule,
+ #[error("unable to find an entry point at {0:?} stage")]
+ MissingEntryPoint(wgt::ShaderStage),
+ #[error("error matching global binding at index {binding} in group {group} against the pipeline layout: {error}")]
+ Binding {
+ group: u32,
+ binding: u32,
+ error: BindingError,
+ },
+ #[error(
+ "error matching the stage input at {location} against the previous stage outputs: {error}"
+ )]
+ Input {
+ location: wgt::ShaderLocation,
+ error: InputError,
+ },
+}
+
+fn get_aligned_type_size(
+ module: &naga::Module,
+ handle: naga::Handle<naga::Type>,
+ allow_unbound: bool,
+) -> wgt::BufferAddress {
+ use naga::TypeInner as Ti;
+ //TODO: take alignment into account!
+ match module.types[handle].inner {
+ Ti::Scalar { kind: _, width } => width as wgt::BufferAddress,
+ Ti::Vector {
+ size,
+ kind: _,
+ width,
+ } => size as wgt::BufferAddress * width as wgt::BufferAddress,
+ Ti::Matrix {
+ rows,
+ columns,
+ width,
+ } => {
+ rows as wgt::BufferAddress * columns as wgt::BufferAddress * width as wgt::BufferAddress
+ }
+ Ti::Pointer { .. } => 4,
+ Ti::Array {
+ base,
+ size: naga::ArraySize::Constant(const_handle),
+ stride,
+ } => {
+ let base_size = match stride {
+ Some(stride) => stride.get() as wgt::BufferAddress,
+ None => get_aligned_type_size(module, base, false),
+ };
+ let count = match module.constants[const_handle].inner {
+ naga::ConstantInner::Uint(value) => value,
+ ref other => panic!("Invalid array size constant: {:?}", other),
+ };
+ base_size * count
+ }
+ Ti::Array {
+ base,
+ size: naga::ArraySize::Dynamic,
+ stride,
+ } if allow_unbound => match stride {
+ Some(stride) => stride.get() as wgt::BufferAddress,
+ None => get_aligned_type_size(module, base, false),
+ },
+ Ti::Struct { ref members } => members.last().map_or(0, |member| {
+ let offset = match member.origin {
+ naga::MemberOrigin::Empty => 0,
+ naga::MemberOrigin::BuiltIn(_) => {
+ tracing::error!("Missing offset on a struct member");
+ 0 // TODO: make it a proper error
+ }
+ naga::MemberOrigin::Offset(offset) => offset as wgt::BufferAddress,
+ };
+ offset + get_aligned_type_size(module, member.ty, false)
+ }),
+ _ => panic!("Unexpected struct field"),
+ }
+}
+
+fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
+ use naga::StorageFormat as Sf;
+ use wgt::TextureFormat as Tf;
+ // Using the table in https://gpuweb.github.io/gpuweb/#plain-color-formats
+ Some(match format {
+ Tf::R32Uint => Sf::R32Uint,
+ Tf::R32Sint => Sf::R32Sint,
+ Tf::R32Float => Sf::R32Float,
+ Tf::Rgba8Unorm => Sf::Rgba8Unorm,
+ Tf::Rgba8Snorm => Sf::Rgba8Snorm,
+ Tf::Rgba8Uint => Sf::Rgba8Uint,
+ Tf::Rgba8Sint => Sf::Rgba8Sint,
+ Tf::Rg32Uint => Sf::Rg32Uint,
+ Tf::Rg32Sint => Sf::Rg32Sint,
+ Tf::Rg32Float => Sf::Rg32Float,
+ Tf::Rgba16Uint => Sf::Rgba16Uint,
+ Tf::Rgba16Sint => Sf::Rgba16Sint,
+ Tf::Rgba16Float => Sf::Rgba16Float,
+ Tf::Rgba32Uint => Sf::Rgba32Uint,
+ Tf::Rgba32Sint => Sf::Rgba32Sint,
+ Tf::Rgba32Float => Sf::Rgba32Float,
+ _ => return None,
+ })
+}
+
+fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
+ use naga::StorageFormat as Sf;
+ use wgt::TextureFormat as Tf;
+ match format {
+ Sf::R8Unorm => Tf::R8Unorm,
+ Sf::R8Snorm => Tf::R8Snorm,
+ Sf::R8Uint => Tf::R8Uint,
+ Sf::R8Sint => Tf::R8Sint,
+ Sf::R16Uint => Tf::R16Uint,
+ Sf::R16Sint => Tf::R16Sint,
+ Sf::R16Float => Tf::R16Float,
+ Sf::Rg8Unorm => Tf::Rg8Unorm,
+ Sf::Rg8Snorm => Tf::Rg8Snorm,
+ Sf::Rg8Uint => Tf::Rg8Uint,
+ Sf::Rg8Sint => Tf::Rg8Sint,
+ Sf::R32Uint => Tf::R32Uint,
+ Sf::R32Sint => Tf::R32Sint,
+ Sf::R32Float => Tf::R32Float,
+ Sf::Rg16Uint => Tf::Rg16Uint,
+ Sf::Rg16Sint => Tf::Rg16Sint,
+ Sf::Rg16Float => Tf::Rg16Float,
+ Sf::Rgba8Unorm => Tf::Rgba8Unorm,
+ Sf::Rgba8Snorm => Tf::Rgba8Snorm,
+ Sf::Rgba8Uint => Tf::Rgba8Uint,
+ Sf::Rgba8Sint => Tf::Rgba8Sint,
+ Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
+ Sf::Rg11b10Float => Tf::Rg11b10Float,
+ Sf::Rg32Uint => Tf::Rg32Uint,
+ Sf::Rg32Sint => Tf::Rg32Sint,
+ Sf::Rg32Float => Tf::Rg32Float,
+ Sf::Rgba16Uint => Tf::Rgba16Uint,
+ Sf::Rgba16Sint => Tf::Rgba16Sint,
+ Sf::Rgba16Float => Tf::Rgba16Float,
+ Sf::Rgba32Uint => Tf::Rgba32Uint,
+ Sf::Rgba32Sint => Tf::Rgba32Sint,
+ Sf::Rgba32Float => Tf::Rgba32Float,
+ }
+}
+
+fn check_binding_use(
+ module: &naga::Module,
+ var: &naga::GlobalVariable,
+ entry: &BindGroupLayoutEntry,
+) -> Result<naga::GlobalUse, BindingError> {
+ match module.types[var.ty].inner {
+ naga::TypeInner::Struct { ref members } => {
+ let (allowed_usage, min_size) = match entry.ty {
+ BindingType::Buffer {
+ ty,
+ has_dynamic_offset: _,
+ min_binding_size,
+ } => {
+ let global_use = match ty {
+ wgt::BufferBindingType::Uniform
+ | wgt::BufferBindingType::Storage { read_only: true } => {
+ naga::GlobalUse::LOAD
+ }
+ wgt::BufferBindingType::Storage { read_only: _ } => naga::GlobalUse::all(),
+ };
+ (global_use, min_binding_size)
+ }
+ _ => return Err(BindingError::WrongType),
+ };
+ let mut actual_size = 0;
+ for (i, member) in members.iter().enumerate() {
+ actual_size += get_aligned_type_size(module, member.ty, i + 1 == members.len());
+ }
+ match min_size {
+ Some(non_zero) if non_zero.get() < actual_size => {
+ return Err(BindingError::WrongBufferSize(actual_size))
+ }
+ _ => (),
+ }
+ Ok(allowed_usage)
+ }
+ naga::TypeInner::Sampler { comparison } => match entry.ty {
+ BindingType::Sampler {
+ filtering: _,
+ comparison: cmp,
+ } => {
+ if cmp == comparison {
+ Ok(naga::GlobalUse::LOAD)
+ } else {
+ Err(BindingError::WrongSamplerComparison)
+ }
+ }
+ _ => Err(BindingError::WrongType),
+ },
+ naga::TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ let view_dimension = match entry.ty {
+ BindingType::Texture { view_dimension, .. }
+ | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
+ _ => {
+ return Err(BindingError::WrongTextureViewDimension {
+ dim,
+ is_array: true,
+ })
+ }
+ };
+ if arrayed {
+ match (dim, view_dimension) {
+ (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
+ (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
+ _ => {
+ return Err(BindingError::WrongTextureViewDimension {
+ dim,
+ is_array: true,
+ })
+ }
+ }
+ } else {
+ match (dim, view_dimension) {
+ (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
+ (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
+ (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
+ (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
+ _ => {
+ return Err(BindingError::WrongTextureViewDimension {
+ dim,
+ is_array: false,
+ })
+ }
+ }
+ }
+ let (expected_class, usage) = match entry.ty {
+ BindingType::Texture {
+ sample_type,
+ view_dimension: _,
+ multisampled: multi,
+ } => {
+ let class = match sample_type {
+ wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
+ kind: naga::ScalarKind::Float,
+ multi,
+ },
+ wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
+ kind: naga::ScalarKind::Sint,
+ multi,
+ },
+ wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
+ kind: naga::ScalarKind::Uint,
+ multi,
+ },
+ wgt::TextureSampleType::Depth => naga::ImageClass::Depth,
+ };
+ (class, naga::GlobalUse::LOAD)
+ }
+ BindingType::StorageTexture {
+ access,
+ format,
+ view_dimension: _,
+ } => {
+ let naga_format = map_storage_format_to_naga(format)
+ .ok_or(BindingError::BadStorageFormat(format))?;
+ let usage = match access {
+ wgt::StorageTextureAccess::ReadOnly => naga::GlobalUse::LOAD,
+ wgt::StorageTextureAccess::WriteOnly => naga::GlobalUse::STORE,
+ };
+ (naga::ImageClass::Storage(naga_format), usage)
+ }
+ _ => return Err(BindingError::WrongType),
+ };
+ if class != expected_class {
+ return Err(BindingError::WrongTextureClass {
+ binding: expected_class,
+ shader: class,
+ });
+ }
+ Ok(usage)
+ }
+ _ => Err(BindingError::WrongType),
+ }
+}
+
+fn is_sub_type(sub: &naga::TypeInner, provided: &naga::TypeInner) -> bool {
+ use naga::TypeInner as Ti;
+
+ match (sub, provided) {
+ (
+ &Ti::Scalar {
+ kind: k0,
+ width: w0,
+ },
+ &Ti::Scalar {
+ kind: k1,
+ width: w1,
+ },
+ ) => k0 == k1 && w0 <= w1,
+ (
+ &Ti::Scalar {
+ kind: k0,
+ width: w0,
+ },
+ &Ti::Vector {
+ size: _,
+ kind: k1,
+ width: w1,
+ },
+ ) => k0 == k1 && w0 <= w1,
+ (
+ &Ti::Vector {
+ size: s0,
+ kind: k0,
+ width: w0,
+ },
+ &Ti::Vector {
+ size: s1,
+ kind: k1,
+ width: w1,
+ },
+ ) => s0 as u8 <= s1 as u8 && k0 == k1 && w0 <= w1,
+ (
+ &Ti::Matrix {
+ columns: c0,
+ rows: r0,
+ width: w0,
+ },
+ &Ti::Matrix {
+ columns: c1,
+ rows: r1,
+ width: w1,
+ },
+ ) => c0 == c1 && r0 == r1 && w0 <= w1,
+ (&Ti::Struct { members: ref m0 }, &Ti::Struct { members: ref m1 }) => m0 == m1,
+ _ => false,
+ }
+}
+
+pub enum MaybeOwned<'a, T> {
+ Owned(T),
+ Borrowed(&'a T),
+}
+
+impl<'a, T> std::ops::Deref for MaybeOwned<'a, T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ match *self {
+ MaybeOwned::Owned(ref value) => value,
+ MaybeOwned::Borrowed(value) => value,
+ }
+ }
+}
+
+pub fn map_vertex_format(format: wgt::VertexFormat) -> naga::TypeInner {
+ use naga::TypeInner as Ti;
+ use wgt::VertexFormat as Vf;
+
+ //Note: Shader always sees data as int, uint, or float.
+ // It doesn't know if the original is normalized in a tighter form.
+ let width = 4;
+ match format {
+ Vf::Uchar2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Uchar4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Char2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Char4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Uchar2Norm => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Uchar4Norm => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Char2Norm => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Char4Norm => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Ushort2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Ushort4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Short2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Short4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Ushort2Norm | Vf::Short2Norm | Vf::Half2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Ushort4Norm | Vf::Short4Norm | Vf::Half4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Float => Ti::Scalar {
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Float2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Float3 => Ti::Vector {
+ size: naga::VectorSize::Tri,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Float4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Float,
+ width,
+ },
+ Vf::Uint => Ti::Scalar {
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Uint2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Uint3 => Ti::Vector {
+ size: naga::VectorSize::Tri,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Uint4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Uint,
+ width,
+ },
+ Vf::Int => Ti::Scalar {
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Int2 => Ti::Vector {
+ size: naga::VectorSize::Bi,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Int3 => Ti::Vector {
+ size: naga::VectorSize::Tri,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ Vf::Int4 => Ti::Vector {
+ size: naga::VectorSize::Quad,
+ kind: naga::ScalarKind::Sint,
+ width,
+ },
+ }
+}
+
+fn map_texture_format(format: wgt::TextureFormat) -> naga::TypeInner {
+ use naga::{ScalarKind as Sk, TypeInner as Ti, VectorSize as Vs};
+ use wgt::TextureFormat as Tf;
+
+ //Note: Shader always sees data as int, uint, or float.
+ // It doesn't know if the original is normalized in a tighter form.
+ let width = 4;
+ match format {
+ Tf::R8Unorm | Tf::R8Snorm => Ti::Scalar {
+ kind: Sk::Float,
+ width,
+ },
+ Tf::R8Uint => Ti::Scalar {
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::R8Sint => Ti::Scalar {
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::R16Uint => Ti::Scalar {
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::R16Sint => Ti::Scalar {
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::R16Float => Ti::Scalar {
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rg8Unorm | Tf::Rg8Snorm => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rg8Uint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rg8Sint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::R32Uint => Ti::Scalar {
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::R32Sint => Ti::Scalar {
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::R32Float => Ti::Scalar {
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rg16Uint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rg16Sint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::Rg16Float => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rgba8Unorm
+ | Tf::Rgba8UnormSrgb
+ | Tf::Rgba8Snorm
+ | Tf::Bgra8Unorm
+ | Tf::Bgra8UnormSrgb => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rgba8Uint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rgba8Sint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::Rgb10a2Unorm => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rg11b10Float => Ti::Vector {
+ size: Vs::Tri,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rg32Uint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rg32Sint => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::Rg32Float => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rgba16Uint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rgba16Sint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::Rgba16Float => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Rgba32Uint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Uint,
+ width,
+ },
+ Tf::Rgba32Sint => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Sint,
+ width,
+ },
+ Tf::Rgba32Float => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Depth32Float | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => {
+ panic!("Unexpected depth format")
+ }
+ Tf::Bc1RgbaUnorm
+ | Tf::Bc1RgbaUnormSrgb
+ | Tf::Bc2RgbaUnorm
+ | Tf::Bc2RgbaUnormSrgb
+ | Tf::Bc3RgbaUnorm
+ | Tf::Bc3RgbaUnormSrgb
+ | Tf::Bc7RgbaUnorm
+ | Tf::Bc7RgbaUnormSrgb => Ti::Vector {
+ size: Vs::Quad,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Bc4RUnorm | Tf::Bc4RSnorm => Ti::Scalar {
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Bc5RgUnorm | Tf::Bc5RgSnorm => Ti::Vector {
+ size: Vs::Bi,
+ kind: Sk::Float,
+ width,
+ },
+ Tf::Bc6hRgbUfloat | Tf::Bc6hRgbSfloat => Ti::Vector {
+ size: Vs::Tri,
+ kind: Sk::Float,
+ width,
+ },
+ }
+}
+
+/// Return true if the fragment `format` is covered by the provided `output`.
+pub fn check_texture_format(format: wgt::TextureFormat, output: &naga::TypeInner) -> bool {
+ let required = map_texture_format(format);
+ is_sub_type(&required, output)
+}
+
+pub type StageInterface<'a> = FastHashMap<wgt::ShaderLocation, MaybeOwned<'a, naga::TypeInner>>;
+
+pub enum IntrospectionBindGroupLayouts<'a> {
+ Given(ArrayVec<[&'a BindEntryMap; MAX_BIND_GROUPS]>),
+ Derived(&'a mut [BindEntryMap]),
+}
+
+fn derive_binding_type(
+ module: &naga::Module,
+ var: &naga::GlobalVariable,
+ usage: naga::GlobalUse,
+) -> Result<BindingType, BindingError> {
+ let ty = &module.types[var.ty];
+ Ok(match ty.inner {
+ naga::TypeInner::Struct { ref members } => {
+ let has_dynamic_offset = false;
+ let mut actual_size = 0;
+ for (i, member) in members.iter().enumerate() {
+ actual_size += get_aligned_type_size(module, member.ty, i + 1 == members.len());
+ }
+ match var.class {
+ naga::StorageClass::Uniform => BindingType::Buffer {
+ ty: wgt::BufferBindingType::Uniform,
+ has_dynamic_offset,
+ min_binding_size: wgt::BufferSize::new(actual_size),
+ },
+ naga::StorageClass::Storage => BindingType::Buffer {
+ ty: wgt::BufferBindingType::Storage {
+ read_only: !usage.contains(naga::GlobalUse::STORE),
+ },
+ has_dynamic_offset,
+ min_binding_size: wgt::BufferSize::new(actual_size),
+ },
+ _ => return Err(BindingError::WrongType),
+ }
+ }
+ naga::TypeInner::Sampler { comparison } => BindingType::Sampler {
+ filtering: true,
+ comparison,
+ },
+ naga::TypeInner::Image {
+ dim,
+ arrayed,
+ class,
+ } => {
+ let view_dimension = match dim {
+ naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
+ naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
+ naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
+ naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
+ naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
+ naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
+ };
+ match class {
+ naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
+ sample_type: match kind {
+ naga::ScalarKind::Float => {
+ wgt::TextureSampleType::Float { filterable: true }
+ }
+ naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
+ naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
+ naga::ScalarKind::Bool => unreachable!(),
+ },
+ view_dimension,
+ multisampled: multi,
+ },
+ naga::ImageClass::Depth => BindingType::Texture {
+ sample_type: wgt::TextureSampleType::Depth,
+ view_dimension,
+ multisampled: false,
+ },
+ naga::ImageClass::Storage(format) => BindingType::StorageTexture {
+ access: if usage.contains(naga::GlobalUse::STORE) {
+ wgt::StorageTextureAccess::WriteOnly
+ } else {
+ wgt::StorageTextureAccess::ReadOnly
+ },
+ view_dimension,
+ format: {
+ let f = map_storage_format_from_naga(format);
+ let original = map_storage_format_to_naga(f)
+ .ok_or(BindingError::BadStorageFormat(f))?;
+ debug_assert_eq!(format, original);
+ f
+ },
+ },
+ }
+ }
+ _ => return Err(BindingError::WrongType),
+ })
+}
+
+pub fn check_stage<'a>(
+ module: &'a naga::Module,
+ mut group_layouts: IntrospectionBindGroupLayouts,
+ entry_point_name: &str,
+ stage_bit: wgt::ShaderStage,
+ inputs: StageInterface<'a>,
+) -> Result<StageInterface<'a>, StageError> {
+ // Since a shader module can have multiple entry points with the same name,
+ // we need to look for one with the right execution model.
+ let shader_stage = match stage_bit {
+ wgt::ShaderStage::VERTEX => naga::ShaderStage::Vertex,
+ wgt::ShaderStage::FRAGMENT => naga::ShaderStage::Fragment,
+ wgt::ShaderStage::COMPUTE => naga::ShaderStage::Compute,
+ _ => unreachable!(),
+ };
+ let entry_point = module
+ .entry_points
+ .get(&(shader_stage, entry_point_name.to_string()))
+ .ok_or(StageError::MissingEntryPoint(stage_bit))?;
+
+ let mut outputs = StageInterface::default();
+ for ((_, var), &usage) in module
+ .global_variables
+ .iter()
+ .zip(&entry_point.function.global_usage)
+ {
+ if usage.is_empty() {
+ continue;
+ }
+ match var.binding {
+ Some(naga::Binding::Resource { group, binding }) => {
+ let result = match group_layouts {
+ IntrospectionBindGroupLayouts::Given(ref layouts) => layouts
+ .get(group as usize)
+ .and_then(|map| map.get(&binding))
+ .ok_or(BindingError::Missing)
+ .and_then(|entry| {
+ if entry.visibility.contains(stage_bit) {
+ Ok(entry)
+ } else {
+ Err(BindingError::Invisible)
+ }
+ })
+ .and_then(|entry| check_binding_use(module, var, entry))
+ .and_then(|allowed_usage| {
+ if allowed_usage.contains(usage) {
+ Ok(())
+ } else {
+ Err(BindingError::WrongUsage(usage))
+ }
+ }),
+ IntrospectionBindGroupLayouts::Derived(ref mut layouts) => layouts
+ .get_mut(group as usize)
+ .ok_or(BindingError::Missing)
+ .and_then(|set| {
+ let ty = derive_binding_type(module, var, usage)?;
+ Ok(match set.entry(binding) {
+ Entry::Occupied(e) if e.get().ty != ty => {
+ return Err(BindingError::InconsistentlyDerivedType)
+ }
+ Entry::Occupied(e) => {
+ e.into_mut().visibility |= stage_bit;
+ }
+ Entry::Vacant(e) => {
+ e.insert(BindGroupLayoutEntry {
+ binding,
+ ty,
+ visibility: stage_bit,
+ count: None,
+ });
+ }
+ })
+ }),
+ };
+ if let Err(error) = result {
+ return Err(StageError::Binding {
+ group,
+ binding,
+ error,
+ });
+ }
+ }
+ Some(naga::Binding::Location(location)) => {
+ let ty = &module.types[var.ty].inner;
+ if usage.contains(naga::GlobalUse::STORE) {
+ outputs.insert(location, MaybeOwned::Borrowed(ty));
+ } else {
+ let result =
+ inputs
+ .get(&location)
+ .ok_or(InputError::Missing)
+ .and_then(|provided| {
+ if is_sub_type(ty, provided) {
+ Ok(())
+ } else {
+ Err(InputError::WrongType)
+ }
+ });
+ if let Err(error) = result {
+ return Err(StageError::Input { location, error });
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ Ok(outputs)
+}