diff options
Diffstat (limited to 'third_party/rust/wgpu-core')
38 files changed, 29555 insertions, 0 deletions
diff --git a/third_party/rust/wgpu-core/.cargo-checksum.json b/third_party/rust/wgpu-core/.cargo-checksum.json new file mode 100644 index 0000000000..8a179daeb9 --- /dev/null +++ b/third_party/rust/wgpu-core/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"75edd4854ae0d6fabafa12ef625163a4c49d7546556b0d38bb667fcccbe52087","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/binding_model.rs":"984e05f8527ae6d2c72d3f141a006f8365da7c5f95afd24601ed0fe6c98b62b2","src/command/bind.rs":"88ca2bedfcd8392efc960f39ca8506024af495dcf1852cf16ae28fbadbdf748d","src/command/bundle.rs":"74e50b87c003ca3647f65409be9a9d05f81f770fa72a9019898f8741b965a66e","src/command/clear.rs":"324ad776e89321d364e091cab915194fd2e9ffea75ef21bbf7eea637059bfcb7","src/command/compute.rs":"d9c754f7f2ef4cde2b202365337eed2feb995993603c3f0cf4acf32a0ed4f54b","src/command/draw.rs":"14a0319da47e4995c2ad97f1102998b0d4beb2f6d07df8a0cb6f08023185ce7a","src/command/memory_init.rs":"347117a84f35927de91e7ab4cc69d19b5b4507a942596c0c725f27b50c428b1e","src/command/mod.rs":"50d5e476fc00e157f6afc1b40d461165b0d1e2e895abd20624dadf7959c06009","src/command/query.rs":"59adc6efaf01c037b6ba5f2b29b7a0063ced1f0334349b812a9611da77cfd078","src/command/render.rs":"a4c8d79f8c26cbfb1907bcdc009543fb4c4a7a63a779423a54b9de2d61416cdf","src/command/transfer.rs":"9873536468a220ba9d4f20dc2e89782fa0715af711dbac6919e515b868e5cc0b","src/conv.rs":"9654736318f9efdf15695eacaf64218febf01cb945b764ce6a966cbd41345a79","src/device/life.rs":"00575c6c8c4d32304c76f1bdbf9b705603b275ffe9d7dad9838d4c47e118f5d6","src/device/mod.rs":"2799305eb6969bef45d7d757482674dfd106956fec933ce54766b9e52a8230a3","src/device/queue.rs":"cad4e1165a5b3366e0d939b0bd20cefc485332c2061bdcd1130c8416136ae0f4","src/device/trace.rs":"f69aa6af36a6defcf391ddb6cf12a56e37e00b4595e95c284cd7fd400394349c","src/error.rs":"48bc91197cfe254559b305c3954db6afa6441f120cb9fedeb7da96a140df6e2b","src/hub.rs":"1afc5d19f1afd132f43aa9b0d4f4f750f88ca18936974e2ae5fd50cb8763569a","src/id.rs":"10e101272faf04495b25e37284b0c7620392bb8ee7f2a82247d8e0a858087d04","src/init_tracker/buffer.rs":"a0ebf54a1e6d269c7b4aa0ac7bb8b04fd2cea3221a1d058ff33cb683b2aea3e9","src/init_tracker/mod.rs":"0867f79f83555390d0982d1dc6dcf0d4340e10cb89aa633d3c3ecc45deb3c78c","src/init_tracker/texture.rs":"37b6584aaca11c407d91f77002dcbb48d8a4876e27edd1b71b7929ef966f901d","src/instance.rs":"da4ba40a8e2db89bf31f3c8c0874c152ea1ed07169dfc12f8e6b15b5f044372f","src/lib.rs":"a40fc046ae2b3cca1f8f5e4e462c07bb51e56dbb17bf7a5736fef6f122c48e9c","src/pipeline.rs":"1d1a25da8e5ba585a79d38e5972413db83deada2f1704789144d28bca8cf1a1c","src/present.rs":"cf7a4a093087953fb16d1c6a45b85649a3971fa6dc56a9f3f424a478dcb84270","src/resource.rs":"1dedb1eaabad43d87d7c6ea5ecc522a0f64972b9348a699bbafc49238b0748e1","src/track/buffer.rs":"0b7a8c6acfe55e3f45dd1e7ef1b3ee8bd4844f61bbabf62aa4f0c391b822803c","src/track/metadata.rs":"9565f700661e81fd534a751a0e3d26022238ad8784cc868333da7030b3571473","src/track/mod.rs":"1ce1fe9a2d2cee35a2b8ec58c52b1ed633481fab4fef7308cfb980a73eaa5928","src/track/range.rs":"5bbfed6e103b3234d9de8e42057022da6d628c2cc1db6bb51b88f87f2d8adf8b","src/track/stateless.rs":"34942ecdf81b05b75892c891c87e065329870459ff8edfca1f99ec9533c334aa","src/track/texture.rs":"7d38b2f4e0cdb3e56ce1e777d8bca4d73ef40e21c56e11fedd69dc27e789256d","src/validation.rs":"dd761dfc717ab62d36311c277d26f4741b297f5dbb1bd1f1e6b6eff9c6cbc57c"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/wgpu-core/Cargo.toml b/third_party/rust/wgpu-core/Cargo.toml new file mode 100644 index 0000000000..7f29a9c0bb --- /dev/null +++ b/third_party/rust/wgpu-core/Cargo.toml @@ -0,0 +1,122 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "wgpu-core" +version = "0.16.0" +authors = ["wgpu developers"] +description = "WebGPU core logic on wgpu-hal" +homepage = "https://wgpu.rs/" +keywords = ["graphics"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/gfx-rs/wgpu" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] +targets = [ + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", + "wasm32-unknown-unknown", +] + +[lib] + +[dependencies] +arrayvec = "0.7" +bit-vec = "0.6" +bitflags = "2" +codespan-reporting = "0.11" +log = "0.4" +parking_lot = ">=0.11,<0.13" +rustc-hash = "1.1" +smallvec = "1" +thiserror = "1" + +[dependencies.hal] +version = "0.16" +path = "../wgpu-hal" +package = "wgpu-hal" + +[dependencies.naga] +version = "0.12.0" +git = "https://github.com/gfx-rs/naga" +rev = "b99d58ea435090e561377949f428bce2c18451bb" +features = [ + "clone", + "span", + "validate", +] + +[dependencies.profiling] +version = "1" +default-features = false + +[dependencies.raw-window-handle] +version = "0.5" +optional = true + +[dependencies.ron] +version = "0.8" +optional = true + +[dependencies.serde] +version = "1" +features = ["serde_derive"] +optional = true + +[dependencies.wgt] +version = "0.16" +path = "../wgpu-types" +package = "wgpu-types" + +[features] +angle = ["hal/gles"] +default = [] +dx11 = ["hal/dx11"] +dx12 = ["hal/dx12"] +gles = ["hal/gles"] +id32 = [] +metal = ["hal/metal"] +renderdoc = ["hal/renderdoc"] +replay = [ + "serde", + "wgt/replay", + "arrayvec/serde", + "naga/deserialize", +] +serial-pass = [ + "serde", + "wgt/serde", + "arrayvec/serde", +] +strict_asserts = ["wgt/strict_asserts"] +trace = [ + "ron", + "serde", + "wgt/trace", + "arrayvec/serde", + "naga/serialize", +] +vulkan = ["hal/vulkan"] +wgsl = ["naga/wgsl-in"] + +[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"emscripten\")))".dependencies.web-sys] +version = "0.3.60" +features = [ + "HtmlCanvasElement", + "OffscreenCanvas", +] diff --git a/third_party/rust/wgpu-core/LICENSE.APACHE b/third_party/rust/wgpu-core/LICENSE.APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/third_party/rust/wgpu-core/LICENSE.APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/third_party/rust/wgpu-core/LICENSE.MIT b/third_party/rust/wgpu-core/LICENSE.MIT new file mode 100644 index 0000000000..4699691b8e --- /dev/null +++ b/third_party/rust/wgpu-core/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 The gfx-rs developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/wgpu-core/src/binding_model.rs b/third_party/rust/wgpu-core/src/binding_model.rs new file mode 100644 index 0000000000..97f56095f4 --- /dev/null +++ b/third_party/rust/wgpu-core/src/binding_model.rs @@ -0,0 +1,854 @@ +use crate::{ + device::{DeviceError, MissingDownlevelFlags, MissingFeatures, SHADER_STAGE_COUNT}, + error::{ErrorFormatter, PrettyError}, + hub::{HalApi, Resource}, + id::{BindGroupLayoutId, BufferId, DeviceId, SamplerId, TextureId, TextureViewId, Valid}, + init_tracker::{BufferInitTrackerAction, TextureInitTrackerAction}, + track::{BindGroupStates, UsageConflict}, + validation::{MissingBufferUsageError, MissingTextureUsageError}, + FastHashMap, Label, LifeGuard, MultiRefCount, Stored, +}; + +use arrayvec::ArrayVec; + +#[cfg(feature = "replay")] +use serde::Deserialize; +#[cfg(feature = "trace")] +use serde::Serialize; + +use std::{borrow::Cow, ops::Range}; + +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum BindGroupLayoutEntryError { + #[error("Cube dimension is not expected for texture storage")] + StorageTextureCube, + #[error("Read-write and read-only storage textures are not allowed by webgpu, they require the native only feature TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES")] + StorageTextureReadWrite, + #[error("Arrays of bindings unsupported for this type of binding")] + ArrayUnsupported, + #[error("Multisampled binding with sample type `TextureSampleType::Float` must have filterable set to false.")] + SampleTypeFloatFilterableBindingMultisampled, + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateBindGroupLayoutError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Conflicting binding at index {0}")] + ConflictBinding(u32), + #[error("Binding {binding} entry is invalid")] + Entry { + binding: u32, + #[source] + error: BindGroupLayoutEntryError, + }, + #[error(transparent)] + TooManyBindings(BindingTypeMaxCountError), + #[error("Binding index {binding} is greater than the maximum index {maximum}")] + InvalidBindingIndex { binding: u32, maximum: u32 }, + #[error("Invalid visibility {0:?}")] + InvalidVisibility(wgt::ShaderStages), +} + +//TODO: refactor this to move out `enum BindingError`. + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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("Texture {0:?} is invalid")] + InvalidTexture(TextureId), + #[error("Sampler {0:?} is invalid")] + InvalidSampler(SamplerId), + #[error( + "Binding count declared with at most {expected} items, but {actual} items were provided" + )] + BindingArrayPartialLengthMismatch { actual: usize, expected: usize }, + #[error( + "Binding count declared with exactly {expected} items, but {actual} items were provided" + )] + BindingArrayLengthMismatch { actual: usize, expected: usize }, + #[error("Array binding provided zero elements")] + BindingArrayZeroLength, + #[error("Bound buffer range {range:?} does not fit in buffer of size {size}")] + BindingRangeTooLarge { + buffer: BufferId, + range: Range<wgt::BufferAddress>, + size: u64, + }, + #[error("Buffer binding size {actual} is less than minimum {min}")] + BindingSizeTooSmall { + buffer: BufferId, + actual: u64, + min: u64, + }, + #[error("Buffer binding size is zero")] + BindingZeroSize(BufferId), + #[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("Binding declared as a single item, but bind group is using it as an array")] + SingleBindingExpected, + #[error("Buffer offset {0} does not respect device's requested `{1}` limit {2}")] + UnalignedBufferOffset(wgt::BufferAddress, &'static str, u32), + #[error( + "Buffer binding {binding} range {given} exceeds `max_*_buffer_binding_size` limit {limit}" + )] + BufferRangeTooLarge { + binding: u32, + given: u32, + limit: u32, + }, + #[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("Texture binding {binding} expects multisampled = {layout_multisampled}, but given a view with samples = {view_samples}")] + InvalidTextureMultisample { + binding: u32, + layout_multisampled: bool, + view_samples: u32, + }, + #[error("Texture binding {binding} expects sample type = {layout_sample_type:?}, but given a view with format = {view_format:?}")] + InvalidTextureSampleType { + binding: u32, + layout_sample_type: wgt::TextureSampleType, + view_format: wgt::TextureFormat, + }, + #[error("Texture binding {binding} expects dimension = {layout_dimension:?}, but given a view with dimension = {view_dimension:?}")] + InvalidTextureDimension { + binding: u32, + layout_dimension: wgt::TextureViewDimension, + view_dimension: wgt::TextureViewDimension, + }, + #[error("Storage texture binding {binding} expects format = {layout_format:?}, but given a view with format = {view_format:?}")] + InvalidStorageTextureFormat { + binding: u32, + layout_format: wgt::TextureFormat, + view_format: wgt::TextureFormat, + }, + #[error("Storage texture bindings must have a single mip level, but given a view with mip_level_count = {mip_level_count:?} at binding {binding}")] + InvalidStorageTextureMipLevelCount { binding: u32, mip_level_count: u32 }, + #[error("Sampler binding {binding} expects comparison = {layout_cmp}, but given a sampler with comparison = {sampler_cmp}")] + WrongSamplerComparison { + binding: u32, + layout_cmp: bool, + sampler_cmp: bool, + }, + #[error("Sampler binding {binding} expects filtering = {layout_flt}, but given a sampler with filtering = {sampler_flt}")] + WrongSamplerFiltering { + binding: u32, + layout_flt: bool, + sampler_flt: bool, + }, + #[error("Bound texture views can not have both depth and stencil aspects enabled")] + DepthStencilAspect, + #[error("The adapter does not support read access for storages texture of format {0:?}")] + StorageReadNotSupported(wgt::TextureFormat), + #[error(transparent)] + ResourceUsageConflict(#[from] UsageConflict), +} + +impl PrettyError for CreateBindGroupError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + match *self { + Self::BindingZeroSize(id) => { + fmt.buffer_label(&id); + } + Self::BindingRangeTooLarge { buffer, .. } => { + fmt.buffer_label(&buffer); + } + Self::BindingSizeTooSmall { buffer, .. } => { + fmt.buffer_label(&buffer); + } + Self::InvalidBuffer(id) => { + fmt.buffer_label(&id); + } + Self::InvalidTextureView(id) => { + fmt.texture_view_label(&id); + } + Self::InvalidSampler(id) => { + fmt.sampler_label(&id); + } + _ => {} + }; + } +} + +#[derive(Clone, Debug, Error)] +pub enum BindingZone { + #[error("Stage {0:?}")] + Stage(wgt::ShaderStages), + #[error("Whole pipeline")] + Pipeline, +} + +#[derive(Clone, Debug, Error)] +#[error("Too many bindings of type {kind:?} in {zone}, limit is {limit}, count was {count}")] +pub struct BindingTypeMaxCountError { + pub kind: BindingTypeMaxCountErrorKind, + pub zone: BindingZone, + pub limit: u32, + 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::ShaderStages, count: u32) { + if stage.contains(wgt::ShaderStages::VERTEX) { + self.vertex += count; + } + if stage.contains(wgt::ShaderStages::FRAGMENT) { + self.fragment += count; + } + if stage.contains(wgt::ShaderStages::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::ShaderStages::NONE; + if max_value == self.vertex { + stage |= wgt::ShaderStages::VERTEX + } + if max_value == self.fragment { + stage |= wgt::ShaderStages::FRAGMENT + } + if max_value == self.compute { + stage |= wgt::ShaderStages::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, + limit, + 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, + limit: limits.max_dynamic_uniform_buffers_per_pipeline_layout, + 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, + limit: limits.max_dynamic_storage_buffers_per_pipeline_layout, + 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>; + +/// Bind group layout. +/// +/// The lifetime of BGLs is a bit special. They are only referenced on CPU +/// without considering GPU operations. And on CPU they get manual +/// inc-refs and dec-refs. In particular, the following objects depend on them: +/// - produced bind groups +/// - produced pipeline layouts +/// - pipelines with implicit layouts +#[derive(Debug)] +pub struct BindGroupLayout<A: hal::Api> { + pub(crate) raw: A::BindGroupLayout, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) multi_ref_count: MultiRefCount, + pub(crate) entries: BindEntryMap, + #[allow(unused)] + pub(crate) dynamic_count: usize, + pub(crate) count_validator: BindingTypeMaxCountValidator, + #[cfg(debug_assertions)] + pub(crate) label: String, +} + +impl<A: hal::Api> Resource for BindGroupLayout<A> { + 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)] +#[non_exhaustive] +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(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[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::ShaderStages, + intersected: wgt::ShaderStages, + }, + #[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 }, +} + +impl PrettyError for CreatePipelineLayoutError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + if let Self::InvalidBindGroupLayout(id) = *self { + fmt.bind_group_layout_label(&id); + }; + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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::ShaderStages, + idx: usize, + matched: wgt::ShaderStages, + }, + #[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::ShaderStages, + idx: usize, + missing: wgt::ShaderStages, + }, + #[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::ShaderStages, + unmatched: wgt::ShaderStages, + }, + #[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`](wgt::Features::PUSH_CONSTANTS) feature must + /// be enabled. + pub push_constant_ranges: Cow<'a, [wgt::PushConstantRange]>, +} + +#[derive(Debug)] +pub struct PipelineLayout<A: hal::Api> { + pub(crate) raw: A::PipelineLayout, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) life_guard: LifeGuard, + pub(crate) bind_group_layout_ids: ArrayVec<Valid<BindGroupLayoutId>, { hal::MAX_BIND_GROUPS }>, + pub(crate) push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>, +} + +impl<A: hal::Api> PipelineLayout<A> { + /// Validate push constants match up with expected ranges. + pub(crate) fn validate_push_constant_ranges( + &self, + stages: wgt::ShaderStages, + 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::ShaderStages::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<A: hal::Api> Resource for PipelineLayout<A> { + const TYPE: &'static str = "PipelineLayout"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[repr(C)] +#[derive(Clone, Debug, Hash, Eq, 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), + BufferArray(Cow<'a, [BufferBinding]>), + Sampler(SamplerId), + SamplerArray(Cow<'a, [SamplerId]>), + TextureView(TextureViewId), + TextureViewArray(Cow<'a, [TextureViewId]>), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum BindError { + #[error( + "Bind group {group} expects {expected} dynamic offset{s0}. However {actual} dynamic offset{s1} were provided.", + s0 = if *.expected >= 2 { "s" } else { "" }, + s1 = if *.actual >= 2 { "s" } else { "" }, + )] + MismatchedDynamicOffsetCount { + group: u32, + actual: usize, + expected: usize, + }, + #[error( + "Dynamic binding index {idx} (targeting bind group {group}, binding {binding}) with value {offset}, does not respect device's requested `{limit_name}` limit: {alignment}" + )] + UnalignedDynamicBinding { + idx: usize, + group: u32, + binding: u32, + offset: u32, + alignment: u32, + limit_name: &'static str, + }, + #[error( + "Dynamic binding offset index {idx} with offset {offset} would overrun the buffer bound to bind group {group} -> binding {binding}. \ + Buffer size is {buffer_size} bytes, the binding binds bytes {binding_range:?}, meaning the maximum the binding can be offset is {maximum_dynamic_offset} bytes", + )] + DynamicBindingOutOfBounds { + idx: usize, + group: u32, + binding: u32, + offset: u32, + buffer_size: wgt::BufferAddress, + binding_range: Range<wgt::BufferAddress>, + maximum_dynamic_offset: wgt::BufferAddress, + }, +} + +#[derive(Debug)] +pub struct BindGroupDynamicBindingData { + /// The index of the binding. + /// + /// Used for more descriptive errors. + pub(crate) binding_idx: u32, + /// The size of the buffer. + /// + /// Used for more descriptive errors. + pub(crate) buffer_size: wgt::BufferAddress, + /// The range that the binding covers. + /// + /// Used for more descriptive errors. + pub(crate) binding_range: Range<wgt::BufferAddress>, + /// The maximum value the dynamic offset can have before running off the end of the buffer. + pub(crate) maximum_dynamic_offset: wgt::BufferAddress, + /// The binding type. + pub(crate) binding_type: wgt::BufferBindingType, +} + +pub(crate) fn buffer_binding_type_alignment( + limits: &wgt::Limits, + binding_type: wgt::BufferBindingType, +) -> (u32, &'static str) { + match binding_type { + wgt::BufferBindingType::Uniform => ( + limits.min_uniform_buffer_offset_alignment, + "min_uniform_buffer_offset_alignment", + ), + wgt::BufferBindingType::Storage { .. } => ( + limits.min_storage_buffer_offset_alignment, + "min_storage_buffer_offset_alignment", + ), + } +} + +pub struct BindGroup<A: HalApi> { + pub(crate) raw: A::BindGroup, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) layout_id: Valid<BindGroupLayoutId>, + pub(crate) life_guard: LifeGuard, + pub(crate) used: BindGroupStates<A>, + pub(crate) used_buffer_ranges: Vec<BufferInitTrackerAction>, + pub(crate) used_texture_ranges: Vec<TextureInitTrackerAction>, + pub(crate) dynamic_binding_info: Vec<BindGroupDynamicBindingData>, + /// Actual binding sizes for buffers that don't have `min_binding_size` + /// specified in BGL. Listed in the order of iteration of `BGL.entries`. + pub(crate) late_buffer_binding_sizes: Vec<wgt::BufferSize>, +} + +impl<A: HalApi> BindGroup<A> { + pub(crate) fn validate_dynamic_bindings( + &self, + bind_group_index: u32, + offsets: &[wgt::DynamicOffset], + limits: &wgt::Limits, + ) -> Result<(), BindError> { + if self.dynamic_binding_info.len() != offsets.len() { + return Err(BindError::MismatchedDynamicOffsetCount { + group: bind_group_index, + expected: self.dynamic_binding_info.len(), + actual: offsets.len(), + }); + } + + for (idx, (info, &offset)) in self + .dynamic_binding_info + .iter() + .zip(offsets.iter()) + .enumerate() + { + let (alignment, limit_name) = buffer_binding_type_alignment(limits, info.binding_type); + if offset as wgt::BufferAddress % alignment as u64 != 0 { + return Err(BindError::UnalignedDynamicBinding { + group: bind_group_index, + binding: info.binding_idx, + idx, + offset, + alignment, + limit_name, + }); + } + + if offset as wgt::BufferAddress > info.maximum_dynamic_offset { + return Err(BindError::DynamicBindingOutOfBounds { + group: bind_group_index, + binding: info.binding_idx, + idx, + offset, + buffer_size: info.buffer_size, + binding_range: info.binding_range.clone(), + maximum_dynamic_offset: info.maximum_dynamic_offset, + }); + } + } + + Ok(()) + } +} + +impl<A: HalApi> Resource for BindGroup<A> { + const TYPE: &'static str = "BindGroup"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum GetBindGroupLayoutError { + #[error("Pipeline is invalid")] + InvalidPipeline, + #[error("Invalid group index {0}")] + InvalidGroupIndex(u32), +} + +#[derive(Clone, Debug, Error, Eq, PartialEq)] +#[error("Buffer is bound with size {bound_size} where the shader expects {shader_size} in group[{group_index}] compact index {compact_index}")] +pub struct LateMinBufferBindingSizeMismatch { + pub group_index: u32, + pub compact_index: usize, + pub shader_size: wgt::BufferAddress, + pub bound_size: wgt::BufferAddress, +} diff --git a/third_party/rust/wgpu-core/src/command/bind.rs b/third_party/rust/wgpu-core/src/command/bind.rs new file mode 100644 index 0000000000..fdcb60c52d --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/bind.rs @@ -0,0 +1,355 @@ +use crate::{ + binding_model::{BindGroup, LateMinBufferBindingSizeMismatch, PipelineLayout}, + device::SHADER_STAGE_COUNT, + hub::{HalApi, Storage}, + id::{BindGroupId, BindGroupLayoutId, PipelineLayoutId, Valid}, + pipeline::LateSizedBufferGroup, + Stored, +}; + +use arrayvec::ArrayVec; + +type BindGroupMask = u8; + +mod compat { + use std::ops::Range; + + #[derive(Debug)] + struct Entry<T> { + assigned: Option<T>, + expected: Option<T>, + } + impl<T> Default for Entry<T> { + fn default() -> Self { + Self { + assigned: None, + expected: None, + } + } + } + impl<T: Copy + PartialEq> Entry<T> { + fn is_active(&self) -> bool { + self.assigned.is_some() && self.expected.is_some() + } + + fn is_valid(&self) -> bool { + self.expected.is_none() || self.expected == self.assigned + } + } + + #[derive(Debug)] + pub struct Manager<T> { + entries: [Entry<T>; hal::MAX_BIND_GROUPS], + } + + impl<T: Copy + PartialEq> Manager<T> { + pub fn new() -> Self { + Self { + entries: Default::default(), + } + } + + fn make_range(&self, start_index: usize) -> Range<usize> { + // find first incompatible entry + let end = self + .entries + .iter() + .position(|e| e.expected.is_none() || e.assigned != e.expected) + .unwrap_or(self.entries.len()); + start_index..end.max(start_index) + } + + pub fn update_expectations(&mut self, expectations: &[T]) -> Range<usize> { + let start_index = self + .entries + .iter() + .zip(expectations) + .position(|(e, &expect)| e.expected != Some(expect)) + .unwrap_or(expectations.len()); + for (e, &expect) in self.entries[start_index..] + .iter_mut() + .zip(expectations[start_index..].iter()) + { + e.expected = Some(expect); + } + for e in self.entries[expectations.len()..].iter_mut() { + e.expected = None; + } + self.make_range(start_index) + } + + pub fn assign(&mut self, index: usize, value: T) -> Range<usize> { + self.entries[index].assigned = Some(value); + self.make_range(index) + } + + pub fn list_active(&self) -> impl Iterator<Item = usize> + '_ { + self.entries + .iter() + .enumerate() + .filter_map(|(i, e)| if e.is_active() { Some(i) } else { None }) + } + + pub fn invalid_mask(&self) -> super::BindGroupMask { + self.entries.iter().enumerate().fold(0, |mask, (i, entry)| { + if entry.is_valid() { + mask + } else { + mask | 1u8 << i + } + }) + } + } + + #[test] + fn test_compatibility() { + let mut man = Manager::<i32>::new(); + man.entries[0] = Entry { + expected: Some(3), + assigned: Some(2), + }; + man.entries[1] = Entry { + expected: Some(1), + assigned: Some(1), + }; + man.entries[2] = Entry { + expected: Some(4), + assigned: Some(5), + }; + // check that we rebind [1] after [0] became compatible + assert_eq!(man.assign(0, 3), 0..2); + // check that nothing is rebound + assert_eq!(man.update_expectations(&[3, 2]), 1..1); + // check that [1] and [2] are rebound on expectations change + assert_eq!(man.update_expectations(&[3, 1, 5]), 1..3); + // reset the first two bindings + assert_eq!(man.update_expectations(&[4, 6, 5]), 0..0); + // check that nothing is rebound, even if there is a match, + // since earlier binding is incompatible. + assert_eq!(man.assign(1, 6), 1..1); + // finally, bind everything + assert_eq!(man.assign(0, 4), 0..3); + } +} + +#[derive(Debug)] +struct LateBufferBinding { + shader_expect_size: wgt::BufferAddress, + bound_size: wgt::BufferAddress, +} + +#[derive(Debug, Default)] +pub(super) struct EntryPayload { + pub(super) group_id: Option<Stored<BindGroupId>>, + pub(super) dynamic_offsets: Vec<wgt::DynamicOffset>, + late_buffer_bindings: Vec<LateBufferBinding>, + /// Since `LateBufferBinding` may contain information about the bindings + /// not used by the pipeline, we need to know when to stop validating. + pub(super) late_bindings_effective_count: usize, +} + +impl EntryPayload { + fn reset(&mut self) { + self.group_id = None; + self.dynamic_offsets.clear(); + self.late_buffer_bindings.clear(); + self.late_bindings_effective_count = 0; + } +} + +#[derive(Debug)] +pub(super) struct Binder { + pub(super) pipeline_layout_id: Option<Valid<PipelineLayoutId>>, //TODO: strongly `Stored` + manager: compat::Manager<Valid<BindGroupLayoutId>>, + payloads: [EntryPayload; hal::MAX_BIND_GROUPS], +} + +impl Binder { + pub(super) fn new() -> Self { + Self { + pipeline_layout_id: None, + manager: compat::Manager::new(), + payloads: Default::default(), + } + } + + pub(super) fn reset(&mut self) { + self.pipeline_layout_id = None; + self.manager = compat::Manager::new(); + for payload in self.payloads.iter_mut() { + payload.reset(); + } + } + + pub(super) fn change_pipeline_layout<'a, A: HalApi>( + &'a mut self, + guard: &Storage<PipelineLayout<A>, PipelineLayoutId>, + new_id: Valid<PipelineLayoutId>, + late_sized_buffer_groups: &[LateSizedBufferGroup], + ) -> (usize, &'a [EntryPayload]) { + let old_id_opt = self.pipeline_layout_id.replace(new_id); + let new = &guard[new_id]; + + let mut bind_range = self.manager.update_expectations(&new.bind_group_layout_ids); + + // Update the buffer binding sizes that are required by shaders. + for (payload, late_group) in self.payloads.iter_mut().zip(late_sized_buffer_groups) { + payload.late_bindings_effective_count = late_group.shader_sizes.len(); + for (late_binding, &shader_expect_size) in payload + .late_buffer_bindings + .iter_mut() + .zip(late_group.shader_sizes.iter()) + { + late_binding.shader_expect_size = shader_expect_size; + } + if late_group.shader_sizes.len() > payload.late_buffer_bindings.len() { + for &shader_expect_size in + late_group.shader_sizes[payload.late_buffer_bindings.len()..].iter() + { + payload.late_buffer_bindings.push(LateBufferBinding { + shader_expect_size, + bound_size: 0, + }); + } + } + } + + if let Some(old_id) = old_id_opt { + let old = &guard[old_id]; + // root constants are the base compatibility property + if old.push_constant_ranges != new.push_constant_ranges { + bind_range.start = 0; + } + } + + (bind_range.start, &self.payloads[bind_range]) + } + + pub(super) fn assign_group<'a, A: HalApi>( + &'a mut self, + index: usize, + bind_group_id: Valid<BindGroupId>, + bind_group: &BindGroup<A>, + offsets: &[wgt::DynamicOffset], + ) -> &'a [EntryPayload] { + log::trace!("\tBinding [{}] = group {:?}", index, bind_group_id); + debug_assert_eq!(A::VARIANT, bind_group_id.0.backend()); + + let payload = &mut self.payloads[index]; + payload.group_id = Some(Stored { + value: bind_group_id, + ref_count: bind_group.life_guard.add_ref(), + }); + payload.dynamic_offsets.clear(); + payload.dynamic_offsets.extend_from_slice(offsets); + + // Fill out the actual binding sizes for buffers, + // whose layout doesn't specify `min_binding_size`. + for (late_binding, late_size) in payload + .late_buffer_bindings + .iter_mut() + .zip(bind_group.late_buffer_binding_sizes.iter()) + { + late_binding.bound_size = late_size.get(); + } + if bind_group.late_buffer_binding_sizes.len() > payload.late_buffer_bindings.len() { + for late_size in + bind_group.late_buffer_binding_sizes[payload.late_buffer_bindings.len()..].iter() + { + payload.late_buffer_bindings.push(LateBufferBinding { + shader_expect_size: 0, + bound_size: late_size.get(), + }); + } + } + + let bind_range = self.manager.assign(index, bind_group.layout_id); + &self.payloads[bind_range] + } + + pub(super) fn list_active(&self) -> impl Iterator<Item = Valid<BindGroupId>> + '_ { + let payloads = &self.payloads; + self.manager + .list_active() + .map(move |index| payloads[index].group_id.as_ref().unwrap().value) + } + + pub(super) fn invalid_mask(&self) -> BindGroupMask { + self.manager.invalid_mask() + } + + /// Scan active buffer bindings corresponding to layouts without `min_binding_size` specified. + pub(super) fn check_late_buffer_bindings( + &self, + ) -> Result<(), LateMinBufferBindingSizeMismatch> { + for group_index in self.manager.list_active() { + let payload = &self.payloads[group_index]; + for (compact_index, late_binding) in payload.late_buffer_bindings + [..payload.late_bindings_effective_count] + .iter() + .enumerate() + { + if late_binding.bound_size < late_binding.shader_expect_size { + return Err(LateMinBufferBindingSizeMismatch { + group_index: group_index as u32, + compact_index, + shader_size: late_binding.shader_expect_size, + bound_size: late_binding.bound_size, + }); + } + } + } + Ok(()) + } +} + +struct PushConstantChange { + stages: wgt::ShaderStages, + 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::ShaderStages::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/third_party/rust/wgpu-core/src/command/bundle.rs b/third_party/rust/wgpu-core/src/command/bundle.rs new file mode 100644 index 0000000000..34d7e84cf4 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/bundle.rs @@ -0,0 +1,1651 @@ +/*! Render Bundles + +A render bundle is a prerecorded sequence of commands that can be replayed on a +command encoder with a single call. A single bundle can replayed any number of +times, on different encoders. Constructing a render bundle lets `wgpu` validate +and analyze its commands up front, so that replaying a bundle can be more +efficient than simply re-recording its commands each time. + +Not all commands are available in bundles; for example, a render bundle may not +contain a [`RenderCommand::SetViewport`] command. + +Most of `wgpu`'s backend graphics APIs have something like bundles. For example, +Vulkan calls them "secondary command buffers", and Metal calls them "indirect +command buffers". Although we plan to take advantage of these platform features +at some point in the future, for now `wgpu`'s implementation of render bundles +does not use them: at the hal level, `wgpu` render bundles just replay the +commands. + +## Render Bundle Isolation + +One important property of render bundles is that the draw calls in a render +bundle depend solely on the pipeline and state established within the render +bundle itself. A draw call in a bundle will never use a vertex buffer, say, that +was set in the `RenderPass` before executing the bundle. We call this property +'isolation', in that a render bundle is somewhat isolated from the passes that +use it. + +Render passes are also isolated from the effects of bundles. After executing a +render bundle, a render pass's pipeline, bind groups, and vertex and index +buffers are are unset, so the bundle cannot affect later draw calls in the pass. + +A render pass is not fully isolated from a bundle's effects on push constant +values. Draw calls following a bundle's execution will see whatever values the +bundle writes to push constant storage. Setting a pipeline initializes any push +constant storage it could access to zero, and this initialization may also be +visible after bundle execution. + +## Render Bundle Lifecycle + +To create a render bundle: + +1) Create a [`RenderBundleEncoder`] by calling + [`Global::device_create_render_bundle_encoder`][Gdcrbe]. + +2) Record commands in the `RenderBundleEncoder` using functions from the + [`bundle_ffi`] module. + +3) Call [`Global::render_bundle_encoder_finish`][Grbef], which analyzes and cleans up + the command stream and returns a `RenderBundleId`. + +4) Then, any number of times, call [`wgpu_render_pass_execute_bundles`][wrpeb] to + execute the bundle as part of some render pass. + +## Implementation + +The most complex part of render bundles is the "finish" step, mostly implemented +in [`RenderBundleEncoder::finish`]. This consumes the commands stored in the +encoder's [`BasePass`], while validating everything, tracking the state, +dropping redundant or unnecessary commands, and presenting the results as a new +[`RenderBundle`]. It doesn't actually execute any commands. + +This step also enforces the 'isolation' property mentioned above: every draw +call is checked to ensure that the resources it uses on were established since +the last time the pipeline was set. This means the bundle can be executed +verbatim without any state tracking. + +### 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 isolation, it doesn't track any bind group invalidations or +index format changes. + +[Gdcrbe]: crate::hub::Global::device_create_render_bundle_encoder +[Grbef]: crate::hub::Global::render_bundle_encoder_finish +[wrpeb]: crate::command::render_ffi::wgpu_render_pass_execute_bundles +!*/ + +#![allow(clippy::reversed_empty_ranges)] + +use crate::{ + binding_model::{self, buffer_binding_type_alignment}, + command::{ + BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, MapPassErr, + PassErrorScope, RenderCommand, RenderCommandError, StateChange, + }, + conv, + device::{ + AttachmentData, Device, DeviceError, MissingDownlevelFlags, + RenderPassCompatibilityCheckType, RenderPassContext, SHADER_STAGE_COUNT, + }, + error::{ErrorFormatter, PrettyError}, + hub::{GlobalIdentityHandlerFactory, HalApi, Hub, Resource, Storage, Token}, + id, + init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction}, + pipeline::{self, PipelineFlags}, + resource, + track::RenderBundleScope, + validation::check_buffer_usage, + Label, LabelHelpers, LifeGuard, Stored, +}; +use arrayvec::ArrayVec; +use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range}; +use thiserror::Error; + +use hal::CommandEncoder as _; + +/// 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, [Option<wgt::TextureFormat>]>, + /// Information about the depth attachment that this render bundle is + /// capable to rendering to. + /// + /// The format must match the format of the depth attachments in the + /// renderpass this render bundle is executed in. + pub depth_stencil: Option<wgt::RenderBundleDepthStencil>, + /// 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, + /// If this render bundle will rendering to multiple array layers in the + /// attachments at the same time. + pub multiview: Option<NonZeroU32>, +} + +#[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, + pub(crate) is_depth_read_only: bool, + pub(crate) is_stencil_read_only: bool, + + // Resource binding dedupe state. + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_bind_groups: BindGroupStateChange, + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_pipeline: StateChange<id::RenderPipelineId>, +} + +impl RenderBundleEncoder { + pub fn new( + desc: &RenderBundleEncoderDescriptor, + parent_id: id::DeviceId, + base: Option<BasePass<RenderCommand>>, + ) -> Result<Self, CreateRenderBundleError> { + let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil { + Some(ds) => { + let aspects = hal::FormatAspects::from(ds.format); + ( + !aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only, + !aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only, + ) + } + // There's no depth/stencil attachment, so these values just don't + // matter. Choose the most accommodating value, to simplify + // validation. + None => (true, true), + }; + + //TODO: validate that attachment formats are renderable, + // have expected aspects, support multisampling. + Ok(Self { + base: base.unwrap_or_else(|| BasePass::new(&desc.label)), + parent_id, + context: RenderPassContext { + attachments: AttachmentData { + colors: if desc.color_formats.len() > hal::MAX_COLOR_ATTACHMENTS { + return Err(CreateRenderBundleError::ColorAttachment( + ColorAttachmentError::TooMany { + given: desc.color_formats.len(), + limit: hal::MAX_COLOR_ATTACHMENTS, + }, + )); + } else { + desc.color_formats.iter().cloned().collect() + }, + resolves: ArrayVec::new(), + depth_stencil: desc.depth_stencil.map(|ds| ds.format), + }, + sample_count: { + let sc = desc.sample_count; + if sc == 0 || sc > 32 || !conv::is_power_of_two_u32(sc) { + return Err(CreateRenderBundleError::InvalidSampleCount(sc)); + } + sc + }, + multiview: desc.multiview, + }, + + is_depth_read_only, + is_stencil_read_only, + current_bind_groups: BindGroupStateChange::new(), + current_pipeline: StateChange::new(), + }) + } + + pub fn dummy(parent_id: id::DeviceId) -> Self { + Self { + base: BasePass::new(&None), + parent_id, + context: RenderPassContext { + attachments: AttachmentData { + colors: ArrayVec::new(), + resolves: ArrayVec::new(), + depth_stencil: None, + }, + sample_count: 0, + multiview: None, + }, + is_depth_read_only: false, + is_stencil_read_only: false, + + current_bind_groups: BindGroupStateChange::new(), + current_pipeline: StateChange::new(), + } + } + + #[cfg(feature = "trace")] + pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand> { + BasePass::from_ref(self.base.as_ref()) + } + + pub fn parent(&self) -> id::DeviceId { + self.parent_id + } + + /// Convert this encoder's commands into a [`RenderBundle`]. + /// + /// We want executing a [`RenderBundle`] to be quick, so we take + /// this opportunity to clean up the [`RenderBundleEncoder`]'s + /// command stream and gather metadata about it that will help + /// keep [`ExecuteBundle`] simple and fast. We remove redundant + /// commands (along with their side data), note resource usage, + /// and accumulate buffer and texture initialization actions. + /// + /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle + pub(crate) fn finish<A: HalApi, G: GlobalIdentityHandlerFactory>( + self, + desc: &RenderBundleDescriptor, + device: &Device<A>, + hub: &Hub<A, G>, + token: &mut Token<Device<A>>, + ) -> Result<RenderBundle<A>, 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 (query_set_guard, mut token) = hub.query_sets.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 { + trackers: RenderBundleScope::new( + &*buffer_guard, + &*texture_guard, + &*bind_group_guard, + &*pipeline_guard, + &*query_set_guard, + ), + pipeline: None, + bind: (0..hal::MAX_BIND_GROUPS).map(|_| None).collect(), + vertex: (0..hal::MAX_VERTEX_BUFFERS).map(|_| None).collect(), + index: None, + flat_dynamic_offsets: Vec::new(), + }; + let mut commands = Vec::new(); + let mut buffer_memory_init_actions = Vec::new(); + let mut texture_memory_init_actions = Vec::new(); + + let base = self.base.as_ref(); + let mut next_dynamic_offset = 0; + + for &command in base.commands { + match command { + RenderCommand::SetBindGroup { + index, + num_dynamic_offsets, + bind_group_id, + } => { + let scope = PassErrorScope::SetBindGroup(bind_group_id); + + let bind_group: &binding_model::BindGroup<A> = state + .trackers + .bind_groups + .add_single(&*bind_group_guard, bind_group_id) + .ok_or(RenderCommandError::InvalidBindGroup(bind_group_id)) + .map_pass_err(scope)?; + self.check_valid_to_use(bind_group.device_id.value) + .map_pass_err(scope)?; + + let max_bind_groups = device.limits.max_bind_groups; + if index >= max_bind_groups { + return Err(RenderCommandError::BindGroupIndexOutOfRange { + index, + max: max_bind_groups, + }) + .map_pass_err(scope); + } + + // Identify the next `num_dynamic_offsets` entries from `base.dynamic_offsets`. + let num_dynamic_offsets = num_dynamic_offsets as usize; + let offsets_range = + next_dynamic_offset..next_dynamic_offset + num_dynamic_offsets; + next_dynamic_offset = offsets_range.end; + let offsets = &base.dynamic_offsets[offsets_range.clone()]; + + 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); + } + + // Check for misaligned offsets. + for (offset, info) in offsets + .iter() + .map(|offset| *offset as wgt::BufferAddress) + .zip(bind_group.dynamic_binding_info.iter()) + { + let (alignment, limit_name) = + buffer_binding_type_alignment(&device.limits, info.binding_type); + if offset % alignment as u64 != 0 { + return Err(RenderCommandError::UnalignedBufferOffset( + offset, limit_name, alignment, + )) + .map_pass_err(scope); + } + } + + buffer_memory_init_actions.extend_from_slice(&bind_group.used_buffer_ranges); + texture_memory_init_actions.extend_from_slice(&bind_group.used_texture_ranges); + + state.set_bind_group(index, bind_group_id, bind_group.layout_id, offsets_range); + unsafe { + state + .trackers + .merge_bind_group(&*texture_guard, &bind_group.used) + .map_pass_err(scope)? + }; + //Note: stateless trackers are not merged: the lifetime reference + // is held to the bind group itself. + } + RenderCommand::SetPipeline(pipeline_id) => { + let scope = PassErrorScope::SetPipelineRender(pipeline_id); + + let pipeline: &pipeline::RenderPipeline<A> = state + .trackers + .render_pipelines + .add_single(&*pipeline_guard, pipeline_id) + .ok_or(RenderCommandError::InvalidPipeline(pipeline_id)) + .map_pass_err(scope)?; + self.check_valid_to_use(pipeline.device_id.value) + .map_pass_err(scope)?; + + self.context + .check_compatible(&pipeline.pass_context, RenderPassCompatibilityCheckType::RenderPipeline) + .map_err(RenderCommandError::IncompatiblePipelineTargets) + .map_pass_err(scope)?; + + if (pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) + && self.is_depth_read_only) + || (pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) + && self.is_stencil_read_only) + { + return Err(RenderCommandError::IncompatiblePipelineRods) + .map_pass_err(scope); + } + + let layout = &pipeline_layout_guard[pipeline.layout_id.value]; + let pipeline_state = PipelineState::new(pipeline_id, pipeline, layout); + + commands.push(command); + + // If this pipeline uses push constants, zero out their values. + if let Some(iter) = pipeline_state.zero_push_constants() { + commands.extend(iter) + } + + state.invalidate_bind_groups(&pipeline_state, layout); + state.pipeline = Some(pipeline_state); + } + RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + } => { + let scope = PassErrorScope::SetIndexBuffer(buffer_id); + let buffer: &resource::Buffer<A> = state + .trackers + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDEX) + .map_pass_err(scope)?; + self.check_valid_to_use(buffer.device_id.value) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::INDEX) + .map_pass_err(scope)?; + + let end = match size { + Some(s) => offset + s.get(), + None => buffer.size, + }; + buffer_memory_init_actions.extend(buffer.initialization_status.create_action( + buffer_id, + offset..end, + MemoryInitKind::NeedsInitializedMemory, + )); + state.set_index_buffer(buffer_id, index_format, offset..end); + } + RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + } => { + let scope = PassErrorScope::SetVertexBuffer(buffer_id); + let buffer: &resource::Buffer<A> = state + .trackers + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::VERTEX) + .map_pass_err(scope)?; + self.check_valid_to_use(buffer.device_id.value) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::VERTEX) + .map_pass_err(scope)?; + + let end = match size { + Some(s) => offset + s.get(), + None => buffer.size, + }; + buffer_memory_init_actions.extend(buffer.initialization_status.create_action( + buffer_id, + offset..end, + MemoryInitKind::NeedsInitializedMemory, + )); + state.vertex[slot as usize] = Some(VertexState::new(buffer_id, offset..end)); + } + RenderCommand::SetPushConstant { + stages, + offset, + size_bytes, + values_offset: _, + } => { + let scope = PassErrorScope::SetPushConstant; + let end_offset = offset + size_bytes; + + let pipeline = state.pipeline(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 { + indexed: false, + indirect: false, + pipeline: state.pipeline_id(), + }; + let pipeline = state.pipeline(scope)?; + let used_bind_groups = pipeline.used_bind_groups; + let vertex_limits = state.vertex_limits(pipeline); + let last_vertex = first_vertex + vertex_count; + if last_vertex > vertex_limits.vertex_limit { + return Err(DrawError::VertexBeyondLimit { + last_vertex, + vertex_limit: vertex_limits.vertex_limit, + slot: vertex_limits.vertex_limit_slot, + }) + .map_pass_err(scope); + } + let last_instance = first_instance + instance_count; + if last_instance > vertex_limits.instance_limit { + return Err(DrawError::InstanceBeyondLimit { + last_instance, + instance_limit: vertex_limits.instance_limit, + slot: vertex_limits.instance_limit_slot, + }) + .map_pass_err(scope); + } + commands.extend(state.flush_vertices()); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); + commands.push(command); + } + RenderCommand::DrawIndexed { + index_count, + instance_count, + first_index, + base_vertex: _, + first_instance, + } => { + let scope = PassErrorScope::Draw { + indexed: true, + indirect: false, + pipeline: state.pipeline_id(), + }; + let pipeline = state.pipeline(scope)?; + let used_bind_groups = pipeline.used_bind_groups; + let index = match state.index { + Some(ref index) => index, + None => return Err(DrawError::MissingIndexBuffer).map_pass_err(scope), + }; + //TODO: validate that base_vertex + max_index() is within the provided range + let vertex_limits = state.vertex_limits(pipeline); + let index_limit = 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 > vertex_limits.instance_limit { + return Err(DrawError::InstanceBeyondLimit { + last_instance, + instance_limit: vertex_limits.instance_limit, + slot: vertex_limits.instance_limit_slot, + }) + .map_pass_err(scope); + } + commands.extend(state.flush_index()); + commands.extend(state.flush_vertices()); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); + commands.push(command); + } + RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: false, + } => { + let scope = PassErrorScope::Draw { + indexed: false, + indirect: true, + pipeline: state.pipeline_id(), + }; + device + .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) + .map_pass_err(scope)?; + + let pipeline = state.pipeline(scope)?; + let used_bind_groups = pipeline.used_bind_groups; + + let buffer: &resource::Buffer<A> = state + .trackers + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) + .map_pass_err(scope)?; + self.check_valid_to_use(buffer.device_id.value) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT) + .map_pass_err(scope)?; + + buffer_memory_init_actions.extend(buffer.initialization_status.create_action( + buffer_id, + offset..(offset + mem::size_of::<wgt::DrawIndirectArgs>() as u64), + MemoryInitKind::NeedsInitializedMemory, + )); + + commands.extend(state.flush_vertices()); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); + commands.push(command); + } + RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: true, + } => { + let scope = PassErrorScope::Draw { + indexed: true, + indirect: true, + pipeline: state.pipeline_id(), + }; + device + .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) + .map_pass_err(scope)?; + + let pipeline = state.pipeline(scope)?; + let used_bind_groups = pipeline.used_bind_groups; + + let buffer: &resource::Buffer<A> = state + .trackers + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) + .map_pass_err(scope)?; + self.check_valid_to_use(buffer.device_id.value) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::INDIRECT) + .map_pass_err(scope)?; + + buffer_memory_init_actions.extend(buffer.initialization_status.create_action( + buffer_id, + offset..(offset + mem::size_of::<wgt::DrawIndirectArgs>() as u64), + MemoryInitKind::NeedsInitializedMemory, + )); + + let index = match state.index { + Some(ref mut index) => index, + None => return Err(DrawError::MissingIndexBuffer).map_pass_err(scope), + }; + + commands.extend(index.flush()); + commands.extend(state.flush_vertices()); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); + commands.push(command); + } + RenderCommand::MultiDrawIndirect { .. } + | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(), + RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(), + RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(), + RenderCommand::PopDebugGroup => unimplemented!(), + RenderCommand::WriteTimestamp { .. } // Must check the TIMESTAMP_QUERY_INSIDE_PASSES feature + | RenderCommand::BeginPipelineStatisticsQuery { .. } + | RenderCommand::EndPipelineStatisticsQuery => unimplemented!(), + RenderCommand::ExecuteBundle(_) + | RenderCommand::SetBlendConstant(_) + | RenderCommand::SetStencilReference(_) + | RenderCommand::SetViewport { .. } + | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"), + } + } + + Ok(RenderBundle { + base: BasePass { + label: desc.label.as_ref().map(|cow| cow.to_string()), + commands, + dynamic_offsets: state.flat_dynamic_offsets, + string_data: Vec::new(), + push_constant_data: Vec::new(), + }, + is_depth_read_only: self.is_depth_read_only, + is_stencil_read_only: self.is_stencil_read_only, + device_id: Stored { + value: id::Valid(self.parent_id), + ref_count: device.life_guard.add_ref(), + }, + used: state.trackers, + buffer_memory_init_actions, + texture_memory_init_actions, + context: self.context, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + }) + } + + fn check_valid_to_use( + &self, + device_id: id::Valid<id::DeviceId>, + ) -> Result<(), RenderBundleErrorInner> { + if device_id.0 != self.parent_id { + return Err(RenderBundleErrorInner::NotValidToUse); + } + + Ok(()) + } + + pub fn set_index_buffer( + &mut self, + buffer_id: id::BufferId, + index_format: wgt::IndexFormat, + offset: wgt::BufferAddress, + size: Option<wgt::BufferSize>, + ) { + self.base.commands.push(RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + }); + } +} + +/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateRenderBundleError { + #[error(transparent)] + ColorAttachment(#[from] ColorAttachmentError), + #[error("Invalid number of samples {0}")] + InvalidSampleCount(u32), +} + +/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ExecutionError { + #[error("Buffer {0:?} is destroyed")] + DestroyedBuffer(id::BufferId), + #[error("Using {0} in a render bundle is not implemented")] + Unimplemented(&'static str), +} +impl PrettyError for ExecutionError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + match *self { + Self::DestroyedBuffer(id) => { + fmt.buffer_label(&id); + } + Self::Unimplemented(_reason) => {} + }; + } +} + +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. +pub struct RenderBundle<A: HalApi> { + // Normalized command stream. It can be executed verbatim, + // without re-binding anything on the pipeline change. + base: BasePass<RenderCommand>, + pub(super) is_depth_read_only: bool, + pub(super) is_stencil_read_only: bool, + pub(crate) device_id: Stored<id::DeviceId>, + pub(crate) used: RenderBundleScope<A>, + pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>, + pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>, + pub(super) context: RenderPassContext, + pub(crate) life_guard: LifeGuard, +} + +unsafe impl<A: HalApi> Send for RenderBundle<A> {} +unsafe impl<A: HalApi> Sync for RenderBundle<A> {} + +impl<A: HalApi> RenderBundle<A> { + /// 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(super) unsafe fn execute( + &self, + raw: &mut A::CommandEncoder, + pipeline_layout_guard: &Storage< + crate::binding_model::PipelineLayout<A>, + id::PipelineLayoutId, + >, + bind_group_guard: &Storage<crate::binding_model::BindGroup<A>, id::BindGroupId>, + pipeline_guard: &Storage<crate::pipeline::RenderPipeline<A>, id::RenderPipelineId>, + buffer_guard: &Storage<crate::resource::Buffer<A>, id::BufferId>, + ) -> Result<(), ExecutionError> { + let mut offsets = self.base.dynamic_offsets.as_slice(); + let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>; + if let Some(ref label) = self.base.label { + unsafe { raw.begin_debug_marker(label) }; + } + + 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(); + unsafe { + raw.set_bind_group( + &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw, + index, + &bind_group.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(); + unsafe { raw.set_render_pipeline(&pipeline.raw) }; + + pipeline_layout_id = Some(pipeline.layout_id.value); + } + RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + } => { + let buffer = buffer_guard + .get(buffer_id) + .unwrap() + .raw + .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; + let bb = hal::BufferBinding { + buffer, + offset, + size, + }; + unsafe { raw.set_index_buffer(bb, index_format) }; + } + RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + } => { + let buffer = buffer_guard + .get(buffer_id) + .unwrap() + .raw + .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; + let bb = hal::BufferBinding { + buffer, + offset, + size, + }; + unsafe { raw.set_vertex_buffer(slot, bb) }; + } + 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]; + + unsafe { + raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice) + } + } else { + super::push_constant_clear( + offset, + size_bytes, + |clear_offset, clear_data| { + unsafe { + raw.set_push_constants( + &pipeline_layout.raw, + stages, + clear_offset, + clear_data, + ) + }; + }, + ); + } + } + RenderCommand::Draw { + vertex_count, + instance_count, + first_vertex, + first_instance, + } => { + unsafe { raw.draw(first_vertex, vertex_count, first_instance, instance_count) }; + } + RenderCommand::DrawIndexed { + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + } => { + unsafe { + raw.draw_indexed( + first_index, + index_count, + base_vertex, + first_instance, + instance_count, + ) + }; + } + RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: false, + } => { + let buffer = buffer_guard + .get(buffer_id) + .unwrap() + .raw + .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; + unsafe { raw.draw_indirect(buffer, offset, 1) }; + } + RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: true, + } => { + let buffer = buffer_guard + .get(buffer_id) + .unwrap() + .raw + .as_ref() + .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?; + unsafe { raw.draw_indexed_indirect(buffer, offset, 1) }; + } + RenderCommand::MultiDrawIndirect { .. } + | RenderCommand::MultiDrawIndirectCount { .. } => { + return Err(ExecutionError::Unimplemented("multi-draw-indirect")) + } + RenderCommand::PushDebugGroup { .. } + | RenderCommand::InsertDebugMarker { .. } + | RenderCommand::PopDebugGroup => { + return Err(ExecutionError::Unimplemented("debug-markers")) + } + RenderCommand::WriteTimestamp { .. } + | RenderCommand::BeginPipelineStatisticsQuery { .. } + | RenderCommand::EndPipelineStatisticsQuery => { + return Err(ExecutionError::Unimplemented("queries")) + } + RenderCommand::ExecuteBundle(_) + | RenderCommand::SetBlendConstant(_) + | RenderCommand::SetStencilReference(_) + | RenderCommand::SetViewport { .. } + | RenderCommand::SetScissor(_) => unreachable!(), + } + } + + if let Some(_) = self.base.label { + unsafe { raw.end_debug_marker() }; + } + + Ok(()) + } +} + +impl<A: HalApi> Resource for RenderBundle<A> { + const TYPE: &'static str = "RenderBundle"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +/// A render bundle's current index buffer state. +/// +/// [`RenderBundleEncoder::finish`] records the currently set index buffer here, +/// and calls [`State::flush_index`] before any indexed draw command to produce +/// a `SetIndexBuffer` command if one is necessary. +#[derive(Debug)] +struct IndexState { + buffer: id::BufferId, + format: wgt::IndexFormat, + range: Range<wgt::BufferAddress>, + is_dirty: bool, +} + +impl IndexState { + /// Return the number of entries in the current index buffer. + /// + /// Panic if no index buffer has been set. + fn limit(&self) -> u32 { + 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 + } + + /// Generate a `SetIndexBuffer` command to prepare for an indexed draw + /// command, if needed. + fn flush(&mut self) -> Option<RenderCommand> { + if self.is_dirty { + self.is_dirty = false; + Some(RenderCommand::SetIndexBuffer { + buffer_id: self.buffer, + index_format: self.format, + offset: self.range.start, + size: wgt::BufferSize::new(self.range.end - self.range.start), + }) + } else { + None + } + } +} + +/// The state of a single vertex buffer slot during render bundle encoding. +/// +/// [`RenderBundleEncoder::finish`] uses this to drop redundant +/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It +/// records one vertex buffer slot's state changes here, and then +/// calls this type's [`flush`] method just before any draw command to +/// produce a `SetVertexBuffer` commands if one is necessary. +/// +/// [`flush`]: IndexState::flush +#[derive(Debug)] +struct VertexState { + buffer: id::BufferId, + range: Range<wgt::BufferAddress>, + is_dirty: bool, +} + +impl VertexState { + fn new(buffer: id::BufferId, range: Range<wgt::BufferAddress>) -> Self { + Self { + buffer, + range, + is_dirty: true, + } + } + + /// Generate a `SetVertexBuffer` command for this slot, if necessary. + /// + /// `slot` is the index of the vertex buffer slot that `self` tracks. + fn flush(&mut self, slot: u32) -> Option<RenderCommand> { + if self.is_dirty { + self.is_dirty = false; + Some(RenderCommand::SetVertexBuffer { + slot, + buffer_id: self.buffer, + offset: self.range.start, + size: wgt::BufferSize::new(self.range.end - self.range.start), + }) + } else { + None + } + } +} + +/// A bind group that has been set at a particular index during render bundle encoding. +#[derive(Debug)] +struct BindState { + /// The id of the bind group set at this index. + bind_group_id: id::BindGroupId, + + /// The layout of `group`. + layout_id: id::Valid<id::BindGroupLayoutId>, + + /// The range of dynamic offsets for this bind group, in the original + /// command stream's `BassPass::dynamic_offsets` array. + dynamic_offsets: Range<usize>, + + /// True if this index's contents have been changed since the last time we + /// generated a `SetBindGroup` command. + is_dirty: bool, +} + +#[derive(Debug)] +struct VertexLimitState { + /// Length of the shortest vertex rate vertex buffer + vertex_limit: u32, + /// Buffer slot which the shortest vertex rate vertex buffer is bound to + vertex_limit_slot: u32, + /// Length of the shortest instance rate vertex buffer + instance_limit: u32, + /// Buffer slot which the shortest instance rate vertex buffer is bound to + instance_limit_slot: u32, +} + +/// The bundle's current pipeline, and some cached information needed for validation. +struct PipelineState { + /// The pipeline's id. + id: id::RenderPipelineId, + + /// The id of the pipeline's layout. + layout_id: id::Valid<id::PipelineLayoutId>, + + /// How this pipeline's vertex shader traverses each vertex buffer, indexed + /// by vertex buffer slot number. + steps: Vec<pipeline::VertexStep>, + + /// Ranges of push constants this pipeline uses, copied from the pipeline + /// layout. + push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>, + + /// The number of bind groups this pipeline uses. + used_bind_groups: usize, +} + +impl PipelineState { + fn new<A: HalApi>( + pipeline_id: id::RenderPipelineId, + pipeline: &pipeline::RenderPipeline<A>, + layout: &binding_model::PipelineLayout<A>, + ) -> Self { + Self { + id: pipeline_id, + layout_id: pipeline.layout_id.value, + steps: pipeline.vertex_steps.to_vec(), + push_constant_ranges: layout.push_constant_ranges.iter().cloned().collect(), + used_bind_groups: layout.bind_group_layout_ids.len(), + } + } + + /// Return a sequence of commands to zero the push constant ranges this + /// pipeline uses. If no initialization is necessary, return `None`. + fn zero_push_constants(&self) -> Option<impl Iterator<Item = RenderCommand>> { + if !self.push_constant_ranges.is_empty() { + let nonoverlapping_ranges = + super::bind::compute_nonoverlapping_ranges(&self.push_constant_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, // write zeros + }), + ) + } else { + None + } + } +} + +/// State for analyzing and cleaning up bundle command streams. +/// +/// To minimize state updates, [`RenderBundleEncoder::finish`] +/// actually just applies commands like [`SetBindGroup`] and +/// [`SetIndexBuffer`] to the simulated state stored here, and then +/// calls the `flush_foo` methods before draw calls to produce the +/// update commands we actually need. +/// +/// [`SetBindGroup`]: RenderCommand::SetBindGroup +/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer +struct State<A: HalApi> { + /// Resources used by this bundle. This will become [`RenderBundle::used`]. + trackers: RenderBundleScope<A>, + + /// The currently set pipeline, if any. + pipeline: Option<PipelineState>, + + /// The bind group set at each index, if any. + bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>, + + /// The state of each vertex buffer slot. + vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>, + + /// The current index buffer, if one has been set. We flush this state + /// before indexed draw commands. + index: Option<IndexState>, + + /// Dynamic offset values used by the cleaned-up command sequence. + /// + /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s + /// [`dynamic_offsets`] list. + /// + /// [`dynamic_offsets`]: BasePass::dynamic_offsets + flat_dynamic_offsets: Vec<wgt::DynamicOffset>, +} + +impl<A: HalApi> State<A> { + fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState { + let mut vert_state = VertexLimitState { + vertex_limit: u32::MAX, + vertex_limit_slot: 0, + instance_limit: u32::MAX, + instance_limit_slot: 0, + }; + for (idx, (vbs, step)) in self.vertex.iter().zip(&pipeline.steps).enumerate() { + if let Some(ref vbs) = *vbs { + let limit = ((vbs.range.end - vbs.range.start) / step.stride) as u32; + match step.mode { + wgt::VertexStepMode::Vertex => { + if limit < vert_state.vertex_limit { + vert_state.vertex_limit = limit; + vert_state.vertex_limit_slot = idx as _; + } + } + wgt::VertexStepMode::Instance => { + if limit < vert_state.instance_limit { + vert_state.instance_limit = limit; + vert_state.instance_limit_slot = idx as _; + } + } + } + } + } + vert_state + } + + /// Return the id of the current pipeline, if any. + fn pipeline_id(&self) -> Option<id::RenderPipelineId> { + self.pipeline.as_ref().map(|p| p.id) + } + + /// Return the current pipeline state. Return an error if none is set. + fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> { + self.pipeline + .as_ref() + .ok_or(DrawError::MissingPipeline) + .map_pass_err(scope) + } + + /// Mark all non-empty bind group table entries from `index` onwards as dirty. + fn invalidate_bind_group_from(&mut self, index: usize) { + for contents in self.bind[index..].iter_mut().flatten() { + contents.is_dirty = true; + } + } + + fn set_bind_group( + &mut self, + slot: u32, + bind_group_id: id::BindGroupId, + layout_id: id::Valid<id::BindGroupLayoutId>, + dynamic_offsets: Range<usize>, + ) { + // If this call wouldn't actually change this index's state, we can + // return early. (If there are dynamic offsets, the range will always + // be different.) + if dynamic_offsets.is_empty() { + if let Some(ref contents) = self.bind[slot as usize] { + if contents.bind_group_id == bind_group_id { + return; + } + } + } + + // Record the index's new state. + self.bind[slot as usize] = Some(BindState { + bind_group_id, + layout_id, + dynamic_offsets, + is_dirty: true, + }); + + // Once we've changed the bind group at a particular index, all + // subsequent indices need to be rewritten. + self.invalidate_bind_group_from(slot as usize + 1); + } + + /// Determine which bind group slots need to be re-set after a pipeline change. + /// + /// Given that we are switching from the current pipeline state to `new`, + /// whose layout is `layout`, mark all the bind group slots that we need to + /// emit new `SetBindGroup` commands for as dirty. + /// + /// According to `wgpu_hal`'s rules: + /// + /// - If the layout of any bind group slot changes, then that slot and + /// all following slots must have their bind groups re-established. + /// + /// - Changing the push constant ranges at all requires re-establishing + /// all bind groups. + fn invalidate_bind_groups( + &mut self, + new: &PipelineState, + layout: &binding_model::PipelineLayout<A>, + ) { + match self.pipeline { + None => { + // Establishing entirely new pipeline state. + self.invalidate_bind_group_from(0); + } + Some(ref old) => { + if old.id == new.id { + // Everything is derived from the pipeline, so if the id has + // not changed, there's no need to consider anything else. + return; + } + + // Any push constant change invalidates all groups. + if old.push_constant_ranges != new.push_constant_ranges { + self.invalidate_bind_group_from(0); + } else { + let first_changed = self + .bind + .iter() + .zip(&layout.bind_group_layout_ids) + .position(|(entry, &layout_id)| match *entry { + Some(ref contents) => contents.layout_id != layout_id, + None => false, + }); + if let Some(slot) = first_changed { + self.invalidate_bind_group_from(slot); + } + } + } + } + } + + /// Set the bundle's current index buffer and its associated parameters. + fn set_index_buffer( + &mut self, + buffer: id::BufferId, + format: wgt::IndexFormat, + range: Range<wgt::BufferAddress>, + ) { + match self.index { + Some(ref current) + if current.buffer == buffer + && current.format == format + && current.range == range => + { + return + } + _ => (), + } + + self.index = Some(IndexState { + buffer, + format, + range, + is_dirty: true, + }); + } + + /// Generate a `SetIndexBuffer` command to prepare for an indexed draw + /// command, if needed. + fn flush_index(&mut self) -> Option<RenderCommand> { + self.index.as_mut().and_then(|index| index.flush()) + } + + fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ { + self.vertex + .iter_mut() + .enumerate() + .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32))) + } + + /// Generate `SetBindGroup` commands for any bind groups that need to be updated. + fn flush_binds( + &mut self, + used_bind_groups: usize, + dynamic_offsets: &[wgt::DynamicOffset], + ) -> impl Iterator<Item = RenderCommand> + '_ { + // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`. + for contents in self.bind[..used_bind_groups].iter().flatten() { + if contents.is_dirty { + self.flat_dynamic_offsets + .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]); + } + } + + // Then, generate `SetBindGroup` commands to update the dirty bind + // groups. After this, all bind groups are clean. + self.bind[..used_bind_groups] + .iter_mut() + .enumerate() + .flat_map(|(i, entry)| { + if let Some(ref mut contents) = *entry { + if contents.is_dirty { + contents.is_dirty = false; + let offsets = &contents.dynamic_offsets; + return Some(RenderCommand::SetBindGroup { + index: i.try_into().unwrap(), + bind_group_id: contents.bind_group_id, + num_dynamic_offsets: (offsets.end - offsets.start) as u8, + }); + } + } + None + }) + } +} + +/// Error encountered when finishing recording a render bundle. +#[derive(Clone, Debug, Error)] +pub(super) enum RenderBundleErrorInner { + #[error("Resource is not valid to use with this render bundle because the resource and the bundle come from different devices")] + NotValidToUse, + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + RenderCommand(RenderCommandError), + #[error(transparent)] + Draw(#[from] DrawError), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +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 PrettyError for RenderBundleError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + // This error is wrapper for the inner error, + // but the scope has useful labels + fmt.error(self); + self.scope.fmt_pretty(fmt); + } +} + +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, RawString}; + use std::{convert::TryInto, slice}; + use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat}; + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `offset_length` elements. + #[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, + ) { + let redundant = unsafe { + bundle.current_bind_groups.set_and_check_redundant( + bind_group_id, + index, + &mut bundle.base.dynamic_offsets, + offsets, + offset_length, + ) + }; + + if redundant { + return; + } + + bundle.base.commands.push(RenderCommand::SetBindGroup { + index, + num_dynamic_offsets: offset_length.try_into().unwrap(), + bind_group_id, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_bundle_set_pipeline( + bundle: &mut RenderBundleEncoder, + pipeline_id: id::RenderPipelineId, + ) { + if bundle.current_pipeline.set_and_check_redundant(pipeline_id) { + return; + } + + bundle + .base + .commands + .push(RenderCommand::SetPipeline(pipeline_id)); + } + + #[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>, + ) { + bundle.base.commands.push(RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_bundle_set_index_buffer( + encoder: &mut RenderBundleEncoder, + buffer: id::BufferId, + index_format: IndexFormat, + offset: BufferAddress, + size: Option<BufferSize>, + ) { + encoder.set_index_buffer(buffer, index_format, offset, size); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `data` elements. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants( + pass: &mut RenderBundleEncoder, + stages: wgt::ShaderStages, + offset: u32, + size_bytes: u32, + data: *const u8, + ) { + 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 = unsafe { 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, + ) { + 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, + ) { + 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, + ) { + bundle.base.commands.push(RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: false, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_bundle_draw_indexed_indirect( + bundle: &mut RenderBundleEncoder, + buffer_id: id::BufferId, + offset: BufferAddress, + ) { + bundle.base.commands.push(RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count: None, + indexed: true, + }); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group( + _bundle: &mut RenderBundleEncoder, + _label: RawString, + ) { + //TODO + } + + #[no_mangle] + pub extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) { + //TODO + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker( + _bundle: &mut RenderBundleEncoder, + _label: RawString, + ) { + //TODO + } +} diff --git a/third_party/rust/wgpu-core/src/command/clear.rs b/third_party/rust/wgpu-core/src/command/clear.rs new file mode 100644 index 0000000000..de11258c42 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/clear.rs @@ -0,0 +1,457 @@ +use std::ops::Range; + +#[cfg(feature = "trace")] +use crate::device::trace::Command as TraceCommand; +use crate::{ + command::CommandBuffer, + get_lowest_common_denom, + hub::{self, Global, GlobalIdentityHandlerFactory, HalApi, Token}, + id::{BufferId, CommandEncoderId, DeviceId, TextureId, Valid}, + init_tracker::{MemoryInitKind, TextureInitRange}, + resource::{Texture, TextureClearMode}, + track::{TextureSelector, TextureTracker}, +}; + +use hal::CommandEncoder as _; +use thiserror::Error; +use wgt::{ + math::align_to, BufferAddress, BufferSize, BufferUsages, ImageSubresourceRange, TextureAspect, +}; + +/// Error encountered while attempting a clear. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ClearError { + #[error("To use clear_texture the CLEAR_TEXTURE feature needs to be enabled")] + MissingClearTextureFeature, + #[error("Command encoder {0:?} is invalid")] + InvalidCommandEncoder(CommandEncoderId), + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), + #[error("Buffer {0:?} is invalid or destroyed")] + InvalidBuffer(BufferId), + #[error("Texture {0:?} is invalid or destroyed")] + InvalidTexture(TextureId), + #[error("Texture {0:?} can not be cleared")] + NoValidTextureClearMode(TextureId), + #[error("Buffer clear size {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] + UnalignedFillSize(BufferSize), + #[error("Buffer offset {0:?} is not a multiple of `COPY_BUFFER_ALIGNMENT`")] + UnalignedBufferOffset(BufferAddress), + #[error("Clear of {start_offset}..{end_offset} would end up overrunning the bounds of the buffer of size {buffer_size}")] + BufferOverrun { + start_offset: BufferAddress, + end_offset: BufferAddress, + buffer_size: BufferAddress, + }, + #[error("Destination buffer is missing the `COPY_DST` usage flag")] + MissingCopyDstUsageFlag(Option<BufferId>, Option<TextureId>), + #[error("Texture lacks the aspects that were specified in the image subresource range. Texture with format {texture_format:?}, specified was {subresource_range_aspects:?}")] + MissingTextureAspect { + texture_format: wgt::TextureFormat, + subresource_range_aspects: TextureAspect, + }, + #[error("Image subresource level range is outside of the texture's level range. texture range is {texture_level_range:?}, \ +whereas subesource range specified start {subresource_base_mip_level} and count {subresource_mip_level_count:?}")] + InvalidTextureLevelRange { + texture_level_range: Range<u32>, + subresource_base_mip_level: u32, + subresource_mip_level_count: Option<u32>, + }, + #[error("Image subresource layer range is outside of the texture's layer range. texture range is {texture_layer_range:?}, \ +whereas subesource range specified start {subresource_base_array_layer} and count {subresource_array_layer_count:?}")] + InvalidTextureLayerRange { + texture_layer_range: Range<u32>, + subresource_base_array_layer: u32, + subresource_array_layer_count: Option<u32>, + }, +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn command_encoder_clear_buffer<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + dst: BufferId, + offset: BufferAddress, + size: Option<BufferSize>, + ) -> Result<(), ClearError> { + profiling::scope!("CommandEncoder::fill_buffer"); + + let hub = A::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(&mut *cmd_buf_guard, command_encoder_id) + .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?; + let (buffer_guard, _) = hub.buffers.read(&mut token); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::ClearBuffer { dst, offset, size }); + } + + let (dst_buffer, dst_pending) = cmd_buf + .trackers + .buffers + .set_single(&*buffer_guard, dst, hal::BufferUses::COPY_DST) + .ok_or(ClearError::InvalidBuffer(dst))?; + let dst_raw = dst_buffer + .raw + .as_ref() + .ok_or(ClearError::InvalidBuffer(dst))?; + if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { + return Err(ClearError::MissingCopyDstUsageFlag(Some(dst), None)); + } + + // Check if offset & size are valid. + if offset % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(ClearError::UnalignedBufferOffset(offset)); + } + if let Some(size) = size { + if size.get() % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(ClearError::UnalignedFillSize(size)); + } + let destination_end_offset = offset + size.get(); + if destination_end_offset > dst_buffer.size { + return Err(ClearError::BufferOverrun { + start_offset: offset, + end_offset: destination_end_offset, + buffer_size: dst_buffer.size, + }); + } + } + + let end = match size { + Some(size) => offset + size.get(), + None => dst_buffer.size, + }; + if offset == end { + log::trace!("Ignoring fill_buffer of size 0"); + return Ok(()); + } + + // Mark dest as initialized. + cmd_buf + .buffer_memory_init_actions + .extend(dst_buffer.initialization_status.create_action( + dst, + offset..end, + MemoryInitKind::ImplicitlyInitialized, + )); + // actual hal barrier & operation + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.transition_buffers(dst_barrier.into_iter()); + cmd_buf_raw.clear_buffer(dst_raw, offset..end); + } + Ok(()) + } + + pub fn command_encoder_clear_texture<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + dst: TextureId, + subresource_range: &ImageSubresourceRange, + ) -> Result<(), ClearError> { + profiling::scope!("CommandEncoder::clear_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, mut token) = hub.devices.write(&mut token); + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id) + .map_err(|_| ClearError::InvalidCommandEncoder(command_encoder_id))?; + let (_, mut token) = hub.buffers.read(&mut token); // skip token + let (texture_guard, _) = hub.textures.read(&mut token); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::ClearTexture { + dst, + subresource_range: *subresource_range, + }); + } + + if !cmd_buf.support_clear_texture { + return Err(ClearError::MissingClearTextureFeature); + } + + let dst_texture = texture_guard + .get(dst) + .map_err(|_| ClearError::InvalidTexture(dst))?; + + // Check if subresource aspects are valid. + let clear_aspects = + hal::FormatAspects::new(dst_texture.desc.format, subresource_range.aspect); + if clear_aspects.is_empty() { + return Err(ClearError::MissingTextureAspect { + texture_format: dst_texture.desc.format, + subresource_range_aspects: subresource_range.aspect, + }); + }; + + // Check if subresource level range is valid + let subresource_mip_range = subresource_range.mip_range(dst_texture.full_range.mips.end); + if dst_texture.full_range.mips.start > subresource_mip_range.start + || dst_texture.full_range.mips.end < subresource_mip_range.end + { + return Err(ClearError::InvalidTextureLevelRange { + texture_level_range: dst_texture.full_range.mips.clone(), + subresource_base_mip_level: subresource_range.base_mip_level, + subresource_mip_level_count: subresource_range.mip_level_count, + }); + } + // Check if subresource layer range is valid + let subresource_layer_range = + subresource_range.layer_range(dst_texture.full_range.layers.end); + if dst_texture.full_range.layers.start > subresource_layer_range.start + || dst_texture.full_range.layers.end < subresource_layer_range.end + { + return Err(ClearError::InvalidTextureLayerRange { + texture_layer_range: dst_texture.full_range.layers.clone(), + subresource_base_array_layer: subresource_range.base_array_layer, + subresource_array_layer_count: subresource_range.array_layer_count, + }); + } + + let device = &device_guard[cmd_buf.device_id.value]; + + clear_texture( + &*texture_guard, + Valid(dst), + TextureInitRange { + mip_range: subresource_mip_range, + layer_range: subresource_layer_range, + }, + cmd_buf.encoder.open(), + &mut cmd_buf.trackers.textures, + &device.alignments, + &device.zero_buffer, + ) + } +} + +pub(crate) fn clear_texture<A: HalApi>( + storage: &hub::Storage<Texture<A>, TextureId>, + dst_texture_id: Valid<TextureId>, + range: TextureInitRange, + encoder: &mut A::CommandEncoder, + texture_tracker: &mut TextureTracker<A>, + alignments: &hal::Alignments, + zero_buffer: &A::Buffer, +) -> Result<(), ClearError> { + let dst_texture = &storage[dst_texture_id]; + + let dst_raw = dst_texture + .inner + .as_raw() + .ok_or(ClearError::InvalidTexture(dst_texture_id.0))?; + + // Issue the right barrier. + let clear_usage = match dst_texture.clear_mode { + TextureClearMode::BufferCopy => hal::TextureUses::COPY_DST, + TextureClearMode::RenderPass { + is_color: false, .. + } => hal::TextureUses::DEPTH_STENCIL_WRITE, + TextureClearMode::RenderPass { is_color: true, .. } => hal::TextureUses::COLOR_TARGET, + TextureClearMode::None => { + return Err(ClearError::NoValidTextureClearMode(dst_texture_id.0)); + } + }; + + let selector = TextureSelector { + mips: range.mip_range.clone(), + layers: range.layer_range.clone(), + }; + + // If we're in a texture-init usecase, we know that the texture is already + // tracked since whatever caused the init requirement, will have caused the + // usage tracker to be aware of the texture. Meaning, that it is safe to + // call call change_replace_tracked if the life_guard is already gone (i.e. + // the user no longer holds on to this texture). + // + // On the other hand, when coming via command_encoder_clear_texture, the + // life_guard is still there since in order to call it a texture object is + // needed. + // + // We could in theory distinguish these two scenarios in the internal + // clear_texture api in order to remove this check and call the cheaper + // change_replace_tracked whenever possible. + let dst_barrier = texture_tracker + .set_single(dst_texture, dst_texture_id.0, selector, clear_usage) + .unwrap() + .map(|pending| pending.into_hal(dst_texture)); + unsafe { + encoder.transition_textures(dst_barrier.into_iter()); + } + + // Record actual clearing + match dst_texture.clear_mode { + TextureClearMode::BufferCopy => clear_texture_via_buffer_copies::<A>( + &dst_texture.desc, + alignments, + zero_buffer, + range, + encoder, + dst_raw, + ), + TextureClearMode::RenderPass { is_color, .. } => { + clear_texture_via_render_passes(dst_texture, range, is_color, encoder)? + } + TextureClearMode::None => { + return Err(ClearError::NoValidTextureClearMode(dst_texture_id.0)); + } + } + Ok(()) +} + +fn clear_texture_via_buffer_copies<A: hal::Api>( + texture_desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>, + alignments: &hal::Alignments, + zero_buffer: &A::Buffer, // Buffer of size device::ZERO_BUFFER_SIZE + range: TextureInitRange, + encoder: &mut A::CommandEncoder, + dst_raw: &A::Texture, +) { + assert_eq!( + hal::FormatAspects::from(texture_desc.format), + hal::FormatAspects::COLOR + ); + + // Gather list of zero_buffer copies and issue a single command then to perform them + let mut zero_buffer_copy_regions = Vec::new(); + let buffer_copy_pitch = alignments.buffer_copy_pitch.get() as u32; + let (block_width, block_height) = texture_desc.format.block_dimensions(); + let block_size = texture_desc.format.block_size(None).unwrap(); + + let bytes_per_row_alignment = get_lowest_common_denom(buffer_copy_pitch, block_size); + + for mip_level in range.mip_range { + let mut mip_size = texture_desc.mip_level_size(mip_level).unwrap(); + // Round to multiple of block size + mip_size.width = align_to(mip_size.width, block_width); + mip_size.height = align_to(mip_size.height, block_height); + + let bytes_per_row = align_to( + mip_size.width / block_width * block_size, + bytes_per_row_alignment, + ); + + let max_rows_per_copy = crate::device::ZERO_BUFFER_SIZE as u32 / bytes_per_row; + // round down to a multiple of rows needed by the texture format + let max_rows_per_copy = max_rows_per_copy / block_height * block_height; + assert!( + max_rows_per_copy > 0, + "Zero buffer size is too small to fill a single row \ + of a texture with format {:?} and desc {:?}", + texture_desc.format, + texture_desc.size + ); + + let z_range = 0..(if texture_desc.dimension == wgt::TextureDimension::D3 { + mip_size.depth_or_array_layers + } else { + 1 + }); + + for array_layer in range.layer_range.clone() { + // TODO: Only doing one layer at a time for volume textures right now. + for z in z_range.clone() { + // May need multiple copies for each subresource! However, we + // assume that we never need to split a row. + let mut num_rows_left = mip_size.height; + while num_rows_left > 0 { + let num_rows = num_rows_left.min(max_rows_per_copy); + + zero_buffer_copy_regions.push(hal::BufferTextureCopy { + buffer_layout: wgt::ImageDataLayout { + offset: 0, + bytes_per_row: Some(bytes_per_row), + rows_per_image: None, + }, + texture_base: hal::TextureCopyBase { + mip_level, + array_layer, + origin: wgt::Origin3d { + x: 0, // Always full rows + y: mip_size.height - num_rows_left, + z, + }, + aspect: hal::FormatAspects::COLOR, + }, + size: hal::CopyExtent { + width: mip_size.width, // full row + height: num_rows, + depth: 1, // Only single slice of volume texture at a time right now + }, + }); + + num_rows_left -= num_rows; + } + } + } + } + + unsafe { + encoder.copy_buffer_to_texture(zero_buffer, dst_raw, zero_buffer_copy_regions.into_iter()); + } +} + +fn clear_texture_via_render_passes<A: hal::Api>( + dst_texture: &Texture<A>, + range: TextureInitRange, + is_color: bool, + encoder: &mut A::CommandEncoder, +) -> Result<(), ClearError> { + assert_eq!(dst_texture.desc.dimension, wgt::TextureDimension::D2); + + let extent_base = wgt::Extent3d { + width: dst_texture.desc.size.width, + height: dst_texture.desc.size.height, + depth_or_array_layers: 1, // Only one layer is cleared at a time. + }; + + for mip_level in range.mip_range { + let extent = extent_base.mip_level_size(mip_level, dst_texture.desc.dimension); + for depth_or_layer in range.layer_range.clone() { + let color_attachments_tmp; + let (color_attachments, depth_stencil_attachment) = if is_color { + color_attachments_tmp = [Some(hal::ColorAttachment { + target: hal::Attachment { + view: dst_texture.get_clear_view(mip_level, depth_or_layer), + usage: hal::TextureUses::COLOR_TARGET, + }, + resolve_target: None, + ops: hal::AttachmentOps::STORE, + clear_value: wgt::Color::TRANSPARENT, + })]; + (&color_attachments_tmp[..], None) + } else { + ( + &[][..], + Some(hal::DepthStencilAttachment { + target: hal::Attachment { + view: dst_texture.get_clear_view(mip_level, depth_or_layer), + usage: hal::TextureUses::DEPTH_STENCIL_WRITE, + }, + depth_ops: hal::AttachmentOps::STORE, + stencil_ops: hal::AttachmentOps::STORE, + clear_value: (0.0, 0), + }), + ) + }; + unsafe { + encoder.begin_render_pass(&hal::RenderPassDescriptor { + label: Some("(wgpu internal) clear_texture clear pass"), + extent, + sample_count: dst_texture.desc.sample_count, + color_attachments, + depth_stencil_attachment, + multiview: None, + }); + encoder.end_render_pass(); + } + } + } + Ok(()) +} diff --git a/third_party/rust/wgpu-core/src/command/compute.rs b/third_party/rust/wgpu-core/src/command/compute.rs new file mode 100644 index 0000000000..d5b514f194 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/compute.rs @@ -0,0 +1,976 @@ +use crate::{ + binding_model::{ + BindError, BindGroup, LateMinBufferBindingSizeMismatch, PushConstantUploadError, + }, + command::{ + bind::Binder, + end_pipeline_statistics_query, + memory_init::{fixup_discarded_surfaces, SurfacesInDiscardState}, + BasePass, BasePassRef, BindGroupStateChange, CommandBuffer, CommandEncoderError, + CommandEncoderStatus, MapPassErr, PassErrorScope, QueryUseError, StateChange, + }, + device::{MissingDownlevelFlags, MissingFeatures}, + error::{ErrorFormatter, PrettyError}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, + id, + init_tracker::MemoryInitKind, + pipeline, + resource::{self, Buffer, Texture}, + track::{Tracker, UsageConflict, UsageScope}, + validation::{check_buffer_usage, MissingBufferUsageError}, + Label, +}; + +use hal::CommandEncoder as _; +use thiserror::Error; + +use std::{fmt, mem, 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: u32, + num_dynamic_offsets: u8, + bind_group_id: id::BindGroupId, + }, + SetPipeline(id::ComputePipelineId), + + /// Set a range of push constants to values stored in [`BasePass::push_constant_data`]. + SetPushConstant { + /// The byte offset within the push constant storage to write to. This + /// must be a multiple of four. + offset: u32, + + /// The number of bytes to write. This must be a multiple of four. + size_bytes: u32, + + /// Index in [`BasePass::push_constant_data`] of the start of the data + /// to be written. + /// + /// Note: this is not a byte offset like `offset`. Rather, it is the + /// index of the first `u32` element in `push_constant_data` to read. + values_offset: u32, + }, + + Dispatch([u32; 3]), + DispatchIndirect { + buffer_id: id::BufferId, + offset: wgt::BufferAddress, + }, + PushDebugGroup { + color: u32, + len: usize, + }, + PopDebugGroup, + InsertDebugMarker { + color: u32, + len: usize, + }, + WriteTimestamp { + query_set_id: id::QuerySetId, + query_index: u32, + }, + BeginPipelineStatisticsQuery { + query_set_id: id::QuerySetId, + query_index: u32, + }, + EndPipelineStatisticsQuery, +} + +#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))] +pub struct ComputePass { + base: BasePass<ComputeCommand>, + parent_id: id::CommandEncoderId, + + // Resource binding dedupe state. + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_bind_groups: BindGroupStateChange, + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_pipeline: StateChange<id::ComputePipelineId>, +} + +impl ComputePass { + pub fn new(parent_id: id::CommandEncoderId, desc: &ComputePassDescriptor) -> Self { + Self { + base: BasePass::new(&desc.label), + parent_id, + + current_bind_groups: BindGroupStateChange::new(), + current_pipeline: StateChange::new(), + } + } + + 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() + ) + } +} + +#[derive(Clone, Debug, Default)] +pub struct ComputePassDescriptor<'a> { + pub label: Label<'a>, +} + +#[derive(Clone, Debug, Error, Eq, PartialEq)] +#[non_exhaustive] +pub enum DispatchError { + #[error("Compute pipeline must be set")] + MissingPipeline, + #[error("The pipeline layout, associated with the current compute pipeline, contains a bind group layout at index {index} which is incompatible with the bind group layout associated with the bind group at {index}")] + IncompatibleBindGroup { + index: u32, + //expected: BindGroupLayoutId, + //provided: Option<(BindGroupLayoutId, BindGroupId)>, + }, + #[error( + "Each current dispatch group size dimension ({current:?}) must be less or equal to {limit}" + )] + InvalidGroupSize { current: [u32; 3], limit: u32 }, + #[error(transparent)] + BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch), +} + +/// 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: u32, max: u32 }, + #[error("Compute pipeline {0:?} is invalid")] + InvalidPipeline(id::ComputePipelineId), + #[error("QuerySet {0:?} is invalid")] + InvalidQuerySet(id::QuerySetId), + #[error("Indirect buffer {0:?} is invalid or destroyed")] + InvalidIndirectBuffer(id::BufferId), + #[error("Indirect buffer uses bytes {offset}..{end_offset} which overruns indirect buffer of size {buffer_size}")] + IndirectBufferOverrun { + offset: u64, + end_offset: u64, + buffer_size: u64, + }, + #[error("Buffer {0:?} is invalid or destroyed")] + InvalidBuffer(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(transparent)] + QueryUse(#[from] QueryUseError), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +impl PrettyError for ComputePassErrorInner { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + match *self { + Self::InvalidBindGroup(id) => { + fmt.bind_group_label(&id); + } + Self::InvalidPipeline(id) => { + fmt.compute_pipeline_label(&id); + } + Self::InvalidIndirectBuffer(id) => { + fmt.buffer_label(&id); + } + _ => {} + }; + } +} + +/// Error encountered when performing a compute pass. +#[derive(Clone, Debug, Error)] +#[error("{scope}")] +pub struct ComputePassError { + pub scope: PassErrorScope, + #[source] + inner: ComputePassErrorInner, +} +impl PrettyError for ComputePassError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + // This error is wrapper for the inner error, + // but the scope has useful labels + fmt.error(self); + self.scope.fmt_pretty(fmt); + } +} + +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(), + }) + } +} + +struct State<A: HalApi> { + binder: Binder, + pipeline: Option<id::ComputePipelineId>, + scope: UsageScope<A>, + debug_scope_depth: u32, +} + +impl<A: HalApi> State<A> { + fn is_ready(&self) -> Result<(), DispatchError> { + 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_none() { + return Err(DispatchError::MissingPipeline); + } + self.binder.check_late_buffer_bindings()?; + + Ok(()) + } + + // `extra_buffer` is there to represent the indirect buffer that is also + // part of the usage scope. + fn flush_states( + &mut self, + raw_encoder: &mut A::CommandEncoder, + base_trackers: &mut Tracker<A>, + bind_group_guard: &Storage<BindGroup<A>, id::BindGroupId>, + buffer_guard: &Storage<Buffer<A>, id::BufferId>, + texture_guard: &Storage<Texture<A>, id::TextureId>, + indirect_buffer: Option<id::Valid<id::BufferId>>, + ) -> Result<(), UsageConflict> { + for id in self.binder.list_active() { + unsafe { + self.scope + .merge_bind_group(texture_guard, &bind_group_guard[id].used)? + }; + // Note: stateless trackers are not merged: the lifetime reference + // is held to the bind group itself. + } + + for id in self.binder.list_active() { + unsafe { + base_trackers.set_and_remove_from_usage_scope_sparse( + texture_guard, + &mut self.scope, + &bind_group_guard[id].used, + ) + } + } + + // Add the state of the indirect buffer if it hasn't been hit before. + unsafe { + base_trackers + .buffers + .set_and_remove_from_usage_scope_sparse(&mut self.scope.buffers, indirect_buffer); + } + + log::trace!("Encoding dispatch barriers"); + + CommandBuffer::drain_barriers(raw_encoder, base_trackers, buffer_guard, texture_guard); + Ok(()) + } +} + +// Common routines between render/compute + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn command_encoder_run_compute_pass<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + pass: &ComputePass, + ) -> Result<(), ComputePassError> { + self.command_encoder_run_compute_pass_impl::<A>(encoder_id, pass.base.as_ref()) + } + + #[doc(hidden)] + pub fn command_encoder_run_compute_pass_impl<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + base: BasePassRef<ComputeCommand>, + ) -> Result<(), ComputePassError> { + profiling::scope!("CommandEncoder::run_compute_pass"); + let init_scope = PassErrorScope::Pass(encoder_id); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (device_guard, mut token) = hub.devices.read(&mut token); + + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + // Spell out the type, to placate rust-analyzer. + // https://github.com/rust-lang/rust-analyzer/issues/12247 + let cmd_buf: &mut CommandBuffer<A> = + CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, encoder_id) + .map_pass_err(init_scope)?; + // will be reset to true if recording is done without errors + cmd_buf.status = CommandEncoderStatus::Error; + let raw = cmd_buf.encoder.open(); + + let device = &device_guard[cmd_buf.device_id.value]; + + #[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 (query_set_guard, mut token) = hub.query_sets.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(), + pipeline: None, + scope: UsageScope::new(&*buffer_guard, &*texture_guard), + debug_scope_depth: 0, + }; + let mut temp_offsets = Vec::new(); + let mut dynamic_offset_count = 0; + let mut string_offset = 0; + let mut active_query = None; + + cmd_buf.trackers.set_size( + Some(&*buffer_guard), + Some(&*texture_guard), + None, + None, + Some(&*bind_group_guard), + Some(&*pipeline_guard), + None, + None, + Some(&*query_set_guard), + ); + + let hal_desc = hal::ComputePassDescriptor { label: base.label }; + unsafe { + raw.begin_compute_pass(&hal_desc); + } + + // Immediate texture inits required because of prior discards. Need to + // be inserted before texture reads. + let mut pending_discard_init_fixups = SurfacesInDiscardState::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 >= 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[dynamic_offset_count + ..dynamic_offset_count + (num_dynamic_offsets as usize)], + ); + dynamic_offset_count += num_dynamic_offsets as usize; + + let bind_group: &BindGroup<A> = cmd_buf + .trackers + .bind_groups + .add_single(&*bind_group_guard, bind_group_id) + .ok_or(ComputePassErrorInner::InvalidBindGroup(bind_group_id)) + .map_pass_err(scope)?; + bind_group + .validate_dynamic_bindings(index, &temp_offsets, &cmd_buf.limits) + .map_pass_err(scope)?; + + cmd_buf.buffer_memory_init_actions.extend( + bind_group.used_buffer_ranges.iter().filter_map( + |action| match buffer_guard.get(action.id) { + Ok(buffer) => buffer.initialization_status.check_action(action), + Err(_) => None, + }, + ), + ); + + for action in bind_group.used_texture_ranges.iter() { + pending_discard_init_fixups.extend( + cmd_buf + .texture_memory_actions + .register_init_action(action, &texture_guard), + ); + } + + let pipeline_layout_id = state.binder.pipeline_layout_id; + let entries = state.binder.assign_group( + index as usize, + id::Valid(bind_group_id), + bind_group, + &temp_offsets, + ); + if !entries.is_empty() { + let pipeline_layout = + &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw; + for (i, e) in entries.iter().enumerate() { + let raw_bg = &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; + unsafe { + raw.set_bind_group( + pipeline_layout, + index + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } + } + } + } + ComputeCommand::SetPipeline(pipeline_id) => { + let scope = PassErrorScope::SetPipelineCompute(pipeline_id); + + state.pipeline = Some(pipeline_id); + + let pipeline: &pipeline::ComputePipeline<A> = cmd_buf + .trackers + .compute_pipelines + .add_single(&*pipeline_guard, pipeline_id) + .ok_or(ComputePassErrorInner::InvalidPipeline(pipeline_id)) + .map_pass_err(scope)?; + + unsafe { + raw.set_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]; + + let (start_index, entries) = state.binder.change_pipeline_layout( + &*pipeline_layout_guard, + pipeline.layout_id.value, + &pipeline.late_sized_buffer_groups, + ); + if !entries.is_empty() { + for (i, e) in entries.iter().enumerate() { + let raw_bg = + &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; + unsafe { + raw.set_bind_group( + &pipeline_layout.raw, + start_index as u32 + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } + } + } + + // 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.set_push_constants( + &pipeline_layout.raw, + wgt::ShaderStages::COMPUTE, + 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( + wgt::ShaderStages::COMPUTE, + offset, + end_offset_bytes, + ) + .map_pass_err(scope)?; + + unsafe { + raw.set_push_constants( + &pipeline_layout.raw, + wgt::ShaderStages::COMPUTE, + offset, + data_slice, + ); + } + } + ComputeCommand::Dispatch(groups) => { + let scope = PassErrorScope::Dispatch { + indirect: false, + pipeline: state.pipeline, + }; + + fixup_discarded_surfaces( + pending_discard_init_fixups.drain(..), + raw, + &texture_guard, + &mut cmd_buf.trackers.textures, + device, + ); + + state.is_ready().map_pass_err(scope)?; + state + .flush_states( + raw, + &mut cmd_buf.trackers, + &*bind_group_guard, + &*buffer_guard, + &*texture_guard, + None, + ) + .map_pass_err(scope)?; + + let groups_size_limit = cmd_buf.limits.max_compute_workgroups_per_dimension; + + if groups[0] > groups_size_limit + || groups[1] > groups_size_limit + || groups[2] > groups_size_limit + { + return Err(ComputePassErrorInner::Dispatch( + DispatchError::InvalidGroupSize { + current: groups, + limit: groups_size_limit, + }, + )) + .map_pass_err(scope); + } + + unsafe { + raw.dispatch(groups); + } + } + ComputeCommand::DispatchIndirect { buffer_id, offset } => { + let scope = PassErrorScope::Dispatch { + indirect: true, + pipeline: state.pipeline, + }; + + state.is_ready().map_pass_err(scope)?; + + device + .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) + .map_pass_err(scope)?; + + let indirect_buffer: &Buffer<A> = state + .scope + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) + .map_pass_err(scope)?; + check_buffer_usage(indirect_buffer.usage, wgt::BufferUsages::INDIRECT) + .map_pass_err(scope)?; + + let end_offset = offset + mem::size_of::<wgt::DispatchIndirectArgs>() as u64; + if end_offset > indirect_buffer.size { + return Err(ComputePassErrorInner::IndirectBufferOverrun { + offset, + end_offset, + buffer_size: indirect_buffer.size, + }) + .map_pass_err(scope); + } + + let buf_raw = indirect_buffer + .raw + .as_ref() + .ok_or(ComputePassErrorInner::InvalidIndirectBuffer(buffer_id)) + .map_pass_err(scope)?; + + let stride = 3 * 4; // 3 integers, x/y/z group size + + cmd_buf.buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.create_action( + buffer_id, + offset..(offset + stride), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + state + .flush_states( + raw, + &mut cmd_buf.trackers, + &*bind_group_guard, + &*buffer_guard, + &*texture_guard, + Some(id::Valid(buffer_id)), + ) + .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[string_offset..string_offset + len]) + .unwrap(); + string_offset += len; + unsafe { + raw.begin_debug_marker(label); + } + } + 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[string_offset..string_offset + len]) + .unwrap(); + string_offset += len; + unsafe { raw.insert_debug_marker(label) } + } + ComputeCommand::WriteTimestamp { + query_set_id, + query_index, + } => { + let scope = PassErrorScope::WriteTimestamp; + + device + .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES) + .map_pass_err(scope)?; + + let query_set: &resource::QuerySet<A> = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(ComputePassErrorInner::InvalidQuerySet(query_set_id)) + .map_pass_err(scope)?; + + query_set + .validate_and_write_timestamp(raw, query_set_id, query_index, None) + .map_pass_err(scope)?; + } + ComputeCommand::BeginPipelineStatisticsQuery { + query_set_id, + query_index, + } => { + let scope = PassErrorScope::BeginPipelineStatisticsQuery; + + let query_set: &resource::QuerySet<A> = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(ComputePassErrorInner::InvalidQuerySet(query_set_id)) + .map_pass_err(scope)?; + + query_set + .validate_and_begin_pipeline_statistics_query( + raw, + query_set_id, + query_index, + None, + &mut active_query, + ) + .map_pass_err(scope)?; + } + ComputeCommand::EndPipelineStatisticsQuery => { + let scope = PassErrorScope::EndPipelineStatisticsQuery; + + end_pipeline_statistics_query(raw, &*query_set_guard, &mut active_query) + .map_pass_err(scope)?; + } + } + } + + unsafe { + raw.end_compute_pass(); + } + cmd_buf.status = CommandEncoderStatus::Recording; + + // There can be entries left in pending_discard_init_fixups if a bind + // group was set, but not used (i.e. no Dispatch occurred) + // + // However, we already altered the discard/init_action state on this + // cmd_buf, so we need to apply the promised changes. + fixup_discarded_surfaces( + pending_discard_init_fixups.into_iter(), + raw, + &texture_guard, + &mut cmd_buf.trackers.textures, + device, + ); + + Ok(()) + } +} + +pub mod compute_ffi { + use super::{ComputeCommand, ComputePass}; + use crate::{id, 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. + #[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, + ) { + let redundant = unsafe { + pass.current_bind_groups.set_and_check_redundant( + bind_group_id, + index, + &mut pass.base.dynamic_offsets, + offsets, + offset_length, + ) + }; + + if redundant { + return; + } + + pass.base.commands.push(ComputeCommand::SetBindGroup { + index, + num_dynamic_offsets: offset_length.try_into().unwrap(), + bind_group_id, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_compute_pass_set_pipeline( + pass: &mut ComputePass, + pipeline_id: id::ComputePipelineId, + ) { + if pass.current_pipeline.set_and_check_redundant(pipeline_id) { + return; + } + + pass.base + .commands + .push(ComputeCommand::SetPipeline(pipeline_id)); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `size_bytes` bytes. + #[no_mangle] + pub unsafe extern "C" fn wgpu_compute_pass_set_push_constant( + pass: &mut ComputePass, + offset: u32, + size_bytes: u32, + data: *const u8, + ) { + 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 = unsafe { 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_workgroups( + pass: &mut ComputePass, + groups_x: u32, + groups_y: u32, + groups_z: u32, + ) { + pass.base + .commands + .push(ComputeCommand::Dispatch([groups_x, groups_y, groups_z])); + } + + #[no_mangle] + pub extern "C" fn wgpu_compute_pass_dispatch_workgroups_indirect( + pass: &mut ComputePass, + buffer_id: id::BufferId, + offset: BufferAddress, + ) { + pass.base + .commands + .push(ComputeCommand::DispatchIndirect { buffer_id, offset }); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_compute_pass_push_debug_group( + pass: &mut ComputePass, + label: RawString, + color: u32, + ) { + let bytes = unsafe { 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) { + pass.base.commands.push(ComputeCommand::PopDebugGroup); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_compute_pass_insert_debug_marker( + pass: &mut ComputePass, + label: RawString, + color: u32, + ) { + let bytes = unsafe { 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(), + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_compute_pass_write_timestamp( + pass: &mut ComputePass, + query_set_id: id::QuerySetId, + query_index: u32, + ) { + pass.base.commands.push(ComputeCommand::WriteTimestamp { + query_set_id, + query_index, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_compute_pass_begin_pipeline_statistics_query( + pass: &mut ComputePass, + query_set_id: id::QuerySetId, + query_index: u32, + ) { + pass.base + .commands + .push(ComputeCommand::BeginPipelineStatisticsQuery { + query_set_id, + query_index, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_compute_pass_end_pipeline_statistics_query(pass: &mut ComputePass) { + pass.base + .commands + .push(ComputeCommand::EndPipelineStatisticsQuery); + } +} diff --git a/third_party/rust/wgpu-core/src/command/draw.rs b/third_party/rust/wgpu-core/src/command/draw.rs new file mode 100644 index 0000000000..b629ffaba0 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/draw.rs @@ -0,0 +1,252 @@ +/*! Draw structures - shared between render passes and bundles. +!*/ + +use crate::{ + binding_model::{LateMinBufferBindingSizeMismatch, PushConstantUploadError}, + error::ErrorFormatter, + id, + track::UsageConflict, + validation::{MissingBufferUsageError, MissingTextureUsageError}, +}; +use wgt::{BufferAddress, BufferSize, Color}; + +use std::num::NonZeroU32; +use thiserror::Error; + +/// Error validating a draw call. +#[derive(Clone, Debug, Error, Eq, PartialEq)] +#[non_exhaustive] +pub enum DrawError { + #[error("Blend constant needs to be set")] + MissingBlendConstant, + #[error("Render pipeline must be set")] + MissingPipeline, + #[error("Vertex buffer {index} must be set")] + MissingVertexBuffer { index: u32 }, + #[error("Index buffer must be set")] + MissingIndexBuffer, + #[error("The pipeline layout, associated with the current render pipeline, contains a bind group layout at index {index} which is incompatible with the bind group layout associated with the bind group at {index}")] + IncompatibleBindGroup { + index: u32, + //expected: BindGroupLayoutId, + //provided: Option<(BindGroupLayoutId, BindGroupId)>, + }, + #[error("Vertex {last_vertex} extends beyond limit {vertex_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Vertex` step-rate vertex buffer?")] + VertexBeyondLimit { + last_vertex: u32, + vertex_limit: u32, + slot: u32, + }, + #[error("Instance {last_instance} extends beyond limit {instance_limit} imposed by the buffer in slot {slot}. Did you bind the correct `Instance` step-rate vertex buffer?")] + InstanceBeyondLimit { + last_instance: u32, + instance_limit: u32, + slot: u32, + }, + #[error("Index {last_index} extends beyond limit {index_limit}. Did you bind the correct index buffer?")] + IndexBeyondLimit { last_index: u32, index_limit: u32 }, + #[error( + "Pipeline index format ({pipeline:?}) and buffer index format ({buffer:?}) do not match" + )] + UnmatchedIndexFormats { + pipeline: wgt::IndexFormat, + buffer: wgt::IndexFormat, + }, + #[error(transparent)] + BindingSizeTooSmall(#[from] LateMinBufferBindingSizeMismatch), +} + +/// Error encountered when encoding a render command. +/// This is the shared error set between render bundles and passes. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum RenderCommandError { + #[error("Bind group {0:?} is invalid")] + InvalidBindGroup(id::BindGroupId), + #[error("Render bundle {0:?} is invalid")] + InvalidRenderBundle(id::RenderBundleId), + #[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] + BindGroupIndexOutOfRange { index: u32, max: u32 }, + #[error("Dynamic buffer offset {0} does not respect device's requested `{1}` limit {2}")] + UnalignedBufferOffset(u64, &'static str, u32), + #[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("QuerySet {0:?} is invalid")] + InvalidQuerySet(id::QuerySetId), + #[error("Render pipeline targets are incompatible with render pass")] + IncompatiblePipelineTargets(#[from] crate::device::RenderPassCompatibilityError), + #[error("Pipeline writes to depth/stencil, while the pass has read-only depth/stencil")] + IncompatiblePipelineRods, + #[error(transparent)] + UsageConflict(#[from] UsageConflict), + #[error("Buffer {0:?} is destroyed")] + DestroyedBuffer(id::BufferId), + #[error(transparent)] + MissingBufferUsage(#[from] MissingBufferUsageError), + #[error(transparent)] + MissingTextureUsage(#[from] MissingTextureUsageError), + #[error(transparent)] + PushConstants(#[from] PushConstantUploadError), + #[error("Viewport width {0} and/or height {1} are less than or equal to 0")] + InvalidViewportDimension(f32, f32), + #[error("Viewport minDepth {0} and/or maxDepth {1} are not in [0, 1]")] + InvalidViewportDepth(f32, f32), + #[error("Scissor {0:?} is not contained in the render target {1:?}")] + InvalidScissorRect(Rect<u32>, wgt::Extent3d), + #[error("Support for {0} is not implemented yet")] + Unimplemented(&'static str), +} +impl crate::error::PrettyError for RenderCommandError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + match *self { + Self::InvalidBindGroup(id) => { + fmt.bind_group_label(&id); + } + Self::InvalidPipeline(id) => { + fmt.render_pipeline_label(&id); + } + Self::UsageConflict(UsageConflict::TextureInvalid { id }) => { + fmt.texture_label(&id); + } + Self::UsageConflict(UsageConflict::BufferInvalid { id }) + | Self::DestroyedBuffer(id) => { + fmt.buffer_label(&id); + } + _ => {} + }; + } +} + +#[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: u32, + num_dynamic_offsets: u8, + bind_group_id: id::BindGroupId, + }, + SetPipeline(id::RenderPipelineId), + SetIndexBuffer { + buffer_id: id::BufferId, + index_format: wgt::IndexFormat, + offset: BufferAddress, + size: Option<BufferSize>, + }, + SetVertexBuffer { + slot: u32, + buffer_id: id::BufferId, + offset: BufferAddress, + size: Option<BufferSize>, + }, + SetBlendConstant(Color), + SetStencilReference(u32), + SetViewport { + rect: Rect<f32>, + //TODO: use half-float to reduce the size? + depth_min: f32, + depth_max: f32, + }, + SetScissor(Rect<u32>), + + /// Set a range of push constants to values stored in [`BasePass::push_constant_data`]. + /// + /// See [`wgpu::RenderPass::set_push_constants`] for a detailed explanation + /// of the restrictions these commands must satisfy. + SetPushConstant { + /// Which stages we are setting push constant values for. + stages: wgt::ShaderStages, + + /// The byte offset within the push constant storage to write to. This + /// must be a multiple of four. + offset: u32, + + /// The number of bytes to write. This must be a multiple of four. + size_bytes: u32, + + /// Index in [`BasePass::push_constant_data`] of the start of the data + /// to be written. + /// + /// Note: this is not a byte offset like `offset`. Rather, it is the + /// index of the first `u32` element in `push_constant_data` to read. + /// + /// `None` means zeros should be written to the destination range, and + /// there is no corresponding data in `push_constant_data`. This is used + /// by render bundles, which explicitly clear out any state that + /// post-bundle code might see. + 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, + }, + WriteTimestamp { + query_set_id: id::QuerySetId, + query_index: u32, + }, + BeginPipelineStatisticsQuery { + query_set_id: id::QuerySetId, + query_index: u32, + }, + EndPipelineStatisticsQuery, + ExecuteBundle(id::RenderBundleId), +} diff --git a/third_party/rust/wgpu-core/src/command/memory_init.rs b/third_party/rust/wgpu-core/src/command/memory_init.rs new file mode 100644 index 0000000000..52735fec51 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/memory_init.rs @@ -0,0 +1,337 @@ +use std::{collections::hash_map::Entry, ops::Range, vec::Drain}; + +use hal::CommandEncoder; + +use crate::{ + device::Device, + hub::{HalApi, Storage}, + id::{self, TextureId}, + init_tracker::*, + resource::{Buffer, Texture}, + track::{TextureTracker, Tracker}, + FastHashMap, +}; + +use super::{clear::clear_texture, BakedCommands, DestroyedBufferError, DestroyedTextureError}; + +/// Surface that was discarded by `StoreOp::Discard` of a preceding renderpass. +/// Any read access to this surface needs to be preceded by a texture initialization. +#[derive(Clone)] +pub(crate) struct TextureSurfaceDiscard { + pub texture: TextureId, + pub mip_level: u32, + pub layer: u32, +} + +pub(crate) type SurfacesInDiscardState = Vec<TextureSurfaceDiscard>; + +#[derive(Default)] +pub(crate) struct CommandBufferTextureMemoryActions { + /// The tracker actions that we need to be executed before the command + /// buffer is executed. + init_actions: Vec<TextureInitTrackerAction>, + /// All the discards that haven't been followed by init again within the + /// command buffer i.e. everything in this list resets the texture init + /// state *after* the command buffer execution + discards: Vec<TextureSurfaceDiscard>, +} + +impl CommandBufferTextureMemoryActions { + pub(crate) fn drain_init_actions(&mut self) -> Drain<TextureInitTrackerAction> { + self.init_actions.drain(..) + } + + pub(crate) fn discard(&mut self, discard: TextureSurfaceDiscard) { + self.discards.push(discard); + } + + // Registers a TextureInitTrackerAction. + // Returns previously discarded surface that need to be initialized *immediately* now. + // Only returns a non-empty list if action is MemoryInitKind::NeedsInitializedMemory. + #[must_use] + pub(crate) fn register_init_action<A: hal::Api>( + &mut self, + action: &TextureInitTrackerAction, + texture_guard: &Storage<Texture<A>, TextureId>, + ) -> SurfacesInDiscardState { + let mut immediately_necessary_clears = SurfacesInDiscardState::new(); + + // Note that within a command buffer we may stack arbitrary memory init + // actions on the same texture Since we react to them in sequence, they + // are going to be dropped again at queue submit + // + // We don't need to add MemoryInitKind::NeedsInitializedMemory to + // init_actions if a surface is part of the discard list. But that would + // mean splitting up the action which is more than we'd win here. + self.init_actions + .extend(match texture_guard.get(action.id) { + Ok(texture) => texture.initialization_status.check_action(action), + Err(_) => return immediately_necessary_clears, // texture no longer exists + }); + + // We expect very few discarded surfaces at any point in time which is + // why a simple linear search is likely best. (i.e. most of the time + // self.discards is empty!) + let init_actions = &mut self.init_actions; + self.discards.retain(|discarded_surface| { + if discarded_surface.texture == action.id + && action.range.layer_range.contains(&discarded_surface.layer) + && action + .range + .mip_range + .contains(&discarded_surface.mip_level) + { + if let MemoryInitKind::NeedsInitializedMemory = action.kind { + immediately_necessary_clears.push(discarded_surface.clone()); + + // Mark surface as implicitly initialized (this is relevant + // because it might have been uninitialized prior to + // discarding + init_actions.push(TextureInitTrackerAction { + id: discarded_surface.texture, + range: TextureInitRange { + mip_range: discarded_surface.mip_level + ..(discarded_surface.mip_level + 1), + layer_range: discarded_surface.layer..(discarded_surface.layer + 1), + }, + kind: MemoryInitKind::ImplicitlyInitialized, + }); + } + false + } else { + true + } + }); + + immediately_necessary_clears + } + + // Shortcut for register_init_action when it is known that the action is an + // implicit init, not requiring any immediate resource init. + pub(crate) fn register_implicit_init<A: hal::Api>( + &mut self, + id: id::Valid<TextureId>, + range: TextureInitRange, + texture_guard: &Storage<Texture<A>, TextureId>, + ) { + let must_be_empty = self.register_init_action( + &TextureInitTrackerAction { + id: id.0, + range, + kind: MemoryInitKind::ImplicitlyInitialized, + }, + texture_guard, + ); + assert!(must_be_empty.is_empty()); + } +} + +// Utility function that takes discarded surfaces from (several calls to) +// register_init_action and initializes them on the spot. +// +// Takes care of barriers as well! +pub(crate) fn fixup_discarded_surfaces< + A: HalApi, + InitIter: Iterator<Item = TextureSurfaceDiscard>, +>( + inits: InitIter, + encoder: &mut A::CommandEncoder, + texture_guard: &Storage<Texture<A>, TextureId>, + texture_tracker: &mut TextureTracker<A>, + device: &Device<A>, +) { + for init in inits { + clear_texture( + texture_guard, + id::Valid(init.texture), + TextureInitRange { + mip_range: init.mip_level..(init.mip_level + 1), + layer_range: init.layer..(init.layer + 1), + }, + encoder, + texture_tracker, + &device.alignments, + &device.zero_buffer, + ) + .unwrap(); + } +} + +impl<A: HalApi> BakedCommands<A> { + // inserts all buffer initializations that are going to be needed for + // executing the commands and updates resource init states accordingly + pub(crate) fn initialize_buffer_memory( + &mut self, + device_tracker: &mut Tracker<A>, + buffer_guard: &mut Storage<Buffer<A>, id::BufferId>, + ) -> Result<(), DestroyedBufferError> { + // Gather init ranges for each buffer so we can collapse them. + // It is not possible to do this at an earlier point since previously + // executed command buffer change the resource init state. + let mut uninitialized_ranges_per_buffer = FastHashMap::default(); + for buffer_use in self.buffer_memory_init_actions.drain(..) { + let buffer = buffer_guard + .get_mut(buffer_use.id) + .map_err(|_| DestroyedBufferError(buffer_use.id))?; + + // align the end to 4 + let end_remainder = buffer_use.range.end % wgt::COPY_BUFFER_ALIGNMENT; + let end = if end_remainder == 0 { + buffer_use.range.end + } else { + buffer_use.range.end + wgt::COPY_BUFFER_ALIGNMENT - end_remainder + }; + let uninitialized_ranges = buffer + .initialization_status + .drain(buffer_use.range.start..end); + + match buffer_use.kind { + MemoryInitKind::ImplicitlyInitialized => {} + MemoryInitKind::NeedsInitializedMemory => { + match uninitialized_ranges_per_buffer.entry(buffer_use.id) { + Entry::Vacant(e) => { + e.insert( + uninitialized_ranges.collect::<Vec<Range<wgt::BufferAddress>>>(), + ); + } + Entry::Occupied(mut e) => { + e.get_mut().extend(uninitialized_ranges); + } + } + } + } + } + + for (buffer_id, mut ranges) in uninitialized_ranges_per_buffer { + // Collapse touching ranges. + ranges.sort_by_key(|r| r.start); + for i in (1..ranges.len()).rev() { + // The memory init tracker made sure of this! + assert!(ranges[i - 1].end <= ranges[i].start); + if ranges[i].start == ranges[i - 1].end { + ranges[i - 1].end = ranges[i].end; + ranges.swap_remove(i); // Ordering not important at this point + } + } + + // Don't do use_replace since the buffer may already no longer have + // a ref_count. + // + // However, we *know* that it is currently in use, so the tracker + // must already know about it. + let transition = device_tracker + .buffers + .set_single(buffer_guard, buffer_id, hal::BufferUses::COPY_DST) + .unwrap() + .1; + + let buffer = buffer_guard + .get_mut(buffer_id) + .map_err(|_| DestroyedBufferError(buffer_id))?; + let raw_buf = buffer.raw.as_ref().ok_or(DestroyedBufferError(buffer_id))?; + + unsafe { + self.encoder.transition_buffers( + transition + .map(|pending| pending.into_hal(buffer)) + .into_iter(), + ); + } + + for range in ranges.iter() { + assert!( + range.start % wgt::COPY_BUFFER_ALIGNMENT == 0, + "Buffer {:?} has an uninitialized range with a start \ + not aligned to 4 (start was {})", + raw_buf, + range.start + ); + assert!( + range.end % wgt::COPY_BUFFER_ALIGNMENT == 0, + "Buffer {:?} has an uninitialized range with an end \ + not aligned to 4 (end was {})", + raw_buf, + range.end + ); + + unsafe { + self.encoder.clear_buffer(raw_buf, range.clone()); + } + } + } + Ok(()) + } + + // inserts all texture initializations that are going to be needed for + // executing the commands and updates resource init states accordingly any + // textures that are left discarded by this command buffer will be marked as + // uninitialized + pub(crate) fn initialize_texture_memory( + &mut self, + device_tracker: &mut Tracker<A>, + texture_guard: &mut Storage<Texture<A>, TextureId>, + device: &Device<A>, + ) -> Result<(), DestroyedTextureError> { + let mut ranges: Vec<TextureInitRange> = Vec::new(); + for texture_use in self.texture_memory_actions.drain_init_actions() { + let texture = texture_guard + .get_mut(texture_use.id) + .map_err(|_| DestroyedTextureError(texture_use.id))?; + + let use_range = texture_use.range; + let affected_mip_trackers = texture + .initialization_status + .mips + .iter_mut() + .enumerate() + .skip(use_range.mip_range.start as usize) + .take((use_range.mip_range.end - use_range.mip_range.start) as usize); + + match texture_use.kind { + MemoryInitKind::ImplicitlyInitialized => { + for (_, mip_tracker) in affected_mip_trackers { + mip_tracker.drain(use_range.layer_range.clone()); + } + } + MemoryInitKind::NeedsInitializedMemory => { + for (mip_level, mip_tracker) in affected_mip_trackers { + for layer_range in mip_tracker.drain(use_range.layer_range.clone()) { + ranges.push(TextureInitRange { + mip_range: (mip_level as u32)..(mip_level as u32 + 1), + layer_range, + }); + } + } + } + } + + // TODO: Could we attempt some range collapsing here? + for range in ranges.drain(..) { + clear_texture( + texture_guard, + id::Valid(texture_use.id), + range, + &mut self.encoder, + &mut device_tracker.textures, + &device.alignments, + &device.zero_buffer, + ) + .unwrap(); + } + } + + // Now that all buffers/textures have the proper init state for before + // cmdbuf start, we discard init states for textures it left discarded + // after its execution. + for surface_discard in self.texture_memory_actions.discards.iter() { + let texture = texture_guard + .get_mut(surface_discard.texture) + .map_err(|_| DestroyedTextureError(surface_discard.texture))?; + texture + .initialization_status + .discard(surface_discard.mip_level, surface_discard.layer); + } + + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/command/mod.rs b/third_party/rust/wgpu-core/src/command/mod.rs new file mode 100644 index 0000000000..8c5e5f2d40 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/mod.rs @@ -0,0 +1,632 @@ +mod bind; +mod bundle; +mod clear; +mod compute; +mod draw; +mod memory_init; +mod query; +mod render; +mod transfer; + +use std::slice; + +pub(crate) use self::clear::clear_texture; +pub use self::{ + bundle::*, clear::ClearError, compute::*, draw::*, query::*, render::*, transfer::*, +}; + +use self::memory_init::CommandBufferTextureMemoryActions; + +use crate::error::{ErrorFormatter, PrettyError}; +use crate::init_tracker::BufferInitTrackerAction; +use crate::track::{Tracker, UsageScope}; +use crate::{ + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, + id, + resource::{Buffer, Texture}, + Label, Stored, +}; + +use hal::CommandEncoder as _; +use thiserror::Error; + +#[cfg(feature = "trace")] +use crate::device::trace::Command as TraceCommand; + +const PUSH_CONSTANT_CLEAR_ARRAY: &[u32] = &[0_u32; 64]; + +#[derive(Debug)] +enum CommandEncoderStatus { + Recording, + Finished, + Error, +} + +struct CommandEncoder<A: hal::Api> { + raw: A::CommandEncoder, + list: Vec<A::CommandBuffer>, + is_open: bool, + label: Option<String>, +} + +//TODO: handle errors better +impl<A: hal::Api> CommandEncoder<A> { + fn close(&mut self) { + if self.is_open { + self.is_open = false; + let cmd_buf = unsafe { self.raw.end_encoding().unwrap() }; + self.list.push(cmd_buf); + } + } + + fn discard(&mut self) { + if self.is_open { + self.is_open = false; + unsafe { self.raw.discard_encoding() }; + } + } + + fn open(&mut self) -> &mut A::CommandEncoder { + if !self.is_open { + self.is_open = true; + let label = self.label.as_deref(); + unsafe { self.raw.begin_encoding(label).unwrap() }; + } + &mut self.raw + } + + fn open_pass(&mut self, label: Option<&str>) { + self.is_open = true; + unsafe { self.raw.begin_encoding(label).unwrap() }; + } +} + +pub struct BakedCommands<A: HalApi> { + pub(crate) encoder: A::CommandEncoder, + pub(crate) list: Vec<A::CommandBuffer>, + pub(crate) trackers: Tracker<A>, + buffer_memory_init_actions: Vec<BufferInitTrackerAction>, + texture_memory_actions: CommandBufferTextureMemoryActions, +} + +pub(crate) struct DestroyedBufferError(pub id::BufferId); +pub(crate) struct DestroyedTextureError(pub id::TextureId); + +pub struct CommandBuffer<A: HalApi> { + encoder: CommandEncoder<A>, + status: CommandEncoderStatus, + pub(crate) device_id: Stored<id::DeviceId>, + pub(crate) trackers: Tracker<A>, + buffer_memory_init_actions: Vec<BufferInitTrackerAction>, + texture_memory_actions: CommandBufferTextureMemoryActions, + limits: wgt::Limits, + support_clear_texture: bool, + #[cfg(feature = "trace")] + pub(crate) commands: Option<Vec<TraceCommand>>, +} + +impl<A: HalApi> CommandBuffer<A> { + pub(crate) fn new( + encoder: A::CommandEncoder, + device_id: Stored<id::DeviceId>, + limits: wgt::Limits, + _downlevel: wgt::DownlevelCapabilities, + features: wgt::Features, + #[cfg(feature = "trace")] enable_tracing: bool, + label: &Label, + ) -> Self { + CommandBuffer { + encoder: CommandEncoder { + raw: encoder, + is_open: false, + list: Vec::new(), + label: crate::LabelHelpers::borrow_option(label).map(|s| s.to_string()), + }, + status: CommandEncoderStatus::Recording, + device_id, + trackers: Tracker::new(), + buffer_memory_init_actions: Default::default(), + texture_memory_actions: Default::default(), + limits, + support_clear_texture: features.contains(wgt::Features::CLEAR_TEXTURE), + #[cfg(feature = "trace")] + commands: if enable_tracing { + Some(Vec::new()) + } else { + None + }, + } + } + + pub(crate) fn insert_barriers_from_tracker( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + head: &Tracker<A>, + buffer_guard: &Storage<Buffer<A>, id::BufferId>, + texture_guard: &Storage<Texture<A>, id::TextureId>, + ) { + profiling::scope!("insert_barriers"); + + base.buffers.set_from_tracker(&head.buffers); + base.textures + .set_from_tracker(texture_guard, &head.textures); + + Self::drain_barriers(raw, base, buffer_guard, texture_guard); + } + + pub(crate) fn insert_barriers_from_scope( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + head: &UsageScope<A>, + buffer_guard: &Storage<Buffer<A>, id::BufferId>, + texture_guard: &Storage<Texture<A>, id::TextureId>, + ) { + profiling::scope!("insert_barriers"); + + base.buffers.set_from_usage_scope(&head.buffers); + base.textures + .set_from_usage_scope(texture_guard, &head.textures); + + Self::drain_barriers(raw, base, buffer_guard, texture_guard); + } + + pub(crate) fn drain_barriers( + raw: &mut A::CommandEncoder, + base: &mut Tracker<A>, + buffer_guard: &Storage<Buffer<A>, id::BufferId>, + texture_guard: &Storage<Texture<A>, id::TextureId>, + ) { + profiling::scope!("drain_barriers"); + + let buffer_barriers = base.buffers.drain().map(|pending| { + let buf = unsafe { &buffer_guard.get_unchecked(pending.id) }; + pending.into_hal(buf) + }); + let texture_barriers = base.textures.drain().map(|pending| { + let tex = unsafe { texture_guard.get_unchecked(pending.id) }; + pending.into_hal(tex) + }); + + unsafe { + raw.transition_buffers(buffer_barriers); + raw.transition_textures(texture_barriers); + } + } +} + +impl<A: HalApi> CommandBuffer<A> { + fn get_encoder_mut( + storage: &mut Storage<Self, id::CommandEncoderId>, + id: id::CommandEncoderId, + ) -> Result<&mut Self, CommandEncoderError> { + match storage.get_mut(id) { + Ok(cmd_buf) => match cmd_buf.status { + CommandEncoderStatus::Recording => Ok(cmd_buf), + CommandEncoderStatus::Finished => Err(CommandEncoderError::NotRecording), + CommandEncoderStatus::Error => Err(CommandEncoderError::Invalid), + }, + Err(_) => Err(CommandEncoderError::Invalid), + } + } + + pub fn is_finished(&self) -> bool { + match self.status { + CommandEncoderStatus::Finished => true, + _ => false, + } + } + + pub(crate) fn into_baked(self) -> BakedCommands<A> { + BakedCommands { + encoder: self.encoder.raw, + list: self.encoder.list, + trackers: self.trackers, + buffer_memory_init_actions: self.buffer_memory_init_actions, + texture_memory_actions: self.texture_memory_actions, + } + } +} + +impl<A: HalApi> crate::hub::Resource for CommandBuffer<A> { + const TYPE: &'static str = "CommandBuffer"; + + fn life_guard(&self) -> &crate::LifeGuard { + unreachable!() + } + + fn label(&self) -> &str { + self.encoder.label.as_ref().map_or("", |s| s.as_str()) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct BasePassRef<'a, C> { + pub label: Option<&'a str>, + pub commands: &'a [C], + pub dynamic_offsets: &'a [wgt::DynamicOffset], + pub string_data: &'a [u8], + pub push_constant_data: &'a [u32], +} + +/// A stream of commands for a render pass or compute pass. +/// +/// This also contains side tables referred to by certain commands, +/// like dynamic offsets for [`SetBindGroup`] or string data for +/// [`InsertDebugMarker`]. +/// +/// Render passes use `BasePass<RenderCommand>`, whereas compute +/// passes use `BasePass<ComputeCommand>`. +/// +/// [`SetBindGroup`]: RenderCommand::SetBindGroup +/// [`InsertDebugMarker`]: RenderCommand::InsertDebugMarker +#[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 label: Option<String>, + + /// The stream of commands. + pub commands: Vec<C>, + + /// Dynamic offsets consumed by [`SetBindGroup`] commands in `commands`. + /// + /// Each successive `SetBindGroup` consumes the next + /// [`num_dynamic_offsets`] values from this list. + pub dynamic_offsets: Vec<wgt::DynamicOffset>, + + /// Strings used by debug instructions. + /// + /// Each successive [`PushDebugGroup`] or [`InsertDebugMarker`] + /// instruction consumes the next `len` bytes from this vector. + pub string_data: Vec<u8>, + + /// Data used by `SetPushConstant` instructions. + /// + /// See the documentation for [`RenderCommand::SetPushConstant`] + /// and [`ComputeCommand::SetPushConstant`] for details. + pub push_constant_data: Vec<u32>, +} + +impl<C: Clone> BasePass<C> { + fn new(label: &Label) -> Self { + Self { + label: label.as_ref().map(|cow| cow.to_string()), + 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 { + label: base.label.map(str::to_string), + 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 { + label: self.label.as_deref(), + commands: &self.commands, + dynamic_offsets: &self.dynamic_offsets, + string_data: &self.string_data, + push_constant_data: &self.push_constant_data, + } + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + _desc: &wgt::CommandBufferDescriptor<Label>, + ) -> (id::CommandBufferId, Option<CommandEncoderError>) { + profiling::scope!("CommandEncoder::finish"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (mut cmd_buf_guard, _) = hub.command_buffers.write(&mut token); + + let error = match cmd_buf_guard.get_mut(encoder_id) { + Ok(cmd_buf) => match cmd_buf.status { + CommandEncoderStatus::Recording => { + cmd_buf.encoder.close(); + cmd_buf.status = CommandEncoderStatus::Finished; + //Note: if we want to stop tracking the swapchain texture view, + // this is the place to do it. + log::trace!("Command buffer {:?}", encoder_id); + None + } + CommandEncoderStatus::Finished => Some(CommandEncoderError::NotRecording), + CommandEncoderStatus::Error => { + cmd_buf.encoder.discard(); + Some(CommandEncoderError::Invalid) + } + }, + Err(_) => Some(CommandEncoderError::Invalid), + }; + + (encoder_id, error) + } + + pub fn command_encoder_push_debug_group<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + label: &str, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::push_debug_group"); + + let hub = A::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(&mut *cmd_buf_guard, encoder_id)?; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::PushDebugGroup(label.to_string())); + } + + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.begin_debug_marker(label); + } + Ok(()) + } + + pub fn command_encoder_insert_debug_marker<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + label: &str, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::insert_debug_marker"); + + let hub = A::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(&mut *cmd_buf_guard, encoder_id)?; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::InsertDebugMarker(label.to_string())); + } + + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.insert_debug_marker(label); + } + Ok(()) + } + + pub fn command_encoder_pop_debug_group<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + ) -> Result<(), CommandEncoderError> { + profiling::scope!("CommandEncoder::pop_debug_marker"); + + let hub = A::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(&mut *cmd_buf_guard, encoder_id)?; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::PopDebugGroup); + } + + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_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, Copy, Clone)] +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 reset(&mut self) { + self.last_state = None; + } +} + +impl<T: Copy + PartialEq> Default for StateChange<T> { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +struct BindGroupStateChange { + last_states: [StateChange<id::BindGroupId>; hal::MAX_BIND_GROUPS], +} + +impl BindGroupStateChange { + fn new() -> Self { + Self { + last_states: [StateChange::new(); hal::MAX_BIND_GROUPS], + } + } + + unsafe fn set_and_check_redundant( + &mut self, + bind_group_id: id::BindGroupId, + index: u32, + dynamic_offsets: &mut Vec<u32>, + offsets: *const wgt::DynamicOffset, + offset_length: usize, + ) -> bool { + // For now never deduplicate bind groups with dynamic offsets. + if offset_length == 0 { + // If this get returns None, that means we're well over the limit, + // so let the call through to get a proper error + if let Some(current_bind_group) = self.last_states.get_mut(index as usize) { + // Bail out if we're binding the same bind group. + if current_bind_group.set_and_check_redundant(bind_group_id) { + return true; + } + } + } else { + // We intentionally remove the memory of this bind group if we have dynamic offsets, + // such that if you try to bind this bind group later with _no_ dynamic offsets it + // tries to bind it again and gives a proper validation error. + if let Some(current_bind_group) = self.last_states.get_mut(index as usize) { + current_bind_group.reset(); + } + dynamic_offsets + .extend_from_slice(unsafe { slice::from_raw_parts(offsets, offset_length) }); + } + false + } + fn reset(&mut self) { + self.last_states = [StateChange::new(); hal::MAX_BIND_GROUPS]; + } +} + +impl Default for BindGroupStateChange { + fn default() -> Self { + Self::new() + } +} + +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, indexed:{indexed} indirect:{indirect}")] + Draw { + indexed: bool, + indirect: bool, + pipeline: Option<id::RenderPipelineId>, + }, + #[error("While resetting queries after the renderpass was ran")] + QueryReset, + #[error("In a write_timestamp command")] + WriteTimestamp, + #[error("In a begin_pipeline_statistics_query command")] + BeginPipelineStatisticsQuery, + #[error("In a end_pipeline_statistics_query command")] + EndPipelineStatisticsQuery, + #[error("In a execute_bundle command")] + ExecuteBundle, + #[error("In a dispatch command, indirect:{indirect}")] + Dispatch { + indirect: bool, + pipeline: Option<id::ComputePipelineId>, + }, + #[error("In a pop_debug_group command")] + PopDebugGroup, +} + +impl PrettyError for PassErrorScope { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + // This error is not in the error chain, only notes are needed + match *self { + Self::Pass(id) => { + fmt.command_buffer_label(&id); + } + Self::SetBindGroup(id) => { + fmt.bind_group_label(&id); + } + Self::SetPipelineRender(id) => { + fmt.render_pipeline_label(&id); + } + Self::SetPipelineCompute(id) => { + fmt.compute_pipeline_label(&id); + } + Self::SetVertexBuffer(id) => { + fmt.buffer_label(&id); + } + Self::SetIndexBuffer(id) => { + fmt.buffer_label(&id); + } + Self::Draw { + pipeline: Some(id), .. + } => { + fmt.render_pipeline_label(&id); + } + Self::Dispatch { + pipeline: Some(id), .. + } => { + fmt.compute_pipeline_label(&id); + } + _ => {} + } + } +} diff --git a/third_party/rust/wgpu-core/src/command/query.rs b/third_party/rust/wgpu-core/src/command/query.rs new file mode 100644 index 0000000000..6181f83fa8 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/query.rs @@ -0,0 +1,431 @@ +use hal::CommandEncoder as _; + +#[cfg(feature = "trace")] +use crate::device::trace::Command as TraceCommand; +use crate::{ + command::{CommandBuffer, CommandEncoderError}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, + id::{self, Id, TypedId}, + init_tracker::MemoryInitKind, + resource::QuerySet, + Epoch, FastHashMap, Index, +}; +use std::{iter, marker::PhantomData}; +use thiserror::Error; +use wgt::BufferAddress; + +#[derive(Debug)] +pub(super) struct QueryResetMap<A: hal::Api> { + map: FastHashMap<Index, (Vec<bool>, Epoch)>, + _phantom: PhantomData<A>, +} +impl<A: hal::Api> QueryResetMap<A> { + pub fn new() -> Self { + Self { + map: FastHashMap::default(), + _phantom: PhantomData, + } + } + + pub fn use_query_set( + &mut self, + id: id::QuerySetId, + query_set: &QuerySet<A>, + query: u32, + ) -> bool { + let (index, epoch, _) = id.unzip(); + let vec_pair = self + .map + .entry(index) + .or_insert_with(|| (vec![false; query_set.desc.count as usize], epoch)); + + std::mem::replace(&mut vec_pair.0[query as usize], true) + } + + pub fn reset_queries( + self, + raw_encoder: &mut A::CommandEncoder, + query_set_storage: &Storage<QuerySet<A>, id::QuerySetId>, + backend: wgt::Backend, + ) -> Result<(), id::QuerySetId> { + for (query_set_id, (state, epoch)) in self.map.into_iter() { + let id = Id::zip(query_set_id, epoch, backend); + let query_set = query_set_storage.get(id).map_err(|_| id)?; + + debug_assert_eq!(state.len(), query_set.desc.count as usize); + + // Need to find all "runs" of values which need resets. If the state vector is: + // [false, true, true, false, true], we want to reset [1..3, 4..5]. This minimizes + // the amount of resets needed. + let mut run_start: Option<u32> = None; + for (idx, value) in state.into_iter().chain(iter::once(false)).enumerate() { + match (run_start, value) { + // We're inside of a run, do nothing + (Some(..), true) => {} + // We've hit the end of a run, dispatch a reset + (Some(start), false) => { + run_start = None; + unsafe { raw_encoder.reset_queries(&query_set.raw, start..idx as u32) }; + } + // We're starting a run + (None, true) => { + run_start = Some(idx as u32); + } + // We're in a run of falses, do nothing. + (None, false) => {} + } + } + } + + Ok(()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SimplifiedQueryType { + Occlusion, + Timestamp, + PipelineStatistics, +} +impl From<wgt::QueryType> for SimplifiedQueryType { + fn from(q: wgt::QueryType) -> Self { + match q { + wgt::QueryType::Occlusion => SimplifiedQueryType::Occlusion, + wgt::QueryType::Timestamp => SimplifiedQueryType::Timestamp, + wgt::QueryType::PipelineStatistics(..) => SimplifiedQueryType::PipelineStatistics, + } + } +} + +/// Error encountered when dealing with queries +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum QueryError { + #[error(transparent)] + Encoder(#[from] CommandEncoderError), + #[error("Error encountered while trying to use queries")] + Use(#[from] QueryUseError), + #[error("Error encountered while trying to resolve a query")] + Resolve(#[from] ResolveError), + #[error("Buffer {0:?} is invalid or destroyed")] + InvalidBuffer(id::BufferId), + #[error("QuerySet {0:?} is invalid or destroyed")] + InvalidQuerySet(id::QuerySetId), +} + +impl crate::error::PrettyError for QueryError { + fn fmt_pretty(&self, fmt: &mut crate::error::ErrorFormatter) { + fmt.error(self); + match *self { + Self::InvalidBuffer(id) => fmt.buffer_label(&id), + Self::InvalidQuerySet(id) => fmt.query_set_label(&id), + + _ => {} + } + } +} + +/// Error encountered while trying to use queries +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum QueryUseError { + #[error("Query {query_index} is out of bounds for a query set of size {query_set_size}")] + OutOfBounds { + query_index: u32, + query_set_size: u32, + }, + #[error("Query {query_index} has already been used within the same renderpass. Queries must only be used once per renderpass")] + UsedTwiceInsideRenderpass { query_index: u32 }, + #[error("Query {new_query_index} was started while query {active_query_index} was already active. No more than one statistic or occlusion query may be active at once")] + AlreadyStarted { + active_query_index: u32, + new_query_index: u32, + }, + #[error("Query was stopped while there was no active query")] + AlreadyStopped, + #[error("A query of type {query_type:?} was started using a query set of type {set_type:?}")] + IncompatibleType { + set_type: SimplifiedQueryType, + query_type: SimplifiedQueryType, + }, +} + +/// Error encountered while trying to resolve a query. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ResolveError { + #[error("Queries can only be resolved to buffers that contain the QUERY_RESOLVE usage")] + MissingBufferUsage, + #[error("Resolve buffer offset has to be aligned to `QUERY_RESOLVE_BUFFER_ALIGNMENT")] + BufferOffsetAlignment, + #[error("Resolving queries {start_query}..{end_query} would overrun the query set of size {query_set_size}")] + QueryOverrun { + start_query: u32, + end_query: u32, + query_set_size: u32, + }, + #[error("Resolving queries {start_query}..{end_query} ({stride} byte queries) will end up overrunning the bounds of the destination buffer of size {buffer_size} using offsets {buffer_start_offset}..{buffer_end_offset}")] + BufferOverrun { + start_query: u32, + end_query: u32, + stride: u32, + buffer_size: BufferAddress, + buffer_start_offset: BufferAddress, + buffer_end_offset: BufferAddress, + }, +} + +impl<A: HalApi> QuerySet<A> { + fn validate_query( + &self, + query_set_id: id::QuerySetId, + query_type: SimplifiedQueryType, + query_index: u32, + reset_state: Option<&mut QueryResetMap<A>>, + ) -> Result<&A::QuerySet, QueryUseError> { + // We need to defer our resets because we are in a renderpass, + // add the usage to the reset map. + if let Some(reset) = reset_state { + let used = reset.use_query_set(query_set_id, self, query_index); + if used { + return Err(QueryUseError::UsedTwiceInsideRenderpass { query_index }); + } + } + + let simple_set_type = SimplifiedQueryType::from(self.desc.ty); + if simple_set_type != query_type { + return Err(QueryUseError::IncompatibleType { + query_type, + set_type: simple_set_type, + }); + } + + if query_index >= self.desc.count { + return Err(QueryUseError::OutOfBounds { + query_index, + query_set_size: self.desc.count, + }); + } + + Ok(&self.raw) + } + + pub(super) fn validate_and_write_timestamp( + &self, + raw_encoder: &mut A::CommandEncoder, + query_set_id: id::QuerySetId, + query_index: u32, + reset_state: Option<&mut QueryResetMap<A>>, + ) -> Result<(), QueryUseError> { + let needs_reset = reset_state.is_none(); + let query_set = self.validate_query( + query_set_id, + SimplifiedQueryType::Timestamp, + query_index, + reset_state, + )?; + + unsafe { + // If we don't have a reset state tracker which can defer resets, we must reset now. + if needs_reset { + raw_encoder.reset_queries(&self.raw, query_index..(query_index + 1)); + } + raw_encoder.write_timestamp(query_set, query_index); + } + + Ok(()) + } + + pub(super) fn validate_and_begin_pipeline_statistics_query( + &self, + raw_encoder: &mut A::CommandEncoder, + query_set_id: id::QuerySetId, + query_index: u32, + reset_state: Option<&mut QueryResetMap<A>>, + active_query: &mut Option<(id::QuerySetId, u32)>, + ) -> Result<(), QueryUseError> { + let needs_reset = reset_state.is_none(); + let query_set = self.validate_query( + query_set_id, + SimplifiedQueryType::PipelineStatistics, + query_index, + reset_state, + )?; + + if let Some((_old_id, old_idx)) = active_query.replace((query_set_id, query_index)) { + return Err(QueryUseError::AlreadyStarted { + active_query_index: old_idx, + new_query_index: query_index, + }); + } + + unsafe { + // If we don't have a reset state tracker which can defer resets, we must reset now. + if needs_reset { + raw_encoder.reset_queries(&self.raw, query_index..(query_index + 1)); + } + raw_encoder.begin_query(query_set, query_index); + } + + Ok(()) + } +} + +pub(super) fn end_pipeline_statistics_query<A: HalApi>( + raw_encoder: &mut A::CommandEncoder, + storage: &Storage<QuerySet<A>, id::QuerySetId>, + active_query: &mut Option<(id::QuerySetId, u32)>, +) -> Result<(), QueryUseError> { + if let Some((query_set_id, query_index)) = active_query.take() { + // We can unwrap here as the validity was validated when the active query was set + let query_set = storage.get(query_set_id).unwrap(); + + unsafe { raw_encoder.end_query(&query_set.raw, query_index) }; + + Ok(()) + } else { + Err(QueryUseError::AlreadyStopped) + } +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn command_encoder_write_timestamp<A: HalApi>( + &self, + command_encoder_id: id::CommandEncoderId, + query_set_id: id::QuerySetId, + query_index: u32, + ) -> Result<(), QueryError> { + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let (query_set_guard, _) = hub.query_sets.read(&mut token); + + let cmd_buf = CommandBuffer::get_encoder_mut(&mut cmd_buf_guard, command_encoder_id)?; + let raw_encoder = cmd_buf.encoder.open(); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::WriteTimestamp { + query_set_id, + query_index, + }); + } + + let query_set = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(QueryError::InvalidQuerySet(query_set_id))?; + + query_set.validate_and_write_timestamp(raw_encoder, query_set_id, query_index, None)?; + + Ok(()) + } + + pub fn command_encoder_resolve_query_set<A: HalApi>( + &self, + command_encoder_id: id::CommandEncoderId, + query_set_id: id::QuerySetId, + start_query: u32, + query_count: u32, + destination: id::BufferId, + destination_offset: BufferAddress, + ) -> Result<(), QueryError> { + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let (query_set_guard, mut token) = hub.query_sets.read(&mut token); + let (buffer_guard, _) = hub.buffers.read(&mut token); + + let cmd_buf = CommandBuffer::get_encoder_mut(&mut cmd_buf_guard, command_encoder_id)?; + let raw_encoder = cmd_buf.encoder.open(); + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::ResolveQuerySet { + query_set_id, + start_query, + query_count, + destination, + destination_offset, + }); + } + + if destination_offset % wgt::QUERY_RESOLVE_BUFFER_ALIGNMENT != 0 { + return Err(QueryError::Resolve(ResolveError::BufferOffsetAlignment)); + } + + let query_set = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(QueryError::InvalidQuerySet(query_set_id))?; + + let (dst_buffer, dst_pending) = cmd_buf + .trackers + .buffers + .set_single(&*buffer_guard, destination, hal::BufferUses::COPY_DST) + .ok_or(QueryError::InvalidBuffer(destination))?; + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + + if !dst_buffer.usage.contains(wgt::BufferUsages::QUERY_RESOLVE) { + return Err(ResolveError::MissingBufferUsage.into()); + } + + let end_query = start_query + query_count; + if end_query > query_set.desc.count { + return Err(ResolveError::QueryOverrun { + start_query, + end_query, + query_set_size: query_set.desc.count, + } + .into()); + } + + let elements_per_query = match query_set.desc.ty { + wgt::QueryType::Occlusion => 1, + wgt::QueryType::PipelineStatistics(ps) => ps.bits().count_ones(), + wgt::QueryType::Timestamp => 1, + }; + let stride = elements_per_query * wgt::QUERY_SIZE; + let bytes_used = (stride * query_count) as BufferAddress; + + let buffer_start_offset = destination_offset; + let buffer_end_offset = buffer_start_offset + bytes_used; + + if buffer_end_offset > dst_buffer.size { + return Err(ResolveError::BufferOverrun { + start_query, + end_query, + stride, + buffer_size: dst_buffer.size, + buffer_start_offset, + buffer_end_offset, + } + .into()); + } + + cmd_buf + .buffer_memory_init_actions + .extend(dst_buffer.initialization_status.create_action( + destination, + buffer_start_offset..buffer_end_offset, + MemoryInitKind::ImplicitlyInitialized, + )); + + unsafe { + raw_encoder.transition_buffers(dst_barrier.into_iter()); + raw_encoder.copy_query_results( + &query_set.raw, + start_query..end_query, + dst_buffer.raw.as_ref().unwrap(), + destination_offset, + wgt::BufferSize::new_unchecked(stride as u64), + ); + } + + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/command/render.rs b/third_party/rust/wgpu-core/src/command/render.rs new file mode 100644 index 0000000000..b38e79c4b4 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/render.rs @@ -0,0 +1,2590 @@ +use crate::{ + binding_model::BindError, + command::{ + self, + bind::Binder, + end_pipeline_statistics_query, + memory_init::{fixup_discarded_surfaces, SurfacesInDiscardState}, + BasePass, BasePassRef, BindGroupStateChange, CommandBuffer, CommandEncoderError, + CommandEncoderStatus, DrawError, ExecutionError, MapPassErr, PassErrorScope, QueryResetMap, + QueryUseError, RenderCommand, RenderCommandError, StateChange, + }, + device::{ + AttachmentData, Device, MissingDownlevelFlags, MissingFeatures, + RenderPassCompatibilityCheckType, RenderPassCompatibilityError, RenderPassContext, + }, + error::{ErrorFormatter, PrettyError}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, + id, + init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction}, + pipeline::{self, PipelineFlags}, + resource::{self, Buffer, Texture, TextureView, TextureViewNotRenderableReason}, + track::{TextureSelector, UsageConflict, UsageScope}, + validation::{ + check_buffer_usage, check_texture_usage, MissingBufferUsageError, MissingTextureUsageError, + }, + Label, Stored, +}; + +use arrayvec::ArrayVec; +use hal::CommandEncoder as _; +use thiserror::Error; +use wgt::{ + BufferAddress, BufferSize, BufferUsages, Color, IndexFormat, TextureUsages, + TextureViewDimension, VertexStepMode, +}; + +#[cfg(any(feature = "serial-pass", feature = "replay"))] +use serde::Deserialize; +#[cfg(any(feature = "serial-pass", feature = "trace"))] +use serde::Serialize; + +use std::{borrow::Cow, fmt, iter, marker::PhantomData, mem, num::NonZeroU32, ops::Range, str}; + +use super::{memory_init::TextureSurfaceDiscard, CommandBufferTextureMemoryActions}; + +/// 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))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +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))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum StoreOp { + /// Discards the content of the render target. + /// + /// If you don't care about the contents of the target, this can be faster. + Discard = 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, Eq, 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 attachment 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, +} + +impl<V> PassChannel<V> { + fn hal_ops(&self) -> hal::AttachmentOps { + let mut ops = hal::AttachmentOps::empty(); + match self.load_op { + LoadOp::Load => ops |= hal::AttachmentOps::LOAD, + LoadOp::Clear => (), + }; + match self.store_op { + StoreOp::Store => ops |= hal::AttachmentOps::STORE, + StoreOp::Discard => (), + }; + ops + } +} + +/// 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 RenderPassColorAttachment { + /// The view to use as an attachment. + pub view: 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 RenderPassDepthStencilAttachment { + /// The view to use as an attachment. + pub view: 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 RenderPassDepthStencilAttachment { + /// Validate the given aspects' read-only flags against their load + /// and store ops. + /// + /// When an aspect is read-only, its load and store ops must be + /// `LoadOp::Load` and `StoreOp::Store`. + /// + /// On success, return a pair `(depth, stencil)` indicating + /// whether the depth and stencil passes are read-only. + fn depth_stencil_read_only( + &self, + aspects: hal::FormatAspects, + ) -> Result<(bool, bool), RenderPassErrorInner> { + let mut depth_read_only = true; + let mut stencil_read_only = true; + + if aspects.contains(hal::FormatAspects::DEPTH) { + if self.depth.read_only + && (self.depth.load_op, self.depth.store_op) != (LoadOp::Load, StoreOp::Store) + { + return Err(RenderPassErrorInner::InvalidDepthOps); + } + depth_read_only = self.depth.read_only; + } + + if aspects.contains(hal::FormatAspects::STENCIL) { + if self.stencil.read_only + && (self.stencil.load_op, self.stencil.store_op) != (LoadOp::Load, StoreOp::Store) + { + return Err(RenderPassErrorInner::InvalidStencilOps); + } + stencil_read_only = self.stencil.read_only; + } + + Ok((depth_read_only, stencil_read_only)) + } +} + +/// Describes the attachments of a render pass. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct RenderPassDescriptor<'a> { + pub label: Label<'a>, + /// The color attachments of the render pass. + pub color_attachments: Cow<'a, [Option<RenderPassColorAttachment>]>, + /// The depth and stencil attachment of the render pass, if any. + pub depth_stencil_attachment: Option<&'a RenderPassDepthStencilAttachment>, +} + +#[cfg_attr(feature = "serial-pass", derive(Deserialize, Serialize))] +pub struct RenderPass { + base: BasePass<RenderCommand>, + parent_id: id::CommandEncoderId, + color_targets: ArrayVec<Option<RenderPassColorAttachment>, { hal::MAX_COLOR_ATTACHMENTS }>, + depth_stencil_target: Option<RenderPassDepthStencilAttachment>, + + // Resource binding dedupe state. + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_bind_groups: BindGroupStateChange, + #[cfg_attr(feature = "serial-pass", serde(skip))] + current_pipeline: StateChange<id::RenderPipelineId>, +} + +impl RenderPass { + pub fn new(parent_id: id::CommandEncoderId, desc: &RenderPassDescriptor) -> Self { + Self { + base: BasePass::new(&desc.label), + parent_id, + color_targets: desc.color_attachments.iter().cloned().collect(), + depth_stencil_target: desc.depth_stencil_attachment.cloned(), + + current_bind_groups: BindGroupStateChange::new(), + current_pipeline: StateChange::new(), + } + } + + 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, + } + } + + pub fn set_index_buffer( + &mut self, + buffer_id: id::BufferId, + index_format: IndexFormat, + offset: BufferAddress, + size: Option<BufferSize>, + ) { + self.base.commands.push(RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + }); + } +} + +impl fmt::Debug for RenderPass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RenderPass") + .field("encoder_id", &self.parent_id) + .field("color_targets", &self.color_targets) + .field("depth_stencil_target", &self.depth_stencil_target) + .field("command count", &self.base.commands.len()) + .field("dynamic offset count", &self.base.dynamic_offsets.len()) + .field( + "push constant u32 count", + &self.base.push_constant_data.len(), + ) + .finish() + } +} + +#[derive(Debug, PartialEq)] +enum OptionalState { + Unused, + Required, + Set, +} + +impl OptionalState { + fn require(&mut self, require: bool) { + if require && *self == Self::Unused { + *self = Self::Required; + } + } +} + +#[derive(Debug, Default)] +struct IndexState { + bound_buffer_view: Option<(id::Valid<id::BufferId>, Range<BufferAddress>)>, + format: Option<IndexFormat>, + pipeline_format: Option<IndexFormat>, + limit: u32, +} + +impl IndexState { + fn update_limit(&mut self) { + self.limit = match self.bound_buffer_view { + Some((_, ref range)) => { + let format = self + .format + .expect("IndexState::update_limit must be called after a index buffer is set"); + let shift = match 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, + step: pipeline::VertexStep, + bound: bool, +} + +impl VertexBufferState { + const EMPTY: Self = Self { + total_size: 0, + step: pipeline::VertexStep { + stride: 0, + mode: VertexStepMode::Vertex, + }, + bound: false, + }; +} + +#[derive(Debug, Default)] +struct VertexState { + inputs: ArrayVec<VertexBufferState, { hal::MAX_VERTEX_BUFFERS }>, + /// Length of the shortest vertex rate vertex buffer + vertex_limit: u32, + /// Buffer slot which the shortest vertex rate vertex buffer is bound to + vertex_limit_slot: u32, + /// Length of the shortest instance rate vertex buffer + instance_limit: u32, + /// Buffer slot which the shortest instance rate vertex buffer is bound to + instance_limit_slot: u32, + /// Total amount of buffers required by the pipeline. + buffers_required: u32, +} + +impl VertexState { + fn update_limits(&mut self) { + self.vertex_limit = u32::MAX; + self.instance_limit = u32::MAX; + for (idx, vbs) in self.inputs.iter().enumerate() { + if vbs.step.stride == 0 || !vbs.bound { + continue; + } + let limit = (vbs.total_size / vbs.step.stride) as u32; + match vbs.step.mode { + VertexStepMode::Vertex => { + if limit < self.vertex_limit { + self.vertex_limit = limit; + self.vertex_limit_slot = idx as _; + } + } + VertexStepMode::Instance => { + if limit < self.instance_limit { + self.instance_limit = limit; + self.instance_limit_slot = idx as _; + } + } + } + } + } + + 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_constant: OptionalState, + stencil_reference: u32, + pipeline: Option<id::RenderPipelineId>, + index: IndexState, + vertex: VertexState, + debug_scope_depth: u32, +} + +impl State { + fn is_ready(&self, indexed: bool) -> Result<(), DrawError> { + // Determine how many vertex buffers have already been bound + let vertex_buffer_count = self.vertex.inputs.iter().take_while(|v| v.bound).count() as u32; + // Compare with the needed quantity + if vertex_buffer_count < self.vertex.buffers_required { + return Err(DrawError::MissingVertexBuffer { + index: vertex_buffer_count, + }); + } + + 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_none() { + return Err(DrawError::MissingPipeline); + } + if self.blend_constant == OptionalState::Required { + return Err(DrawError::MissingBlendConstant); + } + + if indexed { + // Pipeline expects an index buffer + if let Some(pipeline_index_format) = self.index.pipeline_format { + // We have a buffer bound + let buffer_index_format = self.index.format.ok_or(DrawError::MissingIndexBuffer)?; + + // The buffers are different formats + if pipeline_index_format != buffer_index_format { + return Err(DrawError::UnmatchedIndexFormats { + pipeline: pipeline_index_format, + buffer: buffer_index_format, + }); + } + } + } + + self.binder.check_late_buffer_bindings()?; + + Ok(()) + } + + /// Reset the `RenderBundle`-related states. + fn reset_bundle(&mut self) { + self.binder.reset(); + self.pipeline = None; + self.index.reset(); + self.vertex.reset(); + } +} + +/// Describes an attachment location in words. +/// +/// Can be used as "the {loc} has..." or "{loc} has..." +#[derive(Debug, Copy, Clone)] +pub enum AttachmentErrorLocation { + Color { index: usize, resolve: bool }, + Depth, +} + +impl fmt::Display for AttachmentErrorLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AttachmentErrorLocation::Color { + index, + resolve: false, + } => write!(f, "color attachment at index {index}'s texture view"), + AttachmentErrorLocation::Color { + index, + resolve: true, + } => write!( + f, + "color attachment at index {index}'s resolve texture view" + ), + AttachmentErrorLocation::Depth => write!(f, "depth attachment's texture view"), + } + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ColorAttachmentError { + #[error("Attachment format {0:?} is not a color format")] + InvalidFormat(wgt::TextureFormat), + #[error("The number of color attachments {given} exceeds the limit {limit}")] + TooMany { given: usize, limit: usize }, +} + +/// Error encountered when performing a render pass. +#[derive(Clone, Debug, Error)] +pub enum RenderPassErrorInner { + #[error(transparent)] + ColorAttachment(#[from] ColorAttachmentError), + #[error(transparent)] + Encoder(#[from] CommandEncoderError), + #[error("Attachment texture view {0:?} is invalid")] + InvalidAttachment(id::TextureViewId), + #[error("The format of the depth-stencil attachment ({0:?}) is not a depth-stencil format")] + InvalidDepthStencilAttachmentFormat(wgt::TextureFormat), + #[error("The format of the {location} ({format:?}) is not resolvable")] + UnsupportedResolveTargetFormat { + location: AttachmentErrorLocation, + format: wgt::TextureFormat, + }, + #[error("No color attachments or depth attachments were provided, at least one attachment of any kind must be provided")] + MissingAttachments, + #[error("The {location} is not renderable:")] + TextureViewIsNotRenderable { + location: AttachmentErrorLocation, + #[source] + reason: TextureViewNotRenderableReason, + }, + #[error("Attachments have differing sizes: the {expected_location} has extent {expected_extent:?} but is followed by the {actual_location} which has {actual_extent:?}")] + AttachmentsDimensionMismatch { + expected_location: AttachmentErrorLocation, + expected_extent: wgt::Extent3d, + actual_location: AttachmentErrorLocation, + actual_extent: wgt::Extent3d, + }, + #[error("Attachments have differing sample counts: the {expected_location} has count {expected_samples:?} but is followed by the {actual_location} which has count {actual_samples:?}")] + AttachmentSampleCountMismatch { + expected_location: AttachmentErrorLocation, + expected_samples: u32, + actual_location: AttachmentErrorLocation, + actual_samples: u32, + }, + #[error("The resolve source, {location}, must be multi-sampled (has {src} samples) while the resolve destination must not be multisampled (has {dst} samples)")] + InvalidResolveSampleCounts { + location: AttachmentErrorLocation, + src: u32, + dst: u32, + }, + #[error( + "Resource source, {location}, format ({src:?}) must match the resolve destination format ({dst:?})" + )] + MismatchedResolveTextureFormat { + location: AttachmentErrorLocation, + src: wgt::TextureFormat, + dst: wgt::TextureFormat, + }, + #[error("Surface texture is dropped before the render pass is finished")] + SurfaceTextureDropped, + #[error("Not enough memory left")] + OutOfMemory, + #[error("Unable to clear non-present/read-only depth")] + InvalidDepthOps, + #[error("Unable to clear non-present/read-only stencil")] + InvalidStencilOps, + #[error("Setting `values_offset` to be `None` is only for internal use in render bundles")] + InvalidValuesOffset, + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), + #[error("Indirect draw uses bytes {offset}..{end_offset} {} which overruns indirect buffer of size {buffer_size}", + count.map_or_else(String::new, |v| format!("(using count {v})")))] + IndirectBufferOverrun { + count: Option<NonZeroU32>, + 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 has incompatible targets, {0}")] + IncompatibleBundleTargets(#[from] RenderPassCompatibilityError), + #[error( + "Render bundle has incompatible read-only flags: \ + bundle has flags depth = {bundle_depth} and stencil = {bundle_stencil}, \ + while the pass has flags depth = {pass_depth} and stencil = {pass_stencil}. \ + Read-only renderpasses are only compatible with read-only bundles for that aspect." + )] + IncompatibleBundleReadOnlyDepthStencil { + pass_depth: bool, + pass_stencil: bool, + bundle_depth: bool, + bundle_stencil: bool, + }, + #[error(transparent)] + RenderCommand(#[from] RenderCommandError), + #[error(transparent)] + Draw(#[from] DrawError), + #[error(transparent)] + Bind(#[from] BindError), + #[error(transparent)] + QueryUse(#[from] QueryUseError), + #[error("Multiview layer count must match")] + MultiViewMismatch, + #[error( + "Multiview pass texture views with more than one array layer must have D2Array dimension" + )] + MultiViewDimensionMismatch, +} + +impl PrettyError for RenderPassErrorInner { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + if let Self::InvalidAttachment(id) = *self { + fmt.texture_view_label_with_key(&id, "attachment"); + }; + } +} + +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("{scope}")] +pub struct RenderPassError { + pub scope: PassErrorScope, + #[source] + inner: RenderPassErrorInner, +} +impl PrettyError for RenderPassError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + // This error is wrapper for the inner error, + // but the scope has useful labels + fmt.error(self); + self.scope.fmt_pretty(fmt); + } +} + +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(), + }) + } +} + +struct RenderAttachment<'a> { + texture_id: &'a Stored<id::TextureId>, + selector: &'a TextureSelector, + usage: hal::TextureUses, +} + +impl<A: hal::Api> TextureView<A> { + fn to_render_attachment(&self, usage: hal::TextureUses) -> RenderAttachment { + RenderAttachment { + texture_id: &self.parent_id, + selector: &self.selector, + usage, + } + } +} + +const MAX_TOTAL_ATTACHMENTS: usize = hal::MAX_COLOR_ATTACHMENTS + hal::MAX_COLOR_ATTACHMENTS + 1; +type AttachmentDataVec<T> = ArrayVec<T, MAX_TOTAL_ATTACHMENTS>; + +struct RenderPassInfo<'a, A: HalApi> { + context: RenderPassContext, + usage_scope: UsageScope<A>, + /// All render attachments, including depth/stencil + render_attachments: AttachmentDataVec<RenderAttachment<'a>>, + is_depth_read_only: bool, + is_stencil_read_only: bool, + extent: wgt::Extent3d, + _phantom: PhantomData<A>, + + pending_discard_init_fixups: SurfacesInDiscardState, + divergent_discarded_depth_stencil_aspect: Option<(wgt::TextureAspect, &'a TextureView<A>)>, + multiview: Option<NonZeroU32>, +} + +impl<'a, A: HalApi> RenderPassInfo<'a, A> { + fn add_pass_texture_init_actions<V>( + channel: &PassChannel<V>, + texture_memory_actions: &mut CommandBufferTextureMemoryActions, + view: &TextureView<A>, + texture_guard: &Storage<Texture<A>, id::TextureId>, + pending_discard_init_fixups: &mut SurfacesInDiscardState, + ) { + if channel.load_op == LoadOp::Load { + pending_discard_init_fixups.extend(texture_memory_actions.register_init_action( + &TextureInitTrackerAction { + id: view.parent_id.value.0, + range: TextureInitRange::from(view.selector.clone()), + // Note that this is needed even if the target is discarded, + kind: MemoryInitKind::NeedsInitializedMemory, + }, + texture_guard, + )); + } else if channel.store_op == StoreOp::Store { + // Clear + Store + texture_memory_actions.register_implicit_init( + view.parent_id.value, + TextureInitRange::from(view.selector.clone()), + texture_guard, + ); + } + if channel.store_op == StoreOp::Discard { + // the discard happens at the *end* of a pass, but recording the + // discard right away be alright since the texture can't be used + // during the pass anyways + texture_memory_actions.discard(TextureSurfaceDiscard { + texture: view.parent_id.value.0, + mip_level: view.selector.mips.start, + layer: view.selector.layers.start, + }); + } + } + + fn start( + device: &Device<A>, + label: Option<&str>, + color_attachments: &[Option<RenderPassColorAttachment>], + depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, + cmd_buf: &mut CommandBuffer<A>, + view_guard: &'a Storage<TextureView<A>, id::TextureViewId>, + buffer_guard: &'a Storage<Buffer<A>, id::BufferId>, + texture_guard: &'a Storage<Texture<A>, id::TextureId>, + ) -> Result<Self, RenderPassErrorInner> { + profiling::scope!("RenderPassInfo::start"); + + // 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_depth_read_only = false; + let mut is_stencil_read_only = false; + + let mut render_attachments = AttachmentDataVec::<RenderAttachment>::new(); + let mut discarded_surfaces = AttachmentDataVec::new(); + let mut pending_discard_init_fixups = SurfacesInDiscardState::new(); + let mut divergent_discarded_depth_stencil_aspect = None; + + let mut attachment_location = AttachmentErrorLocation::Color { + index: usize::MAX, + resolve: false, + }; + let mut extent = None; + let mut sample_count = 0; + + let mut detected_multiview: Option<Option<NonZeroU32>> = None; + + let mut check_multiview = |view: &TextureView<A>| { + // Get the multiview configuration for this texture view + let layers = view.selector.layers.end - view.selector.layers.start; + let this_multiview = if layers >= 2 { + // Trivially proven by the if above + Some(unsafe { NonZeroU32::new_unchecked(layers) }) + } else { + None + }; + + // Make sure that if this view is a multiview, it is set to be an array + if this_multiview.is_some() && view.desc.dimension != TextureViewDimension::D2Array { + return Err(RenderPassErrorInner::MultiViewDimensionMismatch); + } + + // Validate matching first, or store the first one + if let Some(multiview) = detected_multiview { + if multiview != this_multiview { + return Err(RenderPassErrorInner::MultiViewMismatch); + } + } else { + // Multiview is only supported if the feature is enabled + if this_multiview.is_some() { + device.require_features(wgt::Features::MULTIVIEW)?; + } + + detected_multiview = Some(this_multiview); + } + + Ok(()) + }; + let mut add_view = |view: &TextureView<A>, location| { + let render_extent = view.render_extent.map_err(|reason| { + RenderPassErrorInner::TextureViewIsNotRenderable { location, reason } + })?; + if let Some(ex) = extent { + if ex != render_extent { + return Err(RenderPassErrorInner::AttachmentsDimensionMismatch { + expected_location: attachment_location, + expected_extent: ex, + actual_location: location, + actual_extent: render_extent, + }); + } + } else { + extent = Some(render_extent); + } + if sample_count == 0 { + sample_count = view.samples; + } else if sample_count != view.samples { + return Err(RenderPassErrorInner::AttachmentSampleCountMismatch { + expected_location: attachment_location, + expected_samples: sample_count, + actual_location: location, + actual_samples: view.samples, + }); + } + attachment_location = location; + Ok(()) + }; + + let mut colors = + ArrayVec::<Option<hal::ColorAttachment<A>>, { hal::MAX_COLOR_ATTACHMENTS }>::new(); + let mut depth_stencil = None; + + if let Some(at) = depth_stencil_attachment { + let view: &TextureView<A> = cmd_buf + .trackers + .views + .add_single(view_guard, at.view) + .ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?; + check_multiview(view)?; + add_view(view, AttachmentErrorLocation::Depth)?; + + let ds_aspects = view.desc.aspects(); + if ds_aspects.contains(hal::FormatAspects::COLOR) { + return Err(RenderPassErrorInner::InvalidDepthStencilAttachmentFormat( + view.desc.format, + )); + } + + if !ds_aspects.contains(hal::FormatAspects::STENCIL) + || (at.stencil.load_op == at.depth.load_op + && at.stencil.store_op == at.depth.store_op) + { + Self::add_pass_texture_init_actions( + &at.depth, + &mut cmd_buf.texture_memory_actions, + view, + texture_guard, + &mut pending_discard_init_fixups, + ); + } else if !ds_aspects.contains(hal::FormatAspects::DEPTH) { + Self::add_pass_texture_init_actions( + &at.stencil, + &mut cmd_buf.texture_memory_actions, + view, + texture_guard, + &mut pending_discard_init_fixups, + ); + } else { + // This is the only place (anywhere in wgpu) where Stencil & + // Depth init state can diverge. + // + // To safe us the overhead of tracking init state of texture + // aspects everywhere, we're going to cheat a little bit in + // order to keep the init state of both Stencil and Depth + // aspects in sync. The expectation is that we hit this path + // extremely rarely! + // + // Diverging LoadOp, i.e. Load + Clear: + // + // Record MemoryInitKind::NeedsInitializedMemory for the entire + // surface, a bit wasteful on unit but no negative effect! + // + // Rationale: If the loaded channel is uninitialized it needs + // clearing, the cleared channel doesn't care. (If everything is + // already initialized nothing special happens) + // + // (possible minor optimization: Clear caused by + // NeedsInitializedMemory should know that it doesn't need to + // clear the aspect that was set to C) + let need_init_beforehand = + at.depth.load_op == LoadOp::Load || at.stencil.load_op == LoadOp::Load; + if need_init_beforehand { + pending_discard_init_fixups.extend( + cmd_buf.texture_memory_actions.register_init_action( + &TextureInitTrackerAction { + id: view.parent_id.value.0, + range: TextureInitRange::from(view.selector.clone()), + kind: MemoryInitKind::NeedsInitializedMemory, + }, + texture_guard, + ), + ); + } + + // Diverging Store, i.e. Discard + Store: + // + // Immediately zero out channel that is set to discard after + // we're done with the render pass. This allows us to set the + // entire surface to MemoryInitKind::ImplicitlyInitialized (if + // it isn't already set to NeedsInitializedMemory). + // + // (possible optimization: Delay and potentially drop this zeroing) + if at.depth.store_op != at.stencil.store_op { + if !need_init_beforehand { + cmd_buf.texture_memory_actions.register_implicit_init( + view.parent_id.value, + TextureInitRange::from(view.selector.clone()), + texture_guard, + ); + } + divergent_discarded_depth_stencil_aspect = Some(( + if at.depth.store_op == StoreOp::Discard { + wgt::TextureAspect::DepthOnly + } else { + wgt::TextureAspect::StencilOnly + }, + view, + )); + } else if at.depth.store_op == StoreOp::Discard { + // Both are discarded using the regular path. + discarded_surfaces.push(TextureSurfaceDiscard { + texture: view.parent_id.value.0, + mip_level: view.selector.mips.start, + layer: view.selector.layers.start, + }); + } + } + + (is_depth_read_only, is_stencil_read_only) = at.depth_stencil_read_only(ds_aspects)?; + + let usage = if is_depth_read_only && is_stencil_read_only { + hal::TextureUses::DEPTH_STENCIL_READ | hal::TextureUses::RESOURCE + } else { + hal::TextureUses::DEPTH_STENCIL_WRITE + }; + render_attachments.push(view.to_render_attachment(usage)); + + depth_stencil = Some(hal::DepthStencilAttachment { + target: hal::Attachment { + view: &view.raw, + usage, + }, + depth_ops: at.depth.hal_ops(), + stencil_ops: at.stencil.hal_ops(), + clear_value: (at.depth.clear_value, at.stencil.clear_value), + }); + } + + for (index, attachment) in color_attachments.iter().enumerate() { + let at = if let Some(attachment) = attachment.as_ref() { + attachment + } else { + colors.push(None); + continue; + }; + let color_view: &TextureView<A> = cmd_buf + .trackers + .views + .add_single(view_guard, at.view) + .ok_or(RenderPassErrorInner::InvalidAttachment(at.view))?; + check_multiview(color_view)?; + add_view( + color_view, + AttachmentErrorLocation::Color { + index, + resolve: false, + }, + )?; + + if !color_view + .desc + .aspects() + .contains(hal::FormatAspects::COLOR) + { + return Err(RenderPassErrorInner::ColorAttachment( + ColorAttachmentError::InvalidFormat(color_view.desc.format), + )); + } + + Self::add_pass_texture_init_actions( + &at.channel, + &mut cmd_buf.texture_memory_actions, + color_view, + texture_guard, + &mut pending_discard_init_fixups, + ); + render_attachments + .push(color_view.to_render_attachment(hal::TextureUses::COLOR_TARGET)); + + let mut hal_resolve_target = None; + if let Some(resolve_target) = at.resolve_target { + let resolve_view: &TextureView<A> = cmd_buf + .trackers + .views + .add_single(view_guard, resolve_target) + .ok_or(RenderPassErrorInner::InvalidAttachment(resolve_target))?; + + check_multiview(resolve_view)?; + + let resolve_location = AttachmentErrorLocation::Color { + index, + resolve: true, + }; + + let render_extent = resolve_view.render_extent.map_err(|reason| { + RenderPassErrorInner::TextureViewIsNotRenderable { + location: resolve_location, + reason, + } + })?; + if color_view.render_extent.unwrap() != render_extent { + return Err(RenderPassErrorInner::AttachmentsDimensionMismatch { + expected_location: attachment_location, + expected_extent: extent.unwrap_or_default(), + actual_location: resolve_location, + actual_extent: render_extent, + }); + } + if color_view.samples == 1 || resolve_view.samples != 1 { + return Err(RenderPassErrorInner::InvalidResolveSampleCounts { + location: resolve_location, + src: color_view.samples, + dst: resolve_view.samples, + }); + } + if color_view.desc.format != resolve_view.desc.format { + return Err(RenderPassErrorInner::MismatchedResolveTextureFormat { + location: resolve_location, + src: color_view.desc.format, + dst: resolve_view.desc.format, + }); + } + if !resolve_view + .format_features + .flags + .contains(wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE) + { + return Err(RenderPassErrorInner::UnsupportedResolveTargetFormat { + location: resolve_location, + format: resolve_view.desc.format, + }); + } + + cmd_buf.texture_memory_actions.register_implicit_init( + resolve_view.parent_id.value, + TextureInitRange::from(resolve_view.selector.clone()), + texture_guard, + ); + render_attachments + .push(resolve_view.to_render_attachment(hal::TextureUses::COLOR_TARGET)); + + hal_resolve_target = Some(hal::Attachment { + view: &resolve_view.raw, + usage: hal::TextureUses::COLOR_TARGET, + }); + } + + colors.push(Some(hal::ColorAttachment { + target: hal::Attachment { + view: &color_view.raw, + usage: hal::TextureUses::COLOR_TARGET, + }, + resolve_target: hal_resolve_target, + ops: at.channel.hal_ops(), + clear_value: at.channel.clear_value, + })); + } + + let extent = extent.ok_or(RenderPassErrorInner::MissingAttachments)?; + let multiview = detected_multiview.expect("Multiview was not detected, no attachments"); + + let view_data = AttachmentData { + colors: color_attachments + .iter() + .map(|at| at.as_ref().map(|at| view_guard.get(at.view).unwrap())) + .collect(), + resolves: color_attachments + .iter() + .filter_map(|at| match *at { + Some(RenderPassColorAttachment { + resolve_target: Some(resolve), + .. + }) => Some(view_guard.get(resolve).unwrap()), + _ => None, + }) + .collect(), + depth_stencil: depth_stencil_attachment.map(|at| view_guard.get(at.view).unwrap()), + }; + + let context = RenderPassContext { + attachments: view_data.map(|view| view.desc.format), + sample_count, + multiview, + }; + + let hal_desc = hal::RenderPassDescriptor { + label, + extent, + sample_count, + color_attachments: &colors, + depth_stencil_attachment: depth_stencil, + multiview, + }; + unsafe { + cmd_buf.encoder.raw.begin_render_pass(&hal_desc); + }; + + Ok(Self { + context, + usage_scope: UsageScope::new(buffer_guard, texture_guard), + render_attachments, + is_depth_read_only, + is_stencil_read_only, + extent, + _phantom: PhantomData, + pending_discard_init_fixups, + divergent_discarded_depth_stencil_aspect, + multiview, + }) + } + + fn finish( + mut self, + raw: &mut A::CommandEncoder, + texture_guard: &Storage<Texture<A>, id::TextureId>, + ) -> Result<(UsageScope<A>, SurfacesInDiscardState), RenderPassErrorInner> { + profiling::scope!("RenderPassInfo::finish"); + unsafe { + raw.end_render_pass(); + } + + for ra in self.render_attachments { + if !texture_guard.contains(ra.texture_id.value.0) { + return Err(RenderPassErrorInner::SurfaceTextureDropped); + } + let texture = &texture_guard[ra.texture_id.value]; + check_texture_usage(texture.desc.usage, TextureUsages::RENDER_ATTACHMENT)?; + + // the tracker set of the pass is always in "extend" mode + unsafe { + self.usage_scope + .textures + .merge_single( + texture_guard, + ra.texture_id.value, + Some(ra.selector.clone()), + &ra.texture_id.ref_count, + ra.usage, + ) + .map_err(UsageConflict::from)? + }; + } + + // If either only stencil or depth was discarded, we put in a special + // clear pass to keep the init status of the aspects in sync. We do this + // so we don't need to track init state for depth/stencil aspects + // individually. + // + // Note that we don't go the usual route of "brute force" initializing + // the texture when need arises here, since this path is actually + // something a user may genuinely want (where as the other cases are + // more seen along the lines as gracefully handling a user error). + if let Some((aspect, view)) = self.divergent_discarded_depth_stencil_aspect { + let (depth_ops, stencil_ops) = if aspect == wgt::TextureAspect::DepthOnly { + ( + hal::AttachmentOps::STORE, // clear depth + hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil + ) + } else { + ( + hal::AttachmentOps::LOAD | hal::AttachmentOps::STORE, // unchanged stencil + hal::AttachmentOps::STORE, // clear depth + ) + }; + let desc = hal::RenderPassDescriptor { + label: Some("(wgpu internal) Zero init discarded depth/stencil aspect"), + extent: view.render_extent.unwrap(), + sample_count: view.samples, + color_attachments: &[], + depth_stencil_attachment: Some(hal::DepthStencilAttachment { + target: hal::Attachment { + view: &view.raw, + usage: hal::TextureUses::DEPTH_STENCIL_WRITE, + }, + depth_ops, + stencil_ops, + clear_value: (0.0, 0), + }), + multiview: self.multiview, + }; + unsafe { + raw.begin_render_pass(&desc); + raw.end_render_pass(); + } + } + + Ok((self.usage_scope, self.pending_discard_init_fixups)) + } +} + +// Common routines between render/compute + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn command_encoder_run_render_pass<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + pass: &RenderPass, + ) -> Result<(), RenderPassError> { + self.command_encoder_run_render_pass_impl::<A>( + 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<A: HalApi>( + &self, + encoder_id: id::CommandEncoderId, + base: BasePassRef<RenderCommand>, + color_attachments: &[Option<RenderPassColorAttachment>], + depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, + ) -> Result<(), RenderPassError> { + profiling::scope!("CommandEncoder::run_render_pass"); + let init_scope = PassErrorScope::Pass(encoder_id); + + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, mut token) = hub.devices.read(&mut token); + + let (scope, query_reset_state, pending_discard_init_fixups) = { + let (mut cmb_guard, mut token) = hub.command_buffers.write(&mut token); + + // Spell out the type, to placate rust-analyzer. + // https://github.com/rust-lang/rust-analyzer/issues/12247 + let cmd_buf: &mut CommandBuffer<A> = + CommandBuffer::get_encoder_mut(&mut *cmb_guard, encoder_id) + .map_pass_err(init_scope)?; + // close everything while the new command encoder is filled + cmd_buf.encoder.close(); + // will be reset to true if recording is done without errors + cmd_buf.status = CommandEncoderStatus::Error; + + #[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.to_vec(), + target_depth_stencil: depth_stencil_attachment.cloned(), + }); + } + + let device = &device_guard[cmd_buf.device_id.value]; + cmd_buf.encoder.open_pass(base.label); + + 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 (render_pipeline_guard, mut token) = hub.render_pipelines.read(&mut token); + let (query_set_guard, mut token) = hub.query_sets.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); + + log::trace!( + "Encoding render pass begin in command buffer {:?}", + encoder_id + ); + + let mut info = RenderPassInfo::start( + device, + base.label, + color_attachments, + depth_stencil_attachment, + cmd_buf, + &*view_guard, + &*buffer_guard, + &*texture_guard, + ) + .map_pass_err(init_scope)?; + + cmd_buf.trackers.set_size( + Some(&*buffer_guard), + Some(&*texture_guard), + Some(&*view_guard), + None, + Some(&*bind_group_guard), + None, + Some(&*render_pipeline_guard), + Some(&*bundle_guard), + Some(&*query_set_guard), + ); + + let raw = &mut cmd_buf.encoder.raw; + + let mut state = State { + pipeline_flags: PipelineFlags::empty(), + binder: Binder::new(), + blend_constant: OptionalState::Unused, + stencil_reference: 0, + pipeline: None, + index: IndexState::default(), + vertex: VertexState::default(), + debug_scope_depth: 0, + }; + let mut temp_offsets = Vec::new(); + let mut dynamic_offset_count = 0; + let mut string_offset = 0; + let mut active_query = None; + let mut query_reset_state = QueryResetMap::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 >= 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[dynamic_offset_count + ..dynamic_offset_count + (num_dynamic_offsets as usize)], + ); + dynamic_offset_count += num_dynamic_offsets as usize; + + let bind_group: &crate::binding_model::BindGroup<A> = cmd_buf + .trackers + .bind_groups + .add_single(&*bind_group_guard, bind_group_id) + .ok_or(RenderCommandError::InvalidBindGroup(bind_group_id)) + .map_pass_err(scope)?; + bind_group + .validate_dynamic_bindings(index, &temp_offsets, &cmd_buf.limits) + .map_pass_err(scope)?; + + // merge the resource tracker in + unsafe { + info.usage_scope + .merge_bind_group(&*texture_guard, &bind_group.used) + .map_pass_err(scope)?; + } + //Note: stateless trackers are not merged: the lifetime reference + // is held to the bind group itself. + + cmd_buf.buffer_memory_init_actions.extend( + bind_group.used_buffer_ranges.iter().filter_map(|action| { + match buffer_guard.get(action.id) { + Ok(buffer) => buffer.initialization_status.check_action(action), + Err(_) => None, + } + }), + ); + for action in bind_group.used_texture_ranges.iter() { + info.pending_discard_init_fixups.extend( + cmd_buf + .texture_memory_actions + .register_init_action(action, &texture_guard), + ); + } + + let pipeline_layout_id = state.binder.pipeline_layout_id; + let entries = state.binder.assign_group( + index as usize, + id::Valid(bind_group_id), + bind_group, + &temp_offsets, + ); + if !entries.is_empty() { + let pipeline_layout = + &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw; + for (i, e) in entries.iter().enumerate() { + let raw_bg = + &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; + + unsafe { + raw.set_bind_group( + pipeline_layout, + index + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } + } + } + } + RenderCommand::SetPipeline(pipeline_id) => { + let scope = PassErrorScope::SetPipelineRender(pipeline_id); + state.pipeline = Some(pipeline_id); + + let pipeline: &pipeline::RenderPipeline<A> = cmd_buf + .trackers + .render_pipelines + .add_single(&*render_pipeline_guard, pipeline_id) + .ok_or(RenderCommandError::InvalidPipeline(pipeline_id)) + .map_pass_err(scope)?; + + info.context + .check_compatible( + &pipeline.pass_context, + RenderPassCompatibilityCheckType::RenderPipeline, + ) + .map_err(RenderCommandError::IncompatiblePipelineTargets) + .map_pass_err(scope)?; + + state.pipeline_flags = pipeline.flags; + + if (pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) + && info.is_depth_read_only) + || (pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) + && info.is_stencil_read_only) + { + return Err(RenderCommandError::IncompatiblePipelineRods) + .map_pass_err(scope); + } + + state + .blend_constant + .require(pipeline.flags.contains(PipelineFlags::BLEND_CONSTANT)); + + unsafe { + raw.set_render_pipeline(&pipeline.raw); + } + + if pipeline.flags.contains(PipelineFlags::STENCIL_REFERENCE) { + unsafe { + raw.set_stencil_reference(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]; + + let (start_index, entries) = state.binder.change_pipeline_layout( + &*pipeline_layout_guard, + pipeline.layout_id.value, + &pipeline.late_sized_buffer_groups, + ); + if !entries.is_empty() { + for (i, e) in entries.iter().enumerate() { + let raw_bg = + &bind_group_guard[e.group_id.as_ref().unwrap().value].raw; + + unsafe { + raw.set_bind_group( + &pipeline_layout.raw, + start_index as u32 + i as u32, + raw_bg, + &e.dynamic_offsets, + ); + } + } + } + + // 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.set_push_constants( + &pipeline_layout.raw, + range.stages, + clear_offset, + clear_data, + ); + }, + ); + } + } + + state.index.pipeline_format = pipeline.strip_index_format; + + let vertex_steps_len = pipeline.vertex_steps.len(); + state.vertex.buffers_required = vertex_steps_len as u32; + + // Initialize each `vertex.inputs[i].step` from + // `pipeline.vertex_steps[i]`. Enlarge `vertex.inputs` + // as necessary to accomodate all slots in the + // pipeline. If `vertex.inputs` is longer, fill the + // extra entries with default `VertexStep`s. + while state.vertex.inputs.len() < vertex_steps_len { + state.vertex.inputs.push(VertexBufferState::EMPTY); + } + + // This is worse as a `zip`, but it's close. + let mut steps = pipeline.vertex_steps.iter(); + for input in state.vertex.inputs.iter_mut() { + input.step = steps.next().cloned().unwrap_or_default(); + } + + // Update vertex buffer limits. + state.vertex.update_limits(); + } + RenderCommand::SetIndexBuffer { + buffer_id, + index_format, + offset, + size, + } => { + let scope = PassErrorScope::SetIndexBuffer(buffer_id); + let buffer: &Buffer<A> = info + .usage_scope + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDEX) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, BufferUsages::INDEX) + .map_pass_err(scope)?; + let 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.format = Some(index_format); + state.index.update_limit(); + + cmd_buf.buffer_memory_init_actions.extend( + buffer.initialization_status.create_action( + buffer_id, + offset..end, + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + let bb = hal::BufferBinding { + buffer: buf_raw, + offset, + size, + }; + unsafe { + raw.set_index_buffer(bb, index_format); + } + } + RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + } => { + let scope = PassErrorScope::SetVertexBuffer(buffer_id); + let buffer: &Buffer<A> = info + .usage_scope + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::VERTEX) + .map_pass_err(scope)?; + check_buffer_usage(buffer.usage, BufferUsages::VERTEX) + .map_pass_err(scope)?; + let 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)); + let vertex_state = &mut state.vertex.inputs[slot as usize]; + //TODO: where are we checking that the offset is in bound? + vertex_state.total_size = match size { + Some(s) => s.get(), + None => buffer.size - offset, + }; + vertex_state.bound = true; + + cmd_buf.buffer_memory_init_actions.extend( + buffer.initialization_status.create_action( + buffer_id, + offset..(offset + vertex_state.total_size), + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + let bb = hal::BufferBinding { + buffer: buf_raw, + offset, + size, + }; + unsafe { + raw.set_vertex_buffer(slot, bb); + } + state.vertex.update_limits(); + } + RenderCommand::SetBlendConstant(ref color) => { + state.blend_constant = OptionalState::Set; + let array = [ + color.r as f32, + color.g as f32, + color.b as f32, + color.a as f32, + ]; + unsafe { + raw.set_blend_constants(&array); + } + } + RenderCommand::SetStencilReference(value) => { + state.stencil_reference = value; + if state + .pipeline_flags + .contains(PipelineFlags::STENCIL_REFERENCE) + { + unsafe { + raw.set_stencil_reference(value); + } + } + } + RenderCommand::SetViewport { + ref rect, + depth_min, + depth_max, + } => { + let scope = PassErrorScope::SetViewport; + if rect.w <= 0.0 || rect.h <= 0.0 { + return Err(RenderCommandError::InvalidViewportDimension( + rect.w, rect.h, + )) + .map_pass_err(scope); + } + if !(0.0..=1.0).contains(&depth_min) || !(0.0..=1.0).contains(&depth_max) { + return Err(RenderCommandError::InvalidViewportDepth( + depth_min, depth_max, + )) + .map_pass_err(scope); + } + let r = hal::Rect { + x: rect.x, + y: rect.y, + w: rect.w, + h: rect.h, + }; + unsafe { + raw.set_viewport(&r, 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.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice) + } + } + RenderCommand::SetScissor(ref rect) => { + let scope = PassErrorScope::SetScissorRect; + if rect.w == 0 + || rect.h == 0 + || rect.x + rect.w > info.extent.width + || rect.y + rect.h > info.extent.height + { + return Err(RenderCommandError::InvalidScissorRect(*rect, info.extent)) + .map_pass_err(scope); + } + let r = hal::Rect { + x: rect.x, + y: rect.y, + w: rect.w, + h: rect.h, + }; + unsafe { + raw.set_scissor_rect(&r); + } + } + RenderCommand::Draw { + vertex_count, + instance_count, + first_vertex, + first_instance, + } => { + let indexed = false; + let scope = PassErrorScope::Draw { + indexed, + indirect: false, + pipeline: state.pipeline, + }; + state.is_ready(indexed).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, + slot: state.vertex.vertex_limit_slot, + }) + .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, + slot: state.vertex.instance_limit_slot, + }) + .map_pass_err(scope); + } + + unsafe { + raw.draw(first_vertex, vertex_count, first_instance, instance_count); + } + } + RenderCommand::DrawIndexed { + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + } => { + let indexed = true; + let scope = PassErrorScope::Draw { + indexed, + indirect: false, + pipeline: state.pipeline, + }; + state.is_ready(indexed).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, + slot: state.vertex.instance_limit_slot, + }) + .map_pass_err(scope); + } + + unsafe { + raw.draw_indexed( + first_index, + index_count, + base_vertex, + first_instance, + instance_count, + ); + } + } + RenderCommand::MultiDrawIndirect { + buffer_id, + offset, + count, + indexed, + } => { + let scope = PassErrorScope::Draw { + indexed, + indirect: true, + pipeline: state.pipeline, + }; + state.is_ready(indexed).map_pass_err(scope)?; + + let stride = match indexed { + false => mem::size_of::<wgt::DrawIndirectArgs>(), + true => mem::size_of::<wgt::DrawIndexedIndirectArgs>(), + }; + + if count.is_some() { + device + .require_features(wgt::Features::MULTI_DRAW_INDIRECT) + .map_pass_err(scope)?; + } + device + .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) + .map_pass_err(scope)?; + + let indirect_buffer: &Buffer<A> = info + .usage_scope + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) + .map_pass_err(scope)?; + check_buffer_usage(indirect_buffer.usage, BufferUsages::INDIRECT) + .map_pass_err(scope)?; + let 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 end_offset = offset + stride as u64 * actual_count as u64; + if end_offset > indirect_buffer.size { + return Err(RenderPassErrorInner::IndirectBufferOverrun { + count, + offset, + end_offset, + buffer_size: indirect_buffer.size, + }) + .map_pass_err(scope); + } + + cmd_buf.buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.create_action( + buffer_id, + offset..end_offset, + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + match indexed { + false => unsafe { + raw.draw_indirect(indirect_raw, offset, actual_count); + }, + true => unsafe { + raw.draw_indexed_indirect(indirect_raw, offset, actual_count); + }, + } + } + RenderCommand::MultiDrawIndirectCount { + buffer_id, + offset, + count_buffer_id, + count_buffer_offset, + max_count, + indexed, + } => { + let scope = PassErrorScope::Draw { + indexed, + indirect: true, + pipeline: state.pipeline, + }; + state.is_ready(indexed).map_pass_err(scope)?; + + let stride = match indexed { + false => mem::size_of::<wgt::DrawIndirectArgs>(), + true => mem::size_of::<wgt::DrawIndexedIndirectArgs>(), + } as u64; + + device + .require_features(wgt::Features::MULTI_DRAW_INDIRECT_COUNT) + .map_pass_err(scope)?; + device + .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION) + .map_pass_err(scope)?; + + let indirect_buffer: &Buffer<A> = info + .usage_scope + .buffers + .merge_single(&*buffer_guard, buffer_id, hal::BufferUses::INDIRECT) + .map_pass_err(scope)?; + check_buffer_usage(indirect_buffer.usage, BufferUsages::INDIRECT) + .map_pass_err(scope)?; + let indirect_raw = indirect_buffer + .raw + .as_ref() + .ok_or(RenderCommandError::DestroyedBuffer(buffer_id)) + .map_pass_err(scope)?; + + let count_buffer: &Buffer<A> = info + .usage_scope + .buffers + .merge_single( + &*buffer_guard, + count_buffer_id, + hal::BufferUses::INDIRECT, + ) + .map_pass_err(scope)?; + check_buffer_usage(count_buffer.usage, BufferUsages::INDIRECT) + .map_pass_err(scope)?; + let count_raw = count_buffer + .raw + .as_ref() + .ok_or(RenderCommandError::DestroyedBuffer(count_buffer_id)) + .map_pass_err(scope)?; + + let end_offset = offset + stride * max_count as u64; + if end_offset > indirect_buffer.size { + return Err(RenderPassErrorInner::IndirectBufferOverrun { + count: None, + offset, + end_offset, + buffer_size: indirect_buffer.size, + }) + .map_pass_err(scope); + } + cmd_buf.buffer_memory_init_actions.extend( + indirect_buffer.initialization_status.create_action( + buffer_id, + offset..end_offset, + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + 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); + } + cmd_buf.buffer_memory_init_actions.extend( + count_buffer.initialization_status.create_action( + count_buffer_id, + count_buffer_offset..end_count_offset, + MemoryInitKind::NeedsInitializedMemory, + ), + ); + + match indexed { + false => unsafe { + raw.draw_indirect_count( + indirect_raw, + offset, + count_raw, + count_buffer_offset, + max_count, + ); + }, + true => unsafe { + raw.draw_indexed_indirect_count( + indirect_raw, + offset, + count_raw, + count_buffer_offset, + max_count, + ); + }, + } + } + RenderCommand::PushDebugGroup { color: _, len } => { + state.debug_scope_depth += 1; + let label = + str::from_utf8(&base.string_data[string_offset..string_offset + len]) + .unwrap(); + string_offset += len; + unsafe { + raw.begin_debug_marker(label); + } + } + 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[string_offset..string_offset + len]) + .unwrap(); + string_offset += len; + unsafe { + raw.insert_debug_marker(label); + } + } + RenderCommand::WriteTimestamp { + query_set_id, + query_index, + } => { + let scope = PassErrorScope::WriteTimestamp; + + device + .require_features(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES) + .map_pass_err(scope)?; + + let query_set: &resource::QuerySet<A> = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(RenderCommandError::InvalidQuerySet(query_set_id)) + .map_pass_err(scope)?; + + query_set + .validate_and_write_timestamp( + raw, + query_set_id, + query_index, + Some(&mut query_reset_state), + ) + .map_pass_err(scope)?; + } + RenderCommand::BeginPipelineStatisticsQuery { + query_set_id, + query_index, + } => { + let scope = PassErrorScope::BeginPipelineStatisticsQuery; + + let query_set: &resource::QuerySet<A> = cmd_buf + .trackers + .query_sets + .add_single(&*query_set_guard, query_set_id) + .ok_or(RenderCommandError::InvalidQuerySet(query_set_id)) + .map_pass_err(scope)?; + + query_set + .validate_and_begin_pipeline_statistics_query( + raw, + query_set_id, + query_index, + Some(&mut query_reset_state), + &mut active_query, + ) + .map_pass_err(scope)?; + } + RenderCommand::EndPipelineStatisticsQuery => { + let scope = PassErrorScope::EndPipelineStatisticsQuery; + + end_pipeline_statistics_query(raw, &*query_set_guard, &mut active_query) + .map_pass_err(scope)?; + } + RenderCommand::ExecuteBundle(bundle_id) => { + let scope = PassErrorScope::ExecuteBundle; + let bundle: &command::RenderBundle<A> = cmd_buf + .trackers + .bundles + .add_single(&*bundle_guard, bundle_id) + .ok_or(RenderCommandError::InvalidRenderBundle(bundle_id)) + .map_pass_err(scope)?; + + info.context + .check_compatible( + &bundle.context, + RenderPassCompatibilityCheckType::RenderBundle, + ) + .map_err(RenderPassErrorInner::IncompatibleBundleTargets) + .map_pass_err(scope)?; + + if (info.is_depth_read_only && !bundle.is_depth_read_only) + || (info.is_stencil_read_only && !bundle.is_stencil_read_only) + { + return Err( + RenderPassErrorInner::IncompatibleBundleReadOnlyDepthStencil { + pass_depth: info.is_depth_read_only, + pass_stencil: info.is_stencil_read_only, + bundle_depth: bundle.is_depth_read_only, + bundle_stencil: bundle.is_stencil_read_only, + }, + ) + .map_pass_err(scope); + } + + cmd_buf.buffer_memory_init_actions.extend( + bundle + .buffer_memory_init_actions + .iter() + .filter_map(|action| match buffer_guard.get(action.id) { + Ok(buffer) => buffer.initialization_status.check_action(action), + Err(_) => None, + }), + ); + for action in bundle.texture_memory_init_actions.iter() { + info.pending_discard_init_fixups.extend( + cmd_buf + .texture_memory_actions + .register_init_action(action, &texture_guard), + ); + } + + unsafe { + bundle.execute( + raw, + &*pipeline_layout_guard, + &*bind_group_guard, + &*render_pipeline_guard, + &*buffer_guard, + ) + } + .map_err(|e| match e { + ExecutionError::DestroyedBuffer(id) => { + RenderCommandError::DestroyedBuffer(id) + } + ExecutionError::Unimplemented(what) => { + RenderCommandError::Unimplemented(what) + } + }) + .map_pass_err(scope)?; + + unsafe { + info.usage_scope + .merge_render_bundle(&*texture_guard, &bundle.used) + .map_pass_err(scope)?; + cmd_buf + .trackers + .add_from_render_bundle(&bundle.used) + .map_pass_err(scope)?; + }; + state.reset_bundle(); + } + } + } + + log::trace!("Merging renderpass into cmd_buf {:?}", encoder_id); + let (trackers, pending_discard_init_fixups) = + info.finish(raw, &*texture_guard).map_pass_err(init_scope)?; + + cmd_buf.encoder.close(); + (trackers, query_reset_state, pending_discard_init_fixups) + }; + + let (mut cmb_guard, mut token) = hub.command_buffers.write(&mut token); + let (query_set_guard, mut token) = hub.query_sets.read(&mut token); + let (buffer_guard, mut token) = hub.buffers.read(&mut token); + let (texture_guard, _) = hub.textures.read(&mut token); + + let cmd_buf = cmb_guard.get_mut(encoder_id).unwrap(); + { + let transit = cmd_buf.encoder.open(); + + fixup_discarded_surfaces( + pending_discard_init_fixups.into_iter(), + transit, + &texture_guard, + &mut cmd_buf.trackers.textures, + &device_guard[cmd_buf.device_id.value], + ); + + query_reset_state + .reset_queries( + transit, + &query_set_guard, + cmd_buf.device_id.value.0.backend(), + ) + .map_err(RenderCommandError::InvalidQuerySet) + .map_pass_err(PassErrorScope::QueryReset)?; + + super::CommandBuffer::insert_barriers_from_scope( + transit, + &mut cmd_buf.trackers, + &scope, + &*buffer_guard, + &*texture_guard, + ); + } + + // Before we finish the auxiliary encoder, let's + // get our pass back and place it after. + //Note: we could just hold onto this raw pass while recording the + // auxiliary encoder, but then handling errors and cleaning up + // would be more complicated, so we re-use `open()`/`close()`. + let pass_raw = cmd_buf.encoder.list.pop().unwrap(); + cmd_buf.encoder.close(); + cmd_buf.encoder.list.push(pass_raw); + cmd_buf.status = CommandEncoderStatus::Recording; + + Ok(()) + } +} + +pub mod render_ffi { + use super::{ + super::{Rect, RenderCommand}, + RenderPass, + }; + use crate::{id, RawString}; + use std::{convert::TryInto, ffi, num::NonZeroU32, slice}; + use wgt::{BufferAddress, BufferSize, Color, DynamicOffset, IndexFormat}; + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `offset_length` elements. + #[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, + ) { + let redundant = unsafe { + pass.current_bind_groups.set_and_check_redundant( + bind_group_id, + index, + &mut pass.base.dynamic_offsets, + offsets, + offset_length, + ) + }; + + if redundant { + return; + } + + pass.base.commands.push(RenderCommand::SetBindGroup { + index, + num_dynamic_offsets: offset_length.try_into().unwrap(), + bind_group_id, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_set_pipeline( + pass: &mut RenderPass, + pipeline_id: id::RenderPipelineId, + ) { + if pass.current_pipeline.set_and_check_redundant(pipeline_id) { + return; + } + + pass.base + .commands + .push(RenderCommand::SetPipeline(pipeline_id)); + } + + #[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>, + ) { + pass.base.commands.push(RenderCommand::SetVertexBuffer { + slot, + buffer_id, + offset, + size, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_set_index_buffer( + pass: &mut RenderPass, + buffer: id::BufferId, + index_format: IndexFormat, + offset: BufferAddress, + size: Option<BufferSize>, + ) { + pass.set_index_buffer(buffer, index_format, offset, size); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_set_blend_constant(pass: &mut RenderPass, color: &Color) { + pass.base + .commands + .push(RenderCommand::SetBlendConstant(*color)); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_set_stencil_reference(pass: &mut RenderPass, value: u32) { + 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, + ) { + 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, + ) { + pass.base + .commands + .push(RenderCommand::SetScissor(Rect { x, y, w, h })); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `size_bytes` bytes. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_pass_set_push_constants( + pass: &mut RenderPass, + stages: wgt::ShaderStages, + offset: u32, + size_bytes: u32, + data: *const u8, + ) { + 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 = unsafe { 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + 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, + ) { + pass.base + .commands + .push(RenderCommand::MultiDrawIndirectCount { + buffer_id, + offset, + count_buffer_id, + count_buffer_offset, + max_count, + indexed: true, + }); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_pass_push_debug_group( + pass: &mut RenderPass, + label: RawString, + color: u32, + ) { + let bytes = unsafe { 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) { + pass.base.commands.push(RenderCommand::PopDebugGroup); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given `label` + /// is a valid null-terminated string. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_pass_insert_debug_marker( + pass: &mut RenderPass, + label: RawString, + color: u32, + ) { + let bytes = unsafe { 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 extern "C" fn wgpu_render_pass_write_timestamp( + pass: &mut RenderPass, + query_set_id: id::QuerySetId, + query_index: u32, + ) { + pass.base.commands.push(RenderCommand::WriteTimestamp { + query_set_id, + query_index, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_begin_pipeline_statistics_query( + pass: &mut RenderPass, + query_set_id: id::QuerySetId, + query_index: u32, + ) { + pass.base + .commands + .push(RenderCommand::BeginPipelineStatisticsQuery { + query_set_id, + query_index, + }); + } + + #[no_mangle] + pub extern "C" fn wgpu_render_pass_end_pipeline_statistics_query(pass: &mut RenderPass) { + pass.base + .commands + .push(RenderCommand::EndPipelineStatisticsQuery); + } + + /// # Safety + /// + /// This function is unsafe as there is no guarantee that the given pointer is + /// valid for `render_bundle_ids_length` elements. + #[no_mangle] + pub unsafe extern "C" fn wgpu_render_pass_execute_bundles( + pass: &mut RenderPass, + render_bundle_ids: *const id::RenderBundleId, + render_bundle_ids_length: usize, + ) { + for &bundle_id in + unsafe { slice::from_raw_parts(render_bundle_ids, render_bundle_ids_length) } + { + pass.base + .commands + .push(RenderCommand::ExecuteBundle(bundle_id)); + } + pass.current_pipeline.reset(); + pass.current_bind_groups.reset(); + } +} diff --git a/third_party/rust/wgpu-core/src/command/transfer.rs b/third_party/rust/wgpu-core/src/command/transfer.rs new file mode 100644 index 0000000000..45b2684108 --- /dev/null +++ b/third_party/rust/wgpu-core/src/command/transfer.rs @@ -0,0 +1,1154 @@ +#[cfg(feature = "trace")] +use crate::device::trace::Command as TraceCommand; +use crate::{ + command::{clear_texture, CommandBuffer, CommandEncoderError}, + conv, + device::{Device, MissingDownlevelFlags}, + error::{ErrorFormatter, PrettyError}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, + id::{BufferId, CommandEncoderId, TextureId, Valid}, + init_tracker::{ + has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange, + TextureInitTrackerAction, + }, + resource::{Texture, TextureErrorDimension}, + track::TextureSelector, +}; + +use arrayvec::ArrayVec; +use hal::CommandEncoder as _; +use thiserror::Error; +use wgt::{BufferAddress, BufferUsages, Extent3d, TextureUsages}; + +use std::iter; + +pub type ImageCopyBuffer = wgt::ImageCopyBuffer<BufferId>; +pub type ImageCopyTexture = wgt::ImageCopyTexture<TextureId>; +pub type ImageCopyTextureTagged = wgt::ImageCopyTextureTagged<TextureId>; + +#[derive(Clone, Copy, Debug)] +pub enum CopySide { + Source, + Destination, +} + +/// Error encountered while attempting a data transfer. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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("Destination texture is missing the `RENDER_ATTACHMENT` usage flag")] + MissingRenderAttachmentUsageFlag(TextureId), + #[error("Copy of {start_offset}..{end_offset} would end up overrunning 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 overrunning 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("Unable to select texture aspect {aspect:?} from fromat {format:?}")] + InvalidTextureAspect { + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, + }, + #[error("Unable to select texture mip level {level} out of {total}")] + InvalidTextureMipLevel { level: u32, total: u32 }, + #[error("Texture dimension must be 2D when copying from an external texture")] + InvalidDimensionExternal(TextureId), + #[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 bytes per row needs to be specified since more than one row is copied")] + UnspecifiedBytesPerRow, + #[error("Number of rows per image needs to be specified since more than one image is copied")] + UnspecifiedRowsPerImage, + #[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("Copy source aspects must refer to all aspects of the source texture format")] + CopySrcMissingAspects, + #[error( + "Copy destination aspects must refer to all aspects of the destination texture format" + )] + CopyDstMissingAspects, + #[error("Copy aspect must refer to a single aspect of texture format")] + CopyAspectNotOne, + #[error("Copying from textures with format {format:?} and aspect {aspect:?} is forbidden")] + CopyFromForbiddenTextureFormat { + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, + }, + #[error("Copying to textures with format {format:?} and aspect {aspect:?} is forbidden")] + CopyToForbiddenTextureFormat { + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, + }, + #[error( + "Copying to textures with format {0:?} is forbidden when copying from external texture" + )] + ExternalCopyToForbiddenTextureFormat(wgt::TextureFormat), + #[error("The entire texture must be copied when copying from depth texture")] + InvalidDepthTextureExtent, + #[error( + "Source format ({src_format:?}) and destination format ({dst_format:?}) are not copy-compatible (they may only differ in srgb-ness)" + )] + TextureFormatsNotCopyCompatible { + src_format: wgt::TextureFormat, + dst_format: wgt::TextureFormat, + }, + #[error(transparent)] + MemoryInitFailure(#[from] super::ClearError), + #[error("Cannot encode this copy because of a missing downelevel flag")] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), + #[error("Source texture sample count must be 1, got {sample_count}")] + InvalidSampleCount { sample_count: u32 }, + #[error("Requested mip level {requested} does no exist (count: {count})")] + InvalidMipLevel { requested: u32, count: u32 }, +} + +impl PrettyError for TransferError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + match *self { + Self::InvalidBuffer(id) => { + fmt.buffer_label(&id); + } + Self::InvalidTexture(id) => { + fmt.texture_label(&id); + } + // Self::MissingCopySrcUsageFlag(buf_opt, tex_opt) => { + // if let Some(buf) = buf_opt { + // let name = crate::gfx_select!(buf => global.buffer_label(buf)); + // ret.push_str(&format_label_line("source", &name)); + // } + // if let Some(tex) = tex_opt { + // let name = crate::gfx_select!(tex => global.texture_label(tex)); + // ret.push_str(&format_label_line("source", &name)); + // } + // } + Self::MissingCopyDstUsageFlag(buf_opt, tex_opt) => { + if let Some(buf) = buf_opt { + fmt.buffer_label_with_key(&buf, "destination"); + } + if let Some(tex) = tex_opt { + fmt.texture_label_with_key(&tex, "destination"); + } + } + _ => {} + }; + } +} +/// Error encountered while attempting to do a copy on a command encoder. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CopyError { + #[error(transparent)] + Encoder(#[from] CommandEncoderError), + #[error("Copy error")] + Transfer(#[from] TransferError), +} + +pub(crate) fn extract_texture_selector<A: hal::Api>( + copy_texture: &ImageCopyTexture, + copy_size: &Extent3d, + texture: &Texture<A>, +) -> Result<(TextureSelector, hal::TextureCopyBase), TransferError> { + let format = texture.desc.format; + let copy_aspect = hal::FormatAspects::new(format, copy_texture.aspect); + if copy_aspect.is_empty() { + return Err(TransferError::InvalidTextureAspect { + format, + aspect: copy_texture.aspect, + }); + } + + let (layers, origin_z) = match texture.desc.dimension { + wgt::TextureDimension::D1 => (0..1, 0), + wgt::TextureDimension::D2 => ( + copy_texture.origin.z..copy_texture.origin.z + copy_size.depth_or_array_layers, + 0, + ), + wgt::TextureDimension::D3 => (0..1, copy_texture.origin.z), + }; + let base = hal::TextureCopyBase { + origin: wgt::Origin3d { + x: copy_texture.origin.x, + y: copy_texture.origin.y, + z: origin_z, + }, + // this value will be incremented per copied layer + array_layer: layers.start, + mip_level: copy_texture.mip_level, + aspect: copy_aspect, + }; + let selector = TextureSelector { + mips: copy_texture.mip_level..copy_texture.mip_level + 1, + layers, + }; + + Ok((selector, base)) +} + +/// WebGPU's [validating linear texture data][vltd] algorithm. +/// +/// Copied with some modifications from WebGPU standard. +/// +/// If successful, returns a pair `(bytes, stride)`, where: +/// - `bytes` is the number of buffer bytes required for this copy, and +/// - `stride` number of bytes between array layers. +/// +/// [vltd]: https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-linear-texture-data +pub(crate) fn validate_linear_texture_data( + layout: &wgt::ImageDataLayout, + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, + buffer_size: BufferAddress, + buffer_side: CopySide, + copy_size: &Extent3d, + need_copy_aligned_rows: bool, +) -> Result<(BufferAddress, BufferAddress), TransferError> { + // Convert all inputs to BufferAddress (u64) to avoid some of the overflow issues + // Note: u64 is not always enough to prevent overflow, especially when multiplying + // something with a potentially large depth value, so it is preferrable to validate + // the copy size before calling this function (for example via `validate_texture_copy_range`). + let copy_width = copy_size.width as BufferAddress; + let copy_height = copy_size.height as BufferAddress; + let copy_depth = copy_size.depth_or_array_layers as BufferAddress; + + let offset = layout.offset; + + let block_size = format.block_size(Some(aspect)).unwrap() as BufferAddress; + let (block_width, block_height) = format.block_dimensions(); + let block_width = block_width as BufferAddress; + let block_height = block_height as BufferAddress; + + if copy_width % block_width != 0 { + return Err(TransferError::UnalignedCopyWidth); + } + if copy_height % block_height != 0 { + return Err(TransferError::UnalignedCopyHeight); + } + + let width_in_blocks = copy_width / block_width; + let height_in_blocks = copy_height / block_height; + + let bytes_in_last_row = width_in_blocks * block_size; + + let bytes_per_row = if let Some(bytes_per_row) = layout.bytes_per_row { + let bytes_per_row = bytes_per_row as BufferAddress; + if bytes_per_row < bytes_in_last_row { + return Err(TransferError::InvalidBytesPerRow); + } + bytes_per_row + } else { + if copy_depth > 1 || height_in_blocks > 1 { + return Err(TransferError::UnspecifiedBytesPerRow); + } + 0 + }; + let block_rows_per_image = if let Some(rows_per_image) = layout.rows_per_image { + let rows_per_image = rows_per_image as BufferAddress; + if rows_per_image < height_in_blocks { + return Err(TransferError::InvalidRowsPerImage); + } + rows_per_image + } else { + if copy_depth > 1 { + return Err(TransferError::UnspecifiedRowsPerImage); + } + 0 + }; + + if need_copy_aligned_rows { + let bytes_per_row_alignment = wgt::COPY_BYTES_PER_ROW_ALIGNMENT as BufferAddress; + + let mut offset_alignment = block_size; + if format.is_depth_stencil_format() { + offset_alignment = 4 + } + if offset % offset_alignment != 0 { + return Err(TransferError::UnalignedBufferOffset(offset)); + } + + if bytes_per_row % bytes_per_row_alignment != 0 { + return Err(TransferError::UnalignedBytesPerRow); + } + } + + let bytes_per_image = bytes_per_row * block_rows_per_image; + + let required_bytes_in_copy = if copy_depth == 0 { + 0 + } else { + let mut required_bytes_in_copy = bytes_per_image * (copy_depth - 1); + if height_in_blocks > 0 { + required_bytes_in_copy += bytes_per_row * (height_in_blocks - 1) + bytes_in_last_row; + } + required_bytes_in_copy + }; + + 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, + }); + } + + Ok((required_bytes_in_copy, bytes_per_image)) +} + +/// WebGPU's [validating texture copy range][vtcr] algorithm. +/// +/// Copied with minor modifications from WebGPU standard. +/// +/// Returns the HAL copy extent and the layer count. +/// +/// [vtcr]: https://gpuweb.github.io/gpuweb/#validating-texture-copy-range +pub(crate) fn validate_texture_copy_range( + texture_copy_view: &ImageCopyTexture, + desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>, + texture_side: CopySide, + copy_size: &Extent3d, +) -> Result<(hal::CopyExtent, u32), TransferError> { + let (block_width, block_height) = desc.format.block_dimensions(); + + let extent_virtual = desc.mip_level_size(texture_copy_view.mip_level).ok_or( + TransferError::InvalidTextureMipLevel { + level: texture_copy_view.mip_level, + total: desc.mip_level_count, + }, + )?; + // physical size can be larger than the virtual + let extent = extent_virtual.physical_size(desc.format); + + if desc.format.is_depth_stencil_format() && *copy_size != extent { + return Err(TransferError::InvalidDepthTextureExtent); + } + + /// Return `Ok` if a run `size` texels long starting at `start_offset` falls + /// entirely within `texture_size`. Otherwise, return an appropriate a`Err`. + fn check_dimension( + dimension: TextureErrorDimension, + side: CopySide, + start_offset: u32, + size: u32, + texture_size: u32, + ) -> Result<(), TransferError> { + // Avoid underflow in the subtraction by checking start_offset against + // texture_size first. + if start_offset <= texture_size && size <= texture_size - start_offset { + Ok(()) + } else { + Err(TransferError::TextureOverrun { + start_offset, + end_offset: start_offset.wrapping_add(size), + texture_size, + dimension, + side, + }) + } + } + + check_dimension( + TextureErrorDimension::X, + texture_side, + texture_copy_view.origin.x, + copy_size.width, + extent.width, + )?; + check_dimension( + TextureErrorDimension::Y, + texture_side, + texture_copy_view.origin.y, + copy_size.height, + extent.height, + )?; + check_dimension( + TextureErrorDimension::Z, + texture_side, + texture_copy_view.origin.z, + copy_size.depth_or_array_layers, + extent.depth_or_array_layers, + )?; + + 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); + } + + let (depth, array_layer_count) = match desc.dimension { + wgt::TextureDimension::D1 => (1, 1), + wgt::TextureDimension::D2 => (1, copy_size.depth_or_array_layers), + wgt::TextureDimension::D3 => (copy_size.depth_or_array_layers, 1), + }; + + let copy_extent = hal::CopyExtent { + width: copy_size.width, + height: copy_size.height, + depth, + }; + Ok((copy_extent, array_layer_count)) +} + +fn handle_texture_init<A: HalApi>( + init_kind: MemoryInitKind, + cmd_buf: &mut CommandBuffer<A>, + device: &Device<A>, + copy_texture: &ImageCopyTexture, + copy_size: &Extent3d, + texture_guard: &Storage<Texture<A>, TextureId>, +) { + let init_action = TextureInitTrackerAction { + id: copy_texture.texture, + range: TextureInitRange { + mip_range: copy_texture.mip_level..copy_texture.mip_level + 1, + layer_range: copy_texture.origin.z + ..(copy_texture.origin.z + copy_size.depth_or_array_layers), + }, + kind: init_kind, + }; + + // Register the init action. + let immediate_inits = cmd_buf + .texture_memory_actions + .register_init_action(&{ init_action }, texture_guard); + + // In rare cases we may need to insert an init operation immediately onto the command buffer. + if !immediate_inits.is_empty() { + let cmd_buf_raw = cmd_buf.encoder.open(); + for init in immediate_inits { + clear_texture( + texture_guard, + Valid(init.texture), + TextureInitRange { + mip_range: init.mip_level..(init.mip_level + 1), + layer_range: init.layer..(init.layer + 1), + }, + cmd_buf_raw, + &mut cmd_buf.trackers.textures, + &device.alignments, + &device.zero_buffer, + ) + .unwrap(); + } + } +} + +/// Prepare a transfer's source texture. +/// +/// Ensure the source texture of a transfer is in the right initialization +/// state, and record the state for after the transfer operation. +fn handle_src_texture_init<A: HalApi>( + cmd_buf: &mut CommandBuffer<A>, + device: &Device<A>, + source: &ImageCopyTexture, + copy_size: &Extent3d, + texture_guard: &Storage<Texture<A>, TextureId>, +) -> Result<(), TransferError> { + let _ = texture_guard + .get(source.texture) + .map_err(|_| TransferError::InvalidTexture(source.texture))?; + + handle_texture_init( + MemoryInitKind::NeedsInitializedMemory, + cmd_buf, + device, + source, + copy_size, + texture_guard, + ); + Ok(()) +} + +/// Prepare a transfer's destination texture. +/// +/// Ensure the destination texture of a transfer is in the right initialization +/// state, and record the state for after the transfer operation. +fn handle_dst_texture_init<A: HalApi>( + cmd_buf: &mut CommandBuffer<A>, + device: &Device<A>, + destination: &ImageCopyTexture, + copy_size: &Extent3d, + texture_guard: &Storage<Texture<A>, TextureId>, +) -> Result<(), TransferError> { + let texture = texture_guard + .get(destination.texture) + .map_err(|_| TransferError::InvalidTexture(destination.texture))?; + + // Attention: If we don't write full texture subresources, we need to a full + // clear first since we don't track subrects. This means that in rare cases + // even a *destination* texture of a transfer may need an immediate texture + // init. + let dst_init_kind = if has_copy_partial_init_tracker_coverage( + copy_size, + destination.mip_level, + &texture.desc, + ) { + MemoryInitKind::NeedsInitializedMemory + } else { + MemoryInitKind::ImplicitlyInitialized + }; + + handle_texture_init( + dst_init_kind, + cmd_buf, + device, + destination, + copy_size, + texture_guard, + ); + Ok(()) +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn command_encoder_copy_buffer_to_buffer<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + source: BufferId, + source_offset: BufferAddress, + destination: BufferId, + destination_offset: BufferAddress, + size: BufferAddress, + ) -> Result<(), CopyError> { + profiling::scope!("CommandEncoder::copy_buffer_to_buffer"); + + if source == destination { + return Err(TransferError::SameSourceDestinationBuffer.into()); + } + let hub = A::hub(self); + let mut token = Token::root(); + + let (device_guard, mut token) = hub.devices.read(&mut token); + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let cmd_buf = CommandBuffer::get_encoder_mut(&mut *cmd_buf_guard, command_encoder_id)?; + let (buffer_guard, _) = hub.buffers.read(&mut token); + + let device = &device_guard[cmd_buf.device_id.value]; + + #[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 + .set_single(&*buffer_guard, source, hal::BufferUses::COPY_SRC) + .ok_or(TransferError::InvalidBuffer(source))?; + let src_raw = src_buffer + .raw + .as_ref() + .ok_or(TransferError::InvalidBuffer(source))?; + if !src_buffer.usage.contains(BufferUsages::COPY_SRC) { + return Err(TransferError::MissingCopySrcUsageFlag.into()); + } + // expecting only a single barrier + let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer)); + + let (dst_buffer, dst_pending) = cmd_buf + .trackers + .buffers + .set_single(&*buffer_guard, destination, hal::BufferUses::COPY_DST) + .ok_or(TransferError::InvalidBuffer(destination))?; + let dst_raw = dst_buffer + .raw + .as_ref() + .ok_or(TransferError::InvalidBuffer(destination))?; + if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { + return Err(TransferError::MissingCopyDstUsageFlag(Some(destination), None).into()); + } + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + + if size % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(TransferError::UnalignedCopySize(size).into()); + } + if source_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(TransferError::UnalignedBufferOffset(source_offset).into()); + } + if destination_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(TransferError::UnalignedBufferOffset(destination_offset).into()); + } + if !device + .downlevel + .flags + .contains(wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER) + && (src_buffer.usage.contains(wgt::BufferUsages::INDEX) + || dst_buffer.usage.contains(wgt::BufferUsages::INDEX)) + { + let forbidden_usages = wgt::BufferUsages::VERTEX + | wgt::BufferUsages::UNIFORM + | wgt::BufferUsages::INDIRECT + | wgt::BufferUsages::STORAGE; + if src_buffer.usage.intersects(forbidden_usages) + || dst_buffer.usage.intersects(forbidden_usages) + { + return Err(TransferError::MissingDownlevelFlags(MissingDownlevelFlags( + wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER, + )) + .into()); + } + } + + let source_end_offset = source_offset + size; + let destination_end_offset = destination_offset + size; + if source_end_offset > src_buffer.size { + return Err(TransferError::BufferOverrun { + start_offset: source_offset, + end_offset: source_end_offset, + buffer_size: src_buffer.size, + side: CopySide::Source, + } + .into()); + } + if destination_end_offset > dst_buffer.size { + return Err(TransferError::BufferOverrun { + start_offset: destination_offset, + end_offset: destination_end_offset, + buffer_size: dst_buffer.size, + side: CopySide::Destination, + } + .into()); + } + + if size == 0 { + log::trace!("Ignoring copy_buffer_to_buffer of size 0"); + return Ok(()); + } + + // Make sure source is initialized memory and mark dest as initialized. + cmd_buf + .buffer_memory_init_actions + .extend(dst_buffer.initialization_status.create_action( + destination, + destination_offset..(destination_offset + size), + MemoryInitKind::ImplicitlyInitialized, + )); + cmd_buf + .buffer_memory_init_actions + .extend(src_buffer.initialization_status.create_action( + source, + source_offset..(source_offset + size), + MemoryInitKind::NeedsInitializedMemory, + )); + + let region = hal::BufferCopy { + src_offset: source_offset, + dst_offset: destination_offset, + size: wgt::BufferSize::new(size).unwrap(), + }; + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.transition_buffers(src_barrier.into_iter().chain(dst_barrier)); + cmd_buf_raw.copy_buffer_to_buffer(src_raw, dst_raw, iter::once(region)); + } + Ok(()) + } + + pub fn command_encoder_copy_buffer_to_texture<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + source: &ImageCopyBuffer, + destination: &ImageCopyTexture, + copy_size: &Extent3d, + ) -> Result<(), CopyError> { + profiling::scope!("CommandEncoder::copy_buffer_to_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (device_guard, mut token) = hub.devices.read(&mut token); + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let cmd_buf = CommandBuffer::get_encoder_mut(&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 device = &device_guard[cmd_buf.device_id.value]; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::CopyBufferToTexture { + src: *source, + dst: *destination, + size: *copy_size, + }); + } + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { + log::trace!("Ignoring copy_buffer_to_texture of size 0"); + return Ok(()); + } + + let dst_texture = texture_guard + .get(destination.texture) + .map_err(|_| TransferError::InvalidTexture(destination.texture))?; + + let (hal_copy_size, array_layer_count) = validate_texture_copy_range( + destination, + &dst_texture.desc, + CopySide::Destination, + copy_size, + )?; + + let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, dst_texture)?; + + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_dst_texture_init(cmd_buf, device, destination, copy_size, &texture_guard)?; + + let (src_buffer, src_pending) = cmd_buf + .trackers + .buffers + .set_single(&*buffer_guard, source.buffer, hal::BufferUses::COPY_SRC) + .ok_or(TransferError::InvalidBuffer(source.buffer))?; + let src_raw = src_buffer + .raw + .as_ref() + .ok_or(TransferError::InvalidBuffer(source.buffer))?; + if !src_buffer.usage.contains(BufferUsages::COPY_SRC) { + return Err(TransferError::MissingCopySrcUsageFlag.into()); + } + let src_barrier = src_pending.map(|pending| pending.into_hal(src_buffer)); + + let dst_pending = cmd_buf + .trackers + .textures + .set_single( + dst_texture, + destination.texture, + dst_range, + hal::TextureUses::COPY_DST, + ) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + let dst_raw = dst_texture + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(destination.texture))?; + if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) { + return Err( + TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), + ); + } + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_texture)); + + if !dst_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } + + if !conv::is_valid_copy_dst_texture_format(dst_texture.desc.format, destination.aspect) { + return Err(TransferError::CopyToForbiddenTextureFormat { + format: dst_texture.desc.format, + aspect: destination.aspect, + } + .into()); + } + + let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data( + &source.layout, + dst_texture.desc.format, + destination.aspect, + src_buffer.size, + CopySide::Source, + copy_size, + true, + )?; + + if dst_texture.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; + } + + cmd_buf + .buffer_memory_init_actions + .extend(src_buffer.initialization_status.create_action( + source.buffer, + source.layout.offset..(source.layout.offset + required_buffer_bytes_in_copy), + MemoryInitKind::NeedsInitializedMemory, + )); + + let regions = (0..array_layer_count).map(|rel_array_layer| { + let mut texture_base = dst_base.clone(); + texture_base.array_layer += rel_array_layer; + let mut buffer_layout = source.layout; + buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer; + hal::BufferTextureCopy { + buffer_layout, + texture_base, + size: hal_copy_size, + } + }); + + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.transition_textures(dst_barrier.into_iter()); + cmd_buf_raw.transition_buffers(src_barrier.into_iter()); + cmd_buf_raw.copy_buffer_to_texture(src_raw, dst_raw, regions); + } + Ok(()) + } + + pub fn command_encoder_copy_texture_to_buffer<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + source: &ImageCopyTexture, + destination: &ImageCopyBuffer, + copy_size: &Extent3d, + ) -> Result<(), CopyError> { + profiling::scope!("CommandEncoder::copy_texture_to_buffer"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (device_guard, mut token) = hub.devices.read(&mut token); + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let cmd_buf = CommandBuffer::get_encoder_mut(&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 device = &device_guard[cmd_buf.device_id.value]; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::CopyTextureToBuffer { + src: *source, + dst: *destination, + size: *copy_size, + }); + } + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { + log::trace!("Ignoring copy_texture_to_buffer of size 0"); + return Ok(()); + } + + let src_texture = texture_guard + .get(source.texture) + .map_err(|_| TransferError::InvalidTexture(source.texture))?; + + let (hal_copy_size, array_layer_count) = + validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?; + + let (src_range, src_base) = extract_texture_selector(source, copy_size, src_texture)?; + + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_src_texture_init(cmd_buf, device, source, copy_size, &texture_guard)?; + + let src_pending = cmd_buf + .trackers + .textures + .set_single( + src_texture, + source.texture, + src_range, + hal::TextureUses::COPY_SRC, + ) + .ok_or(TransferError::InvalidTexture(source.texture))?; + let src_raw = src_texture + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(source.texture))?; + if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) { + return Err(TransferError::MissingCopySrcUsageFlag.into()); + } + if src_texture.desc.sample_count != 1 { + return Err(TransferError::InvalidSampleCount { + sample_count: src_texture.desc.sample_count, + } + .into()); + } + if source.mip_level >= src_texture.desc.mip_level_count { + return Err(TransferError::InvalidMipLevel { + requested: source.mip_level, + count: src_texture.desc.mip_level_count, + } + .into()); + } + let src_barrier = src_pending.map(|pending| pending.into_hal(src_texture)); + + let (dst_buffer, dst_pending) = cmd_buf + .trackers + .buffers + .set_single( + &*buffer_guard, + destination.buffer, + hal::BufferUses::COPY_DST, + ) + .ok_or(TransferError::InvalidBuffer(destination.buffer))?; + let dst_raw = dst_buffer + .raw + .as_ref() + .ok_or(TransferError::InvalidBuffer(destination.buffer))?; + if !dst_buffer.usage.contains(BufferUsages::COPY_DST) { + return Err( + TransferError::MissingCopyDstUsageFlag(Some(destination.buffer), None).into(), + ); + } + let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer)); + + if !src_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } + + if !conv::is_valid_copy_src_texture_format(src_texture.desc.format, source.aspect) { + return Err(TransferError::CopyFromForbiddenTextureFormat { + format: src_texture.desc.format, + aspect: source.aspect, + } + .into()); + } + + let (required_buffer_bytes_in_copy, bytes_per_array_layer) = validate_linear_texture_data( + &destination.layout, + src_texture.desc.format, + source.aspect, + dst_buffer.size, + CopySide::Destination, + copy_size, + true, + )?; + + if src_texture.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; + } + + cmd_buf + .buffer_memory_init_actions + .extend(dst_buffer.initialization_status.create_action( + destination.buffer, + destination.layout.offset + ..(destination.layout.offset + required_buffer_bytes_in_copy), + MemoryInitKind::ImplicitlyInitialized, + )); + + let regions = (0..array_layer_count).map(|rel_array_layer| { + let mut texture_base = src_base.clone(); + texture_base.array_layer += rel_array_layer; + let mut buffer_layout = destination.layout; + buffer_layout.offset += rel_array_layer as u64 * bytes_per_array_layer; + hal::BufferTextureCopy { + buffer_layout, + texture_base, + size: hal_copy_size, + } + }); + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.transition_buffers(dst_barrier.into_iter()); + cmd_buf_raw.transition_textures(src_barrier.into_iter()); + cmd_buf_raw.copy_texture_to_buffer( + src_raw, + hal::TextureUses::COPY_SRC, + dst_raw, + regions, + ); + } + Ok(()) + } + + pub fn command_encoder_copy_texture_to_texture<A: HalApi>( + &self, + command_encoder_id: CommandEncoderId, + source: &ImageCopyTexture, + destination: &ImageCopyTexture, + copy_size: &Extent3d, + ) -> Result<(), CopyError> { + profiling::scope!("CommandEncoder::copy_texture_to_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (device_guard, mut token) = hub.devices.read(&mut token); + let (mut cmd_buf_guard, mut token) = hub.command_buffers.write(&mut token); + let cmd_buf = CommandBuffer::get_encoder_mut(&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); + + let device = &device_guard[cmd_buf.device_id.value]; + + #[cfg(feature = "trace")] + if let Some(ref mut list) = cmd_buf.commands { + list.push(TraceCommand::CopyTextureToTexture { + src: *source, + dst: *destination, + size: *copy_size, + }); + } + + if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { + log::trace!("Ignoring copy_texture_to_texture of size 0"); + return Ok(()); + } + + let src_texture = texture_guard + .get(source.texture) + .map_err(|_| TransferError::InvalidTexture(source.texture))?; + let dst_texture = texture_guard + .get(destination.texture) + .map_err(|_| TransferError::InvalidTexture(source.texture))?; + + // src and dst texture format must be copy-compatible + // https://gpuweb.github.io/gpuweb/#copy-compatible + if src_texture.desc.format.remove_srgb_suffix() + != dst_texture.desc.format.remove_srgb_suffix() + { + return Err(TransferError::TextureFormatsNotCopyCompatible { + src_format: src_texture.desc.format, + dst_format: dst_texture.desc.format, + } + .into()); + } + + let (src_copy_size, array_layer_count) = + validate_texture_copy_range(source, &src_texture.desc, CopySide::Source, copy_size)?; + let (dst_copy_size, _) = validate_texture_copy_range( + destination, + &dst_texture.desc, + CopySide::Destination, + copy_size, + )?; + + let (src_range, src_tex_base) = extract_texture_selector(source, copy_size, src_texture)?; + let (dst_range, dst_tex_base) = + extract_texture_selector(destination, copy_size, dst_texture)?; + let src_texture_aspects = hal::FormatAspects::from(src_texture.desc.format); + let dst_texture_aspects = hal::FormatAspects::from(dst_texture.desc.format); + if src_tex_base.aspect != src_texture_aspects { + return Err(TransferError::CopySrcMissingAspects.into()); + } + if dst_tex_base.aspect != dst_texture_aspects { + return Err(TransferError::CopyDstMissingAspects.into()); + } + + // Handle texture init *before* dealing with barrier transitions so we + // have an easier time inserting "immediate-inits" that may be required + // by prior discards in rare cases. + handle_src_texture_init(cmd_buf, device, source, copy_size, &texture_guard)?; + handle_dst_texture_init(cmd_buf, device, destination, copy_size, &texture_guard)?; + + let src_pending = cmd_buf + .trackers + .textures + .set_single( + src_texture, + source.texture, + src_range, + hal::TextureUses::COPY_SRC, + ) + .ok_or(TransferError::InvalidTexture(source.texture))?; + let src_raw = src_texture + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(source.texture))?; + if !src_texture.desc.usage.contains(TextureUsages::COPY_SRC) { + return Err(TransferError::MissingCopySrcUsageFlag.into()); + } + + //TODO: try to avoid this the collection. It's needed because both + // `src_pending` and `dst_pending` try to hold `trackers.textures` mutably. + let mut barriers: ArrayVec<_, 2> = src_pending + .map(|pending| pending.into_hal(src_texture)) + .collect(); + + let dst_pending = cmd_buf + .trackers + .textures + .set_single( + dst_texture, + destination.texture, + dst_range, + hal::TextureUses::COPY_DST, + ) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + let dst_raw = dst_texture + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(destination.texture))?; + if !dst_texture.desc.usage.contains(TextureUsages::COPY_DST) { + return Err( + TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), + ); + } + + barriers.extend(dst_pending.map(|pending| pending.into_hal(dst_texture))); + + let hal_copy_size = hal::CopyExtent { + width: src_copy_size.width.min(dst_copy_size.width), + height: src_copy_size.height.min(dst_copy_size.height), + depth: src_copy_size.depth.min(dst_copy_size.depth), + }; + let regions = (0..array_layer_count).map(|rel_array_layer| { + let mut src_base = src_tex_base.clone(); + let mut dst_base = dst_tex_base.clone(); + src_base.array_layer += rel_array_layer; + dst_base.array_layer += rel_array_layer; + hal::TextureCopy { + src_base, + dst_base, + size: hal_copy_size, + } + }); + let cmd_buf_raw = cmd_buf.encoder.open(); + unsafe { + cmd_buf_raw.transition_textures(barriers.into_iter()); + cmd_buf_raw.copy_texture_to_texture( + src_raw, + hal::TextureUses::COPY_SRC, + dst_raw, + regions, + ); + } + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/conv.rs b/third_party/rust/wgpu-core/src/conv.rs new file mode 100644 index 0000000000..43986c946e --- /dev/null +++ b/third_party/rust/wgpu-core/src/conv.rs @@ -0,0 +1,193 @@ +use crate::resource; + +pub fn is_power_of_two_u16(val: u16) -> bool { + val != 0 && (val & (val - 1)) == 0 +} + +pub fn is_power_of_two_u32(val: u32) -> bool { + val != 0 && (val & (val - 1)) == 0 +} + +pub fn is_valid_copy_src_texture_format( + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, +) -> bool { + use wgt::TextureAspect as Ta; + use wgt::TextureFormat as Tf; + match (format, aspect) { + (Tf::Depth24Plus, _) | (Tf::Depth24PlusStencil8, Ta::DepthOnly) => false, + _ => true, + } +} + +pub fn is_valid_copy_dst_texture_format( + format: wgt::TextureFormat, + aspect: wgt::TextureAspect, +) -> bool { + use wgt::TextureAspect as Ta; + use wgt::TextureFormat as Tf; + match (format, aspect) { + (Tf::Depth24Plus | Tf::Depth32Float, _) + | (Tf::Depth24PlusStencil8 | Tf::Depth32FloatStencil8, Ta::DepthOnly) => false, + _ => true, + } +} + +#[cfg_attr( + any(not(target_arch = "wasm32"), target_os = "emscripten"), + allow(unused) +)] +pub fn is_valid_external_image_copy_dst_texture_format(format: wgt::TextureFormat) -> bool { + use wgt::TextureFormat as Tf; + match format { + Tf::R8Unorm + | Tf::R16Float + | Tf::R32Float + | Tf::Rg8Unorm + | Tf::Rg16Float + | Tf::Rg32Float + | Tf::Rgba8Unorm + | Tf::Rgba8UnormSrgb + | Tf::Bgra8Unorm + | Tf::Bgra8UnormSrgb + | Tf::Rgb10a2Unorm + | Tf::Rgba16Float + | Tf::Rgba32Float => true, + _ => false, + } +} + +pub fn map_buffer_usage(usage: wgt::BufferUsages) -> hal::BufferUses { + let mut u = hal::BufferUses::empty(); + u.set( + hal::BufferUses::MAP_READ, + usage.contains(wgt::BufferUsages::MAP_READ), + ); + u.set( + hal::BufferUses::MAP_WRITE, + usage.contains(wgt::BufferUsages::MAP_WRITE), + ); + u.set( + hal::BufferUses::COPY_SRC, + usage.contains(wgt::BufferUsages::COPY_SRC), + ); + u.set( + hal::BufferUses::COPY_DST, + usage.contains(wgt::BufferUsages::COPY_DST), + ); + u.set( + hal::BufferUses::INDEX, + usage.contains(wgt::BufferUsages::INDEX), + ); + u.set( + hal::BufferUses::VERTEX, + usage.contains(wgt::BufferUsages::VERTEX), + ); + u.set( + hal::BufferUses::UNIFORM, + usage.contains(wgt::BufferUsages::UNIFORM), + ); + u.set( + hal::BufferUses::STORAGE_READ | hal::BufferUses::STORAGE_READ_WRITE, + usage.contains(wgt::BufferUsages::STORAGE), + ); + u.set( + hal::BufferUses::INDIRECT, + usage.contains(wgt::BufferUsages::INDIRECT), + ); + u +} + +pub fn map_texture_usage( + usage: wgt::TextureUsages, + aspect: hal::FormatAspects, +) -> hal::TextureUses { + let mut u = hal::TextureUses::empty(); + u.set( + hal::TextureUses::COPY_SRC, + usage.contains(wgt::TextureUsages::COPY_SRC), + ); + u.set( + hal::TextureUses::COPY_DST, + usage.contains(wgt::TextureUsages::COPY_DST), + ); + u.set( + hal::TextureUses::RESOURCE, + usage.contains(wgt::TextureUsages::TEXTURE_BINDING), + ); + u.set( + hal::TextureUses::STORAGE_READ | hal::TextureUses::STORAGE_READ_WRITE, + usage.contains(wgt::TextureUsages::STORAGE_BINDING), + ); + let is_color = aspect.contains(hal::FormatAspects::COLOR); + u.set( + hal::TextureUses::COLOR_TARGET, + usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) && is_color, + ); + u.set( + hal::TextureUses::DEPTH_STENCIL_READ | hal::TextureUses::DEPTH_STENCIL_WRITE, + usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) && !is_color, + ); + u +} + +pub fn check_texture_dimension_size( + dimension: wgt::TextureDimension, + wgt::Extent3d { + width, + height, + depth_or_array_layers, + }: wgt::Extent3d, + sample_size: u32, + limits: &wgt::Limits, +) -> Result<(), resource::TextureDimensionError> { + use resource::{TextureDimensionError as Tde, TextureErrorDimension as Ted}; + use wgt::TextureDimension::*; + + let (extent_limits, sample_limit) = match dimension { + D1 => ([limits.max_texture_dimension_1d, 1, 1], 1), + D2 => ( + [ + limits.max_texture_dimension_2d, + limits.max_texture_dimension_2d, + limits.max_texture_array_layers, + ], + 32, + ), + D3 => ( + [ + limits.max_texture_dimension_3d, + limits.max_texture_dimension_3d, + limits.max_texture_dimension_3d, + ], + 1, + ), + }; + + for (&dim, (&given, &limit)) in [Ted::X, Ted::Y, Ted::Z].iter().zip( + [width, height, depth_or_array_layers] + .iter() + .zip(extent_limits.iter()), + ) { + if given == 0 { + return Err(Tde::Zero(dim)); + } + if given > limit { + return Err(Tde::LimitExceeded { dim, given, limit }); + } + } + if sample_size == 0 || sample_size > sample_limit || !is_power_of_two_u32(sample_size) { + return Err(Tde::InvalidSampleCount(sample_size)); + } + + Ok(()) +} + +pub fn bind_group_layout_flags(features: wgt::Features) -> hal::BindGroupLayoutFlags { + let mut flags = hal::BindGroupLayoutFlags::empty(); + flags.set( + hal::BindGroupLayoutFlags::PARTIALLY_BOUND, + features.contains(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY), + ); + flags +} diff --git a/third_party/rust/wgpu-core/src/device/life.rs b/third_party/rust/wgpu-core/src/device/life.rs new file mode 100644 index 0000000000..24995e719a --- /dev/null +++ b/third_party/rust/wgpu-core/src/device/life.rs @@ -0,0 +1,911 @@ +#[cfg(feature = "trace")] +use crate::device::trace; +use crate::{ + device::{ + queue::{EncoderInFlight, SubmittedWorkDoneClosure, TempResource}, + DeviceError, + }, + hub::{GlobalIdentityHandlerFactory, HalApi, Hub, Token}, + id, resource, + track::{BindGroupStates, RenderBundleScope, Tracker}, + RefCount, Stored, SubmissionIndex, +}; +use smallvec::SmallVec; + +use hal::Device as _; +use parking_lot::Mutex; +use thiserror::Error; + +use std::mem; + +/// A struct that keeps lists of resources that are no longer needed by the user. +#[derive(Debug, Default)] +pub(super) struct SuspectedResources { + pub(super) buffers: Vec<id::Valid<id::BufferId>>, + pub(super) textures: Vec<id::Valid<id::TextureId>>, + pub(super) texture_views: Vec<id::Valid<id::TextureViewId>>, + pub(super) samplers: Vec<id::Valid<id::SamplerId>>, + pub(super) bind_groups: Vec<id::Valid<id::BindGroupId>>, + pub(super) compute_pipelines: Vec<id::Valid<id::ComputePipelineId>>, + pub(super) render_pipelines: Vec<id::Valid<id::RenderPipelineId>>, + pub(super) bind_group_layouts: Vec<id::Valid<id::BindGroupLayoutId>>, + pub(super) pipeline_layouts: Vec<Stored<id::PipelineLayoutId>>, + pub(super) render_bundles: Vec<id::Valid<id::RenderBundleId>>, + pub(super) query_sets: Vec<id::Valid<id::QuerySetId>>, +} + +impl SuspectedResources { + pub(super) 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(); + self.query_sets.clear(); + } + + pub(super) 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); + self.query_sets.extend_from_slice(&other.query_sets); + } + + pub(super) fn add_render_bundle_scope<A: HalApi>(&mut self, trackers: &RenderBundleScope<A>) { + self.buffers.extend(trackers.buffers.used()); + self.textures.extend(trackers.textures.used()); + self.bind_groups.extend(trackers.bind_groups.used()); + self.render_pipelines + .extend(trackers.render_pipelines.used()); + self.query_sets.extend(trackers.query_sets.used()); + } + + pub(super) fn add_bind_group_states<A: HalApi>(&mut self, trackers: &BindGroupStates<A>) { + 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()); + } +} + +/// Raw backend resources that should be freed shortly. +#[derive(Debug)] +struct NonReferencedResources<A: hal::Api> { + buffers: Vec<A::Buffer>, + textures: Vec<A::Texture>, + texture_views: Vec<A::TextureView>, + samplers: Vec<A::Sampler>, + bind_groups: Vec<A::BindGroup>, + compute_pipes: Vec<A::ComputePipeline>, + render_pipes: Vec<A::RenderPipeline>, + bind_group_layouts: Vec<A::BindGroupLayout>, + pipeline_layouts: Vec<A::PipelineLayout>, + query_sets: Vec<A::QuerySet>, +} + +impl<A: hal::Api> NonReferencedResources<A> { + fn new() -> Self { + Self { + buffers: Vec::new(), + textures: Vec::new(), + texture_views: Vec::new(), + samplers: Vec::new(), + bind_groups: Vec::new(), + compute_pipes: Vec::new(), + render_pipes: Vec::new(), + bind_group_layouts: Vec::new(), + pipeline_layouts: Vec::new(), + query_sets: Vec::new(), + } + } + + fn extend(&mut self, other: Self) { + self.buffers.extend(other.buffers); + self.textures.extend(other.textures); + self.texture_views.extend(other.texture_views); + self.samplers.extend(other.samplers); + self.bind_groups.extend(other.bind_groups); + self.compute_pipes.extend(other.compute_pipes); + self.render_pipes.extend(other.render_pipes); + self.query_sets.extend(other.query_sets); + assert!(other.bind_group_layouts.is_empty()); + assert!(other.pipeline_layouts.is_empty()); + } + + unsafe fn clean(&mut self, device: &A::Device) { + if !self.buffers.is_empty() { + profiling::scope!("destroy_buffers"); + for raw in self.buffers.drain(..) { + unsafe { device.destroy_buffer(raw) }; + } + } + if !self.textures.is_empty() { + profiling::scope!("destroy_textures"); + for raw in self.textures.drain(..) { + unsafe { device.destroy_texture(raw) }; + } + } + if !self.texture_views.is_empty() { + profiling::scope!("destroy_texture_views"); + for raw in self.texture_views.drain(..) { + unsafe { device.destroy_texture_view(raw) }; + } + } + if !self.samplers.is_empty() { + profiling::scope!("destroy_samplers"); + for raw in self.samplers.drain(..) { + unsafe { device.destroy_sampler(raw) }; + } + } + if !self.bind_groups.is_empty() { + profiling::scope!("destroy_bind_groups"); + for raw in self.bind_groups.drain(..) { + unsafe { device.destroy_bind_group(raw) }; + } + } + if !self.compute_pipes.is_empty() { + profiling::scope!("destroy_compute_pipelines"); + for raw in self.compute_pipes.drain(..) { + unsafe { device.destroy_compute_pipeline(raw) }; + } + } + if !self.render_pipes.is_empty() { + profiling::scope!("destroy_render_pipelines"); + for raw in self.render_pipes.drain(..) { + unsafe { device.destroy_render_pipeline(raw) }; + } + } + if !self.bind_group_layouts.is_empty() { + profiling::scope!("destroy_bind_group_layouts"); + for raw in self.bind_group_layouts.drain(..) { + unsafe { device.destroy_bind_group_layout(raw) }; + } + } + if !self.pipeline_layouts.is_empty() { + profiling::scope!("destroy_pipeline_layouts"); + for raw in self.pipeline_layouts.drain(..) { + unsafe { device.destroy_pipeline_layout(raw) }; + } + } + if !self.query_sets.is_empty() { + profiling::scope!("destroy_query_sets"); + for raw in self.query_sets.drain(..) { + unsafe { device.destroy_query_set(raw) }; + } + } + } +} + +/// Resources used by a queue submission, and work to be done once it completes. +struct ActiveSubmission<A: hal::Api> { + /// The index of the submission we track. + /// + /// When `Device::fence`'s value is greater than or equal to this, our queue + /// submission has completed. + index: SubmissionIndex, + + /// Resources to be freed once this queue submission has completed. + /// + /// When the device is polled, for completed submissions, + /// `triage_submissions` merges these into + /// `LifetimeTracker::free_resources`. From there, + /// `LifetimeTracker::cleanup` passes them to the hal to be freed. + /// + /// This includes things like temporary resources and resources that are + /// used by submitted commands but have been dropped by the user (meaning that + /// this submission is their last reference.) + last_resources: NonReferencedResources<A>, + + /// Buffers to be mapped once this submission has completed. + mapped: Vec<id::Valid<id::BufferId>>, + + encoders: Vec<EncoderInFlight<A>>, + work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum WaitIdleError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Tried to wait using a submission index from the wrong device. Submission index is from device {0:?}. Called poll on device {1:?}.")] + WrongSubmissionIndex(id::QueueId, id::DeviceId), + #[error("GPU got stuck :(")] + StuckGpu, +} + +/// Resource tracking for a device. +/// +/// ## Host mapping buffers +/// +/// A buffer cannot be mapped until all active queue submissions that use it +/// have completed. To that end: +/// +/// - Each buffer's `LifeGuard::submission_index` records the index of the +/// most recent queue submission that uses that buffer. +/// +/// - Calling `map_async` adds the buffer to `self.mapped`, and changes +/// `Buffer::map_state` to prevent it from being used in any new +/// submissions. +/// +/// - When the device is polled, the following `LifetimeTracker` methods decide +/// what should happen next: +/// +/// 1) `triage_mapped` drains `self.mapped`, checking the submission index +/// of each buffer against the queue submissions that have finished +/// execution. Buffers used by submissions still in flight go in +/// `self.active[index].mapped`, and the rest go into +/// `self.ready_to_map`. +/// +/// 2) `triage_submissions` moves entries in `self.active[i]` for completed +/// submissions to `self.ready_to_map`. At this point, both +/// `self.active` and `self.ready_to_map` are up to date with the given +/// submission index. +/// +/// 3) `handle_mapping` drains `self.ready_to_map` and actually maps the +/// buffers, collecting a list of notification closures to call. But any +/// buffers that were dropped by the user get moved to +/// `self.free_resources`. +/// +/// 4) `cleanup` frees everything in `free_resources`. +/// +/// Only `self.mapped` holds a `RefCount` for the buffer; it is dropped by +/// `triage_mapped`. +pub(super) struct LifetimeTracker<A: hal::Api> { + /// Resources that the user has requested be mapped, but which are used by + /// queue submissions still in flight. + 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 whose user handle has died (i.e. drop/destroy has been called) + /// and will likely be ready for destruction soon. + pub suspected_resources: SuspectedResources, + + /// Resources used by queue submissions still in flight. One entry per + /// submission, with older submissions appearing before younger. + /// + /// Entries are added by `track_submission` and drained by + /// `LifetimeTracker::triage_submissions`. Lots of methods contribute data + /// to particular entries. + active: Vec<ActiveSubmission<A>>, + + /// Raw backend resources that are neither referenced nor used. + /// + /// These are freed by `LifeTracker::cleanup`, which is called from periodic + /// maintenance functions like `Global::device_poll`, and when a device is + /// destroyed. + free_resources: NonReferencedResources<A>, + + /// Buffers the user has asked us to map, and which are not used by any + /// queue submission still in flight. + ready_to_map: Vec<id::Valid<id::BufferId>>, +} + +impl<A: hal::Api> LifetimeTracker<A> { + 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(), + } + } + + /// Return true if there are no queue submissions still in flight. + pub fn queue_empty(&self) -> bool { + self.active.is_empty() + } + + /// Start tracking resources associated with a new queue submission. + pub fn track_submission( + &mut self, + index: SubmissionIndex, + temp_resources: impl Iterator<Item = TempResource<A>>, + encoders: Vec<EncoderInFlight<A>>, + ) { + let mut last_resources = NonReferencedResources::new(); + for res in temp_resources { + match res { + TempResource::Buffer(raw) => last_resources.buffers.push(raw), + TempResource::Texture(raw, views) => { + last_resources.textures.push(raw); + last_resources.texture_views.extend(views); + } + } + } + + self.active.push(ActiveSubmission { + index, + last_resources, + mapped: Vec::new(), + encoders, + work_done_closures: SmallVec::new(), + }); + } + + pub fn post_submit(&mut self) { + 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), + ); + } + + pub(crate) fn map(&mut self, value: id::Valid<id::BufferId>, ref_count: RefCount) { + self.mapped.push(Stored { value, ref_count }); + } + + /// Sort out the consequences of completed submissions. + /// + /// Assume that all submissions up through `last_done` have completed. + /// + /// - Buffers used by those submissions are now ready to map, if + /// requested. Add any buffers in the submission's [`mapped`] list to + /// [`self.ready_to_map`], where [`LifetimeTracker::handle_mapping`] will find + /// them. + /// + /// - Resources whose final use was in those submissions are now ready to + /// free. Add any resources in the submission's [`last_resources`] table + /// to [`self.free_resources`], where [`LifetimeTracker::cleanup`] will find + /// them. + /// + /// Return a list of [`SubmittedWorkDoneClosure`]s to run. + /// + /// [`mapped`]: ActiveSubmission::mapped + /// [`self.ready_to_map`]: LifetimeTracker::ready_to_map + /// [`last_resources`]: ActiveSubmission::last_resources + /// [`self.free_resources`]: LifetimeTracker::free_resources + /// [`SubmittedWorkDoneClosure`]: crate::device::queue::SubmittedWorkDoneClosure + #[must_use] + pub fn triage_submissions( + &mut self, + last_done: SubmissionIndex, + command_allocator: &Mutex<super::CommandAllocator<A>>, + ) -> SmallVec<[SubmittedWorkDoneClosure; 1]> { + profiling::scope!("triage_submissions"); + + //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| a.index > last_done) + .unwrap_or(self.active.len()); + + let mut work_done_closures = SmallVec::new(); + for a in self.active.drain(..done_count) { + log::trace!("Active submission {} is done", a.index); + self.free_resources.extend(a.last_resources); + self.ready_to_map.extend(a.mapped); + for encoder in a.encoders { + let raw = unsafe { encoder.land() }; + command_allocator.lock().release_encoder(raw); + } + work_done_closures.extend(a.work_done_closures); + } + work_done_closures + } + + pub fn cleanup(&mut self, device: &A::Device) { + profiling::scope!("LifetimeTracker::cleanup"); + unsafe { + self.free_resources.clean(device); + } + } + + pub fn schedule_resource_destruction( + &mut self, + temp_resource: TempResource<A>, + 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), + TempResource::Texture(raw, views) => { + resources.texture_views.extend(views); + resources.textures.push(raw); + } + } + } + + pub fn add_work_done_closure( + &mut self, + closure: SubmittedWorkDoneClosure, + ) -> Option<SubmittedWorkDoneClosure> { + match self.active.last_mut() { + Some(active) => { + active.work_done_closures.push(closure); + None + } + // Note: we can't immediately invoke the closure, since it assumes + // nothing is currently locked in the hubs. + None => Some(closure), + } + } +} + +impl<A: HalApi> LifetimeTracker<A> { + /// Identify resources to free, according to `trackers` and `self.suspected_resources`. + /// + /// Given `trackers`, the [`Tracker`] belonging to same [`Device`] as + /// `self`, and `hub`, the [`Hub`] to which that `Device` belongs: + /// + /// Remove from `trackers` each resource mentioned in + /// [`self.suspected_resources`]. If `trackers` held the final reference to + /// that resource, add it to the appropriate free list, to be destroyed by + /// the hal: + /// + /// - Add resources used by queue submissions still in flight to the + /// [`last_resources`] table of the last such submission's entry in + /// [`self.active`]. When that submission has finished execution. the + /// [`triage_submissions`] method will move them to + /// [`self.free_resources`]. + /// + /// - Add resources that can be freed right now to [`self.free_resources`] + /// directly. [`LifetimeTracker::cleanup`] will take care of them as + /// part of this poll. + /// + /// ## Entrained resources + /// + /// This function finds resources that are used only by other resources + /// ready to be freed, and adds those to the free lists as well. For + /// example, if there's some texture `T` used only by some texture view + /// `TV`, then if `TV` can be freed, `T` gets added to the free lists too. + /// + /// Since `wgpu-core` resource ownership patterns are acyclic, we can visit + /// each type that can be owned after all types that could possibly own + /// it. This way, we can detect all free-able objects in a single pass, + /// simply by starting with types that are roots of the ownership DAG (like + /// render bundles) and working our way towards leaf types (like buffers). + /// + /// [`Device`]: super::Device + /// [`self.suspected_resources`]: LifetimeTracker::suspected_resources + /// [`last_resources`]: ActiveSubmission::last_resources + /// [`self.active`]: LifetimeTracker::active + /// [`triage_submissions`]: LifetimeTracker::triage_submissions + /// [`self.free_resources`]: LifetimeTracker::free_resources + pub(super) fn triage_suspected<G: GlobalIdentityHandlerFactory>( + &mut self, + hub: &Hub<A, G>, + trackers: &Mutex<Tracker<A>>, + #[cfg(feature = "trace")] trace: Option<&Mutex<trace::Trace>>, + token: &mut Token<super::Device<A>>, + ) { + profiling::scope!("triage_suspected"); + + if !self.suspected_resources.render_bundles.is_empty() { + let (mut guard, _) = hub.render_bundles.write(token); + let mut trackers = trackers.lock(); + + while let Some(id) = self.suspected_resources.render_bundles.pop() { + if trackers.bundles.remove_abandoned(id) { + log::debug!("Bundle {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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_render_bundle_scope(&res.used); + } + } + } + } + + if !self.suspected_resources.bind_groups.is_empty() { + let (mut guard, _) = hub.bind_groups.write(token); + let mut trackers = trackers.lock(); + + while let Some(id) = self.suspected_resources.bind_groups.pop() { + if trackers.bind_groups.remove_abandoned(id) { + log::debug!("Bind group {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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_bind_group_states(&res.used); + + self.suspected_resources + .bind_group_layouts + .push(res.layout_id); + + let submit_index = res.life_guard.life_count(); + self.active + .iter_mut() + .find(|a| a.index == submit_index) + .map_or(&mut self.free_resources, |a| &mut a.last_resources) + .bind_groups + .push(res.raw); + } + } + } + } + + if !self.suspected_resources.texture_views.is_empty() { + let (mut guard, _) = hub.texture_views.write(token); + let mut trackers = trackers.lock(); + + let mut list = mem::take(&mut self.suspected_resources.texture_views); + for id in list.drain(..) { + if trackers.views.remove_abandoned(id) { + log::debug!("Texture view {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + t.lock().add(trace::Action::DestroyTextureView(id.0)); + } + + if let Some(res) = hub.texture_views.unregister_locked(id.0, &mut *guard) { + self.suspected_resources.textures.push(res.parent_id.value); + let submit_index = res.life_guard.life_count(); + self.active + .iter_mut() + .find(|a| a.index == submit_index) + .map_or(&mut self.free_resources, |a| &mut a.last_resources) + .texture_views + .push(res.raw); + } + } + } + self.suspected_resources.texture_views = list; + } + + if !self.suspected_resources.textures.is_empty() { + let (mut guard, _) = hub.textures.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.textures.drain(..) { + if trackers.textures.remove_abandoned(id) { + log::debug!("Texture {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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.life_count(); + let raw = match res.inner { + resource::TextureInner::Native { raw: Some(raw) } => raw, + _ => continue, + }; + let non_referenced_resources = self + .active + .iter_mut() + .find(|a| a.index == submit_index) + .map_or(&mut self.free_resources, |a| &mut a.last_resources); + + non_referenced_resources.textures.push(raw); + if let resource::TextureClearMode::RenderPass { clear_views, .. } = + res.clear_mode + { + non_referenced_resources + .texture_views + .extend(clear_views.into_iter()); + } + } + } + } + } + + if !self.suspected_resources.samplers.is_empty() { + let (mut guard, _) = hub.samplers.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.samplers.drain(..) { + if trackers.samplers.remove_abandoned(id) { + log::debug!("Sampler {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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.life_count(); + 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 guard, _) = hub.buffers.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.buffers.drain(..) { + if trackers.buffers.remove_abandoned(id) { + log::debug!("Buffer {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + t.lock().add(trace::Action::DestroyBuffer(id.0)); + } + + if let Some(res) = hub.buffers.unregister_locked(id.0, &mut *guard) { + let submit_index = res.life_guard.life_count(); + if let resource::BufferMapState::Init { stage_buffer, .. } = res.map_state { + self.free_resources.buffers.push(stage_buffer); + } + 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 guard, _) = hub.compute_pipelines.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.compute_pipelines.drain(..) { + if trackers.compute_pipelines.remove_abandoned(id) { + log::debug!("Compute pipeline {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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.life_count(); + 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 guard, _) = hub.render_pipelines.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.render_pipelines.drain(..) { + if trackers.render_pipelines.remove_abandoned(id) { + log::debug!("Render pipeline {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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.life_count(); + self.active + .iter_mut() + .find(|a| a.index == submit_index) + .map_or(&mut self.free_resources, |a| &mut a.last_resources) + .render_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 { + log::debug!("Pipeline layout {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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() { + log::debug!("Bind group layout {:?} will be destroyed", id); + #[cfg(feature = "trace")] + if let Some(t) = trace { + 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.bind_group_layouts.push(lay.raw); + } + } + } + } + + if !self.suspected_resources.query_sets.is_empty() { + let (mut guard, _) = hub.query_sets.write(token); + let mut trackers = trackers.lock(); + + for id in self.suspected_resources.query_sets.drain(..) { + if trackers.query_sets.remove_abandoned(id) { + log::debug!("Query set {:?} will be destroyed", id); + // #[cfg(feature = "trace")] + // trace.map(|t| t.lock().add(trace::Action::DestroyComputePipeline(id.0))); + if let Some(res) = hub.query_sets.unregister_locked(id.0, &mut *guard) { + let submit_index = res.life_guard.life_count(); + self.active + .iter_mut() + .find(|a| a.index == submit_index) + .map_or(&mut self.free_resources, |a| &mut a.last_resources) + .query_sets + .push(res.raw); + } + } + } + } + } + + /// Determine which buffers are ready to map, and which must wait for the + /// GPU. + /// + /// See the documentation for [`LifetimeTracker`] for details. + pub(super) fn triage_mapped<G: GlobalIdentityHandlerFactory>( + &mut self, + hub: &Hub<A, G>, + token: &mut Token<super::Device<A>>, + ) { + 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.life_count(); + log::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); + } + } + + /// Map the buffers in `self.ready_to_map`. + /// + /// Return a list of mapping notifications to send. + /// + /// See the documentation for [`LifetimeTracker`] for details. + #[must_use] + pub(super) fn handle_mapping<G: GlobalIdentityHandlerFactory>( + &mut self, + hub: &Hub<A, G>, + raw: &A::Device, + trackers: &Mutex<Tracker<A>>, + token: &mut Token<super::Device<A>>, + ) -> Vec<super::BufferMapPendingClosure> { + if self.ready_to_map.is_empty() { + return Vec::new(); + } + let (mut buffer_guard, _) = hub.buffers.write(token); + let mut pending_callbacks: Vec<super::BufferMapPendingClosure> = + 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; + log::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, + // Mapping cancelled + resource::BufferMapState::Idle => continue, + // Mapping queued at least twice by map -> unmap -> map + // and was already successfully mapped below + active @ resource::BufferMapState::Active { .. } => { + buffer.map_state = active; + continue; + } + _ => panic!("No pending mapping."), + }; + let status = if mapping.range.start != mapping.range.end { + log::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, + range: mapping.range.start..mapping.range.start + size, + host, + }; + Ok(()) + } + Err(e) => { + log::error!("Mapping failed {:?}", e); + Err(e) + } + } + } else { + buffer.map_state = resource::BufferMapState::Active { + ptr: std::ptr::NonNull::dangling(), + range: mapping.range, + host: mapping.op.host, + }; + Ok(()) + }; + pending_callbacks.push((mapping.op, status)); + } + } + pending_callbacks + } +} diff --git a/third_party/rust/wgpu-core/src/device/mod.rs b/third_party/rust/wgpu-core/src/device/mod.rs new file mode 100644 index 0000000000..36d353b672 --- /dev/null +++ b/third_party/rust/wgpu-core/src/device/mod.rs @@ -0,0 +1,6091 @@ +use crate::{ + binding_model, command, conv, + device::life::WaitIdleError, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Hub, Input, InvalidId, Storage, Token}, + id, + init_tracker::{ + BufferInitTracker, BufferInitTrackerAction, MemoryInitKind, TextureInitRange, + TextureInitTracker, TextureInitTrackerAction, + }, + instance::{self, Adapter, Surface}, + pipeline, present, + resource::{self, BufferAccessResult, BufferMapState, TextureViewNotRenderableReason}, + resource::{BufferAccessError, BufferMapOperation}, + track::{BindGroupStates, TextureSelector, Tracker}, + validation::{self, check_buffer_usage, check_texture_usage}, + FastHashMap, Label, LabelHelpers as _, LifeGuard, MultiRefCount, RefCount, Stored, + SubmissionIndex, DOWNLEVEL_ERROR_MESSAGE, +}; + +use arrayvec::ArrayVec; +use hal::{CommandEncoder as _, Device as _}; +use parking_lot::{Mutex, MutexGuard}; +use smallvec::SmallVec; +use thiserror::Error; +use wgt::{BufferAddress, TextureFormat, TextureSampleType, TextureViewDimension}; + +use std::{borrow::Cow, iter, mem, num::NonZeroU32, ops::Range, ptr}; + +mod life; +pub mod queue; +#[cfg(any(feature = "trace", feature = "replay"))] +pub mod trace; + +pub const SHADER_STAGE_COUNT: usize = 3; +// Should be large enough for the largest possible texture row. This +// value is enough for a 16k texture with float4 format. +pub(crate) const ZERO_BUFFER_SIZE: BufferAddress = 512 << 10; + +const CLEANUP_WAIT_MS: u32 = 5000; + +const IMPLICIT_FAILURE: &str = "failed implicit"; +const EP_FAILURE: &str = "EP is invalid"; + +pub type DeviceDescriptor<'a> = wgt::DeviceDescriptor<Label<'a>>; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Eq, 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<Option<T>, { hal::MAX_COLOR_ATTACHMENTS }>, + pub resolves: ArrayVec<T, { hal::MAX_COLOR_ATTACHMENTS }>, + pub depth_stencil: Option<T>, +} +impl<T: PartialEq> Eq for AttachmentData<T> {} +impl<T> AttachmentData<T> { + pub(crate) fn map<U, F: Fn(&T) -> U>(&self, fun: F) -> AttachmentData<U> { + AttachmentData { + colors: self.colors.iter().map(|c| c.as_ref().map(&fun)).collect(), + resolves: self.resolves.iter().map(&fun).collect(), + depth_stencil: self.depth_stencil.as_ref().map(&fun), + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum RenderPassCompatibilityCheckType { + RenderPipeline, + RenderBundle, +} + +#[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: u32, + pub multiview: Option<NonZeroU32>, +} +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum RenderPassCompatibilityError { + #[error( + "Incompatible color attachments at indices {indices:?}: the RenderPass uses textures with formats {expected:?} but the {ty:?} uses attachments with formats {actual:?}", + )] + IncompatibleColorAttachment { + indices: Vec<usize>, + expected: Vec<Option<TextureFormat>>, + actual: Vec<Option<TextureFormat>>, + ty: RenderPassCompatibilityCheckType, + }, + #[error( + "Incompatible depth-stencil attachment format: the RenderPass uses a texture with format {expected:?} but the {ty:?} uses an attachment with format {actual:?}", + )] + IncompatibleDepthStencilAttachment { + expected: Option<TextureFormat>, + actual: Option<TextureFormat>, + ty: RenderPassCompatibilityCheckType, + }, + #[error( + "Incompatible sample count: the RenderPass uses textures with sample count {expected:?} but the {ty:?} uses attachments with format {actual:?}", + )] + IncompatibleSampleCount { + expected: u32, + actual: u32, + ty: RenderPassCompatibilityCheckType, + }, + #[error("Incompatible multiview setting: the RenderPass uses setting {expected:?} but the {ty:?} uses setting {actual:?}")] + IncompatibleMultiview { + expected: Option<NonZeroU32>, + actual: Option<NonZeroU32>, + ty: RenderPassCompatibilityCheckType, + }, +} + +impl RenderPassContext { + // Assumes the renderpass only contains one subpass + pub(crate) fn check_compatible( + &self, + other: &Self, + ty: RenderPassCompatibilityCheckType, + ) -> Result<(), RenderPassCompatibilityError> { + if self.attachments.colors != other.attachments.colors { + let indices = self + .attachments + .colors + .iter() + .zip(&other.attachments.colors) + .enumerate() + .filter_map(|(idx, (left, right))| (left != right).then_some(idx)) + .collect(); + return Err(RenderPassCompatibilityError::IncompatibleColorAttachment { + indices, + expected: self.attachments.colors.iter().cloned().collect(), + actual: other.attachments.colors.iter().cloned().collect(), + ty, + }); + } + if self.attachments.depth_stencil != other.attachments.depth_stencil { + return Err( + RenderPassCompatibilityError::IncompatibleDepthStencilAttachment { + expected: self.attachments.depth_stencil, + actual: other.attachments.depth_stencil, + ty, + }, + ); + } + if self.sample_count != other.sample_count { + return Err(RenderPassCompatibilityError::IncompatibleSampleCount { + expected: self.sample_count, + actual: other.sample_count, + ty, + }); + } + if self.multiview != other.multiview { + return Err(RenderPassCompatibilityError::IncompatibleMultiview { + expected: self.multiview, + actual: other.multiview, + ty, + }); + } + Ok(()) + } +} + +pub type BufferMapPendingClosure = (BufferMapOperation, BufferAccessResult); + +#[derive(Default)] +pub struct UserClosures { + pub mappings: Vec<BufferMapPendingClosure>, + pub submissions: SmallVec<[queue::SubmittedWorkDoneClosure; 1]>, +} + +impl UserClosures { + fn extend(&mut self, other: Self) { + self.mappings.extend(other.mappings); + self.submissions.extend(other.submissions); + } + + fn fire(self) { + // Note: this logic is specifically moved out of `handle_mapping()` in order to + // have nothing locked by the time we execute users callback code. + for (operation, status) in self.mappings { + operation.callback.call(status); + } + for closure in self.submissions { + closure.call(); + } + } +} + +fn map_buffer<A: hal::Api>( + raw: &A::Device, + buffer: &mut resource::Buffer<A>, + offset: BufferAddress, + size: BufferAddress, + kind: HostMap, +) -> Result<ptr::NonNull<u8>, BufferAccessError> { + let mapping = unsafe { + raw.map_buffer(buffer.raw.as_ref().unwrap(), offset..offset + size) + .map_err(DeviceError::from)? + }; + + buffer.sync_mapped_writes = match kind { + HostMap::Read if !mapping.is_coherent => unsafe { + raw.invalidate_mapped_ranges( + buffer.raw.as_ref().unwrap(), + iter::once(offset..offset + size), + ); + None + }, + HostMap::Write if !mapping.is_coherent => Some(offset..offset + size), + _ => None, + }; + + assert_eq!(offset % wgt::COPY_BUFFER_ALIGNMENT, 0); + assert_eq!(size % wgt::COPY_BUFFER_ALIGNMENT, 0); + // Zero out uninitialized parts of the mapping. (Spec dictates all resources + // behave as if they were initialized with zero) + // + // If this is a read mapping, ideally we would use a `clear_buffer` command + // before reading the data from GPU (i.e. `invalidate_range`). However, this + // would require us to kick off and wait for a command buffer or piggy back + // on an existing one (the later is likely the only worthwhile option). As + // reading uninitialized memory isn't a particular important path to + // support, we instead just initialize the memory here and make sure it is + // GPU visible, so this happens at max only once for every buffer region. + // + // If this is a write mapping zeroing out the memory here is the only + // reasonable way as all data is pushed to GPU anyways. + + // No need to flush if it is flushed later anyways. + let zero_init_needs_flush_now = mapping.is_coherent && buffer.sync_mapped_writes.is_none(); + let mapped = unsafe { std::slice::from_raw_parts_mut(mapping.ptr.as_ptr(), size as usize) }; + + for uninitialized in buffer.initialization_status.drain(offset..(size + offset)) { + // The mapping's pointer is already offset, however we track the + // uninitialized range relative to the buffer's start. + let fill_range = + (uninitialized.start - offset) as usize..(uninitialized.end - offset) as usize; + mapped[fill_range].fill(0); + + if zero_init_needs_flush_now { + unsafe { + raw.flush_mapped_ranges(buffer.raw.as_ref().unwrap(), iter::once(uninitialized)) + }; + } + } + + Ok(mapping.ptr) +} + +struct CommandAllocator<A: hal::Api> { + free_encoders: Vec<A::CommandEncoder>, +} + +impl<A: hal::Api> CommandAllocator<A> { + fn acquire_encoder( + &mut self, + device: &A::Device, + queue: &A::Queue, + ) -> Result<A::CommandEncoder, hal::DeviceError> { + match self.free_encoders.pop() { + Some(encoder) => Ok(encoder), + None => unsafe { + let hal_desc = hal::CommandEncoderDescriptor { label: None, queue }; + device.create_command_encoder(&hal_desc) + }, + } + } + + fn release_encoder(&mut self, encoder: A::CommandEncoder) { + self.free_encoders.push(encoder); + } + + fn dispose(self, device: &A::Device) { + log::info!("Destroying {} command encoders", self.free_encoders.len()); + for cmd_encoder in self.free_encoders { + unsafe { + device.destroy_command_encoder(cmd_encoder); + } + } + } +} + +/// Structure describing a logical device. Some members are internally mutable, +/// stored behind mutexes. +/// +/// TODO: establish clear order of locking for these: +/// `mem_allocator`, `desc_allocator`, `life_tracker`, `trackers`, +/// `render_passes`, `pending_writes`, `trace`. +/// +/// Currently, the rules are: +/// 1. `life_tracker` is locked after `hub.devices`, enforced by the type system +/// 1. `self.trackers` is locked last (unenforced) +/// 1. `self.trace` is locked last (unenforced) +pub struct Device<A: HalApi> { + pub(crate) raw: A::Device, + pub(crate) adapter_id: Stored<id::AdapterId>, + pub(crate) queue: A::Queue, + pub(crate) zero_buffer: A::Buffer, + //pub(crate) cmd_allocator: command::CommandAllocator<A>, + //mem_allocator: Mutex<alloc::MemoryAllocator<A>>, + //desc_allocator: Mutex<descriptor::DescriptorAllocator<A>>, + //Note: The submission index here corresponds to the last submission that is done. + pub(crate) life_guard: LifeGuard, + + /// A clone of `life_guard.ref_count`. + /// + /// Holding a separate clone of the `RefCount` here lets us tell whether the + /// device is referenced by other resources, even if `life_guard.ref_count` + /// was set to `None` by a call to `device_drop`. + ref_count: RefCount, + + command_allocator: Mutex<CommandAllocator<A>>, + pub(crate) active_submission_index: SubmissionIndex, + fence: A::Fence, + + /// All live resources allocated with this [`Device`]. + /// + /// Has to be locked temporarily only (locked last) + pub(crate) trackers: Mutex<Tracker<A>>, + // Life tracker should be locked right after the device and before anything else. + life_tracker: Mutex<life::LifetimeTracker<A>>, + /// Temporary storage for resource management functions. Cleared at the end + /// of every call (unless an error occurs). + temp_suspected: life::SuspectedResources, + pub(crate) alignments: hal::Alignments, + pub(crate) limits: wgt::Limits, + pub(crate) features: wgt::Features, + pub(crate) downlevel: wgt::DownlevelCapabilities, + // 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<A>, + #[cfg(feature = "trace")] + pub(crate) trace: Option<Mutex<trace::Trace>>, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateDeviceError { + #[error("Not enough memory left")] + OutOfMemory, + #[error("Failed to create internal buffer for initializing textures")] + FailedToCreateZeroBuffer(#[from] DeviceError), +} + +impl<A: HalApi> Device<A> { + pub(crate) fn require_features(&self, feature: wgt::Features) -> Result<(), MissingFeatures> { + if self.features.contains(feature) { + Ok(()) + } else { + Err(MissingFeatures(feature)) + } + } + + pub(crate) fn require_downlevel_flags( + &self, + flags: wgt::DownlevelFlags, + ) -> Result<(), MissingDownlevelFlags> { + if self.downlevel.flags.contains(flags) { + Ok(()) + } else { + Err(MissingDownlevelFlags(flags)) + } + } +} + +impl<A: HalApi> Device<A> { + pub(crate) fn new( + open: hal::OpenDevice<A>, + adapter_id: Stored<id::AdapterId>, + alignments: hal::Alignments, + downlevel: wgt::DownlevelCapabilities, + desc: &DeviceDescriptor, + trace_path: Option<&std::path::Path>, + ) -> Result<Self, CreateDeviceError> { + #[cfg(not(feature = "trace"))] + if let Some(_) = trace_path { + log::error!("Feature 'trace' is not enabled"); + } + let fence = + unsafe { open.device.create_fence() }.map_err(|_| CreateDeviceError::OutOfMemory)?; + + let mut com_alloc = CommandAllocator { + free_encoders: Vec::new(), + }; + let pending_encoder = com_alloc + .acquire_encoder(&open.device, &open.queue) + .map_err(|_| CreateDeviceError::OutOfMemory)?; + let mut pending_writes = queue::PendingWrites::<A>::new(pending_encoder); + + // Create zeroed buffer used for texture clears. + let zero_buffer = unsafe { + open.device + .create_buffer(&hal::BufferDescriptor { + label: Some("(wgpu internal) zero init buffer"), + size: ZERO_BUFFER_SIZE, + usage: hal::BufferUses::COPY_SRC | hal::BufferUses::COPY_DST, + memory_flags: hal::MemoryFlags::empty(), + }) + .map_err(DeviceError::from)? + }; + pending_writes.activate(); + unsafe { + pending_writes + .command_encoder + .transition_buffers(iter::once(hal::BufferBarrier { + buffer: &zero_buffer, + usage: hal::BufferUses::empty()..hal::BufferUses::COPY_DST, + })); + pending_writes + .command_encoder + .clear_buffer(&zero_buffer, 0..ZERO_BUFFER_SIZE); + pending_writes + .command_encoder + .transition_buffers(iter::once(hal::BufferBarrier { + buffer: &zero_buffer, + usage: hal::BufferUses::COPY_DST..hal::BufferUses::COPY_SRC, + })); + } + + let life_guard = LifeGuard::new("<device>"); + let ref_count = life_guard.add_ref(); + Ok(Self { + raw: open.device, + adapter_id, + queue: open.queue, + zero_buffer, + life_guard, + ref_count, + command_allocator: Mutex::new(com_alloc), + active_submission_index: 0, + fence, + trackers: Mutex::new(Tracker::new()), + life_tracker: Mutex::new(life::LifetimeTracker::new()), + temp_suspected: life::SuspectedResources::default(), + #[cfg(feature = "trace")] + trace: trace_path.and_then(|path| match trace::Trace::new(path) { + Ok(mut trace) => { + trace.add(trace::Action::Init { + desc: desc.clone(), + backend: A::VARIANT, + }); + Some(Mutex::new(trace)) + } + Err(e) => { + log::error!("Unable to start a trace in '{:?}': {:?}", path, e); + None + } + }), + alignments, + limits: desc.limits.clone(), + features: desc.features, + downlevel, + pending_writes, + }) + } + + 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<A>> { + self.life_tracker.lock() + } + + /// Check this device for completed commands. + /// + /// The `maintain` argument tells how the maintence function should behave, either + /// blocking or just polling the current state of the gpu. + /// + /// Return a pair `(closures, queue_empty)`, where: + /// + /// - `closures` is a list of actions to take: mapping buffers, notifying the user + /// + /// - `queue_empty` is a boolean indicating whether there are more queue + /// submissions still in flight. (We have to take the locks needed to + /// produce this information for other reasons, so we might as well just + /// return it to our callers.) + fn maintain<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>( + &'this self, + hub: &Hub<A, G>, + maintain: wgt::Maintain<queue::WrappedSubmissionIndex>, + token: &mut Token<'token, Self>, + ) -> Result<(UserClosures, bool), WaitIdleError> { + profiling::scope!("Device::maintain"); + let mut life_tracker = self.lock_life(token); + + // Normally, `temp_suspected` exists only to save heap + // allocations: it's cleared at the start of the function + // call, and cleared by the end. But `Global::queue_submit` is + // fallible; if it exits early, it may leave some resources in + // `temp_suspected`. + life_tracker + .suspected_resources + .extend(&self.temp_suspected); + + life_tracker.triage_suspected( + hub, + &self.trackers, + #[cfg(feature = "trace")] + self.trace.as_ref(), + token, + ); + life_tracker.triage_mapped(hub, token); + + let last_done_index = if maintain.is_wait() { + let index_to_wait_for = match maintain { + wgt::Maintain::WaitForSubmissionIndex(submission_index) => { + // We don't need to check to see if the queue id matches + // as we already checked this from inside the poll call. + submission_index.index + } + _ => self.active_submission_index, + }; + unsafe { + self.raw + .wait(&self.fence, index_to_wait_for, CLEANUP_WAIT_MS) + .map_err(DeviceError::from)? + }; + index_to_wait_for + } else { + unsafe { + self.raw + .get_fence_value(&self.fence) + .map_err(DeviceError::from)? + } + }; + + let submission_closures = + life_tracker.triage_submissions(last_done_index, &self.command_allocator); + let mapping_closures = life_tracker.handle_mapping(hub, &self.raw, &self.trackers, token); + life_tracker.cleanup(&self.raw); + + let closures = UserClosures { + mappings: mapping_closures, + submissions: submission_closures, + }; + Ok((closures, life_tracker.queue_empty())) + } + + fn untrack<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>( + &'this mut self, + hub: &Hub<A, G>, + trackers: &Tracker<A>, + 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(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 (query_set_guard, mut token) = hub.query_sets.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_pipelines.used() { + if compute_pipe_guard[id].life_guard.ref_count.is_none() { + self.temp_suspected.compute_pipelines.push(id); + } + } + for id in trackers.render_pipelines.used() { + if render_pipe_guard[id].life_guard.ref_count.is_none() { + self.temp_suspected.render_pipelines.push(id); + } + } + for id in trackers.query_sets.used() { + if query_set_guard[id].life_guard.ref_count.is_none() { + self.temp_suspected.query_sets.push(id); + } + } + } + + self.lock_life(token) + .suspected_resources + .extend(&self.temp_suspected); + + self.temp_suspected.clear(); + } + + fn create_buffer( + &self, + self_id: id::DeviceId, + desc: &resource::BufferDescriptor, + transient: bool, + ) -> Result<resource::Buffer<A>, resource::CreateBufferError> { + debug_assert_eq!(self_id.backend(), A::VARIANT); + + if desc.size > self.limits.max_buffer_size { + return Err(resource::CreateBufferError::MaxBufferSize { + requested: desc.size, + maximum: self.limits.max_buffer_size, + }); + } + + if desc.usage.contains(wgt::BufferUsages::INDEX) + && desc.usage.contains( + wgt::BufferUsages::VERTEX + | wgt::BufferUsages::UNIFORM + | wgt::BufferUsages::INDIRECT + | wgt::BufferUsages::STORAGE, + ) + { + self.require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER)?; + } + + let mut usage = conv::map_buffer_usage(desc.usage); + + if desc.usage.is_empty() || desc.usage.contains_invalid_bits() { + return Err(resource::CreateBufferError::InvalidUsage(desc.usage)); + } + + if !self + .features + .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS) + { + use wgt::BufferUsages as Bu; + let write_mismatch = desc.usage.contains(Bu::MAP_WRITE) + && !(Bu::MAP_WRITE | Bu::COPY_SRC).contains(desc.usage); + let read_mismatch = desc.usage.contains(Bu::MAP_READ) + && !(Bu::MAP_READ | Bu::COPY_DST).contains(desc.usage); + if write_mismatch || read_mismatch { + return Err(resource::CreateBufferError::UsageMismatch(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::BufferUsages::MAP_WRITE) { + // we are going to be copying into it, internally + usage |= hal::BufferUses::COPY_DST; + } + } else { + // We are required to zero out (initialize) all memory. This is done + // on demand using clear_buffer which requires write transfer usage! + usage |= hal::BufferUses::COPY_DST; + } + + let actual_size = if desc.size == 0 { + wgt::COPY_BUFFER_ALIGNMENT + } else if desc.usage.contains(wgt::BufferUsages::VERTEX) { + // Bumping the size by 1 so that we can bind an empty range at the + // end of the buffer. + desc.size + 1 + } else { + desc.size + }; + let clear_remainder = actual_size % wgt::COPY_BUFFER_ALIGNMENT; + let aligned_size = if clear_remainder != 0 { + actual_size + wgt::COPY_BUFFER_ALIGNMENT - clear_remainder + } else { + actual_size + }; + + let mut memory_flags = hal::MemoryFlags::empty(); + memory_flags.set(hal::MemoryFlags::TRANSIENT, transient); + + let hal_desc = hal::BufferDescriptor { + label: desc.label.borrow_option(), + size: aligned_size, + usage, + memory_flags, + }; + let buffer = unsafe { self.raw.create_buffer(&hal_desc) }.map_err(DeviceError::from)?; + + Ok(resource::Buffer { + raw: Some(buffer), + device_id: Stored { + value: id::Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + usage: desc.usage, + size: desc.size, + initialization_status: BufferInitTracker::new(desc.size), + sync_mapped_writes: None, + map_state: resource::BufferMapState::Idle, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + }) + } + + fn create_texture_from_hal( + &self, + hal_texture: A::Texture, + hal_usage: hal::TextureUses, + self_id: id::DeviceId, + desc: &resource::TextureDescriptor, + format_features: wgt::TextureFormatFeatures, + clear_mode: resource::TextureClearMode<A>, + ) -> resource::Texture<A> { + debug_assert_eq!(self_id.backend(), A::VARIANT); + + resource::Texture { + inner: resource::TextureInner::Native { + raw: Some(hal_texture), + }, + device_id: Stored { + value: id::Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + desc: desc.map_label(|_| ()), + hal_usage, + format_features, + initialization_status: TextureInitTracker::new( + desc.mip_level_count, + desc.array_layer_count(), + ), + full_range: TextureSelector { + mips: 0..desc.mip_level_count, + layers: 0..desc.array_layer_count(), + }, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + clear_mode, + } + } + + fn create_texture( + &self, + self_id: id::DeviceId, + adapter: &Adapter<A>, + desc: &resource::TextureDescriptor, + ) -> Result<resource::Texture<A>, resource::CreateTextureError> { + use resource::{CreateTextureError, TextureDimensionError}; + + if desc.usage.is_empty() || desc.usage.contains_invalid_bits() { + return Err(CreateTextureError::InvalidUsage(desc.usage)); + } + + conv::check_texture_dimension_size( + desc.dimension, + desc.size, + desc.sample_count, + &self.limits, + )?; + + if desc.dimension != wgt::TextureDimension::D2 { + // Depth textures can only be 2D + if desc.format.is_depth_stencil_format() { + return Err(CreateTextureError::InvalidDepthDimension( + desc.dimension, + desc.format, + )); + } + // Renderable textures can only be 2D + if desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { + return Err(CreateTextureError::InvalidDimensionUsages( + wgt::TextureUsages::RENDER_ATTACHMENT, + desc.dimension, + )); + } + + // Compressed textures can only be 2D + if desc.format.is_compressed() { + return Err(CreateTextureError::InvalidCompressedDimension( + desc.dimension, + desc.format, + )); + } + } + + if desc.format.is_compressed() { + let (block_width, block_height) = desc.format.block_dimensions(); + + if desc.size.width % block_width != 0 { + return Err(CreateTextureError::InvalidDimension( + TextureDimensionError::NotMultipleOfBlockWidth { + width: desc.size.width, + block_width, + format: desc.format, + }, + )); + } + + if desc.size.height % block_height != 0 { + return Err(CreateTextureError::InvalidDimension( + TextureDimensionError::NotMultipleOfBlockHeight { + height: desc.size.height, + block_height, + format: desc.format, + }, + )); + } + } + + let format_features = self + .describe_format_features(adapter, desc.format) + .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; + + if desc.sample_count > 1 { + if desc.mip_level_count != 1 { + return Err(CreateTextureError::InvalidMipLevelCount { + requested: desc.mip_level_count, + maximum: 1, + }); + } + + if desc.size.depth_or_array_layers != 1 { + return Err(CreateTextureError::InvalidDimension( + TextureDimensionError::MultisampledDepthOrArrayLayer( + desc.size.depth_or_array_layers, + ), + )); + } + + if desc.usage.contains(wgt::TextureUsages::STORAGE_BINDING) { + return Err(CreateTextureError::InvalidMultisampledStorageBinding); + } + + if !desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { + return Err(CreateTextureError::MultisampledNotRenderAttachment); + } + + if !format_features.flags.intersects( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 + | wgt::TextureFormatFeatureFlags::MULTISAMPLE_X2 + | wgt::TextureFormatFeatureFlags::MULTISAMPLE_X8 + | wgt::TextureFormatFeatureFlags::MULTISAMPLE_X16, + ) { + return Err(CreateTextureError::InvalidMultisampledFormat(desc.format)); + } + + if !format_features + .flags + .sample_count_supported(desc.sample_count) + { + return Err(CreateTextureError::InvalidSampleCount( + desc.sample_count, + desc.format, + )); + }; + } + + let mips = desc.mip_level_count; + let max_levels_allowed = desc.size.max_mips(desc.dimension).min(hal::MAX_MIP_LEVELS); + if mips == 0 || mips > max_levels_allowed { + return Err(CreateTextureError::InvalidMipLevelCount { + requested: mips, + maximum: max_levels_allowed, + }); + } + + let missing_allowed_usages = desc.usage - format_features.allowed_usages; + if !missing_allowed_usages.is_empty() { + // detect downlevel incompatibilities + let wgpu_allowed_usages = desc + .format + .guaranteed_format_features(self.features) + .allowed_usages; + let wgpu_missing_usages = desc.usage - wgpu_allowed_usages; + return Err(CreateTextureError::InvalidFormatUsages( + missing_allowed_usages, + desc.format, + wgpu_missing_usages.is_empty(), + )); + } + + let mut hal_view_formats = vec![]; + for format in desc.view_formats.iter() { + if desc.format == *format { + continue; + } + if desc.format.remove_srgb_suffix() != format.remove_srgb_suffix() { + return Err(CreateTextureError::InvalidViewFormat(*format, desc.format)); + } + hal_view_formats.push(*format); + } + if !hal_view_formats.is_empty() { + self.require_downlevel_flags(wgt::DownlevelFlags::VIEW_FORMATS)?; + } + + // Enforce having COPY_DST/DEPTH_STENCIL_WRITE/COLOR_TARGET otherwise we + // wouldn't be able to initialize the texture. + let hal_usage = conv::map_texture_usage(desc.usage, desc.format.into()) + | if desc.format.is_depth_stencil_format() { + hal::TextureUses::DEPTH_STENCIL_WRITE + } else if desc.usage.contains(wgt::TextureUsages::COPY_DST) { + hal::TextureUses::COPY_DST // (set already) + } else { + // Use COPY_DST only if we can't use COLOR_TARGET + if format_features + .allowed_usages + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + && desc.dimension == wgt::TextureDimension::D2 + // Render targets dimension must be 2d + { + hal::TextureUses::COLOR_TARGET + } else { + hal::TextureUses::COPY_DST + } + }; + + let hal_desc = hal::TextureDescriptor { + label: desc.label.borrow_option(), + size: desc.size, + mip_level_count: desc.mip_level_count, + sample_count: desc.sample_count, + dimension: desc.dimension, + format: desc.format, + usage: hal_usage, + memory_flags: hal::MemoryFlags::empty(), + view_formats: hal_view_formats, + }; + + let raw_texture = unsafe { + self.raw + .create_texture(&hal_desc) + .map_err(DeviceError::from)? + }; + + let clear_mode = if hal_usage + .intersects(hal::TextureUses::DEPTH_STENCIL_WRITE | hal::TextureUses::COLOR_TARGET) + { + let (is_color, usage) = if desc.format.is_depth_stencil_format() { + (false, hal::TextureUses::DEPTH_STENCIL_WRITE) + } else { + (true, hal::TextureUses::COLOR_TARGET) + }; + let dimension = match desc.dimension { + wgt::TextureDimension::D1 => wgt::TextureViewDimension::D1, + wgt::TextureDimension::D2 => wgt::TextureViewDimension::D2, + wgt::TextureDimension::D3 => unreachable!(), + }; + + let mut clear_views = SmallVec::new(); + for mip_level in 0..desc.mip_level_count { + for array_layer in 0..desc.size.depth_or_array_layers { + let desc = hal::TextureViewDescriptor { + label: Some("(wgpu internal) clear texture view"), + format: desc.format, + dimension, + usage, + range: wgt::ImageSubresourceRange { + aspect: wgt::TextureAspect::All, + base_mip_level: mip_level, + mip_level_count: Some(1), + base_array_layer: array_layer, + array_layer_count: Some(1), + }, + }; + clear_views.push( + unsafe { self.raw.create_texture_view(&raw_texture, &desc) } + .map_err(DeviceError::from)?, + ); + } + } + resource::TextureClearMode::RenderPass { + clear_views, + is_color, + } + } else { + resource::TextureClearMode::BufferCopy + }; + + let mut texture = self.create_texture_from_hal( + raw_texture, + hal_usage, + self_id, + desc, + format_features, + clear_mode, + ); + texture.hal_usage = hal_usage; + Ok(texture) + } + + fn create_texture_view( + &self, + texture: &resource::Texture<A>, + texture_id: id::TextureId, + desc: &resource::TextureViewDescriptor, + ) -> Result<resource::TextureView<A>, resource::CreateTextureViewError> { + let texture_raw = texture + .inner + .as_raw() + .ok_or(resource::CreateTextureViewError::InvalidTexture)?; + + // resolve TextureViewDescriptor defaults + // https://gpuweb.github.io/gpuweb/#abstract-opdef-resolving-gputextureviewdescriptor-defaults + + let resolved_format = desc.format.unwrap_or_else(|| { + texture + .desc + .format + .aspect_specific_format(desc.range.aspect) + .unwrap_or(texture.desc.format) + }); + + let resolved_dimension = desc + .dimension + .unwrap_or_else(|| match texture.desc.dimension { + wgt::TextureDimension::D1 => wgt::TextureViewDimension::D1, + wgt::TextureDimension::D2 => { + if texture.desc.array_layer_count() == 1 { + wgt::TextureViewDimension::D2 + } else { + wgt::TextureViewDimension::D2Array + } + } + wgt::TextureDimension::D3 => wgt::TextureViewDimension::D3, + }); + + let resolved_mip_level_count = desc.range.mip_level_count.unwrap_or_else(|| { + texture + .desc + .mip_level_count + .saturating_sub(desc.range.base_mip_level) + }); + + let resolved_array_layer_count = + desc.range + .array_layer_count + .unwrap_or_else(|| match resolved_dimension { + wgt::TextureViewDimension::D1 + | wgt::TextureViewDimension::D2 + | wgt::TextureViewDimension::D3 => 1, + wgt::TextureViewDimension::Cube => 6, + wgt::TextureViewDimension::D2Array | wgt::TextureViewDimension::CubeArray => { + texture + .desc + .array_layer_count() + .saturating_sub(desc.range.base_array_layer) + } + }); + + // validate TextureViewDescriptor + + let aspects = hal::FormatAspects::new(texture.desc.format, desc.range.aspect); + if aspects.is_empty() { + return Err(resource::CreateTextureViewError::InvalidAspect { + texture_format: texture.desc.format, + requested_aspect: desc.range.aspect, + }); + } + + let format_is_good = if desc.range.aspect == wgt::TextureAspect::All { + resolved_format == texture.desc.format + || texture.desc.view_formats.contains(&resolved_format) + } else { + Some(resolved_format) + == texture + .desc + .format + .aspect_specific_format(desc.range.aspect) + }; + if !format_is_good { + return Err(resource::CreateTextureViewError::FormatReinterpretation { + texture: texture.desc.format, + view: resolved_format, + }); + } + + // check if multisampled texture is seen as anything but 2D + if texture.desc.sample_count > 1 && resolved_dimension != wgt::TextureViewDimension::D2 { + return Err( + resource::CreateTextureViewError::InvalidMultisampledTextureViewDimension( + resolved_dimension, + ), + ); + } + + // check if the dimension is compatible with the texture + if texture.desc.dimension != resolved_dimension.compatible_texture_dimension() { + return Err( + resource::CreateTextureViewError::InvalidTextureViewDimension { + view: resolved_dimension, + texture: texture.desc.dimension, + }, + ); + } + + match resolved_dimension { + TextureViewDimension::D1 | TextureViewDimension::D2 | TextureViewDimension::D3 => { + if resolved_array_layer_count != 1 { + return Err(resource::CreateTextureViewError::InvalidArrayLayerCount { + requested: resolved_array_layer_count, + dim: resolved_dimension, + }); + } + } + TextureViewDimension::Cube => { + if resolved_array_layer_count != 6 { + return Err( + resource::CreateTextureViewError::InvalidCubemapTextureDepth { + depth: resolved_array_layer_count, + }, + ); + } + } + TextureViewDimension::CubeArray => { + if resolved_array_layer_count % 6 != 0 { + return Err( + resource::CreateTextureViewError::InvalidCubemapArrayTextureDepth { + depth: resolved_array_layer_count, + }, + ); + } + } + _ => {} + } + + match resolved_dimension { + TextureViewDimension::Cube | TextureViewDimension::CubeArray => { + if texture.desc.size.width != texture.desc.size.height { + return Err(resource::CreateTextureViewError::InvalidCubeTextureViewSize); + } + } + _ => {} + } + + if resolved_mip_level_count == 0 { + return Err(resource::CreateTextureViewError::ZeroMipLevelCount); + } + + let mip_level_end = desc + .range + .base_mip_level + .saturating_add(resolved_mip_level_count); + + let level_end = texture.desc.mip_level_count; + if mip_level_end > level_end { + return Err(resource::CreateTextureViewError::TooManyMipLevels { + requested: mip_level_end, + total: level_end, + }); + } + + if resolved_array_layer_count == 0 { + return Err(resource::CreateTextureViewError::ZeroArrayLayerCount); + } + + let array_layer_end = desc + .range + .base_array_layer + .saturating_add(resolved_array_layer_count); + + let layer_end = texture.desc.array_layer_count(); + if array_layer_end > layer_end { + return Err(resource::CreateTextureViewError::TooManyArrayLayers { + requested: array_layer_end, + total: layer_end, + }); + }; + + // https://gpuweb.github.io/gpuweb/#abstract-opdef-renderable-texture-view + let render_extent = 'b: loop { + if !texture + .desc + .usage + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + { + break 'b Err(TextureViewNotRenderableReason::Usage(texture.desc.usage)); + } + + if resolved_dimension != TextureViewDimension::D2 { + break 'b Err(TextureViewNotRenderableReason::Dimension( + resolved_dimension, + )); + } + + if resolved_mip_level_count != 1 { + break 'b Err(TextureViewNotRenderableReason::MipLevelCount( + resolved_mip_level_count, + )); + } + + if resolved_array_layer_count != 1 { + break 'b Err(TextureViewNotRenderableReason::ArrayLayerCount( + resolved_array_layer_count, + )); + } + + if aspects != hal::FormatAspects::from(texture.desc.format) { + break 'b Err(TextureViewNotRenderableReason::Aspects(aspects)); + } + + break 'b Ok(texture + .desc + .compute_render_extent(desc.range.base_mip_level)); + }; + + // filter the usages based on the other criteria + let usage = { + let mask_copy = !(hal::TextureUses::COPY_SRC | hal::TextureUses::COPY_DST); + let mask_dimension = match resolved_dimension { + wgt::TextureViewDimension::Cube | wgt::TextureViewDimension::CubeArray => { + hal::TextureUses::RESOURCE + } + wgt::TextureViewDimension::D3 => { + hal::TextureUses::RESOURCE + | hal::TextureUses::STORAGE_READ + | hal::TextureUses::STORAGE_READ_WRITE + } + _ => hal::TextureUses::all(), + }; + let mask_mip_level = if resolved_mip_level_count == 1 { + hal::TextureUses::all() + } else { + hal::TextureUses::RESOURCE + }; + texture.hal_usage & mask_copy & mask_dimension & mask_mip_level + }; + + log::debug!( + "Create view for texture {:?} filters usages to {:?}", + texture_id, + usage + ); + + // use the combined depth-stencil format for the view + let format = if resolved_format.is_depth_stencil_component(texture.desc.format) { + texture.desc.format + } else { + resolved_format + }; + + let resolved_range = wgt::ImageSubresourceRange { + aspect: desc.range.aspect, + base_mip_level: desc.range.base_mip_level, + mip_level_count: Some(resolved_mip_level_count), + base_array_layer: desc.range.base_array_layer, + array_layer_count: Some(resolved_array_layer_count), + }; + + let hal_desc = hal::TextureViewDescriptor { + label: desc.label.borrow_option(), + format, + dimension: resolved_dimension, + usage, + range: resolved_range, + }; + + let raw = unsafe { + self.raw + .create_texture_view(texture_raw, &hal_desc) + .map_err(|_| resource::CreateTextureViewError::OutOfMemory)? + }; + + let selector = TextureSelector { + mips: desc.range.base_mip_level..mip_level_end, + layers: desc.range.base_array_layer..array_layer_end, + }; + + Ok(resource::TextureView { + raw, + parent_id: Stored { + value: id::Valid(texture_id), + ref_count: texture.life_guard.add_ref(), + }, + device_id: texture.device_id.clone(), + desc: resource::HalTextureViewDescriptor { + format: resolved_format, + dimension: resolved_dimension, + range: resolved_range, + }, + format_features: texture.format_features, + render_extent, + samples: texture.desc.sample_count, + selector, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + }) + } + + fn create_sampler( + &self, + self_id: id::DeviceId, + desc: &resource::SamplerDescriptor, + ) -> Result<resource::Sampler<A>, resource::CreateSamplerError> { + if desc + .address_modes + .iter() + .any(|am| am == &wgt::AddressMode::ClampToBorder) + { + self.require_features(wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER)?; + } + + if desc.border_color == Some(wgt::SamplerBorderColor::Zero) { + self.require_features(wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO)?; + } + + if desc.lod_min_clamp < 0.0 { + return Err(resource::CreateSamplerError::InvalidLodMinClamp( + desc.lod_min_clamp, + )); + } + if desc.lod_max_clamp < desc.lod_min_clamp { + return Err(resource::CreateSamplerError::InvalidLodMaxClamp { + lod_min_clamp: desc.lod_min_clamp, + lod_max_clamp: desc.lod_max_clamp, + }); + } + + if desc.anisotropy_clamp < 1 { + return Err(resource::CreateSamplerError::InvalidAnisotropy( + desc.anisotropy_clamp, + )); + } + + if desc.anisotropy_clamp != 1 { + if !matches!(desc.min_filter, wgt::FilterMode::Linear) { + return Err( + resource::CreateSamplerError::InvalidFilterModeWithAnisotropy { + filter_type: resource::SamplerFilterErrorType::MinFilter, + filter_mode: desc.min_filter, + anisotropic_clamp: desc.anisotropy_clamp, + }, + ); + } + if !matches!(desc.mag_filter, wgt::FilterMode::Linear) { + return Err( + resource::CreateSamplerError::InvalidFilterModeWithAnisotropy { + filter_type: resource::SamplerFilterErrorType::MagFilter, + filter_mode: desc.mag_filter, + anisotropic_clamp: desc.anisotropy_clamp, + }, + ); + } + if !matches!(desc.mipmap_filter, wgt::FilterMode::Linear) { + return Err( + resource::CreateSamplerError::InvalidFilterModeWithAnisotropy { + filter_type: resource::SamplerFilterErrorType::MipmapFilter, + filter_mode: desc.mipmap_filter, + anisotropic_clamp: desc.anisotropy_clamp, + }, + ); + } + } + + let anisotropy_clamp = if self + .downlevel + .flags + .contains(wgt::DownlevelFlags::ANISOTROPIC_FILTERING) + { + // Clamp anisotropy clamp to [1, 16] per the wgpu-hal interface + desc.anisotropy_clamp.min(16) + } else { + // If it isn't supported, set this unconditionally to 1 + 1 + }; + + //TODO: check for wgt::DownlevelFlags::COMPARISON_SAMPLERS + + let hal_desc = hal::SamplerDescriptor { + label: desc.label.borrow_option(), + address_modes: desc.address_modes, + mag_filter: desc.mag_filter, + min_filter: desc.min_filter, + mipmap_filter: desc.mipmap_filter, + lod_clamp: desc.lod_min_clamp..desc.lod_max_clamp, + compare: desc.compare, + anisotropy_clamp, + border_color: desc.border_color, + }; + + let raw = unsafe { + self.raw + .create_sampler(&hal_desc) + .map_err(DeviceError::from)? + }; + 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: desc.compare.is_some(), + filtering: desc.min_filter == wgt::FilterMode::Linear + || desc.mag_filter == wgt::FilterMode::Linear, + }) + } + + fn create_shader_module<'a>( + &self, + self_id: id::DeviceId, + desc: &pipeline::ShaderModuleDescriptor<'a>, + source: pipeline::ShaderModuleSource<'a>, + ) -> Result<pipeline::ShaderModule<A>, pipeline::CreateShaderModuleError> { + let (module, source) = match source { + #[cfg(feature = "wgsl")] + pipeline::ShaderModuleSource::Wgsl(code) => { + profiling::scope!("naga::wgsl::parse_str"); + let module = naga::front::wgsl::parse_str(&code).map_err(|inner| { + pipeline::CreateShaderModuleError::Parsing(pipeline::ShaderError { + source: code.to_string(), + label: desc.label.as_ref().map(|l| l.to_string()), + inner: Box::new(inner), + }) + })?; + (Cow::Owned(module), code.into_owned()) + } + pipeline::ShaderModuleSource::Naga(module) => (module, String::new()), + pipeline::ShaderModuleSource::Dummy(_) => panic!("found `ShaderModuleSource::Dummy`"), + }; + for (_, var) in module.global_variables.iter() { + match var.binding { + Some(ref br) if br.group >= self.limits.max_bind_groups => { + return Err(pipeline::CreateShaderModuleError::InvalidGroupIndex { + bind: br.clone(), + group: br.group, + limit: self.limits.max_bind_groups, + }); + } + _ => continue, + }; + } + + use naga::valid::Capabilities as Caps; + profiling::scope!("naga::validate"); + + let mut caps = Caps::empty(); + caps.set( + Caps::PUSH_CONSTANT, + self.features.contains(wgt::Features::PUSH_CONSTANTS), + ); + caps.set( + Caps::FLOAT64, + self.features.contains(wgt::Features::SHADER_F64), + ); + caps.set( + Caps::PRIMITIVE_INDEX, + self.features + .contains(wgt::Features::SHADER_PRIMITIVE_INDEX), + ); + caps.set( + Caps::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + self.features.contains( + wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ), + ); + caps.set( + Caps::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + self.features.contains( + wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ), + ); + // TODO: This needs a proper wgpu feature + caps.set( + Caps::SAMPLER_NON_UNIFORM_INDEXING, + self.features.contains( + wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ), + ); + caps.set( + Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS, + self.features + .contains(wgt::Features::TEXTURE_FORMAT_16BIT_NORM), + ); + caps.set( + Caps::MULTIVIEW, + self.features.contains(wgt::Features::MULTIVIEW), + ); + caps.set( + Caps::EARLY_DEPTH_TEST, + self.features + .contains(wgt::Features::SHADER_EARLY_DEPTH_TEST), + ); + caps.set( + Caps::MULTISAMPLED_SHADING, + self.downlevel + .flags + .contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING), + ); + + let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), caps) + .validate(&module) + .map_err(|inner| { + pipeline::CreateShaderModuleError::Validation(pipeline::ShaderError { + source, + label: desc.label.as_ref().map(|l| l.to_string()), + inner: Box::new(inner), + }) + })?; + let interface = + validation::Interface::new(&module, &info, self.features, self.limits.clone()); + let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module, info }); + + let hal_desc = hal::ShaderModuleDescriptor { + label: desc.label.borrow_option(), + runtime_checks: desc.shader_bound_checks.runtime_checks(), + }; + let raw = match unsafe { self.raw.create_shader_module(&hal_desc, hal_shader) } { + Ok(raw) => raw, + Err(error) => { + return Err(match error { + hal::ShaderError::Device(error) => { + pipeline::CreateShaderModuleError::Device(error.into()) + } + hal::ShaderError::Compilation(ref msg) => { + log::error!("Shader error: {}", msg); + pipeline::CreateShaderModuleError::Generation + } + }) + } + }; + + Ok(pipeline::ShaderModule { + raw, + device_id: Stored { + value: id::Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + interface: Some(interface), + #[cfg(debug_assertions)] + label: desc.label.borrow_or_default().to_string(), + }) + } + + #[allow(unused_unsafe)] + unsafe fn create_shader_module_spirv<'a>( + &self, + self_id: id::DeviceId, + desc: &pipeline::ShaderModuleDescriptor<'a>, + source: &'a [u32], + ) -> Result<pipeline::ShaderModule<A>, pipeline::CreateShaderModuleError> { + self.require_features(wgt::Features::SPIRV_SHADER_PASSTHROUGH)?; + let hal_desc = hal::ShaderModuleDescriptor { + label: desc.label.borrow_option(), + runtime_checks: desc.shader_bound_checks.runtime_checks(), + }; + let hal_shader = hal::ShaderInput::SpirV(source); + let raw = match unsafe { self.raw.create_shader_module(&hal_desc, hal_shader) } { + Ok(raw) => raw, + Err(error) => { + return Err(match error { + hal::ShaderError::Device(error) => { + pipeline::CreateShaderModuleError::Device(error.into()) + } + hal::ShaderError::Compilation(ref msg) => { + log::error!("Shader error: {}", msg); + pipeline::CreateShaderModuleError::Generation + } + }) + } + }; + + Ok(pipeline::ShaderModule { + raw, + device_id: Stored { + value: id::Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + interface: None, + #[cfg(debug_assertions)] + label: desc.label.borrow_or_default().to_string(), + }) + } + + fn deduplicate_bind_group_layout( + self_id: id::DeviceId, + entry_map: &binding_model::BindEntryMap, + guard: &Storage<binding_model::BindGroupLayout<A>, 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<A>, + bgl_guard: &'a Storage<binding_model::BindGroupLayout<A>, id::BindGroupLayoutId>, + ) -> ArrayVec<&'a binding_model::BindEntryMap, { hal::MAX_BIND_GROUPS }> { + pipeline_layout + .bind_group_layout_ids + .iter() + .map(|&id| &bgl_guard[id].entries) + .collect() + } + + /// Generate information about late-validated buffer bindings for pipelines. + //TODO: should this be combined with `get_introspection_bind_group_layouts` in some way? + fn make_late_sized_buffer_groups<'a>( + shader_binding_sizes: &FastHashMap<naga::ResourceBinding, wgt::BufferSize>, + layout: &binding_model::PipelineLayout<A>, + bgl_guard: &'a Storage<binding_model::BindGroupLayout<A>, id::BindGroupLayoutId>, + ) -> ArrayVec<pipeline::LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }> { + // Given the shader-required binding sizes and the pipeline layout, + // return the filtered list of them in the layout order, + // removing those with given `min_binding_size`. + layout + .bind_group_layout_ids + .iter() + .enumerate() + .map(|(group_index, &bgl_id)| pipeline::LateSizedBufferGroup { + shader_sizes: bgl_guard[bgl_id] + .entries + .values() + .filter_map(|entry| match entry.ty { + wgt::BindingType::Buffer { + min_binding_size: None, + .. + } => { + let rb = naga::ResourceBinding { + group: group_index as u32, + binding: entry.binding, + }; + let shader_size = + shader_binding_sizes.get(&rb).map_or(0, |nz| nz.get()); + Some(shader_size) + } + _ => None, + }) + .collect(), + }) + .collect() + } + + fn create_bind_group_layout( + &self, + self_id: id::DeviceId, + label: Option<&str>, + entry_map: binding_model::BindEntryMap, + ) -> Result<binding_model::BindGroupLayout<A>, binding_model::CreateBindGroupLayoutError> { + #[derive(PartialEq)] + enum WritableStorage { + Yes, + No, + } + + for entry in entry_map.values() { + use wgt::BindingType as Bt; + + let mut required_features = wgt::Features::empty(); + let mut required_downlevel_flags = wgt::DownlevelFlags::empty(); + let (array_feature, writable_storage) = match entry.ty { + Bt::Buffer { + ty: wgt::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: _, + } => ( + Some(wgt::Features::BUFFER_BINDING_ARRAY), + WritableStorage::No, + ), + Bt::Buffer { + ty: wgt::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: _, + } => ( + Some(wgt::Features::BUFFER_BINDING_ARRAY), + WritableStorage::No, + ), + Bt::Buffer { + ty: wgt::BufferBindingType::Storage { read_only }, + .. + } => ( + Some( + wgt::Features::BUFFER_BINDING_ARRAY + | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY, + ), + match read_only { + true => WritableStorage::No, + false => WritableStorage::Yes, + }, + ), + Bt::Sampler { .. } => ( + Some(wgt::Features::TEXTURE_BINDING_ARRAY), + WritableStorage::No, + ), + Bt::Texture { + multisampled: true, + sample_type: TextureSampleType::Float { filterable: true }, + .. + } => { + return Err(binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error: binding_model::BindGroupLayoutEntryError::SampleTypeFloatFilterableBindingMultisampled, + }); + } + Bt::Texture { .. } => ( + Some(wgt::Features::TEXTURE_BINDING_ARRAY), + WritableStorage::No, + ), + Bt::StorageTexture { + access, + view_dimension, + format: _, + } => { + match view_dimension { + wgt::TextureViewDimension::Cube | wgt::TextureViewDimension::CubeArray => { + return Err(binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error: binding_model::BindGroupLayoutEntryError::StorageTextureCube, + }) + } + _ => (), + } + match access { + wgt::StorageTextureAccess::ReadOnly + | wgt::StorageTextureAccess::ReadWrite + if !self.features.contains( + wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + ) => + { + return Err(binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error: binding_model::BindGroupLayoutEntryError::StorageTextureReadWrite, + }); + } + _ => (), + } + ( + Some( + wgt::Features::TEXTURE_BINDING_ARRAY + | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY, + ), + match access { + wgt::StorageTextureAccess::WriteOnly => WritableStorage::Yes, + wgt::StorageTextureAccess::ReadOnly => { + required_features |= + wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES; + WritableStorage::No + } + wgt::StorageTextureAccess::ReadWrite => { + required_features |= + wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES; + WritableStorage::Yes + } + }, + ) + } + }; + + // Validate the count parameter + if entry.count.is_some() { + required_features |= array_feature + .ok_or(binding_model::BindGroupLayoutEntryError::ArrayUnsupported) + .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error, + })?; + } + + if entry.visibility.contains_invalid_bits() { + return Err( + binding_model::CreateBindGroupLayoutError::InvalidVisibility(entry.visibility), + ); + } + + if entry.visibility.contains(wgt::ShaderStages::VERTEX) { + if writable_storage == WritableStorage::Yes { + required_features |= wgt::Features::VERTEX_WRITABLE_STORAGE; + } + if let Bt::Buffer { + ty: wgt::BufferBindingType::Storage { .. }, + .. + } = entry.ty + { + required_downlevel_flags |= wgt::DownlevelFlags::VERTEX_STORAGE; + } + } + if writable_storage == WritableStorage::Yes + && entry.visibility.contains(wgt::ShaderStages::FRAGMENT) + { + required_downlevel_flags |= wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE; + } + + self.require_features(required_features) + .map_err(binding_model::BindGroupLayoutEntryError::MissingFeatures) + .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error, + })?; + self.require_downlevel_flags(required_downlevel_flags) + .map_err(binding_model::BindGroupLayoutEntryError::MissingDownlevelFlags) + .map_err(|error| binding_model::CreateBindGroupLayoutError::Entry { + binding: entry.binding, + error, + })?; + } + + let bgl_flags = conv::bind_group_layout_flags(self.features); + + let mut hal_bindings = entry_map.values().cloned().collect::<Vec<_>>(); + hal_bindings.sort_by_key(|b| b.binding); + let hal_desc = hal::BindGroupLayoutDescriptor { + label, + flags: bgl_flags, + entries: &hal_bindings, + }; + let raw = unsafe { + self.raw + .create_bind_group_layout(&hal_desc) + .map_err(DeviceError::from)? + }; + + 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(), + 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_buffer_binding<'a>( + bb: &binding_model::BufferBinding, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + used_buffer_ranges: &mut Vec<BufferInitTrackerAction>, + dynamic_binding_info: &mut Vec<binding_model::BindGroupDynamicBindingData>, + late_buffer_binding_sizes: &mut FastHashMap<u32, wgt::BufferSize>, + used: &mut BindGroupStates<A>, + storage: &'a Storage<resource::Buffer<A>, id::BufferId>, + limits: &wgt::Limits, + ) -> Result<hal::BufferBinding<'a, A>, binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + + 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, + expected: "UniformBuffer, StorageBuffer or ReadonlyStorageBuffer", + }) + } + }; + let (pub_usage, internal_use, range_limit) = match binding_ty { + wgt::BufferBindingType::Uniform => ( + wgt::BufferUsages::UNIFORM, + hal::BufferUses::UNIFORM, + limits.max_uniform_buffer_binding_size, + ), + wgt::BufferBindingType::Storage { read_only } => ( + wgt::BufferUsages::STORAGE, + if read_only { + hal::BufferUses::STORAGE_READ + } else { + hal::BufferUses::STORAGE_READ_WRITE + }, + limits.max_storage_buffer_binding_size, + ), + }; + + let (align, align_limit_name) = + binding_model::buffer_binding_type_alignment(limits, binding_ty); + if bb.offset % align as u64 != 0 { + return Err(Error::UnalignedBufferOffset( + bb.offset, + align_limit_name, + align, + )); + } + + let buffer = used + .buffers + .add_single(storage, bb.buffer_id, internal_use) + .ok_or(Error::InvalidBuffer(bb.buffer_id))?; + check_buffer_usage(buffer.usage, pub_usage)?; + let raw_buffer = 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 { + buffer: bb.buffer_id, + range: bb.offset..end, + size: buffer.size, + }); + } + (size.get(), end) + } + None => (buffer.size - bb.offset, buffer.size), + }; + + if bind_size > range_limit as u64 { + return Err(Error::BufferRangeTooLarge { + binding, + given: bind_size as u32, + limit: range_limit, + }); + } + + // Record binding info for validating dynamic offsets + if dynamic { + dynamic_binding_info.push(binding_model::BindGroupDynamicBindingData { + binding_idx: binding, + buffer_size: buffer.size, + binding_range: bb.offset..bind_end, + maximum_dynamic_offset: buffer.size - bind_end, + binding_type: binding_ty, + }); + } + + if let Some(non_zero) = min_size { + let min_size = non_zero.get(); + if min_size > bind_size { + return Err(Error::BindingSizeTooSmall { + buffer: bb.buffer_id, + actual: bind_size, + min: min_size, + }); + } + } else { + let late_size = + wgt::BufferSize::new(bind_size).ok_or(Error::BindingZeroSize(bb.buffer_id))?; + late_buffer_binding_sizes.insert(binding, late_size); + } + + assert_eq!(bb.offset % wgt::COPY_BUFFER_ALIGNMENT, 0); + used_buffer_ranges.extend(buffer.initialization_status.create_action( + bb.buffer_id, + bb.offset..bb.offset + bind_size, + MemoryInitKind::NeedsInitializedMemory, + )); + + Ok(hal::BufferBinding { + buffer: raw_buffer, + offset: bb.offset, + size: bb.size, + }) + } + + fn create_texture_binding( + view: &resource::TextureView<A>, + texture_guard: &Storage<resource::Texture<A>, id::TextureId>, + internal_use: hal::TextureUses, + pub_usage: wgt::TextureUsages, + used: &mut BindGroupStates<A>, + used_texture_ranges: &mut Vec<TextureInitTrackerAction>, + ) -> Result<(), binding_model::CreateBindGroupError> { + // Careful here: the texture may no longer have its own ref count, + // if it was deleted by the user. + let texture = used + .textures + .add_single( + texture_guard, + view.parent_id.value.0, + view.parent_id.ref_count.clone(), + Some(view.selector.clone()), + internal_use, + ) + .ok_or(binding_model::CreateBindGroupError::InvalidTexture( + view.parent_id.value.0, + ))?; + check_texture_usage(texture.desc.usage, pub_usage)?; + + used_texture_ranges.push(TextureInitTrackerAction { + id: view.parent_id.value.0, + range: TextureInitRange { + mip_range: view.desc.range.mip_range(texture.desc.mip_level_count), + layer_range: view + .desc + .range + .layer_range(texture.desc.array_layer_count()), + }, + kind: MemoryInitKind::NeedsInitializedMemory, + }); + + Ok(()) + } + + fn create_bind_group<G: GlobalIdentityHandlerFactory>( + &self, + self_id: id::DeviceId, + layout: &binding_model::BindGroupLayout<A>, + desc: &binding_model::BindGroupDescriptor, + hub: &Hub<A, G>, + token: &mut Token<binding_model::BindGroupLayout<A>>, + ) -> Result<binding_model::BindGroup<A>, 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, or re-use allocations + // Record binding info for dynamic offset validation + let mut dynamic_binding_info = Vec::new(); + // Map of binding -> shader reflected size + //Note: we can't collect into a vector right away because + // it needs to be in BGL iteration order, not BG entry order. + let mut late_buffer_binding_sizes = FastHashMap::default(); + // fill out the descriptors + let mut used = BindGroupStates::new(); + + 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); + + let mut used_buffer_ranges = Vec::new(); + let mut used_texture_ranges = Vec::new(); + let mut hal_entries = Vec::with_capacity(desc.entries.len()); + let mut hal_buffers = Vec::new(); + let mut hal_samplers = Vec::new(); + let mut hal_textures = Vec::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 (res_index, count) = match entry.resource { + Br::Buffer(ref bb) => { + let bb = Self::create_buffer_binding( + bb, + binding, + decl, + &mut used_buffer_ranges, + &mut dynamic_binding_info, + &mut late_buffer_binding_sizes, + &mut used, + &*buffer_guard, + &self.limits, + )?; + + let res_index = hal_buffers.len(); + hal_buffers.push(bb); + (res_index, 1) + } + Br::BufferArray(ref bindings_array) => { + let num_bindings = bindings_array.len(); + Self::check_array_binding(self.features, decl.count, num_bindings)?; + + let res_index = hal_buffers.len(); + for bb in bindings_array.iter() { + let bb = Self::create_buffer_binding( + bb, + binding, + decl, + &mut used_buffer_ranges, + &mut dynamic_binding_info, + &mut late_buffer_binding_sizes, + &mut used, + &*buffer_guard, + &self.limits, + )?; + hal_buffers.push(bb); + } + (res_index, num_bindings) + } + Br::Sampler(id) => { + match decl.ty { + wgt::BindingType::Sampler(ty) => { + let sampler = used + .samplers + .add_single(&*sampler_guard, id) + .ok_or(Error::InvalidSampler(id))?; + + // Allowed sampler values for filtering and comparison + let (allowed_filtering, allowed_comparison) = match ty { + wgt::SamplerBindingType::Filtering => (None, false), + wgt::SamplerBindingType::NonFiltering => (Some(false), false), + wgt::SamplerBindingType::Comparison => (None, true), + }; + + if let Some(allowed_filtering) = allowed_filtering { + if allowed_filtering != sampler.filtering { + return Err(Error::WrongSamplerFiltering { + binding, + layout_flt: allowed_filtering, + sampler_flt: sampler.filtering, + }); + } + } + + if allowed_comparison != sampler.comparison { + return Err(Error::WrongSamplerComparison { + binding, + layout_cmp: allowed_comparison, + sampler_cmp: sampler.comparison, + }); + } + + let res_index = hal_samplers.len(); + hal_samplers.push(&sampler.raw); + (res_index, 1) + } + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "Sampler", + }) + } + } + } + Br::SamplerArray(ref bindings_array) => { + let num_bindings = bindings_array.len(); + Self::check_array_binding(self.features, decl.count, num_bindings)?; + + let res_index = hal_samplers.len(); + for &id in bindings_array.iter() { + let sampler = used + .samplers + .add_single(&*sampler_guard, id) + .ok_or(Error::InvalidSampler(id))?; + hal_samplers.push(&sampler.raw); + } + + (res_index, num_bindings) + } + Br::TextureView(id) => { + let view = used + .views + .add_single(&*texture_view_guard, id) + .ok_or(Error::InvalidTextureView(id))?; + let (pub_usage, internal_use) = Self::texture_use_parameters( + binding, + decl, + view, + "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture", + )?; + Self::create_texture_binding( + view, + &texture_guard, + internal_use, + pub_usage, + &mut used, + &mut used_texture_ranges, + )?; + let res_index = hal_textures.len(); + hal_textures.push(hal::TextureBinding { + view: &view.raw, + usage: internal_use, + }); + (res_index, 1) + } + Br::TextureViewArray(ref bindings_array) => { + let num_bindings = bindings_array.len(); + Self::check_array_binding(self.features, decl.count, num_bindings)?; + + let res_index = hal_textures.len(); + for &id in bindings_array.iter() { + let view = used + .views + .add_single(&*texture_view_guard, id) + .ok_or(Error::InvalidTextureView(id))?; + let (pub_usage, internal_use) = + Self::texture_use_parameters(binding, decl, view, + "SampledTextureArray, ReadonlyStorageTextureArray or WriteonlyStorageTextureArray")?; + Self::create_texture_binding( + view, + &texture_guard, + internal_use, + pub_usage, + &mut used, + &mut used_texture_ranges, + )?; + hal_textures.push(hal::TextureBinding { + view: &view.raw, + usage: internal_use, + }); + } + + (res_index, num_bindings) + } + }; + + hal_entries.push(hal::BindGroupEntry { + binding, + resource_index: res_index as u32, + count: count as u32, + }); + } + + used.optimize(); + + hal_entries.sort_by_key(|entry| entry.binding); + for (a, b) in hal_entries.iter().zip(hal_entries.iter().skip(1)) { + if a.binding == b.binding { + return Err(Error::DuplicateBinding(a.binding)); + } + } + + let hal_desc = hal::BindGroupDescriptor { + label: desc.label.borrow_option(), + layout: &layout.raw, + entries: &hal_entries, + buffers: &hal_buffers, + samplers: &hal_samplers, + textures: &hal_textures, + }; + let raw = unsafe { + self.raw + .create_bind_group(&hal_desc) + .map_err(DeviceError::from)? + }; + + // manually add a dependency on BGL + layout.multi_ref_count.inc(); + + Ok(binding_model::BindGroup { + raw, + 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, + used_buffer_ranges, + used_texture_ranges, + dynamic_binding_info, + // collect in the order of BGL iteration + late_buffer_binding_sizes: layout + .entries + .keys() + .flat_map(|binding| late_buffer_binding_sizes.get(binding).cloned()) + .collect(), + }) + } + + fn check_array_binding( + features: wgt::Features, + count: Option<NonZeroU32>, + num_bindings: usize, + ) -> Result<(), super::binding_model::CreateBindGroupError> { + use super::binding_model::CreateBindGroupError as Error; + + if let Some(count) = count { + let count = count.get() as usize; + if count < num_bindings { + return Err(Error::BindingArrayPartialLengthMismatch { + actual: num_bindings, + expected: count, + }); + } + if count != num_bindings + && !features.contains(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY) + { + return Err(Error::BindingArrayLengthMismatch { + actual: num_bindings, + expected: count, + }); + } + if num_bindings == 0 { + return Err(Error::BindingArrayZeroLength); + } + } else { + return Err(Error::SingleBindingExpected); + }; + + Ok(()) + } + + fn texture_use_parameters( + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + view: &crate::resource::TextureView<A>, + expected: &'static str, + ) -> Result<(wgt::TextureUsages, hal::TextureUses), binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + if view + .desc + .aspects() + .contains(hal::FormatAspects::DEPTH | hal::FormatAspects::STENCIL) + { + return Err(Error::DepthStencilAspect); + } + match decl.ty { + wgt::BindingType::Texture { + sample_type, + view_dimension, + multisampled, + } => { + use wgt::TextureSampleType as Tst; + if multisampled != (view.samples != 1) { + return Err(Error::InvalidTextureMultisample { + binding, + layout_multisampled: multisampled, + view_samples: view.samples, + }); + } + let compat_sample_type = view + .desc + .format + .sample_type(Some(view.desc.range.aspect)) + .unwrap(); + match (sample_type, compat_sample_type) { + (Tst::Uint, Tst::Uint) | + (Tst::Sint, Tst::Sint) | + (Tst::Depth, Tst::Depth) | + // if we expect non-filterable, accept anything float + (Tst::Float { filterable: false }, Tst::Float { .. }) | + // if we expect filterable, require it + (Tst::Float { filterable: true }, Tst::Float { filterable: true }) | + // if we expect non-filterable, also accept depth + (Tst::Float { filterable: false }, Tst::Depth) => {} + // if we expect filterable, also accept Float that is defined as + // unfilterable if filterable feature is explicitly enabled (only hit + // if wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES is + // enabled) + (Tst::Float { filterable: true }, Tst::Float { .. }) if view.format_features.flags.contains(wgt::TextureFormatFeatureFlags::FILTERABLE) => {} + _ => { + return Err(Error::InvalidTextureSampleType { + binding, + layout_sample_type: sample_type, + view_format: view.desc.format, + }) + } + } + if view_dimension != view.desc.dimension { + return Err(Error::InvalidTextureDimension { + binding, + layout_dimension: view_dimension, + view_dimension: view.desc.dimension, + }); + } + Ok(( + wgt::TextureUsages::TEXTURE_BINDING, + hal::TextureUses::RESOURCE, + )) + } + wgt::BindingType::StorageTexture { + access, + format, + view_dimension, + } => { + if format != view.desc.format { + return Err(Error::InvalidStorageTextureFormat { + binding, + layout_format: format, + view_format: view.desc.format, + }); + } + if view_dimension != view.desc.dimension { + return Err(Error::InvalidTextureDimension { + binding, + layout_dimension: view_dimension, + view_dimension: view.desc.dimension, + }); + } + + let mip_level_count = view.selector.mips.end - view.selector.mips.start; + if mip_level_count != 1 { + return Err(Error::InvalidStorageTextureMipLevelCount { + binding, + mip_level_count, + }); + } + + let internal_use = match access { + wgt::StorageTextureAccess::WriteOnly => hal::TextureUses::STORAGE_READ_WRITE, + wgt::StorageTextureAccess::ReadOnly => { + if !view + .format_features + .flags + .contains(wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE) + { + return Err(Error::StorageReadNotSupported(view.desc.format)); + } + hal::TextureUses::STORAGE_READ + } + wgt::StorageTextureAccess::ReadWrite => { + if !view + .format_features + .flags + .contains(wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE) + { + return Err(Error::StorageReadNotSupported(view.desc.format)); + } + + hal::TextureUses::STORAGE_READ_WRITE + } + }; + Ok((wgt::TextureUsages::STORAGE_BINDING, internal_use)) + } + _ => Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected, + }), + } + } + + fn create_pipeline_layout( + &self, + self_id: id::DeviceId, + desc: &binding_model::PipelineLayoutDescriptor, + bgl_guard: &Storage<binding_model::BindGroupLayout<A>, id::BindGroupLayoutId>, + ) -> Result<binding_model::PipelineLayout<A>, 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.require_features(wgt::Features::PUSH_CONSTANTS)?; + } + + let mut used_stages = wgt::ShaderStages::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 bgl_vec = desc + .bind_group_layouts + .iter() + .map(|&id| &bgl_guard.get(id).unwrap().raw) + .collect::<Vec<_>>(); + let hal_desc = hal::PipelineLayoutDescriptor { + label: desc.label.borrow_option(), + flags: hal::PipelineLayoutFlags::BASE_VERTEX_INSTANCE, + bind_group_layouts: &bgl_vec, + push_constant_ranges: desc.push_constant_ranges.as_ref(), + }; + + let raw = unsafe { + self.raw + .create_pipeline_layout(&hal_desc) + .map_err(DeviceError::from)? + }; + + 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| { + // manually add a dependency to BGL + 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( + &self, + self_id: id::DeviceId, + implicit_context: Option<ImplicitPipelineContext>, + mut derived_group_layouts: ArrayVec<binding_model::BindEntryMap, { hal::MAX_BIND_GROUPS }>, + bgl_guard: &mut Storage<binding_model::BindGroupLayout<A>, id::BindGroupLayoutId>, + pipeline_layout_guard: &mut Storage<binding_model::PipelineLayout<A>, id::PipelineLayoutId>, + ) -> Result<id::PipelineLayoutId, pipeline::ImplicitLayoutError> { + while derived_group_layouts + .last() + .map_or(false, |map| map.is_empty()) + { + derived_group_layouts.pop(); + } + let mut ids = implicit_context.ok_or(pipeline::ImplicitLayoutError::MissingIds(0))?; + let group_count = derived_group_layouts.len(); + if ids.group_ids.len() < group_count { + log::error!( + "Not enough bind group IDs ({}) specified for the implicit layout ({})", + ids.group_ids.len(), + derived_group_layouts.len() + ); + return Err(pipeline::ImplicitLayoutError::MissingIds(group_count as _)); + } + + for (bgl_id, map) in ids.group_ids.iter_mut().zip(derived_group_layouts) { + match Device::deduplicate_bind_group_layout(self_id, &map, bgl_guard) { + Some(dedup_id) => { + *bgl_id = dedup_id; + } + None => { + let bgl = self.create_bind_group_layout(self_id, None, map)?; + bgl_guard.force_replace(*bgl_id, bgl); + } + }; + } + + let layout_desc = binding_model::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: Cow::Borrowed(&ids.group_ids[..group_count]), + push_constant_ranges: Cow::Borrowed(&[]), //TODO? + }; + let layout = self.create_pipeline_layout(self_id, &layout_desc, bgl_guard)?; + pipeline_layout_guard.force_replace(ids.root_id, layout); + Ok(ids.root_id) + } + + fn create_compute_pipeline<G: GlobalIdentityHandlerFactory>( + &self, + self_id: id::DeviceId, + desc: &pipeline::ComputePipelineDescriptor, + implicit_context: Option<ImplicitPipelineContext>, + hub: &Hub<A, G>, + token: &mut Token<Self>, + ) -> Result<pipeline::ComputePipeline<A>, 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); + + // This has to be done first, or otherwise the IDs may be pointing to entries + // that are not even in the storage. + if let Some(ref ids) = implicit_context { + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + for &bgl_id in ids.group_ids.iter() { + bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + } + } + + self.require_downlevel_flags(wgt::DownlevelFlags::COMPUTE_SHADERS)?; + + let mut derived_group_layouts = + ArrayVec::<binding_model::BindEntryMap, { hal::MAX_BIND_GROUPS }>::new(); + let mut shader_binding_sizes = FastHashMap::default(); + + let io = validation::StageIo::default(); + let (shader_module_guard, _) = hub.shader_modules.read(&mut token); + + let shader_module = shader_module_guard + .get(desc.stage.module) + .map_err(|_| validation::StageError::InvalidModule)?; + + { + let flag = wgt::ShaderStages::COMPUTE; + let provided_layouts = match desc.layout { + Some(pipeline_layout_id) => Some(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()); + } + None + } + }; + if let Some(ref interface) = shader_module.interface { + let _ = interface.check_stage( + provided_layouts.as_ref().map(|p| p.as_slice()), + &mut derived_group_layouts, + &mut shader_binding_sizes, + &desc.stage.entry_point, + flag, + io, + None, + )?; + } + } + + let pipeline_layout_id = match desc.layout { + Some(id) => id, + None => self.derive_pipeline_layout( + self_id, + implicit_context, + derived_group_layouts, + &mut *bgl_guard, + &mut *pipeline_layout_guard, + )?, + }; + let layout = pipeline_layout_guard + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateComputePipelineError::InvalidLayout)?; + + let late_sized_buffer_groups = + Device::make_late_sized_buffer_groups(&shader_binding_sizes, layout, &*bgl_guard); + + let pipeline_desc = hal::ComputePipelineDescriptor { + label: desc.label.borrow_option(), + layout: &layout.raw, + stage: hal::ProgrammableStage { + entry_point: desc.stage.entry_point.as_ref(), + module: &shader_module.raw, + }, + }; + + let raw = + unsafe { self.raw.create_compute_pipeline(&pipeline_desc) }.map_err( + |err| match err { + hal::PipelineError::Device(error) => { + pipeline::CreateComputePipelineError::Device(error.into()) + } + hal::PipelineError::Linkage(_stages, msg) => { + pipeline::CreateComputePipelineError::Internal(msg) + } + hal::PipelineError::EntryPoint(_stage) => { + pipeline::CreateComputePipelineError::Internal(EP_FAILURE.to_string()) + } + }, + )?; + + 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(), + }, + late_sized_buffer_groups, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + }; + Ok(pipeline) + } + + fn create_render_pipeline<G: GlobalIdentityHandlerFactory>( + &self, + self_id: id::DeviceId, + adapter: &Adapter<A>, + desc: &pipeline::RenderPipelineDescriptor, + implicit_context: Option<ImplicitPipelineContext>, + hub: &Hub<A, G>, + token: &mut Token<Self>, + ) -> Result<pipeline::RenderPipeline<A>, pipeline::CreateRenderPipelineError> { + use wgt::TextureFormatFeatureFlags as Tfff; + + //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); + + // This has to be done first, or otherwise the IDs may be pointing to entries + // that are not even in the storage. + if let Some(ref ids) = implicit_context { + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + for &bgl_id in ids.group_ids.iter() { + bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + } + } + + let mut derived_group_layouts = + ArrayVec::<binding_model::BindEntryMap, { hal::MAX_BIND_GROUPS }>::new(); + let mut shader_binding_sizes = FastHashMap::default(); + + let num_attachments = desc.fragment.as_ref().map(|f| f.targets.len()).unwrap_or(0); + if num_attachments > hal::MAX_COLOR_ATTACHMENTS { + return Err(pipeline::CreateRenderPipelineError::ColorAttachment( + command::ColorAttachmentError::TooMany { + given: num_attachments, + limit: hal::MAX_COLOR_ATTACHMENTS, + }, + )); + } + + let color_targets = desc + .fragment + .as_ref() + .map_or(&[][..], |fragment| &fragment.targets); + let depth_stencil_state = desc.depth_stencil.as_ref(); + + let cts: ArrayVec<_, { hal::MAX_COLOR_ATTACHMENTS }> = + color_targets.iter().filter_map(|x| x.as_ref()).collect(); + if !cts.is_empty() && { + let first = &cts[0]; + cts[1..] + .iter() + .any(|ct| ct.write_mask != first.write_mask || ct.blend != first.blend) + } { + log::info!("Color targets: {:?}", color_targets); + self.require_downlevel_flags(wgt::DownlevelFlags::INDEPENDENT_BLEND)?; + } + + let mut io = validation::StageIo::default(); + let mut validated_stages = wgt::ShaderStages::empty(); + + let mut vertex_steps = Vec::with_capacity(desc.vertex.buffers.len()); + let mut vertex_buffers = Vec::with_capacity(desc.vertex.buffers.len()); + let mut total_attributes = 0; + for (i, vb_state) in desc.vertex.buffers.iter().enumerate() { + vertex_steps.push(pipeline::VertexStep { + stride: vb_state.array_stride, + mode: vb_state.step_mode, + }); + if vb_state.attributes.is_empty() { + continue; + } + if vb_state.array_stride > self.limits.max_vertex_buffer_array_stride as u64 { + return Err(pipeline::CreateRenderPipelineError::VertexStrideTooLarge { + index: i as u32, + given: vb_state.array_stride as u32, + limit: self.limits.max_vertex_buffer_array_stride, + }); + } + if vb_state.array_stride % wgt::VERTEX_STRIDE_ALIGNMENT != 0 { + return Err(pipeline::CreateRenderPipelineError::UnalignedVertexStride { + index: i as u32, + stride: vb_state.array_stride, + }); + } + vertex_buffers.push(hal::VertexBufferLayout { + array_stride: vb_state.array_stride, + step_mode: vb_state.step_mode, + attributes: vb_state.attributes.as_ref(), + }); + + for attribute in vb_state.attributes.iter() { + if attribute.offset >= 0x10000000 { + return Err( + pipeline::CreateRenderPipelineError::InvalidVertexAttributeOffset { + location: attribute.shader_location, + offset: attribute.offset, + }, + ); + } + + if let wgt::VertexFormat::Float64 + | wgt::VertexFormat::Float64x2 + | wgt::VertexFormat::Float64x3 + | wgt::VertexFormat::Float64x4 = attribute.format + { + self.require_features(wgt::Features::VERTEX_ATTRIBUTE_64BIT)?; + } + + let previous = io.insert( + attribute.shader_location, + validation::InterfaceVar::vertex_attribute(attribute.format), + ); + + if previous.is_some() { + return Err(pipeline::CreateRenderPipelineError::ShaderLocationClash( + attribute.shader_location, + )); + } + } + total_attributes += vb_state.attributes.len(); + } + + if vertex_buffers.len() > self.limits.max_vertex_buffers as usize { + return Err(pipeline::CreateRenderPipelineError::TooManyVertexBuffers { + given: vertex_buffers.len() as u32, + limit: self.limits.max_vertex_buffers, + }); + } + if total_attributes > self.limits.max_vertex_attributes as usize { + return Err( + pipeline::CreateRenderPipelineError::TooManyVertexAttributes { + given: total_attributes as u32, + limit: self.limits.max_vertex_attributes, + }, + ); + } + + if desc.primitive.strip_index_format.is_some() && !desc.primitive.topology.is_strip() { + return Err( + pipeline::CreateRenderPipelineError::StripIndexFormatForNonStripTopology { + strip_index_format: desc.primitive.strip_index_format, + topology: desc.primitive.topology, + }, + ); + } + + if desc.primitive.unclipped_depth { + self.require_features(wgt::Features::DEPTH_CLIP_CONTROL)?; + } + + if desc.primitive.polygon_mode == wgt::PolygonMode::Line { + self.require_features(wgt::Features::POLYGON_MODE_LINE)?; + } + if desc.primitive.polygon_mode == wgt::PolygonMode::Point { + self.require_features(wgt::Features::POLYGON_MODE_POINT)?; + } + + if desc.primitive.conservative { + self.require_features(wgt::Features::CONSERVATIVE_RASTERIZATION)?; + } + + if desc.primitive.conservative && desc.primitive.polygon_mode != wgt::PolygonMode::Fill { + return Err( + pipeline::CreateRenderPipelineError::ConservativeRasterizationNonFillPolygonMode, + ); + } + + for (i, cs) in color_targets.iter().enumerate() { + if let Some(cs) = cs.as_ref() { + let error = loop { + if cs.write_mask.contains_invalid_bits() { + break Some(pipeline::ColorStateError::InvalidWriteMask(cs.write_mask)); + } + + let format_features = self.describe_format_features(adapter, cs.format)?; + if !format_features + .allowed_usages + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + { + break Some(pipeline::ColorStateError::FormatNotRenderable(cs.format)); + } + let blendable = format_features.flags.contains(Tfff::BLENDABLE); + let filterable = format_features.flags.contains(Tfff::FILTERABLE); + let adapter_specific = self + .features + .contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES); + // according to WebGPU specifications the texture needs to be + // [`TextureFormatFeatureFlags::FILTERABLE`] if blending is set - use + // [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] to elude + // this limitation + if cs.blend.is_some() && (!blendable || (!filterable && !adapter_specific)) { + break Some(pipeline::ColorStateError::FormatNotBlendable(cs.format)); + } + if !hal::FormatAspects::from(cs.format).contains(hal::FormatAspects::COLOR) { + break Some(pipeline::ColorStateError::FormatNotColor(cs.format)); + } + if desc.multisample.count > 1 + && !format_features + .flags + .sample_count_supported(desc.multisample.count) + { + break Some(pipeline::ColorStateError::FormatNotMultisampled(cs.format)); + } + + break None; + }; + if let Some(e) = error { + return Err(pipeline::CreateRenderPipelineError::ColorState(i as u8, e)); + } + } + } + + if let Some(ds) = depth_stencil_state { + let error = loop { + let format_features = self.describe_format_features(adapter, ds.format)?; + if !format_features + .allowed_usages + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + { + break Some(pipeline::DepthStencilStateError::FormatNotRenderable( + ds.format, + )); + } + + let aspect = hal::FormatAspects::from(ds.format); + if ds.is_depth_enabled() && !aspect.contains(hal::FormatAspects::DEPTH) { + break Some(pipeline::DepthStencilStateError::FormatNotDepth(ds.format)); + } + if ds.stencil.is_enabled() && !aspect.contains(hal::FormatAspects::STENCIL) { + break Some(pipeline::DepthStencilStateError::FormatNotStencil( + ds.format, + )); + } + if desc.multisample.count > 1 + && !format_features + .flags + .sample_count_supported(desc.multisample.count) + { + break Some(pipeline::DepthStencilStateError::FormatNotMultisampled( + ds.format, + )); + } + + break None; + }; + if let Some(e) = error { + return Err(pipeline::CreateRenderPipelineError::DepthStencilState(e)); + } + + if ds.bias.clamp != 0.0 { + self.require_downlevel_flags(wgt::DownlevelFlags::DEPTH_BIAS_CLAMP)?; + } + } + + if desc.layout.is_none() { + for _ in 0..self.limits.max_bind_groups { + derived_group_layouts.push(binding_model::BindEntryMap::default()); + } + } + + let samples = { + let sc = desc.multisample.count; + if sc == 0 || sc > 32 || !conv::is_power_of_two_u32(sc) { + return Err(pipeline::CreateRenderPipelineError::InvalidSampleCount(sc)); + } + sc + }; + + let (shader_module_guard, _) = hub.shader_modules.read(&mut token); + + let vertex_stage = { + let stage = &desc.vertex.stage; + let flag = wgt::ShaderStages::VERTEX; + + let shader_module = shader_module_guard.get(stage.module).map_err(|_| { + pipeline::CreateRenderPipelineError::Stage { + stage: flag, + error: validation::StageError::InvalidModule, + } + })?; + + let provided_layouts = match desc.layout { + Some(pipeline_layout_id) => { + let pipeline_layout = pipeline_layout_guard + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; + Some(Device::get_introspection_bind_group_layouts( + pipeline_layout, + &*bgl_guard, + )) + } + None => None, + }; + + if let Some(ref interface) = shader_module.interface { + io = interface + .check_stage( + provided_layouts.as_ref().map(|p| p.as_slice()), + &mut derived_group_layouts, + &mut shader_binding_sizes, + &stage.entry_point, + flag, + io, + desc.depth_stencil.as_ref().map(|d| d.depth_compare), + ) + .map_err(|error| pipeline::CreateRenderPipelineError::Stage { + stage: flag, + error, + })?; + validated_stages |= flag; + } + + hal::ProgrammableStage { + module: &shader_module.raw, + entry_point: stage.entry_point.as_ref(), + } + }; + + let fragment_stage = match desc.fragment { + Some(ref fragment) => { + let flag = wgt::ShaderStages::FRAGMENT; + + let shader_module = + shader_module_guard + .get(fragment.stage.module) + .map_err(|_| pipeline::CreateRenderPipelineError::Stage { + stage: flag, + error: validation::StageError::InvalidModule, + })?; + + let provided_layouts = match desc.layout { + Some(pipeline_layout_id) => Some(Device::get_introspection_bind_group_layouts( + pipeline_layout_guard + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?, + &*bgl_guard, + )), + None => None, + }; + + if validated_stages == wgt::ShaderStages::VERTEX { + if let Some(ref interface) = shader_module.interface { + io = interface + .check_stage( + provided_layouts.as_ref().map(|p| p.as_slice()), + &mut derived_group_layouts, + &mut shader_binding_sizes, + &fragment.stage.entry_point, + flag, + io, + desc.depth_stencil.as_ref().map(|d| d.depth_compare), + ) + .map_err(|error| pipeline::CreateRenderPipelineError::Stage { + stage: flag, + error, + })?; + validated_stages |= flag; + } + } + + Some(hal::ProgrammableStage { + module: &shader_module.raw, + entry_point: fragment.stage.entry_point.as_ref(), + }) + } + None => None, + }; + + if validated_stages.contains(wgt::ShaderStages::FRAGMENT) { + for (i, output) in io.iter() { + match color_targets.get(*i as usize) { + Some(&Some(ref state)) => { + validation::check_texture_format(state.format, &output.ty).map_err( + |pipeline| { + pipeline::CreateRenderPipelineError::ColorState( + *i as u8, + pipeline::ColorStateError::IncompatibleFormat { + pipeline, + shader: output.ty, + }, + ) + }, + )?; + } + _ => { + log::info!( + "The fragment stage {:?} output @location({}) values are ignored", + fragment_stage + .as_ref() + .map_or("", |stage| stage.entry_point), + i + ); + } + } + } + } + let last_stage = match desc.fragment { + Some(_) => wgt::ShaderStages::FRAGMENT, + None => wgt::ShaderStages::VERTEX, + }; + if desc.layout.is_none() && !validated_stages.contains(last_stage) { + return Err(pipeline::ImplicitLayoutError::ReflectionError(last_stage).into()); + } + + let pipeline_layout_id = match desc.layout { + Some(id) => id, + None => self.derive_pipeline_layout( + self_id, + implicit_context, + derived_group_layouts, + &mut *bgl_guard, + &mut *pipeline_layout_guard, + )?, + }; + let layout = pipeline_layout_guard + .get(pipeline_layout_id) + .map_err(|_| pipeline::CreateRenderPipelineError::InvalidLayout)?; + + // Multiview is only supported if the feature is enabled + if desc.multiview.is_some() { + self.require_features(wgt::Features::MULTIVIEW)?; + } + + if !self + .downlevel + .flags + .contains(wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED) + { + for (binding, size) in shader_binding_sizes.iter() { + if size.get() % 16 != 0 { + return Err(pipeline::CreateRenderPipelineError::UnalignedShader { + binding: binding.binding, + group: binding.group, + size: size.get(), + }); + } + } + } + + let late_sized_buffer_groups = + Device::make_late_sized_buffer_groups(&shader_binding_sizes, layout, &*bgl_guard); + + let pipeline_desc = hal::RenderPipelineDescriptor { + label: desc.label.borrow_option(), + layout: &layout.raw, + vertex_buffers: &vertex_buffers, + vertex_stage, + primitive: desc.primitive, + depth_stencil: desc.depth_stencil.clone(), + multisample: desc.multisample, + fragment_stage, + color_targets, + multiview: desc.multiview, + }; + let raw = + unsafe { self.raw.create_render_pipeline(&pipeline_desc) }.map_err( + |err| match err { + hal::PipelineError::Device(error) => { + pipeline::CreateRenderPipelineError::Device(error.into()) + } + hal::PipelineError::Linkage(stage, msg) => { + pipeline::CreateRenderPipelineError::Internal { stage, error: msg } + } + hal::PipelineError::EntryPoint(stage) => { + pipeline::CreateRenderPipelineError::Internal { + stage: hal::auxil::map_naga_stage(stage), + error: EP_FAILURE.to_string(), + } + } + }, + )?; + + let pass_context = RenderPassContext { + attachments: AttachmentData { + colors: color_targets + .iter() + .map(|state| state.as_ref().map(|s| s.format)) + .collect(), + resolves: ArrayVec::new(), + depth_stencil: depth_stencil_state.as_ref().map(|state| state.format), + }, + sample_count: samples, + multiview: desc.multiview, + }; + + let mut flags = pipeline::PipelineFlags::empty(); + for state in color_targets.iter().filter_map(|s| s.as_ref()) { + if let Some(ref bs) = state.blend { + if bs.color.uses_constant() | bs.alpha.uses_constant() { + flags |= pipeline::PipelineFlags::BLEND_CONSTANT; + } + } + } + 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_depth_read_only() { + flags |= pipeline::PipelineFlags::WRITES_DEPTH; + } + if !ds.is_stencil_read_only(desc.primitive.cull_mode) { + flags |= pipeline::PipelineFlags::WRITES_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, + strip_index_format: desc.primitive.strip_index_format, + vertex_steps, + late_sized_buffer_groups, + life_guard: LifeGuard::new(desc.label.borrow_or_default()), + }; + Ok(pipeline) + } + + fn describe_format_features( + &self, + adapter: &Adapter<A>, + format: TextureFormat, + ) -> Result<wgt::TextureFormatFeatures, MissingFeatures> { + self.require_features(format.required_features())?; + + let using_device_features = self + .features + .contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES); + // If we're running downlevel, we need to manually ask the backend what + // we can use as we can't trust WebGPU. + let downlevel = !self.downlevel.is_webgpu_compliant(); + + if using_device_features || downlevel { + Ok(adapter.get_texture_format_features(format)) + } else { + Ok(format.guaranteed_format_features(self.features)) + } + } + + fn wait_for_submit( + &self, + submission_index: SubmissionIndex, + token: &mut Token<Self>, + ) -> Result<(), WaitIdleError> { + let last_done_index = unsafe { + self.raw + .get_fence_value(&self.fence) + .map_err(DeviceError::from)? + }; + if last_done_index < submission_index { + log::info!("Waiting for submission {:?}", submission_index); + unsafe { + self.raw + .wait(&self.fence, submission_index, !0) + .map_err(DeviceError::from)? + }; + let closures = self + .lock_life(token) + .triage_submissions(submission_index, &self.command_allocator); + assert!( + closures.is_empty(), + "wait_for_submit is not expected to work with closures" + ); + } + Ok(()) + } + + fn create_query_set( + &self, + self_id: id::DeviceId, + desc: &resource::QuerySetDescriptor, + ) -> Result<resource::QuerySet<A>, resource::CreateQuerySetError> { + use resource::CreateQuerySetError as Error; + + match desc.ty { + wgt::QueryType::Occlusion => {} + wgt::QueryType::Timestamp => { + self.require_features(wgt::Features::TIMESTAMP_QUERY)?; + } + wgt::QueryType::PipelineStatistics(..) => { + self.require_features(wgt::Features::PIPELINE_STATISTICS_QUERY)?; + } + } + + if desc.count == 0 { + return Err(Error::ZeroCount); + } + + if desc.count > wgt::QUERY_SET_MAX_QUERIES { + return Err(Error::TooManyQueries { + count: desc.count, + maximum: wgt::QUERY_SET_MAX_QUERIES, + }); + } + + let hal_desc = desc.map_label(super::LabelHelpers::borrow_option); + Ok(resource::QuerySet { + raw: unsafe { self.raw.create_query_set(&hal_desc).unwrap() }, + device_id: Stored { + value: id::Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + life_guard: LifeGuard::new(""), + desc: desc.map_label(|_| ()), + }) + } +} + +impl<A: HalApi> Device<A> { + pub(crate) fn destroy_buffer(&self, buffer: resource::Buffer<A>) { + if let Some(raw) = buffer.raw { + unsafe { + self.raw.destroy_buffer(raw); + } + } + } + + pub(crate) fn destroy_command_buffer(&self, cmd_buf: command::CommandBuffer<A>) { + let mut baked = cmd_buf.into_baked(); + unsafe { + baked.encoder.reset_all(baked.list.into_iter()); + } + unsafe { + self.raw.destroy_command_encoder(baked.encoder); + } + } + + /// Wait for idle and remove resources that we can, before we die. + pub(crate) fn prepare_to_die(&mut self) { + self.pending_writes.deactivate(); + let mut life_tracker = self.life_tracker.lock(); + let current_index = self.active_submission_index; + if let Err(error) = unsafe { self.raw.wait(&self.fence, current_index, CLEANUP_WAIT_MS) } { + log::error!("failed to wait for the device: {:?}", error); + } + let _ = life_tracker.triage_submissions(current_index, &self.command_allocator); + life_tracker.cleanup(&self.raw); + #[cfg(feature = "trace")] + { + self.trace = None; + } + } + + pub(crate) fn dispose(self) { + self.pending_writes.dispose(&self.raw); + self.command_allocator.into_inner().dispose(&self.raw); + unsafe { + self.raw.destroy_buffer(self.zero_buffer); + self.raw.destroy_fence(self.fence); + self.raw.exit(self.queue); + } + } +} + +impl<A: HalApi> crate::hub::Resource for Device<A> { + const TYPE: &'static str = "Device"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[derive(Clone, Debug, Error)] +#[error("Device is invalid")] +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::DeviceError> for DeviceError { + fn from(error: hal::DeviceError) -> Self { + match error { + hal::DeviceError::Lost => DeviceError::Lost, + hal::DeviceError::OutOfMemory => DeviceError::OutOfMemory, + } + } +} + +#[derive(Clone, Debug, Error)] +#[error("Features {0:?} are required but not enabled on the device")] +pub struct MissingFeatures(pub wgt::Features); + +#[derive(Clone, Debug, Error)] +#[error( + "Downlevel flags {0:?} are required but not supported on the device.\n{}", + DOWNLEVEL_ERROR_MESSAGE +)] +pub struct MissingDownlevelFlags(pub wgt::DownlevelFlags); + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub struct ImplicitPipelineContext { + pub root_id: id::PipelineLayoutId, + pub group_ids: ArrayVec<id::BindGroupLayoutId, { hal::MAX_BIND_GROUPS }>, +} + +pub struct ImplicitPipelineIds<'a, G: GlobalIdentityHandlerFactory> { + pub root_id: Input<G, id::PipelineLayoutId>, + pub group_ids: &'a [Input<G, id::BindGroupLayoutId>], +} + +impl<G: GlobalIdentityHandlerFactory> ImplicitPipelineIds<'_, G> { + fn prepare<A: HalApi>(self, hub: &Hub<A, G>) -> ImplicitPipelineContext { + ImplicitPipelineContext { + root_id: hub.pipeline_layouts.prepare(self.root_id).into_id(), + group_ids: self + .group_ids + .iter() + .map(|id_in| hub.bind_group_layouts.prepare(id_in.clone()).into_id()) + .collect(), + } + } +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn adapter_is_surface_supported<A: HalApi>( + &self, + adapter_id: id::AdapterId, + surface_id: id::SurfaceId, + ) -> Result<bool, instance::IsSurfaceSupportedError> { + let hub = A::hub(self); + let mut token = Token::root(); + + let (surface_guard, mut token) = self.surfaces.read(&mut token); + let (adapter_guard, mut _token) = hub.adapters.read(&mut token); + let adapter = adapter_guard + .get(adapter_id) + .map_err(|_| instance::IsSurfaceSupportedError::InvalidAdapter)?; + let surface = surface_guard + .get(surface_id) + .map_err(|_| instance::IsSurfaceSupportedError::InvalidSurface)?; + Ok(adapter.is_surface_supported(surface)) + } + + pub fn surface_get_capabilities<A: HalApi>( + &self, + surface_id: id::SurfaceId, + adapter_id: id::AdapterId, + ) -> Result<wgt::SurfaceCapabilities, instance::GetSurfaceSupportError> { + profiling::scope!("Surface::get_capabilities"); + self.fetch_adapter_and_surface::<A, _, _>(surface_id, adapter_id, |adapter, surface| { + let mut hal_caps = surface.get_capabilities(adapter)?; + + hal_caps.formats.sort_by_key(|f| !f.is_srgb()); + + Ok(wgt::SurfaceCapabilities { + formats: hal_caps.formats, + present_modes: hal_caps.present_modes, + alpha_modes: hal_caps.composite_alpha_modes, + }) + }) + } + + fn fetch_adapter_and_surface< + A: HalApi, + F: FnOnce(&Adapter<A>, &Surface) -> Result<B, instance::GetSurfaceSupportError>, + B, + >( + &self, + surface_id: id::SurfaceId, + adapter_id: id::AdapterId, + get_supported_callback: F, + ) -> Result<B, instance::GetSurfaceSupportError> { + let hub = A::hub(self); + let mut token = Token::root(); + + let (surface_guard, mut token) = self.surfaces.read(&mut token); + let (adapter_guard, mut _token) = hub.adapters.read(&mut token); + let adapter = adapter_guard + .get(adapter_id) + .map_err(|_| instance::GetSurfaceSupportError::InvalidAdapter)?; + let surface = surface_guard + .get(surface_id) + .map_err(|_| instance::GetSurfaceSupportError::InvalidSurface)?; + + get_supported_callback(adapter, surface) + } + + pub fn device_features<A: HalApi>( + &self, + device_id: id::DeviceId, + ) -> Result<wgt::Features, InvalidDevice> { + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + ) -> Result<wgt::Limits, InvalidDevice> { + let hub = A::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_downlevel_properties<A: HalApi>( + &self, + device_id: id::DeviceId, + ) -> Result<wgt::DownlevelCapabilities, InvalidDevice> { + let hub = A::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.downlevel.clone()) + } + + pub fn device_create_buffer<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &resource::BufferDescriptor, + id_in: Input<G, id::BufferId>, + ) -> (id::BufferId, Option<resource::CreateBufferError>) { + profiling::scope!("Device::create_buffer"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.buffers.prepare(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(), + }; + #[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::BufferUsages::MAP_WRITE) { + desc.usage |= wgt::BufferUsages::COPY_DST; + } + trace + .lock() + .add(trace::Action::CreateBuffer(fid.id(), desc)); + } + + 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 { + hal::BufferUses::empty() + } else if desc.usage.contains(wgt::BufferUsages::MAP_WRITE) { + // buffer is mappable, so we are just doing that at start + let map_size = buffer.size; + let ptr = if map_size == 0 { + std::ptr::NonNull::dangling() + } else { + match map_buffer(&device.raw, &mut buffer, 0, map_size, HostMap::Write) { + Ok(ptr) => ptr, + Err(e) => { + let raw = buffer.raw.unwrap(); + device.lock_life(&mut token).schedule_resource_destruction( + queue::TempResource::Buffer(raw), + !0, + ); + break e.into(); + } + } + }; + buffer.map_state = resource::BufferMapState::Active { + ptr, + range: 0..map_size, + host: HostMap::Write, + }; + hal::BufferUses::MAP_WRITE + } else { + // buffer needs staging area for initialization only + let stage_desc = wgt::BufferDescriptor { + label: Some(Cow::Borrowed( + "(wgpu internal) initializing unmappable buffer", + )), + size: desc.size, + usage: wgt::BufferUsages::MAP_WRITE | wgt::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }; + let mut stage = match device.create_buffer(device_id, &stage_desc, true) { + Ok(stage) => stage, + Err(e) => { + let raw = buffer.raw.unwrap(); + device + .lock_life(&mut token) + .schedule_resource_destruction(queue::TempResource::Buffer(raw), !0); + break e; + } + }; + let stage_buffer = stage.raw.unwrap(); + let mapping = match unsafe { device.raw.map_buffer(&stage_buffer, 0..stage.size) } { + Ok(mapping) => mapping, + Err(e) => { + let raw = buffer.raw.unwrap(); + let mut life_lock = device.lock_life(&mut token); + life_lock + .schedule_resource_destruction(queue::TempResource::Buffer(raw), !0); + life_lock.schedule_resource_destruction( + queue::TempResource::Buffer(stage_buffer), + !0, + ); + break DeviceError::from(e).into(); + } + }; + + assert_eq!(buffer.size % wgt::COPY_BUFFER_ALIGNMENT, 0); + // Zero initialize memory and then mark both staging and buffer as initialized + // (it's guaranteed that this is the case by the time the buffer is usable) + unsafe { ptr::write_bytes(mapping.ptr.as_ptr(), 0, buffer.size as usize) }; + buffer.initialization_status.drain(0..buffer.size); + stage.initialization_status.drain(0..buffer.size); + + buffer.map_state = resource::BufferMapState::Init { + ptr: mapping.ptr, + needs_flush: !mapping.is_coherent, + stage_buffer, + }; + hal::BufferUses::COPY_DST + }; + + let id = fid.assign(buffer, &mut token); + log::info!("Created buffer {:?} with {:?}", id, desc); + + device + .trackers + .lock() + .buffers + .insert_single(id, ref_count, buffer_use); + + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + /// Assign `id_in` an error with the given `label`. + /// + /// Ensure that future attempts to use `id_in` as a buffer ID will propagate + /// the error, following the WebGPU ["contagious invalidity"] style. + /// + /// Firefox uses this function to comply strictly with the WebGPU spec, + /// which requires [`GPUBufferDescriptor`] validation to be generated on the + /// Device timeline and leave the newly created [`GPUBuffer`] invalid. + /// + /// Ideally, we would simply let [`device_create_buffer`] take care of all + /// of this, but some errors must be detected before we can even construct a + /// [`wgpu_types::BufferDescriptor`] to give it. For example, the WebGPU API + /// allows a `GPUBufferDescriptor`'s [`usage`] property to be any WebIDL + /// `unsigned long` value, but we can't construct a + /// [`wgpu_types::BufferUsages`] value from values with unassigned bits + /// set. This means we must validate `usage` before we can call + /// `device_create_buffer`. + /// + /// When that validation fails, we must arrange for the buffer id to be + /// considered invalid. This method provides the means to do so. + /// + /// ["contagious invalidity"]: https://www.w3.org/TR/webgpu/#invalidity + /// [`GPUBufferDescriptor`]: https://www.w3.org/TR/webgpu/#dictdef-gpubufferdescriptor + /// [`GPUBuffer`]: https://www.w3.org/TR/webgpu/#gpubuffer + /// [`wgpu_types::BufferDescriptor`]: wgt::BufferDescriptor + /// [`device_create_buffer`]: Global::device_create_buffer + /// [`usage`]: https://www.w3.org/TR/webgpu/#dom-gputexturedescriptor-usage + /// [`wgpu_types::BufferUsages`]: wgt::BufferUsages + pub fn create_buffer_error<A: HalApi>(&self, id_in: Input<G, id::BufferId>, label: Label) { + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.buffers.prepare(id_in); + + fid.assign_error(label.borrow_or_default(), &mut token); + } + + pub fn create_render_bundle_error<A: HalApi>( + &self, + id_in: Input<G, id::RenderBundleId>, + label: Label, + ) { + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.render_bundles.prepare(id_in); + + let (_, mut token) = hub.devices.read(&mut token); + fid.assign_error(label.borrow_or_default(), &mut token); + } + + /// Assign `id_in` an error with the given `label`. + /// + /// See `create_buffer_error` for more context and explaination. + pub fn create_texture_error<A: HalApi>(&self, id_in: Input<G, id::TextureId>, label: Label) { + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.textures.prepare(id_in); + + fid.assign_error(label.borrow_or_default(), &mut token); + } + + #[cfg(feature = "replay")] + pub fn device_wait_for_buffer<A: HalApi>( + &self, + device_id: id::DeviceId, + buffer_id: id::BufferId, + ) -> Result<(), WaitIdleError> { + let hub = A::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.life_count(), + Err(_) => return Ok(()), + } + }; + + device_guard + .get(device_id) + .map_err(|_| DeviceError::Invalid)? + .wait_for_submit(last_submission, &mut token) + } + + #[doc(hidden)] + pub fn device_set_buffer_sub_data<A: HalApi>( + &self, + device_id: id::DeviceId, + buffer_id: id::BufferId, + offset: BufferAddress, + data: &[u8], + ) -> BufferAccessResult { + profiling::scope!("Device::set_buffer_sub_data"); + + let hub = A::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(|_| BufferAccessError::Invalid)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::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 raw_buf = buffer.raw.as_ref().unwrap(); + unsafe { + let mapping = device + .raw + .map_buffer(raw_buf, offset..offset + data.len() as u64) + .map_err(DeviceError::from)?; + ptr::copy_nonoverlapping(data.as_ptr(), mapping.ptr.as_ptr(), data.len()); + if !mapping.is_coherent { + device + .raw + .flush_mapped_ranges(raw_buf, iter::once(offset..offset + data.len() as u64)); + } + device + .raw + .unmap_buffer(raw_buf) + .map_err(DeviceError::from)?; + } + + Ok(()) + } + + #[doc(hidden)] + pub fn device_get_buffer_sub_data<A: HalApi>( + &self, + device_id: id::DeviceId, + buffer_id: id::BufferId, + offset: BufferAddress, + data: &mut [u8], + ) -> BufferAccessResult { + profiling::scope!("Device::get_buffer_sub_data"); + + let hub = A::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(|_| BufferAccessError::Invalid)?; + check_buffer_usage(buffer.usage, wgt::BufferUsages::MAP_READ)?; + //assert!(buffer isn't used by the GPU); + + let raw_buf = buffer.raw.as_ref().unwrap(); + unsafe { + let mapping = device + .raw + .map_buffer(raw_buf, offset..offset + data.len() as u64) + .map_err(DeviceError::from)?; + if !mapping.is_coherent { + device.raw.invalidate_mapped_ranges( + raw_buf, + iter::once(offset..offset + data.len() as u64), + ); + } + ptr::copy_nonoverlapping(mapping.ptr.as_ptr(), data.as_mut_ptr(), data.len()); + device + .raw + .unmap_buffer(raw_buf) + .map_err(DeviceError::from)?; + } + + Ok(()) + } + + pub fn buffer_label<A: HalApi>(&self, id: id::BufferId) -> String { + A::hub(self).buffers.label_for_resource(id) + } + + pub fn buffer_destroy<A: HalApi>( + &self, + buffer_id: id::BufferId, + ) -> Result<(), resource::DestroyError> { + profiling::scope!("Buffer::destroy"); + + let map_closure; + // Restrict the locks to this scope. + { + let hub = A::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); + + log::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]; + + map_closure = match &buffer.map_state { + &BufferMapState::Waiting(..) // To get the proper callback behavior. + | &BufferMapState::Init { .. } + | &BufferMapState::Active { .. } + => { + self.buffer_unmap_inner(buffer_id, buffer, device) + .unwrap_or(None) + } + _ => None, + }; + + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::FreeBuffer(buffer_id)); + } + + let raw = 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); + } else { + let last_submit_index = buffer.life_guard.life_count(); + drop(buffer_guard); + device + .lock_life(&mut token) + .schedule_resource_destruction(temp, last_submit_index); + } + } + + // Note: outside the scope where locks are held when calling the callback + if let Some((operation, status)) = map_closure { + operation.callback.call(status); + } + + Ok(()) + } + + pub fn buffer_drop<A: HalApi>(&self, buffer_id: id::BufferId, wait: bool) { + profiling::scope!("Buffer::drop"); + log::debug!("buffer {:?} is dropped", buffer_id); + + let hub = A::hub(self); + let mut token = Token::root(); + + 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.life_count(); + (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) => log::error!("Failed to wait for buffer {:?}: {:?}", buffer_id, e), + } + } + } + + pub fn device_create_texture<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &resource::TextureDescriptor, + id_in: Input<G, id::TextureId>, + ) -> (id::TextureId, Option<resource::CreateTextureError>) { + profiling::scope!("Device::create_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.textures.prepare(id_in); + + let (adapter_guard, mut token) = hub.adapters.read(&mut token); + 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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreateTexture(fid.id(), desc.clone())); + } + + let adapter = &adapter_guard[device.adapter_id.value]; + let texture = match device.create_texture(device_id, adapter, desc) { + Ok(texture) => texture, + Err(error) => break error, + }; + let ref_count = texture.life_guard.add_ref(); + + let id = fid.assign(texture, &mut token); + log::info!("Created texture {:?} with {:?}", id, desc); + + device.trackers.lock().textures.insert_single( + id.0, + ref_count, + hal::TextureUses::UNINITIALIZED, + ); + + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + /// # Safety + /// + /// - `hal_texture` must be created from `device_id` corresponding raw handle. + /// - `hal_texture` must be created respecting `desc` + /// - `hal_texture` must be initialized + pub unsafe fn create_texture_from_hal<A: HalApi>( + &self, + hal_texture: A::Texture, + device_id: id::DeviceId, + desc: &resource::TextureDescriptor, + id_in: Input<G, id::TextureId>, + ) -> (id::TextureId, Option<resource::CreateTextureError>) { + profiling::scope!("Device::create_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.textures.prepare(id_in); + + let (adapter_guard, mut token) = hub.adapters.read(&mut token); + 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(), + }; + + // NB: Any change done through the raw texture handle will not be + // recorded in the replay + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreateTexture(fid.id(), desc.clone())); + } + + let adapter = &adapter_guard[device.adapter_id.value]; + + let format_features = match device + .describe_format_features(adapter, desc.format) + .map_err(|error| resource::CreateTextureError::MissingFeatures(desc.format, error)) + { + Ok(features) => features, + Err(error) => break error, + }; + + let mut texture = device.create_texture_from_hal( + hal_texture, + conv::map_texture_usage(desc.usage, desc.format.into()), + device_id, + desc, + format_features, + resource::TextureClearMode::None, + ); + if desc.usage.contains(wgt::TextureUsages::COPY_DST) { + texture.hal_usage |= hal::TextureUses::COPY_DST; + } + + texture.initialization_status = TextureInitTracker::new(desc.mip_level_count, 0); + + let ref_count = texture.life_guard.add_ref(); + + let id = fid.assign(texture, &mut token); + log::info!("Created texture {:?} with {:?}", id, desc); + + device.trackers.lock().textures.insert_single( + id.0, + ref_count, + hal::TextureUses::UNINITIALIZED, + ); + + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn texture_label<A: HalApi>(&self, id: id::TextureId) -> String { + A::hub(self).textures.label_for_resource(id) + } + + pub fn texture_destroy<A: HalApi>( + &self, + texture_id: id::TextureId, + ) -> Result<(), resource::DestroyError> { + profiling::scope!("Texture::destroy"); + + let hub = A::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); + + log::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 last_submit_index = texture.life_guard.life_count(); + + let clear_views = + match std::mem::replace(&mut texture.clear_mode, resource::TextureClearMode::None) { + resource::TextureClearMode::BufferCopy => SmallVec::new(), + resource::TextureClearMode::RenderPass { clear_views, .. } => clear_views, + resource::TextureClearMode::None => SmallVec::new(), + }; + + match texture.inner { + resource::TextureInner::Native { ref mut raw } => { + let raw = raw.take().ok_or(resource::DestroyError::AlreadyDestroyed)?; + let temp = queue::TempResource::Texture(raw, clear_views); + + if device.pending_writes.dst_textures.contains(&texture_id) { + device.pending_writes.temp_resources.push(temp); + } else { + drop(texture_guard); + device + .lock_life(&mut token) + .schedule_resource_destruction(temp, last_submit_index); + } + } + resource::TextureInner::Surface { .. } => { + for clear_view in clear_views { + unsafe { + device.raw.destroy_texture_view(clear_view); + } + } + // TODO? + } + } + + Ok(()) + } + + pub fn texture_drop<A: HalApi>(&self, texture_id: id::TextureId, wait: bool) { + profiling::scope!("Texture::drop"); + log::debug!("texture {:?} is dropped", texture_id); + + let hub = A::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.life_count(); + (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) => log::error!("Failed to wait for texture {:?}: {:?}", texture_id, e), + } + } + } + + pub fn texture_create_view<A: HalApi>( + &self, + texture_id: id::TextureId, + desc: &resource::TextureViewDescriptor, + id_in: Input<G, id::TextureViewId>, + ) -> (id::TextureViewId, Option<resource::CreateTextureViewError>) { + profiling::scope!("Texture::create_view"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.texture_views.prepare(id_in); + + 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]; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::CreateTextureView { + id: fid.id(), + parent_id: texture_id, + desc: desc.clone(), + }); + } + + 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 = fid.assign(view, &mut token); + + device.trackers.lock().views.insert_single(id, ref_count); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn texture_view_label<A: HalApi>(&self, id: id::TextureViewId) -> String { + A::hub(self).texture_views.label_for_resource(id) + } + + pub fn texture_view_drop<A: HalApi>( + &self, + texture_view_id: id::TextureViewId, + wait: bool, + ) -> Result<(), resource::TextureViewDestroyError> { + profiling::scope!("TextureView::drop"); + log::debug!("texture view {:?} is dropped", texture_view_id); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (last_submit_index, device_id) = { + let (mut texture_view_guard, _) = hub.texture_views.write(&mut token); + + match texture_view_guard.get_mut(texture_view_id) { + Ok(view) => { + let _ref_count = view.life_guard.ref_count.take(); + let last_submit_index = view.life_guard.life_count(); + (last_submit_index, view.device_id.value) + } + 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); + let device = &device_guard[device_id]; + device + .lock_life(&mut token) + .suspected_resources + .texture_views + .push(id::Valid(texture_view_id)); + + if wait { + match device.wait_for_submit(last_submit_index, &mut token) { + Ok(()) => (), + Err(e) => log::error!( + "Failed to wait for texture view {:?}: {:?}", + texture_view_id, + e + ), + } + } + Ok(()) + } + + pub fn device_create_sampler<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &resource::SamplerDescriptor, + id_in: Input<G, id::SamplerId>, + ) -> (id::SamplerId, Option<resource::CreateSamplerError>) { + profiling::scope!("Device::create_sampler"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.samplers.prepare(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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreateSampler(fid.id(), desc.clone())); + } + + 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 = fid.assign(sampler, &mut token); + + device.trackers.lock().samplers.insert_single(id, ref_count); + + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn sampler_label<A: HalApi>(&self, id: id::SamplerId) -> String { + A::hub(self).samplers.label_for_resource(id) + } + + pub fn sampler_drop<A: HalApi>(&self, sampler_id: id::SamplerId) { + profiling::scope!("Sampler::drop"); + log::debug!("sampler {:?} is dropped", sampler_id); + + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &binding_model::BindGroupLayoutDescriptor, + id_in: Input<G, id::BindGroupLayoutId>, + ) -> ( + id::BindGroupLayoutId, + Option<binding_model::CreateBindGroupLayoutError>, + ) { + profiling::scope!("Device::create_bind_group_layout"); + + let mut token = Token::root(); + let hub = A::hub(self); + let fid = hub.bind_group_layouts.prepare(id_in); + + let error = 'outer: loop { + 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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreateBindGroupLayout(fid.id(), desc.clone())); + } + + let mut entry_map = FastHashMap::default(); + for entry in desc.entries.iter() { + if entry.binding > device.limits.max_bindings_per_bind_group { + break 'outer binding_model::CreateBindGroupLayoutError::InvalidBindingIndex { + binding: entry.binding, + maximum: device.limits.max_bindings_per_bind_group, + }; + } + if entry_map.insert(entry.binding, *entry).is_some() { + break 'outer binding_model::CreateBindGroupLayoutError::ConflictBinding( + entry.binding, + ); + } + } + + // 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.borrow_option(), + entry_map, + ) { + Ok(layout) => layout, + Err(e) => break e, + }; + + let id = fid.assign(layout, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn bind_group_layout_label<A: HalApi>(&self, id: id::BindGroupLayoutId) -> String { + A::hub(self).bind_group_layouts.label_for_resource(id) + } + + pub fn bind_group_layout_drop<A: HalApi>(&self, bind_group_layout_id: id::BindGroupLayoutId) { + profiling::scope!("BindGroupLayout::drop"); + log::debug!("bind group layout {:?} is dropped", bind_group_layout_id); + + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &binding_model::PipelineLayoutDescriptor, + id_in: Input<G, id::PipelineLayoutId>, + ) -> ( + id::PipelineLayoutId, + Option<binding_model::CreatePipelineLayoutError>, + ) { + profiling::scope!("Device::create_pipeline_layout"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.pipeline_layouts.prepare(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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreatePipelineLayout(fid.id(), desc.clone())); + } + + 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 = fid.assign(layout, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn pipeline_layout_label<A: HalApi>(&self, id: id::PipelineLayoutId) -> String { + A::hub(self).pipeline_layouts.label_for_resource(id) + } + + pub fn pipeline_layout_drop<A: HalApi>(&self, pipeline_layout_id: id::PipelineLayoutId) { + profiling::scope!("PipelineLayout::drop"); + log::debug!("pipeline layout {:?} is dropped", pipeline_layout_id); + + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &binding_model::BindGroupDescriptor, + id_in: Input<G, id::BindGroupId>, + ) -> (id::BindGroupId, Option<binding_model::CreateBindGroupError>) { + profiling::scope!("Device::create_bind_group"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.bind_groups.prepare(id_in); + + 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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::CreateBindGroup(fid.id(), desc.clone())); + } + + 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 = fid.assign(bind_group, &mut token); + log::debug!("Bind group {:?}", id,); + + device + .trackers + .lock() + .bind_groups + .insert_single(id, ref_count); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn bind_group_label<A: HalApi>(&self, id: id::BindGroupId) -> String { + A::hub(self).bind_groups.label_for_resource(id) + } + + pub fn bind_group_drop<A: HalApi>(&self, bind_group_id: id::BindGroupId) { + profiling::scope!("BindGroup::drop"); + log::debug!("bind group {:?} is dropped", bind_group_id); + + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &pipeline::ShaderModuleDescriptor, + source: pipeline::ShaderModuleSource, + id_in: Input<G, id::ShaderModuleId>, + ) -> ( + id::ShaderModuleId, + Option<pipeline::CreateShaderModuleError>, + ) { + profiling::scope!("Device::create_shader_module"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.shader_modules.prepare(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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + let mut trace = trace.lock(); + let data = match source { + #[cfg(feature = "wgsl")] + pipeline::ShaderModuleSource::Wgsl(ref code) => { + trace.make_binary("wgsl", code.as_bytes()) + } + pipeline::ShaderModuleSource::Naga(ref module) => { + let string = + ron::ser::to_string_pretty(module, ron::ser::PrettyConfig::default()) + .unwrap(); + trace.make_binary("ron", string.as_bytes()) + } + pipeline::ShaderModuleSource::Dummy(_) => { + panic!("found `ShaderModuleSource::Dummy`") + } + }; + trace.add(trace::Action::CreateShaderModule { + id: fid.id(), + desc: desc.clone(), + data, + }); + }; + + let shader = match device.create_shader_module(device_id, desc, source) { + Ok(shader) => shader, + Err(e) => break e, + }; + let id = fid.assign(shader, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + // Unsafe-ness of internal calls has little to do with unsafe-ness of this. + #[allow(unused_unsafe)] + /// # Safety + /// + /// This function passes SPIR-V binary to the backend as-is and can potentially result in a + /// driver crash. + pub unsafe fn device_create_shader_module_spirv<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &pipeline::ShaderModuleDescriptor, + source: Cow<[u32]>, + id_in: Input<G, id::ShaderModuleId>, + ) -> ( + id::ShaderModuleId, + Option<pipeline::CreateShaderModuleError>, + ) { + profiling::scope!("Device::create_shader_module"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.shader_modules.prepare(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(), + }; + #[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(source.as_ptr() as *const u8, source.len() * 4) + }); + trace.add(trace::Action::CreateShaderModule { + id: fid.id(), + desc: desc.clone(), + data, + }); + }; + + let shader = + match unsafe { device.create_shader_module_spirv(device_id, desc, &source) } { + Ok(shader) => shader, + Err(e) => break e, + }; + let id = fid.assign(shader, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn shader_module_label<A: HalApi>(&self, id: id::ShaderModuleId) -> String { + A::hub(self).shader_modules.label_for_resource(id) + } + + pub fn shader_module_drop<A: HalApi>(&self, shader_module_id: id::ShaderModuleId) { + profiling::scope!("ShaderModule::drop"); + log::debug!("shader module {:?} is dropped", shader_module_id); + + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &wgt::CommandEncoderDescriptor<Label>, + id_in: Input<G, id::CommandEncoderId>, + ) -> (id::CommandEncoderId, Option<DeviceError>) { + profiling::scope!("Device::create_command_encoder"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.command_buffers.prepare(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, + }; + let dev_stored = Stored { + value: id::Valid(device_id), + ref_count: device.life_guard.add_ref(), + }; + let encoder = match device + .command_allocator + .lock() + .acquire_encoder(&device.raw, &device.queue) + { + Ok(raw) => raw, + Err(_) => break DeviceError::OutOfMemory, + }; + let command_buffer = command::CommandBuffer::new( + encoder, + dev_stored, + device.limits.clone(), + device.downlevel.clone(), + device.features, + #[cfg(feature = "trace")] + device.trace.is_some(), + &desc.label, + ); + + let id = fid.assign(command_buffer, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn command_buffer_label<A: HalApi>(&self, id: id::CommandBufferId) -> String { + A::hub(self).command_buffers.label_for_resource(id) + } + + pub fn command_encoder_drop<A: HalApi>(&self, command_encoder_id: id::CommandEncoderId) { + profiling::scope!("CommandEncoder::drop"); + log::debug!("command encoder {:?} is dropped", command_encoder_id); + + let hub = A::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.destroy_command_buffer(cmdbuf); + } + } + + pub fn command_buffer_drop<A: HalApi>(&self, command_buffer_id: id::CommandBufferId) { + profiling::scope!("CommandBuffer::drop"); + log::debug!("command buffer {:?} is dropped", command_buffer_id); + self.command_encoder_drop::<A>(command_buffer_id) + } + + pub fn device_create_render_bundle_encoder( + &self, + device_id: id::DeviceId, + desc: &command::RenderBundleEncoderDescriptor, + ) -> ( + id::RenderBundleEncoderId, + Option<command::CreateRenderBundleError>, + ) { + profiling::scope!("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<A: HalApi>( + &self, + bundle_encoder: command::RenderBundleEncoder, + desc: &command::RenderBundleDescriptor, + id_in: Input<G, id::RenderBundleId>, + ) -> (id::RenderBundleId, Option<command::RenderBundleError>) { + profiling::scope!("RenderBundleEncoder::finish"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.render_bundles.prepare(id_in); + + 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, + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::CreateRenderBundle { + id: fid.id(), + desc: trace::new_render_bundle_encoder_descriptor( + desc.label.clone(), + &bundle_encoder.context, + bundle_encoder.is_depth_read_only, + bundle_encoder.is_stencil_read_only, + ), + base: bundle_encoder.to_base_pass(), + }); + } + + let render_bundle = match bundle_encoder.finish(desc, device, hub, &mut token) { + Ok(bundle) => bundle, + Err(e) => break e, + }; + + log::debug!("Render bundle"); + let ref_count = render_bundle.life_guard.add_ref(); + let id = fid.assign(render_bundle, &mut token); + + device.trackers.lock().bundles.insert_single(id, ref_count); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + pub fn render_bundle_label<A: HalApi>(&self, id: id::RenderBundleId) -> String { + A::hub(self).render_bundles.label_for_resource(id) + } + + pub fn render_bundle_drop<A: HalApi>(&self, render_bundle_id: id::RenderBundleId) { + profiling::scope!("RenderBundle::drop"); + log::debug!("render bundle {:?} is dropped", render_bundle_id); + let hub = A::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_query_set<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &resource::QuerySetDescriptor, + id_in: Input<G, id::QuerySetId>, + ) -> (id::QuerySetId, Option<resource::CreateQuerySetError>) { + profiling::scope!("Device::create_query_set"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.query_sets.prepare(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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::CreateQuerySet { + id: fid.id(), + desc: desc.clone(), + }); + } + + let query_set = match device.create_query_set(device_id, desc) { + Ok(query_set) => query_set, + Err(err) => break err, + }; + + let ref_count = query_set.life_guard.add_ref(); + let id = fid.assign(query_set, &mut token); + + device + .trackers + .lock() + .query_sets + .insert_single(id, ref_count); + + return (id.0, None); + }; + + let id = fid.assign_error("", &mut token); + (id, Some(error)) + } + + pub fn query_set_drop<A: HalApi>(&self, query_set_id: id::QuerySetId) { + profiling::scope!("QuerySet::drop"); + log::debug!("query set {:?} is dropped", query_set_id); + + let hub = A::hub(self); + let mut token = Token::root(); + + let device_id = { + let (mut query_set_guard, _) = hub.query_sets.write(&mut token); + let query_set = query_set_guard.get_mut(query_set_id).unwrap(); + query_set.life_guard.ref_count.take(); + query_set.device_id.value + }; + + let (device_guard, mut token) = hub.devices.read(&mut token); + let device = &device_guard[device_id]; + + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::DestroyQuerySet(query_set_id)); + } + + device + .lock_life(&mut token) + .suspected_resources + .query_sets + .push(id::Valid(query_set_id)); + } + + pub fn query_set_label<A: HalApi>(&self, id: id::QuerySetId) -> String { + A::hub(self).query_sets.label_for_resource(id) + } + + pub fn device_create_render_pipeline<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &pipeline::RenderPipelineDescriptor, + id_in: Input<G, id::RenderPipelineId>, + implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>, + ) -> ( + id::RenderPipelineId, + Option<pipeline::CreateRenderPipelineError>, + ) { + profiling::scope!("Device::create_render_pipeline"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let fid = hub.render_pipelines.prepare(id_in); + let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + + let (adapter_guard, mut token) = hub.adapters.read(&mut token); + 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 adapter = &adapter_guard[device.adapter_id.value]; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::CreateRenderPipeline { + id: fid.id(), + desc: desc.clone(), + implicit_context: implicit_context.clone(), + }); + } + + let pipeline = match device.create_render_pipeline( + device_id, + adapter, + desc, + implicit_context, + hub, + &mut token, + ) { + Ok(pair) => pair, + Err(e) => break e, + }; + let ref_count = pipeline.life_guard.add_ref(); + + let id = fid.assign(pipeline, &mut token); + log::info!("Created render pipeline {:?} with {:?}", id, desc); + + device + .trackers + .lock() + .render_pipelines + .insert_single(id, ref_count); + + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, 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<A: HalApi>( + &self, + pipeline_id: id::RenderPipelineId, + index: u32, + id_in: Input<G, id::BindGroupLayoutId>, + ) -> ( + id::BindGroupLayoutId, + Option<binding_model::GetBindGroupLayoutError>, + ) { + let hub = A::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 + .prepare(id_in) + .assign_error("<derived>", &mut token); + (id, Some(error)) + } + + pub fn render_pipeline_label<A: HalApi>(&self, id: id::RenderPipelineId) -> String { + A::hub(self).render_pipelines.label_for_resource(id) + } + + pub fn render_pipeline_drop<A: HalApi>(&self, render_pipeline_id: id::RenderPipelineId) { + profiling::scope!("RenderPipeline::drop"); + log::debug!("render pipeline {:?} is dropped", render_pipeline_id); + let hub = A::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<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &pipeline::ComputePipelineDescriptor, + id_in: Input<G, id::ComputePipelineId>, + implicit_pipeline_ids: Option<ImplicitPipelineIds<G>>, + ) -> ( + id::ComputePipelineId, + Option<pipeline::CreateComputePipelineError>, + ) { + profiling::scope!("Device::create_compute_pipeline"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let fid = hub.compute_pipelines.prepare(id_in); + let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + + 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(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(trace::Action::CreateComputePipeline { + id: fid.id(), + desc: desc.clone(), + implicit_context: implicit_context.clone(), + }); + } + + let pipeline = match device.create_compute_pipeline( + device_id, + desc, + implicit_context, + hub, + &mut token, + ) { + Ok(pair) => pair, + Err(e) => break e, + }; + let ref_count = pipeline.life_guard.add_ref(); + + let id = fid.assign(pipeline, &mut token); + log::info!("Created compute pipeline {:?} with {:?}", id, desc); + + device + .trackers + .lock() + .compute_pipelines + .insert_single(id, ref_count); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, 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<A: HalApi>( + &self, + pipeline_id: id::ComputePipelineId, + index: u32, + id_in: Input<G, id::BindGroupLayoutId>, + ) -> ( + id::BindGroupLayoutId, + Option<binding_model::GetBindGroupLayoutError>, + ) { + let hub = A::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 + .prepare(id_in) + .assign_error("<derived>", &mut token); + (id, Some(error)) + } + + pub fn compute_pipeline_label<A: HalApi>(&self, id: id::ComputePipelineId) -> String { + A::hub(self).compute_pipelines.label_for_resource(id) + } + + pub fn compute_pipeline_drop<A: HalApi>(&self, compute_pipeline_id: id::ComputePipelineId) { + profiling::scope!("ComputePipeline::drop"); + log::debug!("compute pipeline {:?} is dropped", compute_pipeline_id); + let hub = A::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 surface_configure<A: HalApi>( + &self, + surface_id: id::SurfaceId, + device_id: id::DeviceId, + config: &wgt::SurfaceConfiguration<Vec<TextureFormat>>, + ) -> Option<present::ConfigureSurfaceError> { + use hal::{Adapter as _, Surface as _}; + use present::ConfigureSurfaceError as E; + profiling::scope!("surface_configure"); + + fn validate_surface_configuration( + config: &mut hal::SurfaceConfiguration, + caps: &hal::SurfaceCapabilities, + ) -> Result<(), E> { + 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 + { + log::warn!( + "Requested size {}x{} is outside of the supported range: {:?}", + width, + height, + caps.extents + ); + } + if !caps.present_modes.contains(&config.present_mode) { + let new_mode = 'b: loop { + // Automatic present mode checks. + // + // The "Automatic" modes are never supported by the backends. + let fallbacks = match config.present_mode { + wgt::PresentMode::AutoVsync => { + &[wgt::PresentMode::FifoRelaxed, wgt::PresentMode::Fifo][..] + } + // Always end in FIFO to make sure it's always supported + wgt::PresentMode::AutoNoVsync => &[ + wgt::PresentMode::Immediate, + wgt::PresentMode::Mailbox, + wgt::PresentMode::Fifo, + ][..], + _ => { + return Err(E::UnsupportedPresentMode { + requested: config.present_mode, + available: caps.present_modes.clone(), + }); + } + }; + + for &fallback in fallbacks { + if caps.present_modes.contains(&fallback) { + break 'b fallback; + } + } + + unreachable!("Fallback system failed to choose present mode. This is a bug. Mode: {:?}, Options: {:?}", config.present_mode, &caps.present_modes); + }; + + log::info!( + "Automatically choosing presentation mode by rule {:?}. Chose {new_mode:?}", + config.present_mode + ); + config.present_mode = new_mode; + } + if !caps.formats.contains(&config.format) { + return Err(E::UnsupportedFormat { + requested: config.format, + available: caps.formats.clone(), + }); + } + if !caps + .composite_alpha_modes + .contains(&config.composite_alpha_mode) + { + let new_alpha_mode = 'alpha: loop { + // Automatic alpha mode checks. + let fallbacks = match config.composite_alpha_mode { + wgt::CompositeAlphaMode::Auto => &[ + wgt::CompositeAlphaMode::Opaque, + wgt::CompositeAlphaMode::Inherit, + ][..], + _ => { + return Err(E::UnsupportedAlphaMode { + requested: config.composite_alpha_mode, + available: caps.composite_alpha_modes.clone(), + }); + } + }; + + for &fallback in fallbacks { + if caps.composite_alpha_modes.contains(&fallback) { + break 'alpha fallback; + } + } + + unreachable!( + "Fallback system failed to choose alpha mode. This is a bug. \ + AlphaMode: {:?}, Options: {:?}", + config.composite_alpha_mode, &caps.composite_alpha_modes + ); + }; + + log::info!( + "Automatically choosing alpha mode by rule {:?}. Chose {new_alpha_mode:?}", + config.composite_alpha_mode + ); + config.composite_alpha_mode = new_alpha_mode; + } + if !caps.usage.contains(config.usage) { + return Err(E::UnsupportedUsage); + } + if width == 0 || height == 0 { + return Err(E::ZeroArea); + } + Ok(()) + } + + log::info!("configuring surface with {:?}", config); + let hub = A::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, _token) = hub.devices.read(&mut token); + + let error = 'outer: loop { + let device = match device_guard.get(device_id) { + Ok(device) => device, + Err(_) => break DeviceError::Invalid.into(), + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace + .lock() + .add(trace::Action::ConfigureSurface(surface_id, config.clone())); + } + + let surface = match surface_guard.get_mut(surface_id) { + Ok(surface) => surface, + Err(_) => break E::InvalidSurface, + }; + + let caps = unsafe { + let suf = A::get_surface(surface); + let adapter = &adapter_guard[device.adapter_id.value]; + match adapter.raw.adapter.surface_capabilities(&suf.unwrap().raw) { + Some(caps) => caps, + None => break E::UnsupportedQueueFamily, + } + }; + + let mut hal_view_formats = vec![]; + for format in config.view_formats.iter() { + if *format == config.format { + continue; + } + if !caps.formats.contains(&config.format) { + break 'outer E::UnsupportedFormat { + requested: config.format, + available: caps.formats, + }; + } + if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() { + break 'outer E::InvalidViewFormat(*format, config.format); + } + hal_view_formats.push(*format); + } + + if !hal_view_formats.is_empty() { + if let Err(missing_flag) = + device.require_downlevel_flags(wgt::DownlevelFlags::SURFACE_VIEW_FORMATS) + { + break 'outer E::MissingDownlevelFlags(missing_flag); + } + } + + let num_frames = present::DESIRED_NUM_FRAMES + .clamp(*caps.swap_chain_sizes.start(), *caps.swap_chain_sizes.end()); + let mut hal_config = hal::SurfaceConfiguration { + swap_chain_size: num_frames, + present_mode: config.present_mode, + composite_alpha_mode: config.alpha_mode, + format: config.format, + extent: wgt::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + usage: conv::map_texture_usage(config.usage, hal::FormatAspects::COLOR), + view_formats: hal_view_formats, + }; + + if let Err(error) = validate_surface_configuration(&mut hal_config, &caps) { + break error; + } + + match unsafe { + A::get_surface_mut(surface) + .unwrap() + .raw + .configure(&device.raw, &hal_config) + } { + Ok(()) => (), + Err(error) => { + break match error { + hal::SurfaceError::Outdated | hal::SurfaceError::Lost => E::InvalidSurface, + hal::SurfaceError::Device(error) => E::Device(error.into()), + hal::SurfaceError::Other(message) => { + log::error!("surface configuration failed: {}", message); + E::InvalidSurface + } + } + } + } + + if let Some(present) = surface.presentation.take() { + if present.acquired_texture.is_some() { + break E::PreviousOutputExists; + } + } + + surface.presentation = Some(present::Presentation { + device_id: Stored { + value: id::Valid(device_id), + ref_count: device.life_guard.add_ref(), + }, + config: config.clone(), + num_frames, + acquired_texture: None, + }); + + return None; + }; + + Some(error) + } + + #[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<A: HalApi>( + &self, + device_id: id::DeviceId, + ) -> Result<(), InvalidDevice> { + let hub = A::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(()) + } + + /// Check `device_id` for freeable resources and completed buffer mappings. + /// + /// Return `queue_empty` indicating whether there are more queue submissions still in flight. + pub fn device_poll<A: HalApi>( + &self, + device_id: id::DeviceId, + maintain: wgt::Maintain<queue::WrappedSubmissionIndex>, + ) -> Result<bool, WaitIdleError> { + let (closures, queue_empty) = { + if let wgt::Maintain::WaitForSubmissionIndex(submission_index) = maintain { + if submission_index.queue_id != device_id { + return Err(WaitIdleError::WrongSubmissionIndex( + submission_index.queue_id, + device_id, + )); + } + } + + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, mut token) = hub.devices.read(&mut token); + device_guard + .get(device_id) + .map_err(|_| DeviceError::Invalid)? + .maintain(hub, maintain, &mut token)? + }; + + closures.fire(); + + Ok(queue_empty) + } + + /// Poll all devices belonging to the backend `A`. + /// + /// If `force_wait` is true, block until all buffer mappings are done. + /// + /// Return `all_queue_empty` indicating whether there are more queue + /// submissions still in flight. + fn poll_devices<A: HalApi>( + &self, + force_wait: bool, + closures: &mut UserClosures, + ) -> Result<bool, WaitIdleError> { + profiling::scope!("poll_devices"); + + let hub = A::hub(self); + let mut devices_to_drop = vec![]; + let mut all_queue_empty = true; + { + let mut token = Token::root(); + let (device_guard, mut token) = hub.devices.read(&mut token); + + for (id, device) in device_guard.iter(A::VARIANT) { + let maintain = if force_wait { + wgt::Maintain::Wait + } else { + wgt::Maintain::Poll + }; + let (cbs, queue_empty) = device.maintain(hub, maintain, &mut token)?; + all_queue_empty = all_queue_empty && queue_empty; + + // If the device's own `RefCount` clone is the only one left, and + // its submission queue is empty, then it can be freed. + if queue_empty && device.ref_count.load() == 1 { + devices_to_drop.push(id); + } + closures.extend(cbs); + } + } + + for device_id in devices_to_drop { + self.exit_device::<A>(device_id); + } + + Ok(all_queue_empty) + } + + /// Poll all devices on all backends. + /// + /// This is the implementation of `wgpu::Instance::poll_all`. + /// + /// Return `all_queue_empty` indicating whether there are more queue + /// submissions still in flight. + pub fn poll_all_devices(&self, force_wait: bool) -> Result<bool, WaitIdleError> { + let mut closures = UserClosures::default(); + let mut all_queue_empty = true; + + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + { + all_queue_empty = self.poll_devices::<hal::api::Vulkan>(force_wait, &mut closures)? + && all_queue_empty; + } + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + { + all_queue_empty = + self.poll_devices::<hal::api::Metal>(force_wait, &mut closures)? && all_queue_empty; + } + #[cfg(all(feature = "dx12", windows))] + { + all_queue_empty = + self.poll_devices::<hal::api::Dx12>(force_wait, &mut closures)? && all_queue_empty; + } + #[cfg(all(feature = "dx11", windows))] + { + all_queue_empty = + self.poll_devices::<hal::api::Dx11>(force_wait, &mut closures)? && all_queue_empty; + } + #[cfg(feature = "gles")] + { + all_queue_empty = + self.poll_devices::<hal::api::Gles>(force_wait, &mut closures)? && all_queue_empty; + } + + closures.fire(); + + Ok(all_queue_empty) + } + + pub fn device_label<A: HalApi>(&self, id: id::DeviceId) -> String { + A::hub(self).devices.label_for_resource(id) + } + + pub fn device_start_capture<A: HalApi>(&self, id: id::DeviceId) { + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, _) = hub.devices.read(&mut token); + if let Ok(device) = device_guard.get(id) { + unsafe { device.raw.start_capture() }; + } + } + + pub fn device_stop_capture<A: HalApi>(&self, id: id::DeviceId) { + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, _) = hub.devices.read(&mut token); + if let Ok(device) = device_guard.get(id) { + unsafe { device.raw.stop_capture() }; + } + } + + pub fn device_drop<A: HalApi>(&self, device_id: id::DeviceId) { + profiling::scope!("Device::drop"); + log::debug!("device {:?} is dropped", device_id); + + let hub = A::hub(self); + let mut token = Token::root(); + + // For now, just drop the `RefCount` in `device.life_guard`, which + // stands for the user's reference to the device. We'll take care of + // cleaning up the device when we're polled, once its queue submissions + // have completed and it is no longer needed by other resources. + let (mut device_guard, _) = hub.devices.write(&mut token); + if let Ok(device) = device_guard.get_mut(device_id) { + device.life_guard.ref_count.take().unwrap(); + } + } + + /// Exit the unreferenced, inactive device `device_id`. + fn exit_device<A: HalApi>(&self, device_id: id::DeviceId) { + let hub = A::hub(self); + let mut token = Token::root(); + let mut free_adapter_id = None; + { + let (device, mut _token) = hub.devices.unregister(device_id, &mut token); + if let Some(mut device) = device { + // The things `Device::prepare_to_die` takes care are mostly + // unnecessary here. We know our queue is empty, so we don't + // need to wait for submissions or triage them. We know we were + // just polled, so `life_tracker.free_resources` is empty. + debug_assert!(device.lock_life(&mut _token).queue_empty()); + device.pending_writes.deactivate(); + + // 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 { + free_adapter_id = Some(device.adapter_id.value.0); + } + + device.dispose(); + } + } + + // Free the adapter now that we've dropped the `Device` token. + if let Some(free_adapter_id) = free_adapter_id { + let _ = hub.adapters.unregister(free_adapter_id, &mut token); + } + } + + pub fn buffer_map_async<A: HalApi>( + &self, + buffer_id: id::BufferId, + range: Range<BufferAddress>, + op: BufferMapOperation, + ) -> BufferAccessResult { + // User callbacks must not be called while holding buffer_map_async_inner's locks, so we + // defer the error callback if it needs to be called immediately (typically when running + // into errors). + if let Err((op, err)) = self.buffer_map_async_inner::<A>(buffer_id, range, op) { + op.callback.call(Err(err.clone())); + + return Err(err); + } + + Ok(()) + } + + // Returns the mapping callback in case of error so that the callback can be fired outside + // of the locks that are held in this function. + fn buffer_map_async_inner<A: HalApi>( + &self, + buffer_id: id::BufferId, + range: Range<BufferAddress>, + op: BufferMapOperation, + ) -> Result<(), (BufferMapOperation, BufferAccessError)> { + profiling::scope!("Buffer::map_async"); + + let hub = A::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::BufferUsages::MAP_READ, hal::BufferUses::MAP_READ), + HostMap::Write => (wgt::BufferUsages::MAP_WRITE, hal::BufferUses::MAP_WRITE), + }; + + if range.start % wgt::MAP_ALIGNMENT != 0 || range.end % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err((op, 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(|_| BufferAccessError::Invalid); + + let buffer = match buffer { + Ok(b) => b, + Err(e) => { + return Err((op, e)); + } + }; + + if let Err(e) = check_buffer_usage(buffer.usage, pub_usage) { + return Err((op, e.into())); + } + + if range.start > range.end { + return Err(( + op, + BufferAccessError::NegativeRange { + start: range.start, + end: range.end, + }, + )); + } + if range.end > buffer.size { + return Err(( + op, + BufferAccessError::OutOfBoundsOverrun { + index: range.end, + max: buffer.size, + }, + )); + } + + buffer.map_state = match buffer.map_state { + resource::BufferMapState::Init { .. } | resource::BufferMapState::Active { .. } => { + return Err((op, BufferAccessError::AlreadyMapped)); + } + resource::BufferMapState::Waiting(_) => { + return Err((op, BufferAccessError::MapAlreadyPending)); + } + resource::BufferMapState::Idle => { + resource::BufferMapState::Waiting(resource::BufferPendingMapping { + range, + op, + _parent_ref_count: buffer.life_guard.add_ref(), + }) + } + }; + log::debug!("Buffer {:?} map state -> Waiting", buffer_id); + + let device = &device_guard[buffer.device_id.value]; + + let ret = (buffer.device_id.value, buffer.life_guard.add_ref()); + + let mut trackers = device.trackers.lock(); + trackers + .buffers + .set_single(&*buffer_guard, buffer_id, internal_use); + trackers.buffers.drain(); + + ret + }; + + let device = &device_guard[device_id]; + + device + .lock_life(&mut token) + .map(id::Valid(buffer_id), ref_count); + + Ok(()) + } + + pub fn buffer_get_mapped_range<A: HalApi>( + &self, + buffer_id: id::BufferId, + offset: BufferAddress, + size: Option<BufferAddress>, + ) -> Result<(*mut u8, u64), BufferAccessError> { + profiling::scope!("Buffer::get_mapped_range"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (buffer_guard, _) = hub.buffers.read(&mut token); + let buffer = buffer_guard + .get(buffer_id) + .map_err(|_| BufferAccessError::Invalid)?; + + let range_size = if let Some(size) = size { + size + } else if offset > buffer.size { + 0 + } else { + buffer.size - offset + }; + + if offset % wgt::MAP_ALIGNMENT != 0 { + return Err(BufferAccessError::UnalignedOffset { offset }); + } + if range_size % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(BufferAccessError::UnalignedRangeSize { range_size }); + } + + match buffer.map_state { + resource::BufferMapState::Init { ptr, .. } => { + // offset (u64) can not be < 0, so no need to validate the lower bound + if offset + range_size > buffer.size { + return Err(BufferAccessError::OutOfBoundsOverrun { + index: offset + range_size - 1, + max: buffer.size, + }); + } + unsafe { Ok((ptr.as_ptr().offset(offset as isize), range_size)) } + } + resource::BufferMapState::Active { ptr, ref range, .. } => { + if offset < range.start { + return Err(BufferAccessError::OutOfBoundsUnderrun { + index: offset, + min: range.start, + }); + } + if offset + range_size > range.end { + return Err(BufferAccessError::OutOfBoundsOverrun { + index: offset + range_size - 1, + max: range.end, + }); + } + // ptr points to the beginning of the range we mapped in map_async + // rather thant the beginning of the buffer. + let relative_offset = (offset - range.start) as isize; + unsafe { Ok((ptr.as_ptr().offset(relative_offset), range_size)) } + } + resource::BufferMapState::Idle | resource::BufferMapState::Waiting(_) => { + Err(BufferAccessError::NotMapped) + } + } + } + + fn buffer_unmap_inner<A: HalApi>( + &self, + buffer_id: id::BufferId, + buffer: &mut resource::Buffer<A>, + device: &mut Device<A>, + ) -> Result<Option<BufferMapPendingClosure>, BufferAccessError> { + log::debug!("Buffer {:?} map state -> Idle", buffer_id); + match mem::replace(&mut buffer.map_state, resource::BufferMapState::Idle) { + resource::BufferMapState::Init { + ptr, + stage_buffer, + 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 { + unsafe { + device + .raw + .flush_mapped_ranges(&stage_buffer, iter::once(0..buffer.size)); + } + } + + let raw_buf = buffer.raw.as_ref().ok_or(BufferAccessError::Destroyed)?; + + buffer.life_guard.use_at(device.active_submission_index + 1); + let region = wgt::BufferSize::new(buffer.size).map(|size| hal::BufferCopy { + src_offset: 0, + dst_offset: 0, + size, + }); + let transition_src = hal::BufferBarrier { + buffer: &stage_buffer, + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }; + let transition_dst = hal::BufferBarrier { + buffer: raw_buf, + usage: hal::BufferUses::empty()..hal::BufferUses::COPY_DST, + }; + let encoder = device.pending_writes.activate(); + unsafe { + encoder.transition_buffers( + iter::once(transition_src).chain(iter::once(transition_dst)), + ); + if buffer.size > 0 { + encoder.copy_buffer_to_buffer(&stage_buffer, raw_buf, region.into_iter()); + } + } + device + .pending_writes + .consume_temp(queue::TempResource::Buffer(stage_buffer)); + device.pending_writes.dst_buffers.insert(buffer_id); + } + resource::BufferMapState::Idle => { + return Err(BufferAccessError::NotMapped); + } + resource::BufferMapState::Waiting(pending) => { + return Ok(Some((pending.op, Err(BufferAccessError::MapAborted)))); + } + resource::BufferMapState::Active { ptr, range, host } => { + if host == HostMap::Write { + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + let mut trace = trace.lock(); + let size = range.end - range.start; + 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: range.clone(), + queued: false, + }); + } + let _ = (ptr, range); + } + unsafe { + device + .raw + .unmap_buffer(buffer.raw.as_ref().unwrap()) + .map_err(DeviceError::from)? + }; + } + } + Ok(None) + } + + pub fn buffer_unmap<A: HalApi>(&self, buffer_id: id::BufferId) -> BufferAccessResult { + profiling::scope!("unmap", "Buffer"); + + let closure; + { + // Restrict the locks to this scope. + let hub = A::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(|_| BufferAccessError::Invalid)?; + let device = &mut device_guard[buffer.device_id.value]; + + closure = self.buffer_unmap_inner(buffer_id, buffer, device) + } + + // Note: outside the scope where locks are held when calling the callback + if let Some((operation, status)) = closure? { + operation.callback.call(status); + } + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/device/queue.rs b/third_party/rust/wgpu-core/src/device/queue.rs new file mode 100644 index 0000000000..8ee687269d --- /dev/null +++ b/third_party/rust/wgpu-core/src/device/queue.rs @@ -0,0 +1,1429 @@ +#[cfg(feature = "trace")] +use crate::device::trace::Action; +use crate::{ + command::{ + extract_texture_selector, validate_linear_texture_data, validate_texture_copy_range, + ClearError, CommandBuffer, CopySide, ImageCopyTexture, TransferError, + }, + conv, + device::{DeviceError, WaitIdleError}, + get_lowest_common_denom, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Input, Token}, + id, + init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange}, + resource::{BufferAccessError, BufferMapState, StagingBuffer, TextureInner}, + track, FastHashSet, SubmissionIndex, +}; + +use hal::{CommandEncoder as _, Device as _, Queue as _}; +use parking_lot::Mutex; +use smallvec::SmallVec; +use std::{iter, mem, ptr}; +use thiserror::Error; + +/// Number of command buffers that we generate from the same pool +/// for the write_xxx commands, before the pool is recycled. +/// +/// If we don't stop at some point, the pool will grow forever, +/// without a concrete moment of when it can be cleared. +const WRITE_COMMAND_BUFFERS_PER_POOL: usize = 64; + +#[repr(C)] +pub struct SubmittedWorkDoneClosureC { + pub callback: unsafe extern "C" fn(user_data: *mut u8), + pub user_data: *mut u8, +} + +unsafe impl Send for SubmittedWorkDoneClosureC {} + +pub struct SubmittedWorkDoneClosure { + // We wrap this so creating the enum in the C variant can be unsafe, + // allowing our call function to be safe. + inner: SubmittedWorkDoneClosureInner, +} + +enum SubmittedWorkDoneClosureInner { + Rust { + callback: Box<dyn FnOnce() + Send + 'static>, + }, + C { + inner: SubmittedWorkDoneClosureC, + }, +} + +impl SubmittedWorkDoneClosure { + pub fn from_rust(callback: Box<dyn FnOnce() + Send + 'static>) -> Self { + Self { + inner: SubmittedWorkDoneClosureInner::Rust { callback }, + } + } + + /// # Safety + /// + /// - The callback pointer must be valid to call with the provided `user_data` + /// pointer. + /// + /// - Both pointers must point to `'static` data, as the callback may happen at + /// an unspecified time. + pub unsafe fn from_c(inner: SubmittedWorkDoneClosureC) -> Self { + Self { + inner: SubmittedWorkDoneClosureInner::C { inner }, + } + } + + pub(crate) fn call(self) { + match self.inner { + SubmittedWorkDoneClosureInner::Rust { callback } => callback(), + // SAFETY: the contract of the call to from_c says that this unsafe is sound. + SubmittedWorkDoneClosureInner::C { inner } => unsafe { + (inner.callback)(inner.user_data) + }, + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WrappedSubmissionIndex { + pub queue_id: id::QueueId, + pub index: SubmissionIndex, +} + +/// A texture or buffer to be freed soon. +/// +/// This is just a tagged raw texture or buffer, generally about to be added to +/// some other more specific container like: +/// +/// - `PendingWrites::temp_resources`: resources used by queue writes and +/// unmaps, waiting to be folded in with the next queue submission +/// +/// - `ActiveSubmission::last_resources`: temporary resources used by a queue +/// submission, to be freed when it completes +/// +/// - `LifetimeTracker::free_resources`: resources to be freed in the next +/// `maintain` call, no longer used anywhere +#[derive(Debug)] +pub enum TempResource<A: hal::Api> { + Buffer(A::Buffer), + Texture(A::Texture, SmallVec<[A::TextureView; 1]>), +} + +/// A queue execution for a particular command encoder. +pub(super) struct EncoderInFlight<A: hal::Api> { + raw: A::CommandEncoder, + cmd_buffers: Vec<A::CommandBuffer>, +} + +impl<A: hal::Api> EncoderInFlight<A> { + pub(super) unsafe fn land(mut self) -> A::CommandEncoder { + unsafe { self.raw.reset_all(self.cmd_buffers.into_iter()) }; + self.raw + } +} + +/// A private command encoder for writes made directly on the device +/// or queue. +/// +/// Operations like `buffer_unmap`, `queue_write_buffer`, and +/// `queue_write_texture` need to copy data to the GPU. At the hal +/// level, this must be done by encoding and submitting commands, but +/// these operations are not associated with any specific wgpu command +/// buffer. +/// +/// Instead, `Device::pending_writes` owns one of these values, which +/// has its own hal command encoder and resource lists. The commands +/// accumulated here are automatically submitted to the queue the next +/// time the user submits a wgpu command buffer, ahead of the user's +/// commands. +/// +/// All uses of [`StagingBuffer`]s end up here. +#[derive(Debug)] +pub(crate) struct PendingWrites<A: hal::Api> { + pub command_encoder: A::CommandEncoder, + pub is_active: bool, + pub temp_resources: Vec<TempResource<A>>, + pub dst_buffers: FastHashSet<id::BufferId>, + pub dst_textures: FastHashSet<id::TextureId>, + pub executing_command_buffers: Vec<A::CommandBuffer>, +} + +impl<A: hal::Api> PendingWrites<A> { + pub fn new(command_encoder: A::CommandEncoder) -> Self { + Self { + command_encoder, + is_active: false, + temp_resources: Vec::new(), + dst_buffers: FastHashSet::default(), + dst_textures: FastHashSet::default(), + executing_command_buffers: Vec::new(), + } + } + + pub fn dispose(mut self, device: &A::Device) { + unsafe { + if self.is_active { + self.command_encoder.discard_encoding(); + } + self.command_encoder + .reset_all(self.executing_command_buffers.into_iter()); + device.destroy_command_encoder(self.command_encoder); + } + + for resource in self.temp_resources { + match resource { + TempResource::Buffer(buffer) => unsafe { + device.destroy_buffer(buffer); + }, + TempResource::Texture(texture, views) => unsafe { + for view in views.into_iter() { + device.destroy_texture_view(view); + } + device.destroy_texture(texture); + }, + } + } + } + + pub fn consume_temp(&mut self, resource: TempResource<A>) { + self.temp_resources.push(resource); + } + + fn consume(&mut self, buffer: StagingBuffer<A>) { + self.temp_resources.push(TempResource::Buffer(buffer.raw)); + } + + #[must_use] + fn pre_submit(&mut self) -> Option<&A::CommandBuffer> { + self.dst_buffers.clear(); + self.dst_textures.clear(); + if self.is_active { + let cmd_buf = unsafe { self.command_encoder.end_encoding().unwrap() }; + self.is_active = false; + self.executing_command_buffers.push(cmd_buf); + self.executing_command_buffers.last() + } else { + None + } + } + + #[must_use] + fn post_submit( + &mut self, + command_allocator: &Mutex<super::CommandAllocator<A>>, + device: &A::Device, + queue: &A::Queue, + ) -> Option<EncoderInFlight<A>> { + if self.executing_command_buffers.len() >= WRITE_COMMAND_BUFFERS_PER_POOL { + let new_encoder = command_allocator + .lock() + .acquire_encoder(device, queue) + .unwrap(); + Some(EncoderInFlight { + raw: mem::replace(&mut self.command_encoder, new_encoder), + cmd_buffers: mem::take(&mut self.executing_command_buffers), + }) + } else { + None + } + } + + pub fn activate(&mut self) -> &mut A::CommandEncoder { + if !self.is_active { + unsafe { + self.command_encoder + .begin_encoding(Some("(wgpu internal) PendingWrites")) + .unwrap(); + } + self.is_active = true; + } + &mut self.command_encoder + } + + pub fn deactivate(&mut self) { + if self.is_active { + unsafe { + self.command_encoder.discard_encoding(); + } + self.is_active = false; + } + } +} + +fn prepare_staging_buffer<A: HalApi>( + device: &mut A::Device, + size: wgt::BufferAddress, +) -> Result<(StagingBuffer<A>, *mut u8), DeviceError> { + profiling::scope!("prepare_staging_buffer"); + let stage_desc = hal::BufferDescriptor { + label: Some("(wgpu internal) Staging"), + size, + usage: hal::BufferUses::MAP_WRITE | hal::BufferUses::COPY_SRC, + memory_flags: hal::MemoryFlags::TRANSIENT, + }; + + let buffer = unsafe { device.create_buffer(&stage_desc)? }; + let mapping = unsafe { device.map_buffer(&buffer, 0..size) }?; + + let staging_buffer = StagingBuffer { + raw: buffer, + size, + is_coherent: mapping.is_coherent, + }; + + Ok((staging_buffer, mapping.ptr.as_ptr())) +} + +impl<A: hal::Api> StagingBuffer<A> { + unsafe fn flush(&self, device: &A::Device) -> Result<(), DeviceError> { + if !self.is_coherent { + unsafe { device.flush_mapped_ranges(&self.raw, iter::once(0..self.size)) }; + } + unsafe { device.unmap_buffer(&self.raw)? }; + Ok(()) + } +} + +#[derive(Clone, Debug, Error)] +#[error("Queue is invalid")] +pub struct InvalidQueue; + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum QueueWriteError { + #[error(transparent)] + Queue(#[from] DeviceError), + #[error(transparent)] + Transfer(#[from] TransferError), + #[error(transparent)] + MemoryInitFailure(#[from] ClearError), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum QueueSubmitError { + #[error(transparent)] + Queue(#[from] DeviceError), + #[error("Buffer {0:?} is destroyed")] + DestroyedBuffer(id::BufferId), + #[error("Texture {0:?} is destroyed")] + DestroyedTexture(id::TextureId), + #[error(transparent)] + Unmap(#[from] BufferAccessError), + #[error("Buffer {0:?} is still mapped")] + BufferStillMapped(id::BufferId), + #[error("Surface output was dropped before the command buffer got submitted")] + SurfaceOutputDropped, + #[error("Surface was unconfigured before the command buffer got submitted")] + SurfaceUnconfigured, + #[error("GPU got stuck :(")] + StuckGpu, +} + +//TODO: move out common parts of write_xxx. + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn queue_write_buffer<A: HalApi>( + &self, + queue_id: id::QueueId, + buffer_id: id::BufferId, + buffer_offset: wgt::BufferAddress, + data: &[u8], + ) -> Result<(), QueueWriteError> { + profiling::scope!("Queue::write_buffer"); + + let hub = A::hub(self); + let root_token = &mut Token::root(); + + let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); + let device = device_guard + .get_mut(queue_id) + .map_err(|_| DeviceError::Invalid)?; + + let data_size = data.len() as wgt::BufferAddress; + + #[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_size, + queued: true, + }); + } + + if data_size == 0 { + log::trace!("Ignoring write_buffer of size 0"); + return Ok(()); + } + + // Platform validation requires that the staging buffer always be + // freed, even if an error occurs. All paths from here must call + // `device.pending_writes.consume`. + let (staging_buffer, staging_buffer_ptr) = + prepare_staging_buffer(&mut device.raw, data_size)?; + + if let Err(flush_error) = unsafe { + profiling::scope!("copy"); + ptr::copy_nonoverlapping(data.as_ptr(), staging_buffer_ptr, data.len()); + staging_buffer.flush(&device.raw) + } { + device.pending_writes.consume(staging_buffer); + return Err(flush_error.into()); + } + + let result = self.queue_write_staging_buffer_impl( + device, + device_token, + &staging_buffer, + buffer_id, + buffer_offset, + ); + + device.pending_writes.consume(staging_buffer); + result + } + + pub fn queue_create_staging_buffer<A: HalApi>( + &self, + queue_id: id::QueueId, + buffer_size: wgt::BufferSize, + id_in: Input<G, id::StagingBufferId>, + ) -> Result<(id::StagingBufferId, *mut u8), QueueWriteError> { + profiling::scope!("Queue::create_staging_buffer"); + let hub = A::hub(self); + let root_token = &mut Token::root(); + + let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); + let device = device_guard + .get_mut(queue_id) + .map_err(|_| DeviceError::Invalid)?; + + let (staging_buffer, staging_buffer_ptr) = + prepare_staging_buffer(&mut device.raw, buffer_size.get())?; + + let fid = hub.staging_buffers.prepare(id_in); + let id = fid.assign(staging_buffer, device_token); + + Ok((id.0, staging_buffer_ptr)) + } + + pub fn queue_write_staging_buffer<A: HalApi>( + &self, + queue_id: id::QueueId, + buffer_id: id::BufferId, + buffer_offset: wgt::BufferAddress, + staging_buffer_id: id::StagingBufferId, + ) -> Result<(), QueueWriteError> { + profiling::scope!("Queue::write_staging_buffer"); + let hub = A::hub(self); + let root_token = &mut Token::root(); + + let (mut device_guard, ref mut device_token) = hub.devices.write(root_token); + let device = device_guard + .get_mut(queue_id) + .map_err(|_| DeviceError::Invalid)?; + + let staging_buffer = hub + .staging_buffers + .unregister(staging_buffer_id, device_token) + .0 + .ok_or(TransferError::InvalidBuffer(buffer_id))?; + + // At this point, we have taken ownership of the staging_buffer from the + // user. Platform validation requires that the staging buffer always + // be freed, even if an error occurs. All paths from here must call + // `device.pending_writes.consume`. + if let Err(flush_error) = unsafe { staging_buffer.flush(&device.raw) } { + device.pending_writes.consume(staging_buffer); + return Err(flush_error.into()); + } + + let result = self.queue_write_staging_buffer_impl( + device, + device_token, + &staging_buffer, + buffer_id, + buffer_offset, + ); + + device.pending_writes.consume(staging_buffer); + result + } + + pub fn queue_validate_write_buffer<A: HalApi>( + &self, + _queue_id: id::QueueId, + buffer_id: id::BufferId, + buffer_offset: u64, + buffer_size: u64, + ) -> Result<(), QueueWriteError> { + profiling::scope!("Queue::validate_write_buffer"); + let hub = A::hub(self); + let root_token = &mut Token::root(); + + let (_, ref mut device_token) = hub.devices.read(root_token); + + let buffer_guard = hub.buffers.read(device_token).0; + let buffer = buffer_guard + .get(buffer_id) + .map_err(|_| TransferError::InvalidBuffer(buffer_id))?; + + self.queue_validate_write_buffer_impl(buffer, buffer_id, buffer_offset, buffer_size)?; + + Ok(()) + } + + fn queue_validate_write_buffer_impl<A: HalApi>( + &self, + buffer: &super::resource::Buffer<A>, + buffer_id: id::BufferId, + buffer_offset: u64, + buffer_size: u64, + ) -> Result<(), TransferError> { + if !buffer.usage.contains(wgt::BufferUsages::COPY_DST) { + return Err(TransferError::MissingCopyDstUsageFlag( + Some(buffer_id), + None, + )); + } + if buffer_size % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(TransferError::UnalignedCopySize(buffer_size)); + } + if buffer_offset % wgt::COPY_BUFFER_ALIGNMENT != 0 { + return Err(TransferError::UnalignedBufferOffset(buffer_offset)); + } + if buffer_offset + buffer_size > buffer.size { + return Err(TransferError::BufferOverrun { + start_offset: buffer_offset, + end_offset: buffer_offset + buffer_size, + buffer_size: buffer.size, + side: CopySide::Destination, + }); + } + + Ok(()) + } + + fn queue_write_staging_buffer_impl<A: HalApi>( + &self, + device: &mut super::Device<A>, + device_token: &mut Token<super::Device<A>>, + staging_buffer: &StagingBuffer<A>, + buffer_id: id::BufferId, + buffer_offset: u64, + ) -> Result<(), QueueWriteError> { + let hub = A::hub(self); + + let buffer_guard = hub.buffers.read(device_token).0; + + let mut trackers = device.trackers.lock(); + let (dst, transition) = trackers + .buffers + .set_single(&buffer_guard, buffer_id, hal::BufferUses::COPY_DST) + .ok_or(TransferError::InvalidBuffer(buffer_id))?; + let dst_raw = dst + .raw + .as_ref() + .ok_or(TransferError::InvalidBuffer(buffer_id))?; + + let src_buffer_size = staging_buffer.size; + self.queue_validate_write_buffer_impl(dst, buffer_id, buffer_offset, src_buffer_size)?; + + dst.life_guard.use_at(device.active_submission_index + 1); + + let region = wgt::BufferSize::new(src_buffer_size).map(|size| hal::BufferCopy { + src_offset: 0, + dst_offset: buffer_offset, + size, + }); + let barriers = iter::once(hal::BufferBarrier { + buffer: &staging_buffer.raw, + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }) + .chain(transition.map(|pending| pending.into_hal(dst))); + let encoder = device.pending_writes.activate(); + unsafe { + encoder.transition_buffers(barriers); + encoder.copy_buffer_to_buffer(&staging_buffer.raw, dst_raw, region.into_iter()); + } + + device.pending_writes.dst_buffers.insert(buffer_id); + + // Ensure the overwritten bytes are marked as initialized so + // they don't need to be nulled prior to mapping or binding. + { + drop(buffer_guard); + let mut buffer_guard = hub.buffers.write(device_token).0; + + let dst = buffer_guard.get_mut(buffer_id).unwrap(); + dst.initialization_status + .drain(buffer_offset..(buffer_offset + src_buffer_size)); + } + + Ok(()) + } + + pub fn queue_write_texture<A: HalApi>( + &self, + queue_id: id::QueueId, + destination: &ImageCopyTexture, + data: &[u8], + data_layout: &wgt::ImageDataLayout, + size: &wgt::Extent3d, + ) -> Result<(), QueueWriteError> { + profiling::scope!("Queue::write_texture"); + + let hub = A::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)?; + + #[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, + data: data_path, + layout: *data_layout, + size: *size, + }); + } + + if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { + log::trace!("Ignoring write_texture of size 0"); + return Ok(()); + } + + let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later? + let dst = texture_guard + .get_mut(destination.texture) + .map_err(|_| TransferError::InvalidTexture(destination.texture))?; + + if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) { + return Err( + TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), + ); + } + + // Note: Doing the copy range validation early is important because ensures that the + // dimensions are not going to cause overflow in other parts of the validation. + let (hal_copy_size, array_layer_count) = + validate_texture_copy_range(destination, &dst.desc, CopySide::Destination, size)?; + + let (selector, dst_base) = extract_texture_selector(destination, size, dst)?; + + if !dst_base.aspect.is_one() { + return Err(TransferError::CopyAspectNotOne.into()); + } + + if !conv::is_valid_copy_dst_texture_format(dst.desc.format, destination.aspect) { + return Err(TransferError::CopyToForbiddenTextureFormat { + format: dst.desc.format, + aspect: destination.aspect, + } + .into()); + } + + // Note: `_source_bytes_per_array_layer` is ignored since we + // have a staging copy, and it can have a different value. + let (_, _source_bytes_per_array_layer) = validate_linear_texture_data( + data_layout, + dst.desc.format, + destination.aspect, + data.len() as wgt::BufferAddress, + CopySide::Source, + size, + false, + )?; + + if dst.desc.format.is_depth_stencil_format() { + device + .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES) + .map_err(TransferError::from)?; + } + + let (block_width, block_height) = dst.desc.format.block_dimensions(); + let width_blocks = size.width / block_width; + let height_blocks = size.height / block_height; + + let block_rows_per_image = data_layout.rows_per_image.unwrap_or( + // doesn't really matter because we need this only if we copy + // more than one layer, and then we validate for this being not + // None + size.height, + ); + + let block_size = dst + .desc + .format + .block_size(Some(destination.aspect)) + .unwrap(); + let bytes_per_row_alignment = + get_lowest_common_denom(device.alignments.buffer_copy_pitch.get() as u32, block_size); + let stage_bytes_per_row = + wgt::math::align_to(block_size * width_blocks, bytes_per_row_alignment); + + let block_rows_in_copy = + (size.depth_or_array_layers - 1) * block_rows_per_image + height_blocks; + let stage_size = stage_bytes_per_row as u64 * block_rows_in_copy as u64; + + let mut trackers = device.trackers.lock(); + let encoder = device.pending_writes.activate(); + + // If the copy does not fully cover the layers, we need to initialize to + // zero *first* as we don't keep track of partial texture layer inits. + // + // Strictly speaking we only need to clear the areas of a layer + // untouched, but this would get increasingly messy. + let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 { + // volume textures don't have a layer range as array volumes aren't supported + 0..1 + } else { + destination.origin.z..destination.origin.z + size.depth_or_array_layers + }; + if dst.initialization_status.mips[destination.mip_level as usize] + .check(init_layer_range.clone()) + .is_some() + { + if has_copy_partial_init_tracker_coverage(size, destination.mip_level, &dst.desc) { + for layer_range in dst.initialization_status.mips[destination.mip_level as usize] + .drain(init_layer_range) + .collect::<Vec<std::ops::Range<u32>>>() + { + crate::command::clear_texture( + &*texture_guard, + id::Valid(destination.texture), + TextureInitRange { + mip_range: destination.mip_level..(destination.mip_level + 1), + layer_range, + }, + encoder, + &mut trackers.textures, + &device.alignments, + &device.zero_buffer, + ) + .map_err(QueueWriteError::from)?; + } + } else { + dst.initialization_status.mips[destination.mip_level as usize] + .drain(init_layer_range); + } + } + + // Re-get `dst` immutably here, so that the mutable borrow of the + // `texture_guard.get_mut` above ends in time for the `clear_texture` + // call above. Since we've held `texture_guard` the whole time, we know + // the texture hasn't gone away in the mean time, so we can unwrap. + let dst = texture_guard.get(destination.texture).unwrap(); + let transition = trackers + .textures + .set_single( + dst, + destination.texture, + selector, + hal::TextureUses::COPY_DST, + ) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + + dst.life_guard.use_at(device.active_submission_index + 1); + + let dst_raw = dst + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(destination.texture))?; + + let bytes_per_row = data_layout + .bytes_per_row + .unwrap_or(width_blocks * block_size); + + // Platform validation requires that the staging buffer always be + // freed, even if an error occurs. All paths from here must call + // `device.pending_writes.consume`. + let (staging_buffer, staging_buffer_ptr) = + prepare_staging_buffer(&mut device.raw, stage_size)?; + + if stage_bytes_per_row == bytes_per_row { + profiling::scope!("copy aligned"); + // Fast path if the data is already being aligned optimally. + unsafe { + ptr::copy_nonoverlapping( + data.as_ptr().offset(data_layout.offset as isize), + staging_buffer_ptr, + stage_size as usize, + ); + } + } else { + profiling::scope!("copy chunked"); + // Copy row by row into the optimal alignment. + let copy_bytes_per_row = stage_bytes_per_row.min(bytes_per_row) as usize; + for layer in 0..size.depth_or_array_layers { + let rows_offset = layer * block_rows_per_image; + for row in 0..height_blocks { + unsafe { + ptr::copy_nonoverlapping( + data.as_ptr().offset( + data_layout.offset as isize + + (rows_offset + row) as isize * bytes_per_row as isize, + ), + staging_buffer_ptr.offset( + (rows_offset + row) as isize * stage_bytes_per_row as isize, + ), + copy_bytes_per_row, + ); + } + } + } + } + + if let Err(e) = unsafe { staging_buffer.flush(&device.raw) } { + device.pending_writes.consume(staging_buffer); + return Err(e.into()); + } + + let regions = (0..array_layer_count).map(|rel_array_layer| { + let mut texture_base = dst_base.clone(); + texture_base.array_layer += rel_array_layer; + hal::BufferTextureCopy { + buffer_layout: wgt::ImageDataLayout { + offset: rel_array_layer as u64 + * block_rows_per_image as u64 + * stage_bytes_per_row as u64, + bytes_per_row: Some(stage_bytes_per_row), + rows_per_image: Some(block_rows_per_image), + }, + texture_base, + size: hal_copy_size, + } + }); + let barrier = hal::BufferBarrier { + buffer: &staging_buffer.raw, + usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, + }; + + unsafe { + encoder.transition_textures(transition.map(|pending| pending.into_hal(dst))); + encoder.transition_buffers(iter::once(barrier)); + encoder.copy_buffer_to_texture(&staging_buffer.raw, dst_raw, regions); + } + + device.pending_writes.consume(staging_buffer); + device + .pending_writes + .dst_textures + .insert(destination.texture); + + Ok(()) + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn queue_copy_external_image_to_texture<A: HalApi>( + &self, + queue_id: id::QueueId, + source: &wgt::ImageCopyExternalImage, + destination: crate::command::ImageCopyTextureTagged, + size: wgt::Extent3d, + ) -> Result<(), QueueWriteError> { + profiling::scope!("Queue::copy_external_image_to_texture"); + + let hub = A::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)?; + + if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 { + log::trace!("Ignoring write_texture of size 0"); + return Ok(()); + } + + let mut needs_flag = false; + needs_flag |= matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_)); + needs_flag |= source.origin != wgt::Origin2d::ZERO; + needs_flag |= destination.color_space != wgt::PredefinedColorSpace::Srgb; + #[allow(clippy::bool_comparison)] + if matches!(source.source, wgt::ExternalImageSource::ImageBitmap(_)) { + needs_flag |= source.flip_y != false; + needs_flag |= destination.premultiplied_alpha != false; + } + + if needs_flag { + device + .require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES) + .map_err(TransferError::from)?; + } + + let src_width = source.source.width(); + let src_height = source.source.height(); + + let (mut texture_guard, _) = hub.textures.write(&mut token); // For clear we need write access to the texture. TODO: Can we acquire write lock later? + let dst = texture_guard.get_mut(destination.texture).unwrap(); + + if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) { + return Err( + TransferError::ExternalCopyToForbiddenTextureFormat(dst.desc.format).into(), + ); + } + if dst.desc.dimension != wgt::TextureDimension::D2 { + return Err(TransferError::InvalidDimensionExternal(destination.texture).into()); + } + if !dst.desc.usage.contains(wgt::TextureUsages::COPY_DST) { + return Err( + TransferError::MissingCopyDstUsageFlag(None, Some(destination.texture)).into(), + ); + } + if !dst + .desc + .usage + .contains(wgt::TextureUsages::RENDER_ATTACHMENT) + { + return Err( + TransferError::MissingRenderAttachmentUsageFlag(destination.texture).into(), + ); + } + if dst.desc.sample_count != 1 { + return Err(TransferError::InvalidSampleCount { + sample_count: dst.desc.sample_count, + } + .into()); + } + + if source.origin.x + size.width > src_width { + return Err(TransferError::TextureOverrun { + start_offset: source.origin.x, + end_offset: source.origin.x + size.width, + texture_size: src_width, + dimension: crate::resource::TextureErrorDimension::X, + side: CopySide::Source, + } + .into()); + } + if source.origin.y + size.height > src_height { + return Err(TransferError::TextureOverrun { + start_offset: source.origin.y, + end_offset: source.origin.y + size.height, + texture_size: src_height, + dimension: crate::resource::TextureErrorDimension::Y, + side: CopySide::Source, + } + .into()); + } + if size.depth_or_array_layers != 1 { + return Err(TransferError::TextureOverrun { + start_offset: 0, + end_offset: size.depth_or_array_layers, + texture_size: 1, + dimension: crate::resource::TextureErrorDimension::Z, + side: CopySide::Source, + } + .into()); + } + + // Note: Doing the copy range validation early is important because ensures that the + // dimensions are not going to cause overflow in other parts of the validation. + let (hal_copy_size, _) = validate_texture_copy_range( + &destination.to_untagged(), + &dst.desc, + CopySide::Destination, + &size, + )?; + + let (selector, dst_base) = + extract_texture_selector(&destination.to_untagged(), &size, dst)?; + + let mut trackers = device.trackers.lock(); + let encoder = device.pending_writes.activate(); + + // If the copy does not fully cover the layers, we need to initialize to + // zero *first* as we don't keep track of partial texture layer inits. + // + // Strictly speaking we only need to clear the areas of a layer + // untouched, but this would get increasingly messy. + let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 { + // volume textures don't have a layer range as array volumes aren't supported + 0..1 + } else { + destination.origin.z..destination.origin.z + size.depth_or_array_layers + }; + if dst.initialization_status.mips[destination.mip_level as usize] + .check(init_layer_range.clone()) + .is_some() + { + if has_copy_partial_init_tracker_coverage(&size, destination.mip_level, &dst.desc) { + for layer_range in dst.initialization_status.mips[destination.mip_level as usize] + .drain(init_layer_range) + .collect::<Vec<std::ops::Range<u32>>>() + { + crate::command::clear_texture( + &*texture_guard, + id::Valid(destination.texture), + TextureInitRange { + mip_range: destination.mip_level..(destination.mip_level + 1), + layer_range, + }, + encoder, + &mut trackers.textures, + &device.alignments, + &device.zero_buffer, + ) + .map_err(QueueWriteError::from)?; + } + } else { + dst.initialization_status.mips[destination.mip_level as usize] + .drain(init_layer_range); + } + } + + let dst = texture_guard.get(destination.texture).unwrap(); + + let transitions = trackers + .textures + .set_single( + dst, + destination.texture, + selector, + hal::TextureUses::COPY_DST, + ) + .ok_or(TransferError::InvalidTexture(destination.texture))?; + + dst.life_guard.use_at(device.active_submission_index + 1); + + let dst_raw = dst + .inner + .as_raw() + .ok_or(TransferError::InvalidTexture(destination.texture))?; + + let regions = hal::TextureCopy { + src_base: hal::TextureCopyBase { + mip_level: 0, + array_layer: 0, + origin: source.origin.to_3d(0), + aspect: hal::FormatAspects::COLOR, + }, + dst_base, + size: hal_copy_size, + }; + + unsafe { + encoder.transition_textures(transitions.map(|pending| pending.into_hal(dst))); + encoder.copy_external_image_to_texture( + source, + dst_raw, + destination.premultiplied_alpha, + iter::once(regions), + ); + } + + Ok(()) + } + + pub fn queue_submit<A: HalApi>( + &self, + queue_id: id::QueueId, + command_buffer_ids: &[id::CommandBufferId], + ) -> Result<WrappedSubmissionIndex, QueueSubmitError> { + profiling::scope!("Queue::submit"); + + let (submit_index, callbacks) = { + let hub = A::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)?; + device.temp_suspected.clear(); + device.active_submission_index += 1; + let submit_index = device.active_submission_index; + let mut active_executions = Vec::new(); + let mut used_surface_textures = track::TextureUsageScope::new(); + + { + let (mut command_buffer_guard, mut token) = hub.command_buffers.write(&mut token); + + if !command_buffer_ids.is_empty() { + profiling::scope!("prepare"); + + let (render_bundle_guard, mut token) = hub.render_bundles.read(&mut token); + let (_, mut token) = hub.pipeline_layouts.read(&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 (mut texture_guard, mut token) = hub.textures.write(&mut token); + let (texture_view_guard, mut token) = hub.texture_views.read(&mut token); + let (sampler_guard, mut token) = hub.samplers.read(&mut token); + let (query_set_guard, _) = hub.query_sets.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 { + // we reset the used surface textures every time we use + // it, so make sure to set_size on it. + used_surface_textures.set_size(texture_guard.len()); + + #[allow(unused_mut)] + let mut cmdbuf = match hub + .command_buffers + .unregister_locked(cmb_id, &mut *command_buffer_guard) + { + Some(cmdbuf) => cmdbuf, + None => continue, + }; + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(Action::Submit( + submit_index, + cmdbuf.commands.take().unwrap(), + )); + } + if !cmdbuf.is_finished() { + device.destroy_command_buffer(cmdbuf); + continue; + } + + // optimize the tracked states + // cmdbuf.trackers.optimize(); + + // update submission IDs + for id in cmdbuf.trackers.buffers.used() { + let buffer = &mut buffer_guard[id]; + let raw_buf = match buffer.raw { + Some(ref raw) => raw, + None => { + return Err(QueueSubmitError::DestroyedBuffer(id.0)); + } + }; + if !buffer.life_guard.use_at(submit_index) { + if let BufferMapState::Active { .. } = buffer.map_state { + log::warn!("Dropped buffer has a pending mapping."); + unsafe { device.raw.unmap_buffer(raw_buf) } + .map_err(DeviceError::from)?; + } + device.temp_suspected.buffers.push(id); + } else { + match buffer.map_state { + BufferMapState::Idle => (), + _ => return Err(QueueSubmitError::BufferStillMapped(id.0)), + } + } + } + for id in cmdbuf.trackers.textures.used() { + let texture = &mut texture_guard[id]; + let should_extend = match texture.inner { + TextureInner::Native { raw: None } => { + return Err(QueueSubmitError::DestroyedTexture(id.0)); + } + TextureInner::Native { raw: Some(_) } => false, + TextureInner::Surface { + ref mut has_work, .. + } => { + *has_work = true; + true + } + }; + if !texture.life_guard.use_at(submit_index) { + device.temp_suspected.textures.push(id); + } + if should_extend { + unsafe { + let ref_count = cmdbuf.trackers.textures.get_ref_count(id); + used_surface_textures + .merge_single( + &*texture_guard, + id, + None, + ref_count, + hal::TextureUses::PRESENT, + ) + .unwrap(); + }; + } + } + 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() { + let bg = &bind_group_guard[id]; + if !bg.life_guard.use_at(submit_index) { + device.temp_suspected.bind_groups.push(id); + } + // We need to update the submission indices for the contained + // state-less (!) resources as well, so that they don't get + // deleted too early if the parent bind group goes out of scope. + for sub_id in bg.used.views.used() { + texture_view_guard[sub_id].life_guard.use_at(submit_index); + } + for sub_id in bg.used.samplers.used() { + sampler_guard[sub_id].life_guard.use_at(submit_index); + } + } + // assert!(cmdbuf.trackers.samplers.is_empty()); + for id in cmdbuf.trackers.compute_pipelines.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_pipelines.used() { + if !render_pipe_guard[id].life_guard.use_at(submit_index) { + device.temp_suspected.render_pipelines.push(id); + } + } + for id in cmdbuf.trackers.query_sets.used() { + if !query_set_guard[id].life_guard.use_at(submit_index) { + device.temp_suspected.query_sets.push(id); + } + } + for id in cmdbuf.trackers.bundles.used() { + let bundle = &render_bundle_guard[id]; + if !bundle.life_guard.use_at(submit_index) { + device.temp_suspected.render_bundles.push(id); + } + // We need to update the submission indices for the contained + // state-less (!) resources as well, excluding the bind groups. + // They don't get deleted too early if the bundle goes out of scope. + for sub_id in bundle.used.render_pipelines.used() { + render_pipe_guard[sub_id].life_guard.use_at(submit_index); + } + for sub_id in bundle.used.query_sets.used() { + query_set_guard[sub_id].life_guard.use_at(submit_index); + } + } + + let mut baked = cmdbuf.into_baked(); + // execute resource transitions + unsafe { + baked + .encoder + .begin_encoding(Some("(wgpu internal) Transit")) + .map_err(DeviceError::from)? + }; + log::trace!("Stitching command buffer {:?} before submission", cmb_id); + baked + .initialize_buffer_memory(&mut *trackers, &mut *buffer_guard) + .map_err(|err| QueueSubmitError::DestroyedBuffer(err.0))?; + baked + .initialize_texture_memory(&mut *trackers, &mut *texture_guard, device) + .map_err(|err| QueueSubmitError::DestroyedTexture(err.0))?; + //Note: stateless trackers are not merged: + // device already knows these resources exist. + CommandBuffer::insert_barriers_from_tracker( + &mut baked.encoder, + &mut *trackers, + &baked.trackers, + &*buffer_guard, + &*texture_guard, + ); + + let transit = unsafe { baked.encoder.end_encoding().unwrap() }; + baked.list.insert(0, transit); + + // Transition surface textures into `Present` state. + // Note: we could technically do it after all of the command buffers, + // but here we have a command encoder by hand, so it's easier to use it. + if !used_surface_textures.is_empty() { + unsafe { + baked + .encoder + .begin_encoding(Some("(wgpu internal) Present")) + .map_err(DeviceError::from)? + }; + trackers + .textures + .set_from_usage_scope(&*texture_guard, &used_surface_textures); + let texture_barriers = trackers.textures.drain().map(|pending| { + let tex = unsafe { texture_guard.get_unchecked(pending.id) }; + pending.into_hal(tex) + }); + let present = unsafe { + baked.encoder.transition_textures(texture_barriers); + baked.encoder.end_encoding().unwrap() + }; + baked.list.push(present); + used_surface_textures = track::TextureUsageScope::new(); + } + + // done + active_executions.push(EncoderInFlight { + raw: baked.encoder, + cmd_buffers: baked.list, + }); + } + + log::trace!("Device after submission {}", submit_index); + } + + let super::Device { + ref mut pending_writes, + ref mut queue, + ref mut fence, + .. + } = *device; + + { + // TODO: These blocks have a few organizational issues, and + // should be refactored. + // + // 1) It's similar to the code we have per-command-buffer + // (at the begin and end) Maybe we can merge some? + // + // 2) It's doing the extra locking unconditionally. Maybe we + // can only do so if any surfaces are being written to? + let (_, mut token) = hub.buffers.read(&mut token); // skip token + let (mut texture_guard, _) = hub.textures.write(&mut token); + + used_surface_textures.set_size(texture_guard.len()); + + for &id in pending_writes.dst_textures.iter() { + let texture = texture_guard.get_mut(id).unwrap(); + match texture.inner { + TextureInner::Native { raw: None } => { + return Err(QueueSubmitError::DestroyedTexture(id)); + } + TextureInner::Native { raw: Some(_) } => {} + TextureInner::Surface { + ref mut has_work, .. + } => { + *has_work = true; + let ref_count = texture.life_guard.add_ref(); + unsafe { + used_surface_textures + .merge_single( + &*texture_guard, + id::Valid(id), + None, + &ref_count, + hal::TextureUses::PRESENT, + ) + .unwrap() + }; + } + } + } + + if !used_surface_textures.is_empty() { + let mut trackers = device.trackers.lock(); + + trackers + .textures + .set_from_usage_scope(&*texture_guard, &used_surface_textures); + let texture_barriers = trackers.textures.drain().map(|pending| { + let tex = unsafe { texture_guard.get_unchecked(pending.id) }; + pending.into_hal(tex) + }); + + unsafe { + pending_writes + .command_encoder + .transition_textures(texture_barriers); + }; + } + } + + let refs = pending_writes + .pre_submit() + .into_iter() + .chain( + active_executions + .iter() + .flat_map(|pool_execution| pool_execution.cmd_buffers.iter()), + ) + .collect::<Vec<_>>(); + unsafe { + queue + .submit(&refs, Some((fence, submit_index))) + .map_err(DeviceError::from)?; + } + } + + profiling::scope!("cleanup"); + if let Some(pending_execution) = device.pending_writes.post_submit( + &device.command_allocator, + &device.raw, + &device.queue, + ) { + active_executions.push(pending_execution); + } + + // this will register the new submission to the life time tracker + let mut pending_write_resources = mem::take(&mut device.pending_writes.temp_resources); + device.lock_life(&mut token).track_submission( + submit_index, + pending_write_resources.drain(..), + active_executions, + ); + + // This will schedule destruction of all resources that are no longer needed + // by the user but used in the command stream, among other things. + let (closures, _) = match device.maintain(hub, wgt::Maintain::Poll, &mut token) { + Ok(closures) => closures, + Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)), + Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu), + Err(WaitIdleError::WrongSubmissionIndex(..)) => unreachable!(), + }; + + // pending_write_resources has been drained, so it's empty, but we + // want to retain its heap allocation. + device.pending_writes.temp_resources = pending_write_resources; + device.temp_suspected.clear(); + device.lock_life(&mut token).post_submit(); + + (submit_index, closures) + }; + + // the closures should execute with nothing locked! + callbacks.fire(); + + Ok(WrappedSubmissionIndex { + queue_id, + index: submit_index, + }) + } + + pub fn queue_get_timestamp_period<A: HalApi>( + &self, + queue_id: id::QueueId, + ) -> Result<f32, InvalidQueue> { + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, _) = hub.devices.read(&mut token); + match device_guard.get(queue_id) { + Ok(device) => Ok(unsafe { device.queue.get_timestamp_period() }), + Err(_) => Err(InvalidQueue), + } + } + + pub fn queue_on_submitted_work_done<A: HalApi>( + &self, + queue_id: id::QueueId, + closure: SubmittedWorkDoneClosure, + ) -> Result<(), InvalidQueue> { + //TODO: flush pending writes + let closure_opt = { + let hub = A::hub(self); + let mut token = Token::root(); + let (device_guard, mut token) = hub.devices.read(&mut token); + match device_guard.get(queue_id) { + Ok(device) => device.lock_life(&mut token).add_work_done_closure(closure), + Err(_) => return Err(InvalidQueue), + } + }; + if let Some(closure) = closure_opt { + closure.call(); + } + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/device/trace.rs b/third_party/rust/wgpu-core/src/device/trace.rs new file mode 100644 index 0000000000..57f82c181e --- /dev/null +++ b/third_party/rust/wgpu-core/src/device/trace.rs @@ -0,0 +1,234 @@ +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: crate::Label<'a>, + context: &'a super::RenderPassContext, + depth_read_only: bool, + stencil_read_only: bool, +) -> crate::command::RenderBundleEncoderDescriptor<'a> { + crate::command::RenderBundleEncoderDescriptor { + label, + color_formats: Cow::Borrowed(&context.attachments.colors), + depth_stencil: context.attachments.depth_stencil.map(|format| { + wgt::RenderBundleDepthStencil { + format, + depth_read_only, + stencil_read_only, + } + }), + sample_count: context.sample_count, + multiview: context.multiview, + } +} + +#[allow(clippy::large_enum_variant)] +#[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, + }, + ConfigureSurface( + id::SurfaceId, + wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>, + ), + 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), + GetSurfaceTexture { + id: id::TextureId, + parent_id: id::SurfaceId, + }, + Present(id::SurfaceId), + DiscardSurfaceTexture(id::SurfaceId), + 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, + desc: crate::pipeline::ShaderModuleDescriptor<'a>, + data: FileName, + }, + DestroyShaderModule(id::ShaderModuleId), + CreateComputePipeline { + id: id::ComputePipelineId, + desc: crate::pipeline::ComputePipelineDescriptor<'a>, + #[cfg_attr(feature = "replay", serde(default))] + implicit_context: Option<super::ImplicitPipelineContext>, + }, + DestroyComputePipeline(id::ComputePipelineId), + CreateRenderPipeline { + id: id::RenderPipelineId, + desc: crate::pipeline::RenderPipelineDescriptor<'a>, + #[cfg_attr(feature = "replay", serde(default))] + implicit_context: Option<super::ImplicitPipelineContext>, + }, + DestroyRenderPipeline(id::RenderPipelineId), + CreateRenderBundle { + id: id::RenderBundleId, + desc: crate::command::RenderBundleEncoderDescriptor<'a>, + base: crate::command::BasePass<crate::command::RenderCommand>, + }, + DestroyRenderBundle(id::RenderBundleId), + CreateQuerySet { + id: id::QuerySetId, + desc: crate::resource::QuerySetDescriptor<'a>, + }, + DestroyQuerySet(id::QuerySetId), + WriteBuffer { + id: id::BufferId, + data: FileName, + range: Range<wgt::BufferAddress>, + queued: bool, + }, + WriteTexture { + to: crate::command::ImageCopyTexture, + data: FileName, + layout: wgt::ImageDataLayout, + 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::ImageCopyBuffer, + dst: crate::command::ImageCopyTexture, + size: wgt::Extent3d, + }, + CopyTextureToBuffer { + src: crate::command::ImageCopyTexture, + dst: crate::command::ImageCopyBuffer, + size: wgt::Extent3d, + }, + CopyTextureToTexture { + src: crate::command::ImageCopyTexture, + dst: crate::command::ImageCopyTexture, + size: wgt::Extent3d, + }, + ClearBuffer { + dst: id::BufferId, + offset: wgt::BufferAddress, + size: Option<wgt::BufferSize>, + }, + ClearTexture { + dst: id::TextureId, + subresource_range: wgt::ImageSubresourceRange, + }, + WriteTimestamp { + query_set_id: id::QuerySetId, + query_index: u32, + }, + ResolveQuerySet { + query_set_id: id::QuerySetId, + start_query: u32, + query_count: u32, + destination: id::BufferId, + destination_offset: wgt::BufferAddress, + }, + PushDebugGroup(String), + PopDebugGroup, + InsertDebugMarker(String), + RunComputePass { + base: crate::command::BasePass<crate::command::ComputeCommand>, + }, + RunRenderPass { + base: crate::command::BasePass<crate::command::RenderCommand>, + target_colors: Vec<Option<crate::command::RenderPassColorAttachment>>, + target_depth_stencil: Option<crate::command::RenderPassDepthStencilAttachment>, + }, +} + +#[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> { + log::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) => { + log::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/third_party/rust/wgpu-core/src/error.rs b/third_party/rust/wgpu-core/src/error.rs new file mode 100644 index 0000000000..596595245b --- /dev/null +++ b/third_party/rust/wgpu-core/src/error.rs @@ -0,0 +1,190 @@ +use core::fmt; +use std::error::Error; + +use crate::{ + gfx_select, + hub::{Global, IdentityManagerFactory}, +}; + +pub struct ErrorFormatter<'a> { + writer: &'a mut dyn fmt::Write, + global: &'a Global<IdentityManagerFactory>, +} + +impl<'a> ErrorFormatter<'a> { + pub fn error(&mut self, err: &dyn Error) { + writeln!(self.writer, " {err}").expect("Error formatting error"); + } + + pub fn note(&mut self, note: &dyn fmt::Display) { + writeln!(self.writer, " note: {note}").expect("Error formatting error"); + } + + pub fn label(&mut self, label_key: &str, label_value: &str) { + if !label_key.is_empty() && !label_value.is_empty() { + self.note(&format!("{label_key} = `{label_value}`")); + } + } + + pub fn bind_group_label(&mut self, id: &crate::id::BindGroupId) { + let global = self.global; + let label = gfx_select!(id => global.bind_group_label(*id)); + self.label("bind group", &label); + } + + pub fn bind_group_layout_label(&mut self, id: &crate::id::BindGroupLayoutId) { + let global = self.global; + let label = gfx_select!(id => global.bind_group_layout_label(*id)); + self.label("bind group layout", &label); + } + + pub fn render_pipeline_label(&mut self, id: &crate::id::RenderPipelineId) { + let global = self.global; + let label = gfx_select!(id => global.render_pipeline_label(*id)); + self.label("render pipeline", &label); + } + + pub fn compute_pipeline_label(&mut self, id: &crate::id::ComputePipelineId) { + let global = self.global; + let label = gfx_select!(id => global.compute_pipeline_label(*id)); + self.label("compute pipeline", &label); + } + + pub fn buffer_label_with_key(&mut self, id: &crate::id::BufferId, key: &str) { + let global = self.global; + let label = gfx_select!(id => global.buffer_label(*id)); + self.label(key, &label); + } + + pub fn buffer_label(&mut self, id: &crate::id::BufferId) { + self.buffer_label_with_key(id, "buffer"); + } + + pub fn texture_label_with_key(&mut self, id: &crate::id::TextureId, key: &str) { + let global = self.global; + let label = gfx_select!(id => global.texture_label(*id)); + self.label(key, &label); + } + + pub fn texture_label(&mut self, id: &crate::id::TextureId) { + self.texture_label_with_key(id, "texture"); + } + + pub fn texture_view_label_with_key(&mut self, id: &crate::id::TextureViewId, key: &str) { + let global = self.global; + let label = gfx_select!(id => global.texture_view_label(*id)); + self.label(key, &label); + } + + pub fn texture_view_label(&mut self, id: &crate::id::TextureViewId) { + self.texture_view_label_with_key(id, "texture view"); + } + + pub fn sampler_label(&mut self, id: &crate::id::SamplerId) { + let global = self.global; + let label = gfx_select!(id => global.sampler_label(*id)); + self.label("sampler", &label); + } + + pub fn command_buffer_label(&mut self, id: &crate::id::CommandBufferId) { + let global = self.global; + let label = gfx_select!(id => global.command_buffer_label(*id)); + self.label("command buffer", &label); + } + + pub fn query_set_label(&mut self, id: &crate::id::QuerySetId) { + let global = self.global; + let label = gfx_select!(id => global.query_set_label(*id)); + self.label("query set", &label); + } +} + +pub trait PrettyError: Error + Sized { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + } +} + +pub fn format_pretty_any( + writer: &mut dyn fmt::Write, + global: &Global<IdentityManagerFactory>, + error: &(dyn Error + 'static), +) { + let mut fmt = ErrorFormatter { writer, global }; + + if let Some(pretty_err) = error.downcast_ref::<ContextError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + + if let Some(pretty_err) = error.downcast_ref::<crate::command::RenderCommandError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::binding_model::CreateBindGroupError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = + error.downcast_ref::<crate::binding_model::CreatePipelineLayoutError>() + { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::ExecutionError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::RenderPassErrorInner>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::RenderPassError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::ComputePassErrorInner>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::ComputePassError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::RenderBundleError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::TransferError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::PassErrorScope>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::track::UsageConflict>() { + return pretty_err.fmt_pretty(&mut fmt); + } + if let Some(pretty_err) = error.downcast_ref::<crate::command::QueryError>() { + return pretty_err.fmt_pretty(&mut fmt); + } + + // default + fmt.error(error) +} + +#[derive(Debug)] +pub struct ContextError { + pub string: &'static str, + pub cause: Box<dyn Error + Send + Sync + 'static>, + pub label_key: &'static str, + pub label: String, +} + +impl PrettyError for ContextError { + fn fmt_pretty(&self, fmt: &mut ErrorFormatter) { + fmt.error(self); + fmt.label(self.label_key, &self.label); + } +} + +impl fmt::Display for ContextError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "In {}", self.string) + } +} + +impl Error for ContextError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(self.cause.as_ref()) + } +} diff --git a/third_party/rust/wgpu-core/src/hub.rs b/third_party/rust/wgpu-core/src/hub.rs new file mode 100644 index 0000000000..0a3b5d954e --- /dev/null +++ b/third_party/rust/wgpu-core/src/hub.rs @@ -0,0 +1,1603 @@ +/*! Allocating resource ids, and tracking the resources they refer to. + +The `wgpu_core` API uses identifiers of type [`Id<R>`] to refer to +resources of type `R`. For example, [`id::DeviceId`] is an alias for +`Id<Device<Empty>>`, and [`id::BufferId`] is an alias for +`Id<Buffer<Empty>>`. `Id` implements `Copy`, `Hash`, `Eq`, `Ord`, and +of course `Debug`. + +Each `Id` contains not only an index for the resource it denotes but +also a [`Backend`] indicating which `wgpu` backend it belongs to. You +can use the [`gfx_select`] macro to dynamically dispatch on an id's +backend to a function specialized at compile time for a specific +backend. See that macro's documentation for details. + +`Id`s also incorporate a generation number, for additional validation. + +The resources to which identifiers refer are freed explicitly. +Attempting to use an identifier for a resource that has been freed +elicits an error result. + +## Assigning ids to resources + +The users of `wgpu_core` generally want resource ids to be assigned +in one of two ways: + +- Users like `wgpu` want `wgpu_core` to assign ids to resources itself. + For example, `wgpu` expects to call `Global::device_create_buffer` + and have the return value indicate the newly created buffer's id. + +- Users like `player` and Firefox want to allocate ids themselves, and + pass `Global::device_create_buffer` and friends the id to assign the + new resource. + +To accommodate either pattern, `wgpu_core` methods that create +resources all expect an `id_in` argument that the caller can use to +specify the id, and they all return the id used. For example, the +declaration of `Global::device_create_buffer` looks like this: + +```ignore +impl<G: GlobalIdentityHandlerFactory> Global<G> { + /* ... */ + pub fn device_create_buffer<A: HalApi>( + &self, + device_id: id::DeviceId, + desc: &resource::BufferDescriptor, + id_in: Input<G, id::BufferId>, + ) -> (id::BufferId, Option<resource::CreateBufferError>) { + /* ... */ + } + /* ... */ +} +``` + +Users that want to assign resource ids themselves pass in the id they +want as the `id_in` argument, whereas users that want `wgpu_core` +itself to choose ids always pass `()`. In either case, the id +ultimately assigned is returned as the first element of the tuple. + +Producing true identifiers from `id_in` values is the job of an +[`IdentityHandler`] implementation, which has an associated type +[`Input`] saying what type of `id_in` values it accepts, and a +[`process`] method that turns such values into true identifiers of +type `I`. There are two kinds of `IdentityHandler`s: + +- Users that want `wgpu_core` to assign ids generally use + [`IdentityManager`] ([wrapped in a mutex]). Its `Input` type is + `()`, and it tracks assigned ids and generation numbers as + necessary. (This is what `wgpu` does.) + +- Users that want to assign ids themselves use an `IdentityHandler` + whose `Input` type is `I` itself, and whose `process` method simply + passes the `id_in` argument through unchanged. For example, the + `player` crate uses an `IdentityPassThrough` type whose `process` + method simply adjusts the id's backend (since recordings can be + replayed on a different backend than the one they were created on) + but passes the rest of the id's content through unchanged. + +Because an `IdentityHandler<I>` can only create ids for a single +resource type `I`, constructing a [`Global`] entails constructing a +separate `IdentityHandler<I>` for each resource type `I` that the +`Global` will manage: an `IdentityHandler<DeviceId>`, an +`IdentityHandler<TextureId>`, and so on. + +The [`Global::new`] function could simply take a large collection of +`IdentityHandler<I>` implementations as arguments, but that would be +ungainly. Instead, `Global::new` expects a `factory` argument that +implements the [`GlobalIdentityHandlerFactory`] trait, which extends +[`IdentityHandlerFactory<I>`] for each resource id type `I`. This +trait, in turn, has a `spawn` method that constructs an +`IdentityHandler<I>` for the `Global` to use. + +What this means is that the types of resource creation functions' +`id_in` arguments depend on the `Global`'s `G` type parameter. A +`Global<G>`'s `IdentityHandler<I>` implementation is: + +```ignore +<G as IdentityHandlerFactory<I>>::Filter +``` + +where `Filter` is an associated type of the `IdentityHandlerFactory` trait. +Thus, its `id_in` type is: + +```ignore +<<G as IdentityHandlerFactory<I>>::Filter as IdentityHandler<I>>::Input +``` + +The [`Input<G, I>`] type is an alias for this construction. + +## Id allocation and streaming + +Perhaps surprisingly, allowing users to assign resource ids themselves +enables major performance improvements in some applications. + +The `wgpu_core` API is designed for use by Firefox's [WebGPU] +implementation. For security, web content and GPU use must be kept +segregated in separate processes, with all interaction between them +mediated by an inter-process communication protocol. As web content uses +the WebGPU API, the content process sends messages to the GPU process, +which interacts with the platform's GPU APIs on content's behalf, +occasionally sending results back. + +In a classic Rust API, a resource allocation function takes parameters +describing the resource to create, and if creation succeeds, it returns +the resource id in a `Result::Ok` value. However, this design is a poor +fit for the split-process design described above: content must wait for +the reply to its buffer-creation message (say) before it can know which +id it can use in the next message that uses that buffer. On a common +usage pattern, the classic Rust design imposes the latency of a full +cross-process round trip. + +We can avoid incurring these round-trip latencies simply by letting the +content process assign resource ids itself. With this approach, content +can choose an id for the new buffer, send a message to create the +buffer, and then immediately send the next message operating on that +buffer, since it already knows its id. Allowing content and GPU process +activity to be pipelined greatly improves throughput. + +To help propagate errors correctly in this style of usage, when resource +creation fails, the id supplied for that resource is marked to indicate +as much, allowing subsequent operations using that id to be properly +flagged as errors as well. + +[`gfx_select`]: crate::gfx_select +[`Input`]: IdentityHandler::Input +[`process`]: IdentityHandler::process +[`Id<R>`]: crate::id::Id +[wrapped in a mutex]: trait.IdentityHandler.html#impl-IdentityHandler%3CI%3E-for-Mutex%3CIdentityManager%3E +[WebGPU]: https://www.w3.org/TR/webgpu/ + +*/ + +use crate::{ + binding_model::{BindGroup, BindGroupLayout, PipelineLayout}, + command::{CommandBuffer, RenderBundle}, + device::Device, + id, + instance::{Adapter, HalSurface, Instance, Surface}, + pipeline::{ComputePipeline, RenderPipeline, ShaderModule}, + resource::{Buffer, QuerySet, Sampler, StagingBuffer, Texture, TextureClearMode, TextureView}, + 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, mem, ops}; + +/// A simple structure to allocate [`Id`] identifiers. +/// +/// Calling [`alloc`] returns a fresh, never-before-seen id. Calling [`free`] +/// marks an id as dead; it will never be returned again by `alloc`. +/// +/// Use `IdentityManager::default` to construct new instances. +/// +/// `IdentityManager` returns `Id`s whose index values are suitable for use as +/// indices into a `Storage<T>` that holds those ids' referents: +/// +/// - Every live id has a distinct index value. Each live id's index selects a +/// distinct element in the vector. +/// +/// - `IdentityManager` prefers low index numbers. If you size your vector to +/// accommodate the indices produced here, the vector's length will reflect +/// the highwater mark of actual occupancy. +/// +/// - `IdentityManager` reuses the index values of freed ids before returning +/// ids with new index values. Freed vector entries get reused. +/// +/// See the module-level documentation for an overview of how this +/// fits together. +/// +/// [`Id`]: crate::id::Id +/// [`Backend`]: wgt::Backend; +/// [`alloc`]: IdentityManager::alloc +/// [`free`]: IdentityManager::free +#[derive(Debug, Default)] +pub struct IdentityManager { + /// Available index values. If empty, then `epochs.len()` is the next index + /// to allocate. + free: Vec<Index>, + + /// The next or currently-live epoch value associated with each `Id` index. + /// + /// If there is a live id with index `i`, then `epochs[i]` is its epoch; any + /// id with the same index but an older epoch is dead. + /// + /// If index `i` is currently unused, `epochs[i]` is the epoch to use in its + /// next `Id`. + epochs: Vec<Epoch>, +} + +impl IdentityManager { + /// Allocate a fresh, never-before-seen id with the given `backend`. + /// + /// The backend is incorporated into the id, so that ids allocated with + /// different `backend` values are always distinct. + pub fn alloc<I: id::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 + } + } + } + + /// Free `id`. It will never be returned from `alloc` again. + pub fn free<I: id::TypedId + Debug>(&mut self, id: I) { + let (index, epoch, _backend) = id.unzip(); + let pe = &mut self.epochs[index as usize]; + assert_eq!(*pe, epoch); + // If the epoch reaches EOL, the index doesn't go + // into the free list, will never be reused again. + if epoch < id::EPOCH_MASK { + *pe = epoch + 1; + self.free.push(index); + } + } +} + +/// An entry in a `Storage::map` table. +#[derive(Debug)] +enum Element<T> { + /// There are no live ids with this index. + Vacant, + + /// There is one live id with this index, allocated at the given + /// epoch. + Occupied(T, Epoch), + + /// Like `Occupied`, but an error occurred when creating the + /// resource. + /// + /// The given `String` is the resource's descriptor label. + Error(Epoch, String), +} + +#[derive(Clone, Debug, Default)] +pub struct StorageReport { + pub num_occupied: usize, + pub num_vacant: usize, + pub num_error: usize, + pub element_size: usize, +} + +impl StorageReport { + pub fn is_empty(&self) -> bool { + self.num_occupied + self.num_vacant + self.num_error == 0 + } +} + +#[derive(Clone, Debug)] +pub(crate) struct InvalidId; + +/// A table of `T` values indexed by the id type `I`. +/// +/// The table is represented as a vector indexed by the ids' index +/// values, so you should use an id allocator like `IdentityManager` +/// that keeps the index values dense and close to zero. +#[derive(Debug)] +pub struct Storage<T, I: id::TypedId> { + map: Vec<Element<T>>, + kind: &'static str, + _phantom: PhantomData<I>, +} + +impl<T, I: id::TypedId> ops::Index<id::Valid<I>> for Storage<T, I> { + type Output = T; + fn index(&self, id: id::Valid<I>) -> &T { + self.get(id.0).unwrap() + } +} + +impl<T, I: id::TypedId> ops::IndexMut<id::Valid<I>> for Storage<T, I> { + fn index_mut(&mut self, id: id::Valid<I>) -> &mut T { + self.get_mut(id.0).unwrap() + } +} + +impl<T, I: id::TypedId> Storage<T, I> { + pub(crate) fn contains(&self, id: I) -> bool { + let (index, epoch, _) = id.unzip(); + match self.map.get(index as usize) { + Some(&Element::Vacant) => false, + Some(&Element::Occupied(_, storage_epoch) | &Element::Error(storage_epoch, _)) => { + storage_epoch == epoch + } + None => false, + } + } + + /// Attempts to get a reference to an item behind a potentially invalid ID. + /// + /// Returns [`None`] if there is an epoch mismatch, or the entry is empty. + /// + /// This function is primarily intended for the `as_hal` family of functions + /// where you may need to fallibly get a object backed by an id that could + /// be in a different hub. + pub(crate) fn try_get(&self, id: I) -> Result<Option<&T>, InvalidId> { + let (index, epoch, _) = id.unzip(); + let (result, storage_epoch) = match self.map.get(index as usize) { + Some(&Element::Occupied(ref v, epoch)) => (Ok(Some(v)), epoch), + Some(&Element::Vacant) => return Ok(None), + Some(&Element::Error(epoch, ..)) => (Err(InvalidId), epoch), + None => return Err(InvalidId), + }; + assert_eq!( + epoch, storage_epoch, + "{}[{}] is no longer alive", + self.kind, index + ); + result + } + + /// 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.get(index as usize) { + Some(&Element::Occupied(ref v, epoch)) => (Ok(v), epoch), + Some(&Element::Vacant) => panic!("{}[{}] does not exist", self.kind, index), + Some(&Element::Error(epoch, ..)) => (Err(InvalidId), epoch), + None => return Err(InvalidId), + }; + 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.get_mut(index as usize) { + Some(&mut Element::Occupied(ref mut v, epoch)) => (Ok(v), epoch), + Some(&mut Element::Vacant) | None => panic!("{}[{}] does not exist", self.kind, index), + Some(&mut Element::Error(epoch, ..)) => (Err(InvalidId), epoch), + }; + assert_eq!( + epoch, storage_epoch, + "{}[{}] is no longer alive", + self.kind, index + ); + result + } + + pub(crate) unsafe fn get_unchecked(&self, id: u32) -> &T { + match self.map[id as usize] { + Element::Occupied(ref v, _) => v, + Element::Vacant => panic!("{}[{}] does not exist", self.kind, id), + Element::Error(_, _) => panic!(""), + } + } + + pub(crate) fn label_for_invalid_id(&self, id: I) -> &str { + let (index, _, _) = id.unzip(); + match self.map.get(index as usize) { + Some(&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 {index:?} is already occupied"), + } + } + + 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 force_replace(&mut self, id: I, value: T) { + let (index, epoch, _) = id.unzip(); + self.map[index as usize] = Element::Occupied(value, epoch); + } + + 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, + }) + } + + pub(crate) fn len(&self) -> usize { + self.map.len() + } + + fn generate_report(&self) -> StorageReport { + let mut report = StorageReport { + element_size: mem::size_of::<T>(), + ..Default::default() + }; + for element in self.map.iter() { + match *element { + Element::Occupied(..) => report.num_occupied += 1, + Element::Vacant => report.num_vacant += 1, + Element::Error(..) => report.num_error += 1, + } + } + report + } +} + +/// Type system for enforcing the lock order on [`Hub`] fields. +/// +/// If type `A` implements `Access<B>`, that means we are allowed to +/// proceed with locking resource `B` after we lock `A`. +/// +/// The implementations of `Access` basically describe the edges in an +/// acyclic directed graph of lock transitions. As long as it doesn't have +/// cycles, any number of threads can acquire locks along paths through +/// the graph without deadlock. That is, if you look at each thread's +/// lock acquisitions as steps along a path in the graph, then because +/// there are no cycles in the graph, there must always be some thread +/// that is able to acquire its next lock, or that is about to release +/// a lock. (Assume that no thread just sits on its locks forever.) +/// +/// Locks must be acquired in the following order: +/// +/// - [`Adapter`] +/// - [`Device`] +/// - [`CommandBuffer`] +/// - [`RenderBundle`] +/// - [`PipelineLayout`] +/// - [`BindGroupLayout`] +/// - [`BindGroup`] +/// - [`ComputePipeline`] +/// - [`RenderPipeline`] +/// - [`ShaderModule`] +/// - [`Buffer`] +/// - [`StagingBuffer`] +/// - [`Texture`] +/// - [`TextureView`] +/// - [`Sampler`] +/// - [`QuerySet`] +/// +/// That is, you may only acquire a new lock on a `Hub` field if it +/// appears in the list after all the other fields you're already +/// holding locks for. When you are holding no locks, you can start +/// anywhere. +/// +/// It's fine to add more `Access` implementations as needed, as long +/// as you do not introduce a cycle. In other words, as long as there +/// is some ordering you can put the resource types in that respects +/// the extant `Access` implementations, that's fine. +/// +/// See the documentation for [`Hub`] for more details. +pub trait Access<A> {} + +pub enum Root {} + +// These impls are arranged so that the target types (that is, the `T` +// in `Access<T>`) appear in locking order. +// +// 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<A: HalApi> Access<Adapter<A>> for Root {} +impl<A: HalApi> Access<Adapter<A>> for Surface {} +impl<A: HalApi> Access<Device<A>> for Root {} +impl<A: HalApi> Access<Device<A>> for Surface {} +impl<A: HalApi> Access<Device<A>> for Adapter<A> {} +impl<A: HalApi> Access<CommandBuffer<A>> for Root {} +impl<A: HalApi> Access<CommandBuffer<A>> for Device<A> {} +impl<A: HalApi> Access<RenderBundle<A>> for Device<A> {} +impl<A: HalApi> Access<RenderBundle<A>> for CommandBuffer<A> {} +impl<A: HalApi> Access<PipelineLayout<A>> for Root {} +impl<A: HalApi> Access<PipelineLayout<A>> for Device<A> {} +impl<A: HalApi> Access<PipelineLayout<A>> for RenderBundle<A> {} +impl<A: HalApi> Access<BindGroupLayout<A>> for Root {} +impl<A: HalApi> Access<BindGroupLayout<A>> for Device<A> {} +impl<A: HalApi> Access<BindGroupLayout<A>> for PipelineLayout<A> {} +impl<A: HalApi> Access<BindGroup<A>> for Root {} +impl<A: HalApi> Access<BindGroup<A>> for Device<A> {} +impl<A: HalApi> Access<BindGroup<A>> for BindGroupLayout<A> {} +impl<A: HalApi> Access<BindGroup<A>> for PipelineLayout<A> {} +impl<A: HalApi> Access<BindGroup<A>> for CommandBuffer<A> {} +impl<A: HalApi> Access<ComputePipeline<A>> for Device<A> {} +impl<A: HalApi> Access<ComputePipeline<A>> for BindGroup<A> {} +impl<A: HalApi> Access<RenderPipeline<A>> for Device<A> {} +impl<A: HalApi> Access<RenderPipeline<A>> for BindGroup<A> {} +impl<A: HalApi> Access<RenderPipeline<A>> for ComputePipeline<A> {} +impl<A: HalApi> Access<ShaderModule<A>> for Device<A> {} +impl<A: HalApi> Access<ShaderModule<A>> for BindGroupLayout<A> {} +impl<A: HalApi> Access<Buffer<A>> for Root {} +impl<A: HalApi> Access<Buffer<A>> for Device<A> {} +impl<A: HalApi> Access<Buffer<A>> for BindGroupLayout<A> {} +impl<A: HalApi> Access<Buffer<A>> for BindGroup<A> {} +impl<A: HalApi> Access<Buffer<A>> for CommandBuffer<A> {} +impl<A: HalApi> Access<Buffer<A>> for ComputePipeline<A> {} +impl<A: HalApi> Access<Buffer<A>> for RenderPipeline<A> {} +impl<A: HalApi> Access<Buffer<A>> for QuerySet<A> {} +impl<A: HalApi> Access<StagingBuffer<A>> for Device<A> {} +impl<A: HalApi> Access<Texture<A>> for Root {} +impl<A: HalApi> Access<Texture<A>> for Device<A> {} +impl<A: HalApi> Access<Texture<A>> for Buffer<A> {} +impl<A: HalApi> Access<TextureView<A>> for Root {} +impl<A: HalApi> Access<TextureView<A>> for Device<A> {} +impl<A: HalApi> Access<TextureView<A>> for Texture<A> {} +impl<A: HalApi> Access<Sampler<A>> for Root {} +impl<A: HalApi> Access<Sampler<A>> for Device<A> {} +impl<A: HalApi> Access<Sampler<A>> for TextureView<A> {} +impl<A: HalApi> Access<QuerySet<A>> for Root {} +impl<A: HalApi> Access<QuerySet<A>> for Device<A> {} +impl<A: HalApi> Access<QuerySet<A>> for CommandBuffer<A> {} +impl<A: HalApi> Access<QuerySet<A>> for RenderPipeline<A> {} +impl<A: HalApi> Access<QuerySet<A>> for ComputePipeline<A> {} +impl<A: HalApi> Access<QuerySet<A>> for Sampler<A> {} + +#[cfg(debug_assertions)] +thread_local! { + /// Per-thread state checking `Token<Root>` creation in debug builds. + /// + /// This is the number of `Token` values alive on the current + /// thread. Since `Token` creation respects the [`Access`] graph, + /// there can never be more tokens alive than there are fields of + /// [`Hub`], so a `u8` is plenty. + static ACTIVE_TOKEN: Cell<u8> = Cell::new(0); +} + +/// A zero-size permission token to lock some fields of [`Hub`]. +/// +/// Access to a `Token<T>` grants permission to lock any field of +/// [`Hub`] following the one of type [`Registry<T, ...>`], where +/// "following" is as defined by the [`Access`] implementations. +/// +/// Calling [`Token::root()`] returns a `Token<Root>`, which grants +/// permission to lock any field. Dynamic checks ensure that each +/// thread has at most one `Token<Root>` live at a time, in debug +/// builds. +/// +/// The locking methods on `Registry<T, ...>` take a `&'t mut +/// Token<A>`, and return a fresh `Token<'t, T>` and a lock guard with +/// lifetime `'t`, so the caller cannot access their `Token<A>` again +/// until they have dropped both the `Token<T>` and the lock guard. +/// +/// Tokens are `!Send`, so one thread can't send its permissions to +/// another. +pub(crate) struct Token<'a, T: 'a> { + // The `*const` makes us `!Send` and `!Sync`. + level: PhantomData<&'a *const T>, +} + +impl<'a, T> Token<'a, T> { + /// Return a new token for a locked field. + /// + /// This should only be used by `Registry` locking methods. + 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> { + /// Return a `Token<Root>`, granting permission to lock any [`Hub`] field. + /// + /// Debug builds check dynamically that each thread has at most + /// one root token at a time. + 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); + }); + } +} + +/// A type that can build true ids from proto-ids, and free true ids. +/// +/// For some implementations, the true id is based on the proto-id. +/// The caller is responsible for providing well-allocated proto-ids. +/// +/// For other implementations, the proto-id carries no information +/// (it's `()`, say), and this `IdentityHandler` type takes care of +/// allocating a fresh true id. +/// +/// See the module-level documentation for details. +pub trait IdentityHandler<I>: Debug { + /// The type of proto-id consumed by this filter, to produce a true id. + type Input: Clone + Debug; + + /// Given a proto-id value `id`, return a true id for `backend`. + fn process(&self, id: Self::Input, backend: Backend) -> I; + + /// Free the true id `id`. + fn free(&self, id: I); +} + +impl<I: id::TypedId + Debug> IdentityHandler<I> for Mutex<IdentityManager> { + type Input = (); + fn process(&self, _id: Self::Input, backend: Backend) -> I { + self.lock().alloc(backend) + } + fn free(&self, id: I) { + self.lock().free(id) + } +} + +/// A type that can produce [`IdentityHandler`] filters for ids of type `I`. +/// +/// See the module-level documentation for details. +pub trait IdentityHandlerFactory<I> { + /// The type of filter this factory constructs. + /// + /// "Filter" and "handler" seem to both mean the same thing here: + /// something that can produce true ids from proto-ids. + type Filter: IdentityHandler<I>; + + /// Create an [`IdentityHandler<I>`] implementation that can + /// transform proto-ids into ids of type `I`. + /// + /// [`IdentityHandler<I>`]: IdentityHandler + fn spawn(&self) -> Self::Filter; +} + +/// A global identity handler factory based on [`IdentityManager`]. +/// +/// Each of this type's `IdentityHandlerFactory<I>::spawn` methods +/// returns a `Mutex<IdentityManager<I>>`, which allocates fresh `I` +/// ids itself, and takes `()` as its proto-id type. +#[derive(Debug)] +pub struct IdentityManagerFactory; + +impl<I: id::TypedId + Debug> IdentityHandlerFactory<I> for IdentityManagerFactory { + type Filter = Mutex<IdentityManager>; + fn spawn(&self) -> Self::Filter { + Mutex::new(IdentityManager::default()) + } +} + +/// A factory that can build [`IdentityHandler`]s for all resource +/// types. +pub trait GlobalIdentityHandlerFactory: + IdentityHandlerFactory<id::AdapterId> + + IdentityHandlerFactory<id::DeviceId> + + IdentityHandlerFactory<id::PipelineLayoutId> + + IdentityHandlerFactory<id::ShaderModuleId> + + IdentityHandlerFactory<id::BindGroupLayoutId> + + IdentityHandlerFactory<id::BindGroupId> + + IdentityHandlerFactory<id::CommandBufferId> + + IdentityHandlerFactory<id::RenderBundleId> + + IdentityHandlerFactory<id::RenderPipelineId> + + IdentityHandlerFactory<id::ComputePipelineId> + + IdentityHandlerFactory<id::QuerySetId> + + IdentityHandlerFactory<id::BufferId> + + IdentityHandlerFactory<id::StagingBufferId> + + IdentityHandlerFactory<id::TextureId> + + IdentityHandlerFactory<id::TextureViewId> + + IdentityHandlerFactory<id::SamplerId> + + IdentityHandlerFactory<id::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: id::TypedId, F: IdentityHandlerFactory<I>> { + identity: F::Filter, + data: RwLock<Storage<T, I>>, + backend: Backend, +} + +impl<T: Resource, I: id::TypedId, F: IdentityHandlerFactory<I>> Registry<T, I, F> { + fn new(backend: Backend, factory: &F) -> Self { + Self { + identity: factory.spawn(), + 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(), + data: RwLock::new(Storage { + map: Vec::new(), + kind, + _phantom: PhantomData, + }), + backend: Backend::Empty, + } + } +} + +#[must_use] +pub(crate) struct FutureId<'a, I: id::TypedId, T> { + id: I, + data: &'a RwLock<Storage<T, I>>, +} + +impl<I: id::TypedId + Copy, T> FutureId<'_, I, T> { + #[cfg(feature = "trace")] + pub fn id(&self) -> I { + self.id + } + + pub fn into_id(self) -> I { + self.id + } + + pub fn assign<'a, A: Access<T>>(self, value: T, _: &'a mut Token<A>) -> id::Valid<I> { + self.data.write().insert(self.id, value); + id::Valid(self.id) + } + + pub fn assign_error<'a, A: Access<T>>(self, label: &str, _: &'a mut Token<A>) -> I { + self.data.write().insert_error(self.id, label); + self.id + } +} + +impl<T: Resource, I: id::TypedId + Copy, F: IdentityHandlerFactory<I>> Registry<T, I, F> { + pub(crate) fn prepare( + &self, + id_in: <F::Filter as IdentityHandler<I>>::Input, + ) -> FutureId<I, T> { + FutureId { + id: self.identity.process(id_in, self.backend), + data: &self.data, + } + } + + /// Acquire read access to this `Registry`'s contents. + /// + /// The caller must present a mutable reference to a `Token<A>`, + /// for some type `A` that comes before this `Registry`'s resource + /// type `T` in the lock ordering. A `Token<Root>` grants + /// permission to lock any field; see [`Token::root`]. + /// + /// Once the read lock is acquired, return a new `Token<T>`, along + /// with a read guard for this `Registry`'s [`Storage`], which can + /// be indexed by id to get at the actual resources. + /// + /// The borrow checker ensures that the caller cannot again access + /// its `Token<A>` until it has dropped both the guard and the + /// `Token<T>`. + /// + /// See the [`Hub`] type for more details on locking. + pub(crate) 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()) + } + + /// Acquire write access to this `Registry`'s contents. + /// + /// The caller must present a mutable reference to a `Token<A>`, + /// for some type `A` that comes before this `Registry`'s resource + /// type `T` in the lock ordering. A `Token<Root>` grants + /// permission to lock any field; see [`Token::root`]. + /// + /// Once the lock is acquired, return a new `Token<T>`, along with + /// a write guard for this `Registry`'s [`Storage`], which can be + /// indexed by id to get at the actual resources. + /// + /// The borrow checker ensures that the caller cannot again access + /// its `Token<A>` until it has dropped both the guard and the + /// `Token<T>`. + /// + /// See the [`Hub`] type for more details on locking. + pub(crate) 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()) + } + + /// Unregister the resource at `id`. + /// + /// The caller must prove that it already holds a write lock for + /// this `Registry` by passing a mutable reference to this + /// `Registry`'s storage, obtained from the write guard returned + /// by a previous call to [`write`], as the `guard` parameter. + 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 + } + + /// Unregister the resource at `id` and return its value, if any. + /// + /// The caller must present a mutable reference to a `Token<A>`, + /// for some type `A` that comes before this `Registry`'s resource + /// type `T` in the lock ordering. + /// + /// This returns a `Token<T>`, but it's almost useless, because it + /// doesn't return a lock guard to go with it: its only effect is + /// to make the token you passed to this function inaccessible. + /// However, the `Token<T>` can be used to satisfy some functions' + /// bureacratic expectations that you will have one available. + /// + /// The borrow checker ensures that the caller cannot again access + /// its `Token<A>` until it has dropped both the guard and the + /// `Token<T>`. + /// + /// See the [`Hub`] type for more details on locking. + pub(crate) 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 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 HubReport { + pub adapters: StorageReport, + pub devices: StorageReport, + pub pipeline_layouts: StorageReport, + pub shader_modules: StorageReport, + pub bind_group_layouts: StorageReport, + pub bind_groups: StorageReport, + pub command_buffers: StorageReport, + pub render_bundles: StorageReport, + pub render_pipelines: StorageReport, + pub compute_pipelines: StorageReport, + pub query_sets: StorageReport, + pub buffers: StorageReport, + pub textures: StorageReport, + pub texture_views: StorageReport, + pub samplers: StorageReport, +} + +impl HubReport { + pub fn is_empty(&self) -> bool { + self.adapters.is_empty() + } +} + +#[allow(rustdoc::private_intra_doc_links)] +/// All the resources for a particular backend in a [`Global`]. +/// +/// To obtain `global`'s `Hub` for some [`HalApi`] backend type `A`, +/// call [`A::hub(global)`]. +/// +/// ## Locking +/// +/// Each field in `Hub` is a [`Registry`] holding all the values of a +/// particular type of resource, all protected by a single [`RwLock`]. +/// So for example, to access any [`Buffer`], you must acquire a read +/// lock on the `Hub`s entire [`buffers`] registry. The lock guard +/// gives you access to the `Registry`'s [`Storage`], which you can +/// then index with the buffer's id. (Yes, this design causes +/// contention; see [#2272].) +/// +/// But most `wgpu` operations require access to several different +/// kinds of resource, so you often need to hold locks on several +/// different fields of your [`Hub`] simultaneously. To avoid +/// deadlock, there is an ordering imposed on the fields, and you may +/// only acquire new locks on fields that come *after* all those you +/// are already holding locks on, in this ordering. (The ordering is +/// described in the documentation for the [`Access`] trait.) +/// +/// We use Rust's type system to statically check that `wgpu_core` can +/// only ever acquire locks in the correct order: +/// +/// - A value of type [`Token<T>`] represents proof that the owner +/// only holds locks on the `Hub` fields holding resources of type +/// `T` or earlier in the lock ordering. A special value of type +/// `Token<Root>`, obtained by calling [`Token::root`], represents +/// proof that no `Hub` field locks are held. +/// +/// - To lock the `Hub` field holding resources of type `T`, you must +/// call its [`read`] or [`write`] methods. These require you to +/// pass in a `&mut Token<A>`, for some `A` that implements +/// [`Access<T>`]. This implementation exists only if `T` follows `A` +/// in the field ordering, which statically ensures that you are +/// indeed allowed to lock this new `Hub` field. +/// +/// - The locking methods return both an [`RwLock`] guard that you can +/// use to access the field's resources, and a new `Token<T>` value. +/// These both borrow from the lifetime of your `Token<A>`, so since +/// you passed that by mutable reference, you cannot access it again +/// until you drop the new token and lock guard. +/// +/// Because a thread only ever has access to the `Token<T>` for the +/// last resource type `T` it holds a lock for, and the `Access` trait +/// implementations only permit acquiring locks for types `U` that +/// follow `T` in the lock ordering, it is statically impossible for a +/// program to violate the locking order. +/// +/// This does assume that threads cannot call `Token<Root>` when they +/// already hold locks (dynamically enforced in debug builds) and that +/// threads cannot send their `Token`s to other threads (enforced by +/// making `Token` neither `Send` nor `Sync`). +/// +/// [`A::hub(global)`]: HalApi::hub +/// [`RwLock`]: parking_lot::RwLock +/// [`buffers`]: Hub::buffers +/// [`read`]: Registry::read +/// [`write`]: Registry::write +/// [`Token<T>`]: Token +/// [`Access<T>`]: Access +/// [#2272]: https://github.com/gfx-rs/wgpu/pull/2272 +pub struct Hub<A: HalApi, F: GlobalIdentityHandlerFactory> { + pub adapters: Registry<Adapter<A>, id::AdapterId, F>, + pub devices: Registry<Device<A>, id::DeviceId, F>, + pub pipeline_layouts: Registry<PipelineLayout<A>, id::PipelineLayoutId, F>, + pub shader_modules: Registry<ShaderModule<A>, id::ShaderModuleId, F>, + pub bind_group_layouts: Registry<BindGroupLayout<A>, id::BindGroupLayoutId, F>, + pub bind_groups: Registry<BindGroup<A>, id::BindGroupId, F>, + pub command_buffers: Registry<CommandBuffer<A>, id::CommandBufferId, F>, + pub render_bundles: Registry<RenderBundle<A>, id::RenderBundleId, F>, + pub render_pipelines: Registry<RenderPipeline<A>, id::RenderPipelineId, F>, + pub compute_pipelines: Registry<ComputePipeline<A>, id::ComputePipelineId, F>, + pub query_sets: Registry<QuerySet<A>, id::QuerySetId, F>, + pub buffers: Registry<Buffer<A>, id::BufferId, F>, + pub staging_buffers: Registry<StagingBuffer<A>, id::StagingBufferId, F>, + pub textures: Registry<Texture<A>, id::TextureId, F>, + pub texture_views: Registry<TextureView<A>, id::TextureViewId, F>, + pub samplers: Registry<Sampler<A>, id::SamplerId, F>, +} + +impl<A: HalApi, F: GlobalIdentityHandlerFactory> Hub<A, F> { + fn new(factory: &F) -> Self { + Self { + adapters: Registry::new(A::VARIANT, factory), + devices: Registry::new(A::VARIANT, factory), + pipeline_layouts: Registry::new(A::VARIANT, factory), + shader_modules: Registry::new(A::VARIANT, factory), + bind_group_layouts: Registry::new(A::VARIANT, factory), + bind_groups: Registry::new(A::VARIANT, factory), + command_buffers: Registry::new(A::VARIANT, factory), + render_bundles: Registry::new(A::VARIANT, factory), + render_pipelines: Registry::new(A::VARIANT, factory), + compute_pipelines: Registry::new(A::VARIANT, factory), + query_sets: Registry::new(A::VARIANT, factory), + buffers: Registry::new(A::VARIANT, factory), + staging_buffers: Registry::new(A::VARIANT, factory), + textures: Registry::new(A::VARIANT, factory), + texture_views: Registry::new(A::VARIANT, factory), + samplers: Registry::new(A::VARIANT, factory), + } + } + + //TODO: instead of having a hacky `with_adapters` parameter, + // we should have `clear_device(device_id)` that specifically destroys + // everything related to a logical device. + fn clear(&self, surface_guard: &mut Storage<Surface, id::SurfaceId>, with_adapters: bool) { + use crate::resource::TextureInner; + use hal::{Device as _, Surface as _}; + + let mut devices = self.devices.data.write(); + for element in devices.map.iter_mut() { + if let Element::Occupied(ref mut device, _) = *element { + device.prepare_to_die(); + } + } + + // destroy command buffers first, since otherwise DX12 isn't happy + for element in self.command_buffers.data.write().map.drain(..) { + if let Element::Occupied(command_buffer, _) = element { + let device = &devices[command_buffer.device_id.value]; + device.destroy_command_buffer(command_buffer); + } + } + + 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); + } + } + } + + for element in self.texture_views.data.write().map.drain(..) { + if let Element::Occupied(texture_view, _) = element { + let device = &devices[texture_view.device_id.value]; + unsafe { + device.raw.destroy_texture_view(texture_view.raw); + } + } + } + + for element in self.textures.data.write().map.drain(..) { + if let Element::Occupied(texture, _) = element { + let device = &devices[texture.device_id.value]; + if let TextureInner::Native { raw: Some(raw) } = texture.inner { + unsafe { + device.raw.destroy_texture(raw); + } + } + if let TextureClearMode::RenderPass { clear_views, .. } = texture.clear_mode { + for view in clear_views { + unsafe { + device.raw.destroy_texture_view(view); + } + } + } + } + } + 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.bind_groups.data.write().map.drain(..) { + if let Element::Occupied(bind_group, _) = element { + let device = &devices[bind_group.device_id.value]; + unsafe { + device.raw.destroy_bind_group(bind_group.raw); + } + } + } + + 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_bind_group_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_render_pipeline(pipeline.raw); + } + } + } + + for element in surface_guard.map.iter_mut() { + if let Element::Occupied(ref mut surface, _epoch) = *element { + if surface + .presentation + .as_ref() + .map_or(wgt::Backend::Empty, |p| p.backend()) + != A::VARIANT + { + continue; + } + if let Some(present) = surface.presentation.take() { + let device = &devices[present.device_id.value]; + let suf = A::get_surface_mut(surface); + unsafe { + suf.unwrap().raw.unconfigure(&device.raw); + //TODO: we could destroy the surface here + } + } + } + } + + for element in self.query_sets.data.write().map.drain(..) { + if let Element::Occupied(query_set, _) = element { + let device = &devices[query_set.device_id.value]; + unsafe { + device.raw.destroy_query_set(query_set.raw); + } + } + } + + for element in devices.map.drain(..) { + if let Element::Occupied(device, _) = element { + device.dispose(); + } + } + + if with_adapters { + drop(devices); + self.adapters.data.write().map.clear(); + } + } + + pub(crate) fn surface_unconfigure( + &self, + device_id: id::Valid<id::DeviceId>, + surface: &mut HalSurface<A>, + ) { + use hal::Surface as _; + + let devices = self.devices.data.read(); + let device = &devices[device_id]; + unsafe { + surface.raw.unconfigure(&device.raw); + } + } + + pub fn generate_report(&self) -> HubReport { + HubReport { + adapters: self.adapters.data.read().generate_report(), + devices: self.devices.data.read().generate_report(), + pipeline_layouts: self.pipeline_layouts.data.read().generate_report(), + shader_modules: self.shader_modules.data.read().generate_report(), + bind_group_layouts: self.bind_group_layouts.data.read().generate_report(), + bind_groups: self.bind_groups.data.read().generate_report(), + command_buffers: self.command_buffers.data.read().generate_report(), + render_bundles: self.render_bundles.data.read().generate_report(), + render_pipelines: self.render_pipelines.data.read().generate_report(), + compute_pipelines: self.compute_pipelines.data.read().generate_report(), + query_sets: self.query_sets.data.read().generate_report(), + buffers: self.buffers.data.read().generate_report(), + textures: self.textures.data.read().generate_report(), + texture_views: self.texture_views.data.read().generate_report(), + samplers: self.samplers.data.read().generate_report(), + } + } +} + +pub struct Hubs<F: GlobalIdentityHandlerFactory> { + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: Hub<hal::api::Vulkan, F>, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + metal: Hub<hal::api::Metal, F>, + #[cfg(all(feature = "dx12", windows))] + dx12: Hub<hal::api::Dx12, F>, + #[cfg(all(feature = "dx11", windows))] + dx11: Hub<hal::api::Dx11, F>, + #[cfg(feature = "gles")] + gl: Hub<hal::api::Gles, F>, +} + +impl<F: GlobalIdentityHandlerFactory> Hubs<F> { + fn new(factory: &F) -> Self { + Self { + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: Hub::new(factory), + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + metal: Hub::new(factory), + #[cfg(all(feature = "dx12", windows))] + dx12: Hub::new(factory), + #[cfg(all(feature = "dx11", windows))] + dx11: Hub::new(factory), + #[cfg(feature = "gles")] + gl: Hub::new(factory), + } + } +} + +#[derive(Debug)] +pub struct GlobalReport { + pub surfaces: StorageReport, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + pub vulkan: Option<HubReport>, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + pub metal: Option<HubReport>, + #[cfg(all(feature = "dx12", windows))] + pub dx12: Option<HubReport>, + #[cfg(all(feature = "dx11", windows))] + pub dx11: Option<HubReport>, + #[cfg(feature = "gles")] + pub gl: Option<HubReport>, +} + +pub struct Global<G: GlobalIdentityHandlerFactory> { + pub instance: Instance, + pub surfaces: Registry<Surface, id::SurfaceId, G>, + hubs: Hubs<G>, +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn new(name: &str, factory: G, instance_desc: wgt::InstanceDescriptor) -> Self { + profiling::scope!("Global::new"); + Self { + instance: Instance::new(name, instance_desc), + surfaces: Registry::without_backend(&factory, "Surface"), + hubs: Hubs::new(&factory), + } + } + + /// # Safety + /// + /// Refer to the creation of wgpu-hal Instance for every backend. + pub unsafe fn from_hal_instance<A: HalApi>( + name: &str, + factory: G, + hal_instance: A::Instance, + ) -> Self { + profiling::scope!("Global::new"); + Self { + instance: A::create_instance_from_hal(name, hal_instance), + surfaces: Registry::without_backend(&factory, "Surface"), + hubs: Hubs::new(&factory), + } + } + + /// # Safety + /// + /// - The raw instance handle returned must not be manually destroyed. + pub unsafe fn instance_as_hal<A: HalApi>(&self) -> Option<&A::Instance> { + A::instance_as_hal(&self.instance) + } + + /// # Safety + /// + /// - The raw handles obtained from the Instance must not be manually destroyed + pub unsafe fn from_instance(factory: G, instance: Instance) -> Self { + profiling::scope!("Global::new"); + Self { + instance, + surfaces: Registry::without_backend(&factory, "Surface"), + hubs: Hubs::new(&factory), + } + } + + pub fn clear_backend<A: HalApi>(&self, _dummy: ()) { + let mut surface_guard = self.surfaces.data.write(); + let hub = A::hub(self); + // this is used for tests, which keep the adapter + hub.clear(&mut surface_guard, false); + } + + pub fn generate_report(&self) -> GlobalReport { + GlobalReport { + surfaces: self.surfaces.data.read().generate_report(), + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: if self.instance.vulkan.is_some() { + Some(self.hubs.vulkan.generate_report()) + } else { + None + }, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + metal: if self.instance.metal.is_some() { + Some(self.hubs.metal.generate_report()) + } else { + None + }, + #[cfg(all(feature = "dx12", windows))] + dx12: if self.instance.dx12.is_some() { + Some(self.hubs.dx12.generate_report()) + } else { + None + }, + #[cfg(all(feature = "dx11", windows))] + dx11: if self.instance.dx11.is_some() { + Some(self.hubs.dx11.generate_report()) + } else { + None + }, + #[cfg(feature = "gles")] + gl: if self.instance.gl.is_some() { + Some(self.hubs.gl.generate_report()) + } else { + None + }, + } + } +} + +impl<G: GlobalIdentityHandlerFactory> Drop for Global<G> { + fn drop(&mut self) { + profiling::scope!("Global::drop"); + log::info!("Dropping Global"); + let mut surface_guard = self.surfaces.data.write(); + + // destroy hubs before the instance gets dropped + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + { + self.hubs.vulkan.clear(&mut surface_guard, true); + } + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + { + self.hubs.metal.clear(&mut surface_guard, true); + } + #[cfg(all(feature = "dx12", windows))] + { + self.hubs.dx12.clear(&mut surface_guard, true); + } + #[cfg(all(feature = "dx11", windows))] + { + self.hubs.dx11.clear(&mut surface_guard, true); + } + #[cfg(feature = "gles")] + { + self.hubs.gl.clear(&mut surface_guard, true); + } + + // destroy surfaces + for element in surface_guard.map.drain(..) { + if let Element::Occupied(surface, _) = element { + self.instance.destroy_surface(surface); + } + } + } +} + +pub trait HalApi: hal::Api { + const VARIANT: Backend; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance; + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance>; + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G>; + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>>; + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>>; +} + +impl HalApi for hal::api::Empty { + const VARIANT: Backend = Backend::Empty; + fn create_instance_from_hal(_: &str, _: Self::Instance) -> Instance { + unimplemented!("called empty api") + } + fn instance_as_hal(_: &Instance) -> Option<&Self::Instance> { + unimplemented!("called empty api") + } + fn hub<G: GlobalIdentityHandlerFactory>(_: &Global<G>) -> &Hub<Self, G> { + unimplemented!("called empty api") + } + fn get_surface(_: &Surface) -> Option<&HalSurface<Self>> { + unimplemented!("called empty api") + } + fn get_surface_mut(_: &mut Surface) -> Option<&mut HalSurface<Self>> { + unimplemented!("called empty api") + } +} + +#[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] +impl HalApi for hal::api::Vulkan { + const VARIANT: Backend = Backend::Vulkan; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { + Instance { + name: name.to_owned(), + vulkan: Some(hal_instance), + ..Default::default() + } + } + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { + instance.vulkan.as_ref() + } + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> { + &global.hubs.vulkan + } + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>> { + surface.vulkan.as_ref() + } + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>> { + surface.vulkan.as_mut() + } +} + +#[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] +impl HalApi for hal::api::Metal { + const VARIANT: Backend = Backend::Metal; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { + Instance { + name: name.to_owned(), + metal: Some(hal_instance), + ..Default::default() + } + } + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { + instance.metal.as_ref() + } + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> { + &global.hubs.metal + } + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>> { + surface.metal.as_ref() + } + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>> { + surface.metal.as_mut() + } +} + +#[cfg(all(feature = "dx12", windows))] +impl HalApi for hal::api::Dx12 { + const VARIANT: Backend = Backend::Dx12; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { + Instance { + name: name.to_owned(), + dx12: Some(hal_instance), + ..Default::default() + } + } + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { + instance.dx12.as_ref() + } + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> { + &global.hubs.dx12 + } + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>> { + surface.dx12.as_ref() + } + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>> { + surface.dx12.as_mut() + } +} + +#[cfg(all(feature = "dx11", windows))] +impl HalApi for hal::api::Dx11 { + const VARIANT: Backend = Backend::Dx11; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { + Instance { + name: name.to_owned(), + dx11: Some(hal_instance), + ..Default::default() + } + } + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { + instance.dx11.as_ref() + } + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> { + &global.hubs.dx11 + } + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>> { + surface.dx11.as_ref() + } + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>> { + surface.dx11.as_mut() + } +} + +#[cfg(feature = "gles")] +impl HalApi for hal::api::Gles { + const VARIANT: Backend = Backend::Gl; + fn create_instance_from_hal(name: &str, hal_instance: Self::Instance) -> Instance { + #[allow(clippy::needless_update)] + Instance { + name: name.to_owned(), + gl: Some(hal_instance), + ..Default::default() + } + } + fn instance_as_hal(instance: &Instance) -> Option<&Self::Instance> { + instance.gl.as_ref() + } + fn hub<G: GlobalIdentityHandlerFactory>(global: &Global<G>) -> &Hub<Self, G> { + &global.hubs.gl + } + fn get_surface(surface: &Surface) -> Option<&HalSurface<Self>> { + surface.gl.as_ref() + } + fn get_surface_mut(surface: &mut Surface) -> Option<&mut HalSurface<Self>> { + surface.gl.as_mut() + } +} + +#[cfg(test)] +fn _test_send_sync(global: &Global<IdentityManagerFactory>) { + fn test_internal<T: Send + Sync>(_: T) {} + test_internal(global) +} + +#[test] +fn test_epoch_end_of_life() { + use id::TypedId as _; + let mut man = IdentityManager::default(); + man.epochs.push(id::EPOCH_MASK); + man.free.push(0); + let id1 = man.alloc::<id::BufferId>(Backend::Empty); + assert_eq!(id1.unzip().0, 0); + man.free(id1); + let id2 = man.alloc::<id::BufferId>(Backend::Empty); + // confirm that the index 0 is no longer re-used + assert_eq!(id2.unzip().0, 1); +} diff --git a/third_party/rust/wgpu-core/src/id.rs b/third_party/rust/wgpu-core/src/id.rs new file mode 100644 index 0000000000..9c654e3ca9 --- /dev/null +++ b/third_party/rust/wgpu-core/src/id.rs @@ -0,0 +1,273 @@ +use crate::{Epoch, Index}; +use std::{cmp::Ordering, fmt, marker::PhantomData}; +use wgt::Backend; + +#[cfg(feature = "id32")] +type IdType = u32; +#[cfg(not(feature = "id32"))] +type IdType = u64; +#[cfg(feature = "id32")] +type NonZeroId = std::num::NonZeroU32; +#[cfg(not(feature = "id32"))] +type NonZeroId = std::num::NonZeroU64; +#[cfg(feature = "id32")] +type ZippedIndex = u16; +#[cfg(not(feature = "id32"))] +type ZippedIndex = Index; + +const INDEX_BITS: usize = std::mem::size_of::<ZippedIndex>() * 8; +const EPOCH_BITS: usize = INDEX_BITS - BACKEND_BITS; +const BACKEND_BITS: usize = 3; +const BACKEND_SHIFT: usize = INDEX_BITS * 2 - BACKEND_BITS; +pub const EPOCH_MASK: u32 = (1 << (EPOCH_BITS)) - 1; +type Dummy = hal::api::Empty; + +/// An identifier for a wgpu object. +/// +/// An `Id<T>` value identifies a value stored in a [`Global`]'s [`Hub`]'s [`Storage`]. +/// `Storage` implements [`Index`] and [`IndexMut`], accepting `Id` values as indices. +/// +/// ## Note on `Id` typing +/// +/// You might assume that an `Id<T>` can only be used to retrieve a resource of +/// type `T`, but that is not quite the case. The id types in `wgpu-core`'s +/// public API ([`TextureId`], for example) can refer to resources belonging to +/// any backend, but the corresponding resource types ([`Texture<A>`], for +/// example) are always parameterized by a specific backend `A`. +/// +/// So the `T` in `Id<T>` is usually a resource type like `Texture<Empty>`, +/// where [`Empty`] is the `wgpu_hal` dummy back end. These empty types are +/// never actually used, beyond just making sure you access each `Storage` with +/// the right kind of identifier. The members of [`Hub<A>`] pair up each +/// `X<Empty>` type with the resource type `X<A>`, for some specific backend +/// `A`. +/// +/// [`Global`]: crate::hub::Global +/// [`Hub`]: crate::hub::Hub +/// [`Hub<A>`]: crate::hub::Hub +/// [`Storage`]: crate::hub::Storage +/// [`Texture<A>`]: crate::resource::Texture +/// [`Index`]: std::ops::Index +/// [`IndexMut`]: std::ops::IndexMut +/// [`Registry`]: crate::hub::Registry +/// [`Empty`]: hal::api::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>(NonZeroId, 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> { + /// # Safety + /// + /// The raw id must be valid for the type. + pub unsafe fn from_raw(raw: NonZeroId) -> Self { + Self(raw, PhantomData) + } + + #[allow(dead_code)] + pub(crate) fn dummy(index: u32) -> Valid<Self> { + Valid(Id::zip(index, 1, Backend::Empty)) + } + + pub fn backend(self) -> Backend { + match self.0.get() >> (BACKEND_SHIFT) as u8 { + 0 => Backend::Empty, + 1 => Backend::Vulkan, + 2 => Backend::Metal, + 3 => Backend::Dx12, + 4 => Backend::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); + +/// Trait carrying methods for direct `Id` access. +/// +/// Most `wgpu-core` clients should not use this trait. Unusual clients that +/// need to construct `Id` values directly, or access their components, like the +/// WGPU recording player, may use this trait to do so. +pub trait TypedId: Copy { + fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self; + fn unzip(self) -> (Index, Epoch, Backend); + fn into_raw(self) -> NonZeroId; +} + +#[allow(trivial_numeric_casts)] +impl<T> TypedId for Id<T> { + fn zip(index: Index, epoch: Epoch, backend: Backend) -> Self { + assert_eq!(0, epoch >> EPOCH_BITS); + assert_eq!(0, (index as IdType) >> INDEX_BITS); + let v = index as IdType + | ((epoch as IdType) << INDEX_BITS) + | ((backend as IdType) << BACKEND_SHIFT); + Id(NonZeroId::new(v).unwrap(), PhantomData) + } + + fn unzip(self) -> (Index, Epoch, Backend) { + ( + (self.0.get() as ZippedIndex) as Index, + (((self.0.get() >> INDEX_BITS) as ZippedIndex) & (EPOCH_MASK as ZippedIndex)) as Index, + self.backend(), + ) + } + + fn into_raw(self) -> NonZeroId { + self.0 + } +} + +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 StagingBufferId = Id<crate::resource::StagingBuffer<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<Dummy>>; +pub type QuerySetId = Id<crate::resource::QuerySet<Dummy>>; + +#[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); + let (_id, _epoch, backend) = id.unzip(); + assert_eq!(id.backend(), b); + assert_eq!(backend, b); + } +} + +#[test] +fn test_id() { + let last_index = ((1u64 << INDEX_BITS) - 1) as Index; + let indexes = [1, last_index / 2 - 1, last_index / 2 + 1, last_index]; + let epochs = [1, EPOCH_MASK / 2 - 1, EPOCH_MASK / 2 + 1, EPOCH_MASK]; + let backends = [ + Backend::Empty, + Backend::Vulkan, + Backend::Metal, + Backend::Dx12, + Backend::Dx11, + Backend::Gl, + ]; + for &i in &indexes { + for &e in &epochs { + for &b in &backends { + let id: Id<()> = Id::zip(i, e, b); + let (index, epoch, backend) = id.unzip(); + assert_eq!(index, i); + assert_eq!(epoch, e); + assert_eq!(backend, b); + } + } + } +} diff --git a/third_party/rust/wgpu-core/src/init_tracker/buffer.rs b/third_party/rust/wgpu-core/src/init_tracker/buffer.rs new file mode 100644 index 0000000000..ea9b9f6a8d --- /dev/null +++ b/third_party/rust/wgpu-core/src/init_tracker/buffer.rs @@ -0,0 +1,35 @@ +use super::{InitTracker, MemoryInitKind}; +use crate::id::BufferId; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub(crate) struct BufferInitTrackerAction { + pub id: BufferId, + pub range: Range<wgt::BufferAddress>, + pub kind: MemoryInitKind, +} + +pub(crate) type BufferInitTracker = InitTracker<wgt::BufferAddress>; + +impl BufferInitTracker { + /// Checks if an action has/requires any effect on the initialization status + /// and shrinks its range if possible. + pub(crate) fn check_action( + &self, + action: &BufferInitTrackerAction, + ) -> Option<BufferInitTrackerAction> { + self.create_action(action.id, action.range.clone(), action.kind) + } + + /// Creates an action if it would have any effect on the initialization + /// status and shrinks the range if possible. + pub(crate) fn create_action( + &self, + id: BufferId, + query_range: Range<wgt::BufferAddress>, + kind: MemoryInitKind, + ) -> Option<BufferInitTrackerAction> { + self.check(query_range) + .map(|range| BufferInitTrackerAction { id, range, kind }) + } +} diff --git a/third_party/rust/wgpu-core/src/init_tracker/mod.rs b/third_party/rust/wgpu-core/src/init_tracker/mod.rs new file mode 100644 index 0000000000..c7e78bc993 --- /dev/null +++ b/third_party/rust/wgpu-core/src/init_tracker/mod.rs @@ -0,0 +1,380 @@ +/*! Lazy initialization of texture and buffer memory. + +The WebGPU specification requires all texture & buffer memory to be +zero initialized on first read. To avoid unnecessary inits, we track +the initialization status of every resource and perform inits lazily. + +The granularity is different for buffers and textures: + +- Buffer: Byte granularity to support usecases with large, partially + bound buffers well. + +- Texture: Mip-level per layer. That is, a 2D surface is either + completely initialized or not, subrects are not tracked. + +Every use of a buffer/texture generates a InitTrackerAction which are +recorded and later resolved at queue submit by merging them with the +current state and each other in execution order. + +It is important to note that from the point of view of the memory init +system there are two kind of writes: + +- **Full writes**: Any kind of memcpy operation. These cause a + `MemoryInitKind.ImplicitlyInitialized` action. + +- **(Potentially) partial writes**: For example, write use in a + Shader. The system is not able to determine if a resource is fully + initialized afterwards but is no longer allowed to perform any + clears, therefore this leads to a + `MemoryInitKind.ImplicitlyInitialized` action, exactly like a read + would. + + */ + +use smallvec::SmallVec; +use std::{fmt, iter, ops::Range}; + +mod buffer; +mod texture; + +pub(crate) use buffer::{BufferInitTracker, BufferInitTrackerAction}; +pub(crate) use texture::{ + has_copy_partial_init_tracker_coverage, TextureInitRange, TextureInitTracker, + TextureInitTrackerAction, +}; + +#[derive(Debug, Clone, Copy)] +pub(crate) enum MemoryInitKind { + // The memory range is going to be written by an already initialized source, + // thus doesn't need extra attention other than marking as initialized. + ImplicitlyInitialized, + // The memory range is going to be read, therefore needs to ensure prior + // initialization. + NeedsInitializedMemory, +} + +// Most of the time a resource is either fully uninitialized (one element) or +// initialized (zero elements). +type UninitializedRangeVec<Idx> = SmallVec<[Range<Idx>; 1]>; + +/// Tracks initialization status of a linear range from 0..size +#[derive(Debug, Clone)] +pub(crate) struct InitTracker<Idx: Ord + Copy + Default> { + // Ordered, non overlapping list of all uninitialized ranges. + uninitialized_ranges: UninitializedRangeVec<Idx>, +} + +pub(crate) struct InitTrackerDrain<'a, Idx: fmt::Debug + Ord + Copy> { + uninitialized_ranges: &'a mut UninitializedRangeVec<Idx>, + drain_range: Range<Idx>, + first_index: usize, + next_index: usize, +} + +impl<'a, Idx> Iterator for InitTrackerDrain<'a, Idx> +where + Idx: fmt::Debug + Ord + Copy, +{ + type Item = Range<Idx>; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(r) = self + .uninitialized_ranges + .get(self.next_index) + .and_then(|range| { + if range.start < self.drain_range.end { + Some(range.clone()) + } else { + None + } + }) + { + self.next_index += 1; + Some(r.start.max(self.drain_range.start)..r.end.min(self.drain_range.end)) + } else { + let num_affected = self.next_index - self.first_index; + if num_affected == 0 { + return None; + } + let first_range = &mut self.uninitialized_ranges[self.first_index]; + + // Split one "big" uninitialized range? + if num_affected == 1 + && first_range.start < self.drain_range.start + && first_range.end > self.drain_range.end + { + let old_start = first_range.start; + first_range.start = self.drain_range.end; + self.uninitialized_ranges + .insert(self.first_index, old_start..self.drain_range.start); + } + // Adjust border ranges and delete everything in-between. + else { + let remove_start = if first_range.start >= self.drain_range.start { + self.first_index + } else { + first_range.end = self.drain_range.start; + self.first_index + 1 + }; + + let last_range = &mut self.uninitialized_ranges[self.next_index - 1]; + let remove_end = if last_range.end <= self.drain_range.end { + self.next_index + } else { + last_range.start = self.drain_range.end; + self.next_index - 1 + }; + + self.uninitialized_ranges.drain(remove_start..remove_end); + } + + None + } + } +} + +impl<'a, Idx> Drop for InitTrackerDrain<'a, Idx> +where + Idx: fmt::Debug + Ord + Copy, +{ + fn drop(&mut self) { + if self.next_index <= self.first_index { + for _ in self {} + } + } +} + +impl<Idx> InitTracker<Idx> +where + Idx: fmt::Debug + Ord + Copy + Default, +{ + pub(crate) fn new(size: Idx) -> Self { + Self { + uninitialized_ranges: iter::once(Idx::default()..size).collect(), + } + } + + // Checks if there's any uninitialized ranges within a query. + // + // If there are any, the range returned a the subrange of the query_range + // that contains all these uninitialized regions. Returned range may be + // larger than necessary (tradeoff for making this function O(log n)) + pub(crate) fn check(&self, query_range: Range<Idx>) -> Option<Range<Idx>> { + let index = self + .uninitialized_ranges + .partition_point(|r| r.end <= query_range.start); + self.uninitialized_ranges + .get(index) + .and_then(|start_range| { + if start_range.start < query_range.end { + let start = start_range.start.max(query_range.start); + match self.uninitialized_ranges.get(index + 1) { + Some(next_range) => { + if next_range.start < query_range.end { + // Would need to keep iterating for more + // accurate upper bound. Don't do that here. + Some(start..query_range.end) + } else { + Some(start..start_range.end.min(query_range.end)) + } + } + None => Some(start..start_range.end.min(query_range.end)), + } + } else { + None + } + }) + } + + // Drains uninitialized ranges in a query range. + pub(crate) fn drain(&mut self, drain_range: Range<Idx>) -> InitTrackerDrain<Idx> { + let index = self + .uninitialized_ranges + .partition_point(|r| r.end <= drain_range.start); + InitTrackerDrain { + drain_range, + uninitialized_ranges: &mut self.uninitialized_ranges, + first_index: index, + next_index: index, + } + } +} + +impl InitTracker<u32> { + // Makes a single entry uninitialized if not already uninitialized + #[allow(dead_code)] + pub(crate) fn discard(&mut self, pos: u32) { + // first range where end>=idx + let r_idx = self.uninitialized_ranges.partition_point(|r| r.end < pos); + if let Some(r) = self.uninitialized_ranges.get(r_idx) { + // Extend range at end + if r.end == pos { + // merge with next? + if let Some(right) = self.uninitialized_ranges.get(r_idx + 1) { + if right.start == pos + 1 { + self.uninitialized_ranges[r_idx] = r.start..right.end; + self.uninitialized_ranges.remove(r_idx + 1); + return; + } + } + self.uninitialized_ranges[r_idx] = r.start..(pos + 1); + } else if r.start > pos { + // may still extend range at beginning + if r.start == pos + 1 { + self.uninitialized_ranges[r_idx] = pos..r.end; + } else { + // previous range end must be smaller than idx, therefore no merge possible + self.uninitialized_ranges.push(pos..(pos + 1)); + } + } + } else { + self.uninitialized_ranges.push(pos..(pos + 1)); + } + } +} + +#[cfg(test)] +mod test { + use std::ops::Range; + + type Tracker = super::InitTracker<u32>; + + #[test] + fn check_for_newly_created_tracker() { + let tracker = Tracker::new(10); + assert_eq!(tracker.check(0..10), Some(0..10)); + assert_eq!(tracker.check(0..3), Some(0..3)); + assert_eq!(tracker.check(3..4), Some(3..4)); + assert_eq!(tracker.check(4..10), Some(4..10)); + } + + #[test] + fn check_for_drained_tracker() { + let mut tracker = Tracker::new(10); + tracker.drain(0..10); + assert_eq!(tracker.check(0..10), None); + assert_eq!(tracker.check(0..3), None); + assert_eq!(tracker.check(3..4), None); + assert_eq!(tracker.check(4..10), None); + } + + #[test] + fn check_for_partially_filled_tracker() { + let mut tracker = Tracker::new(25); + // Two regions of uninitialized memory + tracker.drain(0..5); + tracker.drain(10..15); + tracker.drain(20..25); + + assert_eq!(tracker.check(0..25), Some(5..25)); // entire range + + assert_eq!(tracker.check(0..5), None); // left non-overlapping + assert_eq!(tracker.check(3..8), Some(5..8)); // left overlapping region + assert_eq!(tracker.check(3..17), Some(5..17)); // left overlapping region + contained region + + // right overlapping region + contained region (yes, doesn't fix range end!) + assert_eq!(tracker.check(8..22), Some(8..22)); + // right overlapping region + assert_eq!(tracker.check(17..22), Some(17..20)); + // right non-overlapping + assert_eq!(tracker.check(20..25), None); + } + + #[test] + fn drain_already_drained() { + let mut tracker = Tracker::new(30); + tracker.drain(10..20); + + // Overlapping with non-cleared + tracker.drain(5..15); // Left overlap + tracker.drain(15..25); // Right overlap + tracker.drain(0..30); // Inner overlap + + // Clear fully cleared + tracker.drain(0..30); + + assert_eq!(tracker.check(0..30), None); + } + + #[test] + fn drain_never_returns_ranges_twice_for_same_range() { + let mut tracker = Tracker::new(19); + assert_eq!(tracker.drain(0..19).count(), 1); + assert_eq!(tracker.drain(0..19).count(), 0); + + let mut tracker = Tracker::new(17); + assert_eq!(tracker.drain(5..8).count(), 1); + assert_eq!(tracker.drain(5..8).count(), 0); + assert_eq!(tracker.drain(1..3).count(), 1); + assert_eq!(tracker.drain(1..3).count(), 0); + assert_eq!(tracker.drain(7..13).count(), 1); + assert_eq!(tracker.drain(7..13).count(), 0); + } + + #[test] + fn drain_splits_ranges_correctly() { + let mut tracker = Tracker::new(1337); + assert_eq!( + tracker.drain(21..42).collect::<Vec<Range<u32>>>(), + vec![21..42] + ); + assert_eq!( + tracker.drain(900..1000).collect::<Vec<Range<u32>>>(), + vec![900..1000] + ); + + // Splitted ranges. + assert_eq!( + tracker.drain(5..1003).collect::<Vec<Range<u32>>>(), + vec![5..21, 42..900, 1000..1003] + ); + assert_eq!( + tracker.drain(0..1337).collect::<Vec<Range<u32>>>(), + vec![0..5, 1003..1337] + ); + } + + #[test] + fn discard_adds_range_on_cleared() { + let mut tracker = Tracker::new(10); + tracker.drain(0..10); + tracker.discard(0); + tracker.discard(5); + tracker.discard(9); + assert_eq!(tracker.check(0..1), Some(0..1)); + assert_eq!(tracker.check(1..5), None); + assert_eq!(tracker.check(5..6), Some(5..6)); + assert_eq!(tracker.check(6..9), None); + assert_eq!(tracker.check(9..10), Some(9..10)); + } + + #[test] + fn discard_does_nothing_on_uncleared() { + let mut tracker = Tracker::new(10); + tracker.discard(0); + tracker.discard(5); + tracker.discard(9); + assert_eq!(tracker.uninitialized_ranges.len(), 1); + assert_eq!(tracker.uninitialized_ranges[0], 0..10); + } + + #[test] + fn discard_extends_ranges() { + let mut tracker = Tracker::new(10); + tracker.drain(3..7); + tracker.discard(2); + tracker.discard(7); + assert_eq!(tracker.uninitialized_ranges.len(), 2); + assert_eq!(tracker.uninitialized_ranges[0], 0..3); + assert_eq!(tracker.uninitialized_ranges[1], 7..10); + } + + #[test] + fn discard_merges_ranges() { + let mut tracker = Tracker::new(10); + tracker.drain(3..4); + tracker.discard(3); + assert_eq!(tracker.uninitialized_ranges.len(), 1); + assert_eq!(tracker.uninitialized_ranges[0], 0..10); + } +} diff --git a/third_party/rust/wgpu-core/src/init_tracker/texture.rs b/third_party/rust/wgpu-core/src/init_tracker/texture.rs new file mode 100644 index 0000000000..17368e1014 --- /dev/null +++ b/third_party/rust/wgpu-core/src/init_tracker/texture.rs @@ -0,0 +1,103 @@ +use super::{InitTracker, MemoryInitKind}; +use crate::{id::TextureId, track::TextureSelector}; +use arrayvec::ArrayVec; +use std::ops::Range; + +#[derive(Debug, Clone)] +pub(crate) struct TextureInitRange { + pub(crate) mip_range: Range<u32>, + // Strictly array layers. We do *not* track volume slices separately. + pub(crate) layer_range: Range<u32>, +} + +// Returns true if a copy operation doesn't fully cover the texture init +// tracking granularity. I.e. if this function returns true for a pending copy +// operation, the target texture needs to be ensured to be initialized first! +pub(crate) fn has_copy_partial_init_tracker_coverage( + copy_size: &wgt::Extent3d, + mip_level: u32, + desc: &wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>, +) -> bool { + let target_size = desc.mip_level_size(mip_level).unwrap(); + copy_size.width != target_size.width + || copy_size.height != target_size.height + || (desc.dimension == wgt::TextureDimension::D3 + && copy_size.depth_or_array_layers != target_size.depth_or_array_layers) +} + +impl From<TextureSelector> for TextureInitRange { + fn from(selector: TextureSelector) -> Self { + TextureInitRange { + mip_range: selector.mips, + layer_range: selector.layers, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct TextureInitTrackerAction { + pub(crate) id: TextureId, + pub(crate) range: TextureInitRange, + pub(crate) kind: MemoryInitKind, +} + +pub(crate) type TextureLayerInitTracker = InitTracker<u32>; + +#[derive(Debug)] +pub(crate) struct TextureInitTracker { + pub mips: ArrayVec<TextureLayerInitTracker, { hal::MAX_MIP_LEVELS as usize }>, +} + +impl TextureInitTracker { + pub(crate) fn new(mip_level_count: u32, depth_or_array_layers: u32) -> Self { + TextureInitTracker { + mips: std::iter::repeat(TextureLayerInitTracker::new(depth_or_array_layers)) + .take(mip_level_count as usize) + .collect(), + } + } + + pub(crate) fn check_action( + &self, + action: &TextureInitTrackerAction, + ) -> Option<TextureInitTrackerAction> { + let mut mip_range_start = std::usize::MAX; + let mut mip_range_end = std::usize::MIN; + let mut layer_range_start = std::u32::MAX; + let mut layer_range_end = std::u32::MIN; + + for (i, mip_tracker) in self + .mips + .iter() + .enumerate() + .take(action.range.mip_range.end as usize) + .skip(action.range.mip_range.start as usize) + { + if let Some(uninitialized_layer_range) = + mip_tracker.check(action.range.layer_range.clone()) + { + mip_range_start = mip_range_start.min(i); + mip_range_end = i + 1; + layer_range_start = layer_range_start.min(uninitialized_layer_range.start); + layer_range_end = layer_range_end.max(uninitialized_layer_range.end); + }; + } + + if mip_range_start < mip_range_end && layer_range_start < layer_range_end { + Some(TextureInitTrackerAction { + id: action.id, + range: TextureInitRange { + mip_range: mip_range_start as u32..mip_range_end as u32, + layer_range: layer_range_start..layer_range_end, + }, + kind: action.kind, + }) + } else { + None + } + } + + pub(crate) fn discard(&mut self, mip_level: u32, layer: u32) { + self.mips[mip_level as usize].discard(layer); + } +} diff --git a/third_party/rust/wgpu-core/src/instance.rs b/third_party/rust/wgpu-core/src/instance.rs new file mode 100644 index 0000000000..c5989f4bf4 --- /dev/null +++ b/third_party/rust/wgpu-core/src/instance.rs @@ -0,0 +1,1146 @@ +use crate::{ + device::{Device, DeviceDescriptor}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Input, Token}, + id::{AdapterId, DeviceId, SurfaceId, Valid}, + present::Presentation, + LabelHelpers, LifeGuard, Stored, DOWNLEVEL_WARNING_MESSAGE, +}; + +use wgt::{Backend, Backends, PowerPreference}; + +use hal::{Adapter as _, Instance as _}; +use thiserror::Error; + +pub type RequestAdapterOptions = wgt::RequestAdapterOptions<SurfaceId>; +type HalInstance<A> = <A as hal::Api>::Instance; +//TODO: remove this +pub struct HalSurface<A: hal::Api> { + pub raw: A::Surface, + //pub acquired_texture: Option<A::SurfaceTexture>, +} + +#[derive(Clone, Debug, Error)] +#[error("Limit '{name}' value {requested} is better than allowed {allowed}")] +pub struct FailedLimit { + name: &'static str, + requested: u64, + allowed: u64, +} + +fn check_limits(requested: &wgt::Limits, allowed: &wgt::Limits) -> Vec<FailedLimit> { + let mut failed = Vec::new(); + + requested.check_limits_with_fail_fn(allowed, false, |name, requested, allowed| { + failed.push(FailedLimit { + name, + requested, + allowed, + }) + }); + + failed +} + +#[test] +fn downlevel_default_limits_less_than_default_limits() { + let res = check_limits(&wgt::Limits::downlevel_defaults(), &wgt::Limits::default()); + assert!( + res.is_empty(), + "Downlevel limits are greater than default limits", + ) +} + +#[derive(Default)] +pub struct Instance { + #[allow(dead_code)] + pub name: String, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + pub vulkan: Option<HalInstance<hal::api::Vulkan>>, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + pub metal: Option<HalInstance<hal::api::Metal>>, + #[cfg(all(feature = "dx12", windows))] + pub dx12: Option<HalInstance<hal::api::Dx12>>, + #[cfg(all(feature = "dx11", windows))] + pub dx11: Option<HalInstance<hal::api::Dx11>>, + #[cfg(feature = "gles")] + pub gl: Option<HalInstance<hal::api::Gles>>, +} + +impl Instance { + pub fn new(name: &str, instance_desc: wgt::InstanceDescriptor) -> Self { + fn init<A: HalApi>(_: A, instance_desc: &wgt::InstanceDescriptor) -> Option<A::Instance> { + if instance_desc.backends.contains(A::VARIANT.into()) { + let mut flags = hal::InstanceFlags::empty(); + if cfg!(debug_assertions) { + flags |= hal::InstanceFlags::VALIDATION; + flags |= hal::InstanceFlags::DEBUG; + } + let hal_desc = hal::InstanceDescriptor { + name: "wgpu", + flags, + dx12_shader_compiler: instance_desc.dx12_shader_compiler.clone(), + }; + unsafe { hal::Instance::init(&hal_desc).ok() } + } else { + None + } + } + + Self { + name: name.to_string(), + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: init(hal::api::Vulkan, &instance_desc), + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + metal: init(hal::api::Metal, &instance_desc), + #[cfg(all(feature = "dx12", windows))] + dx12: init(hal::api::Dx12, &instance_desc), + #[cfg(all(feature = "dx11", windows))] + dx11: init(hal::api::Dx11, &instance_desc), + #[cfg(feature = "gles")] + gl: init(hal::api::Gles, &instance_desc), + } + } + + pub(crate) fn destroy_surface(&self, surface: Surface) { + fn destroy<A: HalApi>( + _: A, + instance: &Option<A::Instance>, + surface: Option<HalSurface<A>>, + ) { + unsafe { + if let Some(suf) = surface { + instance.as_ref().unwrap().destroy_surface(suf.raw); + } + } + } + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + destroy(hal::api::Vulkan, &self.vulkan, surface.vulkan); + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + destroy(hal::api::Metal, &self.metal, surface.metal); + #[cfg(all(feature = "dx12", windows))] + destroy(hal::api::Dx12, &self.dx12, surface.dx12); + #[cfg(all(feature = "dx11", windows))] + destroy(hal::api::Dx11, &self.dx11, surface.dx11); + #[cfg(feature = "gles")] + destroy(hal::api::Gles, &self.gl, surface.gl); + } +} + +pub struct Surface { + pub(crate) presentation: Option<Presentation>, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + pub vulkan: Option<HalSurface<hal::api::Vulkan>>, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + pub metal: Option<HalSurface<hal::api::Metal>>, + #[cfg(all(feature = "dx12", windows))] + pub dx12: Option<HalSurface<hal::api::Dx12>>, + #[cfg(all(feature = "dx11", windows))] + pub dx11: Option<HalSurface<hal::api::Dx11>>, + #[cfg(feature = "gles")] + pub gl: Option<HalSurface<hal::api::Gles>>, +} + +impl crate::hub::Resource for Surface { + const TYPE: &'static str = "Surface"; + + fn life_guard(&self) -> &LifeGuard { + unreachable!() + } + + fn label(&self) -> &str { + "<Surface>" + } +} + +impl Surface { + pub fn get_capabilities<A: HalApi>( + &self, + adapter: &Adapter<A>, + ) -> Result<hal::SurfaceCapabilities, GetSurfaceSupportError> { + let suf = A::get_surface(self).ok_or(GetSurfaceSupportError::Unsupported)?; + profiling::scope!("surface_capabilities"); + let caps = unsafe { + adapter + .raw + .adapter + .surface_capabilities(&suf.raw) + .ok_or(GetSurfaceSupportError::Unsupported)? + }; + + Ok(caps) + } +} + +pub struct Adapter<A: hal::Api> { + pub(crate) raw: hal::ExposedAdapter<A>, + life_guard: LifeGuard, +} + +impl<A: HalApi> Adapter<A> { + fn new(mut raw: hal::ExposedAdapter<A>) -> Self { + // WebGPU requires this offset alignment as lower bound on all adapters. + const MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND: u32 = 32; + + let limits = &mut raw.capabilities.limits; + + limits.min_uniform_buffer_offset_alignment = limits + .min_uniform_buffer_offset_alignment + .max(MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND); + limits.min_storage_buffer_offset_alignment = limits + .min_storage_buffer_offset_alignment + .max(MIN_BUFFER_OFFSET_ALIGNMENT_LOWER_BOUND); + + Self { + raw, + life_guard: LifeGuard::new("<Adapter>"), + } + } + + pub fn is_surface_supported(&self, surface: &Surface) -> bool { + let suf = A::get_surface(surface); + + // If get_surface returns None, then the API does not advertise support for the surface. + // + // This could occur if the user is running their app on Wayland but Vulkan does not support + // VK_KHR_wayland_surface. + match suf { + Some(suf) => unsafe { self.raw.adapter.surface_capabilities(&suf.raw) }.is_some(), + None => false, + } + } + + pub(crate) fn get_texture_format_features( + &self, + format: wgt::TextureFormat, + ) -> wgt::TextureFormatFeatures { + use hal::TextureFormatCapabilities as Tfc; + + let caps = unsafe { self.raw.adapter.texture_format_capabilities(format) }; + let mut allowed_usages = wgt::TextureUsages::empty(); + + allowed_usages.set(wgt::TextureUsages::COPY_SRC, caps.contains(Tfc::COPY_SRC)); + allowed_usages.set(wgt::TextureUsages::COPY_DST, caps.contains(Tfc::COPY_DST)); + allowed_usages.set( + wgt::TextureUsages::TEXTURE_BINDING, + caps.contains(Tfc::SAMPLED), + ); + allowed_usages.set( + wgt::TextureUsages::STORAGE_BINDING, + caps.contains(Tfc::STORAGE), + ); + allowed_usages.set( + wgt::TextureUsages::RENDER_ATTACHMENT, + caps.intersects(Tfc::COLOR_ATTACHMENT | Tfc::DEPTH_STENCIL_ATTACHMENT), + ); + + let mut flags = wgt::TextureFormatFeatureFlags::empty(); + flags.set( + wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE, + caps.contains(Tfc::STORAGE_READ_WRITE), + ); + + flags.set( + wgt::TextureFormatFeatureFlags::FILTERABLE, + caps.contains(Tfc::SAMPLED_LINEAR), + ); + + flags.set( + wgt::TextureFormatFeatureFlags::BLENDABLE, + caps.contains(Tfc::COLOR_ATTACHMENT_BLEND), + ); + + flags.set( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_X2, + caps.contains(Tfc::MULTISAMPLE_X2), + ); + flags.set( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4, + caps.contains(Tfc::MULTISAMPLE_X4), + ); + flags.set( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_X8, + caps.contains(Tfc::MULTISAMPLE_X8), + ); + flags.set( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_X16, + caps.contains(Tfc::MULTISAMPLE_X16), + ); + + flags.set( + wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, + caps.contains(Tfc::MULTISAMPLE_RESOLVE), + ); + + wgt::TextureFormatFeatures { + allowed_usages, + flags, + } + } + + fn create_device_from_hal( + &self, + self_id: AdapterId, + open: hal::OpenDevice<A>, + desc: &DeviceDescriptor, + trace_path: Option<&std::path::Path>, + ) -> Result<Device<A>, RequestDeviceError> { + let caps = &self.raw.capabilities; + Device::new( + open, + Stored { + value: Valid(self_id), + ref_count: self.life_guard.add_ref(), + }, + caps.alignments.clone(), + caps.downlevel.clone(), + desc, + trace_path, + ) + .or(Err(RequestDeviceError::OutOfMemory)) + } + + fn create_device( + &self, + self_id: AdapterId, + desc: &DeviceDescriptor, + trace_path: Option<&std::path::Path>, + ) -> Result<Device<A>, RequestDeviceError> { + // Verify all features were exposed by the adapter + if !self.raw.features.contains(desc.features) { + return Err(RequestDeviceError::UnsupportedFeature( + desc.features - self.raw.features, + )); + } + + let caps = &self.raw.capabilities; + if wgt::Backends::PRIMARY.contains(wgt::Backends::from(A::VARIANT)) + && !caps.downlevel.is_webgpu_compliant() + { + let missing_flags = wgt::DownlevelFlags::compliant() - caps.downlevel.flags; + log::warn!( + "Missing downlevel flags: {:?}\n{}", + missing_flags, + DOWNLEVEL_WARNING_MESSAGE + ); + log::info!("{:#?}", caps.downlevel); + } + + // Verify feature preconditions + if desc + .features + .contains(wgt::Features::MAPPABLE_PRIMARY_BUFFERS) + && self.raw.info.device_type == wgt::DeviceType::DiscreteGpu + { + log::warn!( + "Feature MAPPABLE_PRIMARY_BUFFERS enabled on a discrete gpu. \ + This is a massive performance footgun and likely not what you wanted" + ); + } + + if let Some(_) = desc.label { + //TODO + } + + if let Some(failed) = check_limits(&desc.limits, &caps.limits).pop() { + return Err(RequestDeviceError::LimitsExceeded(failed)); + } + + let open = unsafe { self.raw.adapter.open(desc.features, &desc.limits) }.map_err( + |err| match err { + hal::DeviceError::Lost => RequestDeviceError::DeviceLost, + hal::DeviceError::OutOfMemory => RequestDeviceError::OutOfMemory, + }, + )?; + + self.create_device_from_hal(self_id, open, desc, trace_path) + } +} + +impl<A: hal::Api> crate::hub::Resource for Adapter<A> { + const TYPE: &'static str = "Adapter"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum IsSurfaceSupportedError { + #[error("Invalid adapter")] + InvalidAdapter, + #[error("Invalid surface")] + InvalidSurface, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum GetSurfaceSupportError { + #[error("Invalid adapter")] + InvalidAdapter, + #[error("Invalid surface")] + InvalidSurface, + #[error("Surface is not supported by the adapter")] + Unsupported, +} + +#[derive(Clone, Debug, Error)] +/// Error when requesting a device from the adaptor +#[non_exhaustive] +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(transparent)] + LimitsExceeded(#[from] FailedLimit), + #[error("Device has no queue supporting graphics")] + NoGraphicsQueue, + #[error("Not enough memory left")] + OutOfMemory, + #[error("Unsupported features were requested: {0:?}")] + UnsupportedFeature(wgt::Features), +} + +pub enum AdapterInputs<'a, I> { + IdSet(&'a [I], fn(&I) -> Backend), + Mask(Backends, 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 + } + } + } + } +} + +#[derive(Clone, Debug, Error)] +#[error("Adapter is invalid")] +pub struct InvalidAdapter; + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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, + display_handle: raw_window_handle::RawDisplayHandle, + window_handle: raw_window_handle::RawWindowHandle, + id_in: Input<G, SurfaceId>, + ) -> SurfaceId { + profiling::scope!("Instance::create_surface"); + + fn init<A: hal::Api>( + inst: &Option<A::Instance>, + display_handle: raw_window_handle::RawDisplayHandle, + window_handle: raw_window_handle::RawWindowHandle, + ) -> Option<HalSurface<A>> { + inst.as_ref().and_then(|inst| unsafe { + match inst.create_surface(display_handle, window_handle) { + Ok(raw) => Some(HalSurface { + raw, + //acquired_texture: None, + }), + Err(e) => { + log::warn!("Error: {:?}", e); + None + } + } + }) + } + + let surface = Surface { + presentation: None, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: init::<hal::api::Vulkan>(&self.instance.vulkan, display_handle, window_handle), + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + metal: init::<hal::api::Metal>(&self.instance.metal, display_handle, window_handle), + #[cfg(all(feature = "dx12", windows))] + dx12: init::<hal::api::Dx12>(&self.instance.dx12, display_handle, window_handle), + #[cfg(all(feature = "dx11", windows))] + dx11: init::<hal::api::Dx11>(&self.instance.dx11, display_handle, window_handle), + #[cfg(feature = "gles")] + gl: init::<hal::api::Gles>(&self.instance.gl, display_handle, window_handle), + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + + /// # Safety + /// + /// `layer` must be a valid pointer. + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + pub unsafe fn instance_create_surface_metal( + &self, + layer: *mut std::ffi::c_void, + id_in: Input<G, SurfaceId>, + ) -> SurfaceId { + profiling::scope!("Instance::create_surface_metal"); + + let surface = Surface { + presentation: None, + metal: self.instance.metal.as_ref().map(|inst| HalSurface { + raw: { + // we don't want to link to metal-rs for this + #[allow(clippy::transmute_ptr_to_ref)] + inst.create_surface_from_layer(unsafe { std::mem::transmute(layer) }) + }, + //acquired_texture: None, + }), + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: None, + #[cfg(feature = "gles")] + gl: None, + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn create_surface_webgl_canvas( + &self, + canvas: web_sys::HtmlCanvasElement, + id_in: Input<G, SurfaceId>, + ) -> Result<SurfaceId, hal::InstanceError> { + profiling::scope!("Instance::create_surface_webgl_canvas"); + + let surface = Surface { + presentation: None, + gl: self + .instance + .gl + .as_ref() + .map(|inst| { + Ok(HalSurface { + raw: inst.create_surface_from_canvas(canvas)?, + }) + }) + .transpose()?, + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + Ok(id.0) + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn create_surface_webgl_offscreen_canvas( + &self, + canvas: web_sys::OffscreenCanvas, + id_in: Input<G, SurfaceId>, + ) -> Result<SurfaceId, hal::InstanceError> { + profiling::scope!("Instance::create_surface_webgl_offscreen_canvas"); + + let surface = Surface { + presentation: None, + gl: self + .instance + .gl + .as_ref() + .map(|inst| { + Ok(HalSurface { + raw: inst.create_surface_from_offscreen_canvas(canvas)?, + }) + }) + .transpose()?, + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + Ok(id.0) + } + + #[cfg(all(feature = "dx12", windows))] + /// # Safety + /// + /// The visual must be valid and able to be used to make a swapchain with. + pub unsafe fn instance_create_surface_from_visual( + &self, + visual: *mut std::ffi::c_void, + id_in: Input<G, SurfaceId>, + ) -> SurfaceId { + profiling::scope!("Instance::instance_create_surface_from_visual"); + + let surface = Surface { + presentation: None, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: None, + dx12: self.instance.dx12.as_ref().map(|inst| HalSurface { + raw: unsafe { inst.create_surface_from_visual(visual as _) }, + }), + dx11: None, + #[cfg(feature = "gles")] + gl: None, + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + + #[cfg(all(feature = "dx12", windows))] + /// # Safety + /// + /// The surface_handle must be valid and able to be used to make a swapchain with. + pub unsafe fn instance_create_surface_from_surface_handle( + &self, + surface_handle: *mut std::ffi::c_void, + id_in: Input<G, SurfaceId>, + ) -> SurfaceId { + profiling::scope!("Instance::instance_create_surface_from_surface_handle"); + + let surface = Surface { + presentation: None, + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + vulkan: None, + dx12: self.instance.dx12.as_ref().map(|inst| HalSurface { + raw: unsafe { inst.create_surface_from_surface_handle(surface_handle) }, + }), + dx11: None, + #[cfg(feature = "gles")] + gl: None, + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + + pub fn surface_drop(&self, id: SurfaceId) { + profiling::scope!("Surface::drop"); + let mut token = Token::root(); + let (surface, _) = self.surfaces.unregister(id, &mut token); + let mut surface = surface.unwrap(); + + fn unconfigure<G: GlobalIdentityHandlerFactory, A: HalApi>( + global: &Global<G>, + surface: &mut HalSurface<A>, + present: &Presentation, + ) { + let hub = HalApi::hub(global); + hub.surface_unconfigure(present.device_id.value, surface); + } + + if let Some(present) = surface.presentation.take() { + match present.backend() { + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + Backend::Vulkan => unconfigure(self, surface.vulkan.as_mut().unwrap(), &present), + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + Backend::Metal => unconfigure(self, surface.metal.as_mut().unwrap(), &present), + #[cfg(all(feature = "dx12", windows))] + Backend::Dx12 => unconfigure(self, surface.dx12.as_mut().unwrap(), &present), + #[cfg(all(feature = "dx11", windows))] + Backend::Dx11 => unconfigure(self, surface.dx11.as_mut().unwrap(), &present), + #[cfg(feature = "gles")] + Backend::Gl => unconfigure(self, surface.gl.as_mut().unwrap(), &present), + _ => unreachable!(), + } + } + + self.instance.destroy_surface(surface); + } + + fn enumerate<A: HalApi>( + &self, + _: A, + instance: &Option<A::Instance>, + inputs: &AdapterInputs<Input<G, AdapterId>>, + list: &mut Vec<AdapterId>, + ) { + let inst = match *instance { + Some(ref inst) => inst, + None => return, + }; + let id_backend = match inputs.find(A::VARIANT) { + Some(id) => id, + None => return, + }; + + profiling::scope!("enumerating", &*format!("{:?}", A::VARIANT)); + let hub = HalApi::hub(self); + let mut token = Token::root(); + + let hal_adapters = unsafe { inst.enumerate_adapters() }; + for raw in hal_adapters { + let adapter = Adapter::new(raw); + log::info!("Adapter {:?} {:?}", A::VARIANT, adapter.raw.info); + let id = hub + .adapters + .prepare(id_backend.clone()) + .assign(adapter, &mut token); + list.push(id.0); + } + } + + pub fn enumerate_adapters(&self, inputs: AdapterInputs<Input<G, AdapterId>>) -> Vec<AdapterId> { + profiling::scope!("Instance::enumerate_adapters"); + + let mut adapters = Vec::new(); + + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + self.enumerate( + hal::api::Vulkan, + &self.instance.vulkan, + &inputs, + &mut adapters, + ); + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + self.enumerate( + hal::api::Metal, + &self.instance.metal, + &inputs, + &mut adapters, + ); + #[cfg(all(feature = "dx12", windows))] + self.enumerate(hal::api::Dx12, &self.instance.dx12, &inputs, &mut adapters); + #[cfg(all(feature = "dx11", windows))] + self.enumerate(hal::api::Dx11, &self.instance.dx11, &inputs, &mut adapters); + #[cfg(feature = "gles")] + self.enumerate(hal::api::Gles, &self.instance.gl, &inputs, &mut adapters); + + adapters + } + + fn select<A: HalApi>( + &self, + selected: &mut usize, + new_id: Option<Input<G, AdapterId>>, + mut list: Vec<hal::ExposedAdapter<A>>, + ) -> Option<AdapterId> { + match selected.checked_sub(list.len()) { + Some(left) => { + *selected = left; + None + } + None => { + let mut token = Token::root(); + let adapter = Adapter::new(list.swap_remove(*selected)); + log::info!("Adapter {:?} {:?}", A::VARIANT, adapter.raw.info); + let id = HalApi::hub(self) + .adapters + .prepare(new_id.unwrap()) + .assign(adapter, &mut token); + Some(id.0) + } + } + } + + pub fn request_adapter( + &self, + desc: &RequestAdapterOptions, + inputs: AdapterInputs<Input<G, AdapterId>>, + ) -> Result<AdapterId, RequestAdapterError> { + profiling::scope!("Instance::pick_adapter"); + + fn gather<A: HalApi, I: Clone>( + _: A, + instance: Option<&A::Instance>, + inputs: &AdapterInputs<I>, + compatible_surface: Option<&Surface>, + force_software: bool, + device_types: &mut Vec<wgt::DeviceType>, + ) -> (Option<I>, Vec<hal::ExposedAdapter<A>>) { + let id = inputs.find(A::VARIANT); + match instance { + Some(inst) if id.is_some() => { + let mut adapters = unsafe { inst.enumerate_adapters() }; + if force_software { + adapters.retain(|exposed| exposed.info.device_type == wgt::DeviceType::Cpu); + } + if let Some(surface) = compatible_surface { + let surface = &A::get_surface(surface); + adapters.retain(|exposed| unsafe { + // If the surface does not exist for this backend, + // then the surface is not supported. + surface.is_some() + && exposed + .adapter + .surface_capabilities(&surface.unwrap().raw) + .is_some() + }); + } + device_types.extend(adapters.iter().map(|ad| ad.info.device_type)); + (id, adapters) + } + _ => (id, Vec::new()), + } + } + + let mut token = Token::root(); + let (surface_guard, _) = 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(); + + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + let (id_vulkan, adapters_vk) = gather( + hal::api::Vulkan, + self.instance.vulkan.as_ref(), + &inputs, + compatible_surface, + desc.force_fallback_adapter, + &mut device_types, + ); + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + let (id_metal, adapters_metal) = gather( + hal::api::Metal, + self.instance.metal.as_ref(), + &inputs, + compatible_surface, + desc.force_fallback_adapter, + &mut device_types, + ); + #[cfg(all(feature = "dx12", windows))] + let (id_dx12, adapters_dx12) = gather( + hal::api::Dx12, + self.instance.dx12.as_ref(), + &inputs, + compatible_surface, + desc.force_fallback_adapter, + &mut device_types, + ); + #[cfg(all(feature = "dx11", windows))] + let (id_dx11, adapters_dx11) = gather( + hal::api::Dx11, + self.instance.dx11.as_ref(), + &inputs, + compatible_surface, + desc.force_fallback_adapter, + &mut device_types, + ); + #[cfg(feature = "gles")] + let (id_gl, adapters_gl) = gather( + hal::api::Gles, + self.instance.gl.as_ref(), + &inputs, + compatible_surface, + desc.force_fallback_adapter, + &mut device_types, + ); + + // need to free the token to be used by `select` + drop(surface_guard); + drop(token); + if device_types.is_empty() { + return Err(RequestAdapterError::NotFound); + } + + let (mut integrated, mut discrete, mut virt, mut cpu, mut other) = + (None, None, None, None, None); + + for (i, ty) in device_types.into_iter().enumerate() { + match ty { + wgt::DeviceType::IntegratedGpu => { + integrated = integrated.or(Some(i)); + } + wgt::DeviceType::DiscreteGpu => { + discrete = discrete.or(Some(i)); + } + wgt::DeviceType::VirtualGpu => { + virt = virt.or(Some(i)); + } + wgt::DeviceType::Cpu => { + cpu = cpu.or(Some(i)); + } + wgt::DeviceType::Other => { + other = other.or(Some(i)); + } + } + } + + let preferred_gpu = match desc.power_preference { + // Since devices of type "Other" might really be "Unknown" and come + // from APIs like OpenGL that don't specify device type, Prefer more + // Specific types over Other. + // + // This means that backends which do provide accurate device types + // will be preferred if their device type indicates an actual + // hardware GPU (integrated or discrete). + PowerPreference::LowPower => integrated.or(discrete).or(other).or(virt).or(cpu), + PowerPreference::HighPerformance => discrete.or(integrated).or(other).or(virt).or(cpu), + }; + + let mut selected = preferred_gpu.unwrap_or(0); + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + if let Some(id) = self.select(&mut selected, id_vulkan, adapters_vk) { + return Ok(id); + } + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + if let Some(id) = self.select(&mut selected, id_metal, adapters_metal) { + return Ok(id); + } + #[cfg(all(feature = "dx12", windows))] + if let Some(id) = self.select(&mut selected, id_dx12, adapters_dx12) { + return Ok(id); + } + #[cfg(all(feature = "dx11", windows))] + if let Some(id) = self.select(&mut selected, id_dx11, adapters_dx11) { + return Ok(id); + } + #[cfg(feature = "gles")] + if let Some(id) = self.select(&mut selected, id_gl, adapters_gl) { + return Ok(id); + } + let _ = selected; + + log::warn!("Some adapters are present, but enumerating them failed!"); + Err(RequestAdapterError::NotFound) + } + + /// # Safety + /// + /// `hal_adapter` must be created from this global internal instance handle. + pub unsafe fn create_adapter_from_hal<A: HalApi>( + &self, + hal_adapter: hal::ExposedAdapter<A>, + input: Input<G, AdapterId>, + ) -> AdapterId { + profiling::scope!("Instance::create_adapter_from_hal"); + + let mut token = Token::root(); + let fid = A::hub(self).adapters.prepare(input); + + match A::VARIANT { + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] + Backend::Vulkan => fid.assign(Adapter::new(hal_adapter), &mut token).0, + #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] + Backend::Metal => fid.assign(Adapter::new(hal_adapter), &mut token).0, + #[cfg(all(feature = "dx12", windows))] + Backend::Dx12 => fid.assign(Adapter::new(hal_adapter), &mut token).0, + #[cfg(all(feature = "dx11", windows))] + Backend::Dx11 => fid.assign(Adapter::new(hal_adapter), &mut token).0, + #[cfg(feature = "gles")] + Backend::Gl => fid.assign(Adapter::new(hal_adapter), &mut token).0, + _ => unreachable!(), + } + } + + pub fn adapter_get_info<A: HalApi>( + &self, + adapter_id: AdapterId, + ) -> Result<wgt::AdapterInfo, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + adapter_guard + .get(adapter_id) + .map(|adapter| adapter.raw.info.clone()) + .map_err(|_| InvalidAdapter) + } + + pub fn adapter_get_texture_format_features<A: HalApi>( + &self, + adapter_id: AdapterId, + format: wgt::TextureFormat, + ) -> Result<wgt::TextureFormatFeatures, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + adapter_guard + .get(adapter_id) + .map(|adapter| adapter.get_texture_format_features(format)) + .map_err(|_| InvalidAdapter) + } + + pub fn adapter_features<A: HalApi>( + &self, + adapter_id: AdapterId, + ) -> Result<wgt::Features, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + adapter_guard + .get(adapter_id) + .map(|adapter| adapter.raw.features) + .map_err(|_| InvalidAdapter) + } + + pub fn adapter_limits<A: HalApi>( + &self, + adapter_id: AdapterId, + ) -> Result<wgt::Limits, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + adapter_guard + .get(adapter_id) + .map(|adapter| adapter.raw.capabilities.limits.clone()) + .map_err(|_| InvalidAdapter) + } + + pub fn adapter_downlevel_capabilities<A: HalApi>( + &self, + adapter_id: AdapterId, + ) -> Result<wgt::DownlevelCapabilities, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + adapter_guard + .get(adapter_id) + .map(|adapter| adapter.raw.capabilities.downlevel.clone()) + .map_err(|_| InvalidAdapter) + } + + pub fn adapter_get_presentation_timestamp<A: HalApi>( + &self, + adapter_id: AdapterId, + ) -> Result<wgt::PresentationTimestamp, InvalidAdapter> { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + let adapter = adapter_guard.get(adapter_id).map_err(|_| InvalidAdapter)?; + + Ok(unsafe { adapter.raw.adapter.get_presentation_timestamp() }) + } + + pub fn adapter_drop<A: HalApi>(&self, adapter_id: AdapterId) { + profiling::scope!("Adapter::drop"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (mut adapter_guard, _) = hub.adapters.write(&mut token); + + let free = match adapter_guard.get_mut(adapter_id) { + Ok(adapter) => adapter.life_guard.ref_count.take().unwrap().load() == 1, + Err(_) => true, + }; + if free { + hub.adapters + .unregister_locked(adapter_id, &mut *adapter_guard); + } + } +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn adapter_request_device<A: HalApi>( + &self, + adapter_id: AdapterId, + desc: &DeviceDescriptor, + trace_path: Option<&std::path::Path>, + id_in: Input<G, DeviceId>, + ) -> (DeviceId, Option<RequestDeviceError>) { + profiling::scope!("Adapter::request_device"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.devices.prepare(id_in); + + 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 = fid.assign(device, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } + + /// # Safety + /// + /// - `hal_device` must be created from `adapter_id` or its internal handle. + /// - `desc` must be a subset of `hal_device` features and limits. + pub unsafe fn create_device_from_hal<A: HalApi>( + &self, + adapter_id: AdapterId, + hal_device: hal::OpenDevice<A>, + desc: &DeviceDescriptor, + trace_path: Option<&std::path::Path>, + id_in: Input<G, DeviceId>, + ) -> (DeviceId, Option<RequestDeviceError>) { + profiling::scope!("Adapter::create_device_from_hal"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.devices.prepare(id_in); + + 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_from_hal(adapter_id, hal_device, desc, trace_path) { + Ok(device) => device, + Err(e) => break e, + }; + let id = fid.assign(device, &mut token); + return (id.0, None); + }; + + let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + (id, Some(error)) + } +} + +/// Generates a set of backends from a comma separated list of case-insensitive backend names. +/// +/// Whitespace is stripped, so both 'gl, dx12' and 'gl,dx12' are valid. +/// +/// Always returns WEBGPU on wasm over webgpu. +/// +/// Names: +/// - vulkan = "vulkan" or "vk" +/// - dx12 = "dx12" or "d3d12" +/// - dx11 = "dx11" or "d3d11" +/// - metal = "metal" or "mtl" +/// - gles = "opengl" or "gles" or "gl" +/// - webgpu = "webgpu" +pub fn parse_backends_from_comma_list(string: &str) -> Backends { + let mut backends = Backends::empty(); + for backend in string.to_lowercase().split(',') { + backends |= match backend.trim() { + "vulkan" | "vk" => Backends::VULKAN, + "dx12" | "d3d12" => Backends::DX12, + "dx11" | "d3d11" => Backends::DX11, + "metal" | "mtl" => Backends::METAL, + "opengl" | "gles" | "gl" => Backends::GL, + "webgpu" => Backends::BROWSER_WEBGPU, + b => { + log::warn!("unknown backend string '{}'", b); + continue; + } + } + } + + if backends.is_empty() { + log::warn!("no valid backend strings found!"); + } + + backends +} diff --git a/third_party/rust/wgpu-core/src/lib.rs b/third_party/rust/wgpu-core/src/lib.rs new file mode 100644 index 0000000000..fc80f0017d --- /dev/null +++ b/third_party/rust/wgpu-core/src/lib.rs @@ -0,0 +1,444 @@ +/*! This library safely implements WebGPU on native platforms. + * It is designed for integration into browsers, as well as wrapping + * into other language-specific user-friendly libraries. + */ + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![allow( + // It is much clearer to assert negative conditions with eq! false + clippy::bool_assert_comparison, + // We use loops for getting early-out of scope without closures. + clippy::never_loop, + // We don't use syntax sugar where it's not necessary. + clippy::match_like_matches_macro, + // Redundant matching is more explicit. + clippy::redundant_pattern_matching, + // Explicit lifetimes are often easier to reason about. + clippy::needless_lifetimes, + // No need for defaults in the internal types. + clippy::new_without_default, + // Needless updates are more scaleable, easier to play with features. + clippy::needless_update, + // Need many arguments for some core functions to be able to re-use code in many situations. + clippy::too_many_arguments, + // For some reason `rustc` can warn about these in const generics even + // though they are required. + unused_braces, + // Clashes with clippy::pattern_type_mismatch + clippy::needless_borrowed_reference, +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unsafe_op_in_unsafe_fn, + unused_extern_crates, + unused_qualifications, + // We don't match on a reference, unless required. + clippy::pattern_type_mismatch, +)] + +pub mod binding_model; +pub mod command; +mod conv; +pub mod device; +pub mod error; +pub mod hub; +pub mod id; +mod init_tracker; +pub mod instance; +pub mod pipeline; +pub mod present; +pub mod resource; +mod track; +mod validation; + +pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_ATTACHMENTS, MAX_VERTEX_BUFFERS}; + +use atomic::{AtomicUsize, Ordering}; + +use std::{borrow::Cow, os::raw::c_char, ptr, sync::atomic}; + +/// The index of a queue submission. +/// +/// These are the values stored in `Device::fence`. +type SubmissionIndex = hal::FenceValue; + +type Index = u32; +type Epoch = u32; + +pub type RawString = *const c_char; +pub type Label<'a> = Option<Cow<'a, str>>; + +trait LabelHelpers<'a> { + fn borrow_option(&'a self) -> Option<&'a str>; + fn borrow_or_default(&'a self) -> &'a str; +} +impl<'a> LabelHelpers<'a> for Label<'a> { + fn borrow_option(&'a self) -> Option<&'a str> { + self.as_ref().map(|cow| cow.as_ref()) + } + fn borrow_or_default(&'a self) -> &'a str { + self.borrow_option().unwrap_or_default() + } +} + +/// Reference count object that is 1:1 with each reference. +/// +/// All the clones of a given `RefCount` point to the same +/// heap-allocated atomic reference count. When the count drops to +/// zero, only the count is freed. No other automatic cleanup takes +/// place; this is just a reference count, not a smart pointer. +/// +/// `RefCount` values are created only by [`LifeGuard::new`] and by +/// `Clone`, so every `RefCount` is implicitly tied to some +/// [`LifeGuard`]. +#[derive(Debug)] +struct RefCount(ptr::NonNull<AtomicUsize>); + +unsafe impl Send for RefCount {} +unsafe impl Sync for RefCount {} + +impl RefCount { + const MAX: usize = 1 << 24; + + /// Construct a new `RefCount`, with an initial count of 1. + fn new() -> RefCount { + let bx = Box::new(AtomicUsize::new(1)); + Self(unsafe { ptr::NonNull::new_unchecked(Box::into_raw(bx)) }) + } + + fn load(&self) -> usize { + unsafe { self.0.as_ref() }.load(Ordering::Acquire) + } +} + +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 { + if self.0.as_ref().fetch_sub(1, Ordering::AcqRel) == 1 { + drop(Box::from_raw(self.0.as_ptr())); + } + } + } +} + +/// Reference count object that tracks multiple references. +/// Unlike `RefCount`, it's manually inc()/dec() called. +#[derive(Debug)] +struct MultiRefCount(AtomicUsize); + +impl MultiRefCount { + fn new() -> Self { + Self(AtomicUsize::new(1)) + } + + fn inc(&self) { + self.0.fetch_add(1, Ordering::AcqRel); + } + + fn dec_and_check_empty(&self) -> bool { + self.0.fetch_sub(1, Ordering::AcqRel) == 1 + } +} + +/// Information needed to decide when it's safe to free some wgpu-core +/// resource. +/// +/// Each type representing a `wgpu-core` resource, like [`Device`], +/// [`Buffer`], etc., contains a `LifeGuard` which indicates whether +/// it is safe to free. +/// +/// A resource may need to be retained for any of several reasons: +/// +/// - The user may hold a reference to it (via a `wgpu::Buffer`, say). +/// +/// - Other resources may depend on it (a texture view's backing +/// texture, for example). +/// +/// - It may be used by commands sent to the GPU that have not yet +/// finished execution. +/// +/// [`Device`]: device::Device +/// [`Buffer`]: resource::Buffer +#[derive(Debug)] +pub struct LifeGuard { + /// `RefCount` for the user's reference to this resource. + /// + /// When the user first creates a `wgpu-core` resource, this `RefCount` is + /// created along with the resource's `LifeGuard`. When the user drops the + /// resource, we swap this out for `None`. Note that the resource may + /// still be held alive by other resources. + /// + /// Any `Stored<T>` value holds a clone of this `RefCount` along with the id + /// of a `T` resource. + ref_count: Option<RefCount>, + + /// The index of the last queue submission in which the resource + /// was used. + /// + /// Each queue submission is fenced and assigned an index number + /// sequentially. Thus, when a queue submission completes, we know any + /// resources used in that submission and any lower-numbered submissions are + /// no longer in use by the GPU. + submission_index: AtomicUsize, + + /// The `label` from the descriptor used to create the resource. + #[cfg(debug_assertions)] + pub(crate) label: String, +} + +impl LifeGuard { + #[allow(unused_variables)] + fn new(label: &str) -> Self { + Self { + ref_count: Some(RefCount::new()), + submission_index: AtomicUsize::new(0), + #[cfg(debug_assertions)] + label: label.to_string(), + } + } + + fn add_ref(&self) -> RefCount { + self.ref_count.clone().unwrap() + } + + /// Record that this resource will be used by the queue submission with the + /// given index. + /// + /// Returns `true` if the resource is still held by the user. + fn use_at(&self, submit_index: SubmissionIndex) -> bool { + self.submission_index + .store(submit_index as _, Ordering::Release); + self.ref_count.is_some() + } + + fn life_count(&self) -> SubmissionIndex { + self.submission_index.load(Ordering::Acquire) as _ + } +} + +#[derive(Clone, Debug)] +struct Stored<T> { + value: id::Valid<T>, + ref_count: RefCount, +} + +const DOWNLEVEL_WARNING_MESSAGE: &str = "The underlying API or device in use does not \ +support enough features to be a fully compliant implementation of WebGPU. A subset of the features can still be used. \ +If you are running this program on native and not in a browser and wish to limit the features you use to the supported subset, \ +call Adapter::downlevel_properties or Device::downlevel_properties to get a listing of the features the current \ +platform supports."; +const DOWNLEVEL_ERROR_MESSAGE: &str = "This is not an invalid use of WebGPU: the underlying API or device does not \ +support enough features to be a fully compliant implementation. A subset of the features can still be used. \ +If you are running this program on native and not in a browser and wish to work around this issue, call \ +Adapter::downlevel_properties or Device::downlevel_properties to get a listing of the features the current \ +platform supports."; + +// #[cfg] attributes in exported macros are interesting! +// +// The #[cfg] conditions in a macro's expansion are evaluated using the +// configuration options (features, target architecture and os, etc.) in force +// where the macro is *used*, not where it is *defined*. That is, if crate A +// defines a macro like this: +// +// #[macro_export] +// macro_rules! if_bleep { +// { } => { +// #[cfg(feature = "bleep")] +// bleep(); +// } +// } +// +// and then crate B uses it like this: +// +// fn f() { +// if_bleep! { } +// } +// +// then it is crate B's `"bleep"` feature, not crate A's, that determines +// whether the macro expands to a function call or an empty statement. The +// entire configuration predicate is evaluated in the use's context, not the +// definition's. +// +// Since `wgpu-core` selects back ends using features, we need to make sure the +// arms of the `gfx_select!` macro are pruned according to `wgpu-core`'s +// features, not those of whatever crate happens to be using `gfx_select!`. This +// means we can't use `#[cfg]` attributes in `gfx_select!`s definition itself. +// Instead, for each backend, `gfx_select!` must use a macro whose definition is +// selected by `#[cfg]` in `wgpu-core`. The configuration predicate is still +// evaluated when the macro is used; we've just moved the `#[cfg]` into a macro +// used by `wgpu-core` itself. + +/// Define an exported macro named `$public` that expands to an expression if +/// the feature `$feature` is enabled, or to a panic otherwise. +/// +/// This is used in the definition of `gfx_select!`, to dispatch the +/// call to the appropriate backend, but panic if that backend was not +/// compiled in. +/// +/// For a call like this: +/// +/// ```ignore +/// define_backend_caller! { name, private, "feature" if cfg_condition } +/// ``` +/// +/// define a macro `name`, used like this: +/// +/// ```ignore +/// name!(expr) +/// ``` +/// +/// that expands to `expr` if `#[cfg(cfg_condition)]` is enabled, or a +/// panic otherwise. The panic message complains that `"feature"` is +/// not enabled. +/// +/// Because of odd technical limitations on exporting macros expanded +/// by other macros, you must supply both a public-facing name for the +/// macro and a private name, `$private`, which is never used +/// outside this macro. For details: +/// <https://github.com/rust-lang/rust/pull/52234#issuecomment-976702997> +macro_rules! define_backend_caller { + { $public:ident, $private:ident, $feature:literal if $cfg:meta } => { + #[cfg($cfg)] + #[macro_export] + macro_rules! $private { + ( $call:expr ) => ( $call ) + } + + #[cfg(not($cfg))] + #[macro_export] + macro_rules! $private { + ( $call:expr ) => ( + panic!("Identifier refers to disabled backend feature {:?}", $feature) + ) + } + + // See note about rust-lang#52234 above. + #[doc(hidden)] pub use $private as $public; + } +} + +// Define a macro for each `gfx_select!` match arm. For example, +// +// gfx_if_vulkan!(expr) +// +// expands to `expr` if the `"vulkan"` feature is enabled, or to a panic +// otherwise. +define_backend_caller! { gfx_if_vulkan, gfx_if_vulkan_hidden, "vulkan" if all(feature = "vulkan", not(target_arch = "wasm32")) } +define_backend_caller! { gfx_if_metal, gfx_if_metal_hidden, "metal" if all(feature = "metal", any(target_os = "macos", target_os = "ios")) } +define_backend_caller! { gfx_if_dx12, gfx_if_dx12_hidden, "dx12" if all(feature = "dx12", windows) } +define_backend_caller! { gfx_if_dx11, gfx_if_dx11_hidden, "dx11" if all(feature = "dx11", windows) } +define_backend_caller! { gfx_if_gles, gfx_if_gles_hidden, "gles" if feature = "gles" } + +/// Dispatch on an [`Id`]'s backend to a backend-generic method. +/// +/// Uses of this macro have the form: +/// +/// ```ignore +/// +/// gfx_select!(id => value.method(args...)) +/// +/// ``` +/// +/// This expands to an expression that calls `value.method::<A>(args...)` for +/// the backend `A` selected by `id`. The expansion matches on `id.backend()`, +/// with an arm for each backend type in [`wgpu_types::Backend`] which calls the +/// specialization of `method` for the given backend. This allows resource +/// identifiers to select backends dynamically, even though many `wgpu_core` +/// methods are compiled and optimized for a specific back end. +/// +/// This macro is typically used to call methods on [`wgpu_core::hub::Global`], +/// many of which take a single `hal::Api` type parameter. For example, to +/// create a new buffer on the device indicated by `device_id`, one would say: +/// +/// ```ignore +/// gfx_select!(device_id => global.device_create_buffer(device_id, ...)) +/// ``` +/// +/// where the `device_create_buffer` method is defined like this: +/// +/// ```ignore +/// impl<...> Global<...> { +/// pub fn device_create_buffer<A: hal::Api>(&self, ...) -> ... +/// { ... } +/// } +/// ``` +/// +/// That `gfx_select!` call uses `device_id`'s backend to select the right +/// backend type `A` for a call to `Global::device_create_buffer<A>`. +/// +/// However, there's nothing about this macro that is specific to `hub::Global`. +/// For example, Firefox's embedding of `wgpu_core` defines its own types with +/// methods that take `hal::Api` type parameters. Firefox uses `gfx_select!` to +/// dynamically dispatch to the right specialization based on the resource's id. +/// +/// [`wgpu_types::Backend`]: wgt::Backend +/// [`wgpu_core::hub::Global`]: crate::hub::Global +/// [`Id`]: id::Id +#[macro_export] +macro_rules! gfx_select { + ($id:expr => $global:ident.$method:ident( $($param:expr),* )) => { + match $id.backend() { + wgt::Backend::Vulkan => $crate::gfx_if_vulkan!($global.$method::<$crate::api::Vulkan>( $($param),* )), + wgt::Backend::Metal => $crate::gfx_if_metal!($global.$method::<$crate::api::Metal>( $($param),* )), + wgt::Backend::Dx12 => $crate::gfx_if_dx12!($global.$method::<$crate::api::Dx12>( $($param),* )), + wgt::Backend::Dx11 => $crate::gfx_if_dx11!($global.$method::<$crate::api::Dx11>( $($param),* )), + wgt::Backend::Gl => $crate::gfx_if_gles!($global.$method::<$crate::api::Gles>( $($param),+ )), + other => panic!("Unexpected backend {:?}", other), + } + }; +} + +/// Fast hash map used internally. +type FastHashMap<K, V> = + std::collections::HashMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>; +/// Fast hash set used internally. +type FastHashSet<K> = + std::collections::HashSet<K, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>; + +#[inline] +pub(crate) 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 +} + +#[inline] +pub(crate) 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; + } + } +} + +#[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/third_party/rust/wgpu-core/src/pipeline.rs b/third_party/rust/wgpu-core/src/pipeline.rs new file mode 100644 index 0000000000..beb71dca16 --- /dev/null +++ b/third_party/rust/wgpu-core/src/pipeline.rs @@ -0,0 +1,438 @@ +use crate::{ + binding_model::{CreateBindGroupLayoutError, CreatePipelineLayoutError}, + command::ColorAttachmentError, + device::{DeviceError, MissingDownlevelFlags, MissingFeatures, RenderPassContext}, + hub::Resource, + id::{DeviceId, PipelineLayoutId, ShaderModuleId}, + validation, Label, LifeGuard, Stored, +}; +use arrayvec::ArrayVec; +use std::{borrow::Cow, error::Error, fmt, marker::PhantomData, num::NonZeroU32}; +use thiserror::Error; + +/// Information about buffer bindings, which +/// is validated against the shader (and pipeline) +/// at draw time as opposed to initialization time. +#[derive(Debug)] +pub(crate) struct LateSizedBufferGroup { + // The order has to match `BindGroup::late_buffer_binding_sizes`. + pub(crate) shader_sizes: Vec<wgt::BufferAddress>, +} + +#[allow(clippy::large_enum_variant)] +pub enum ShaderModuleSource<'a> { + #[cfg(feature = "wgsl")] + Wgsl(Cow<'a, str>), + Naga(Cow<'static, naga::Module>), + /// Dummy variant because `Naga` doesn't have a lifetime and without enough active features it + /// could be the last one active. + #[doc(hidden)] + Dummy(PhantomData<&'a ()>), +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub struct ShaderModuleDescriptor<'a> { + pub label: Label<'a>, + #[cfg_attr(feature = "serde", serde(default))] + pub shader_bound_checks: wgt::ShaderBoundChecks, +} + +#[derive(Debug)] +pub struct ShaderModule<A: hal::Api> { + pub(crate) raw: A::ShaderModule, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) interface: Option<validation::Interface>, + #[cfg(debug_assertions)] + pub(crate) label: String, +} + +impl<A: hal::Api> Resource for ShaderModule<A> { + 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)] +pub struct ShaderError<E> { + pub source: String, + pub label: Option<String>, + pub inner: Box<E>, +} +#[cfg(feature = "wgsl")] +impl fmt::Display for ShaderError<naga::front::wgsl::ParseError> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let label = self.label.as_deref().unwrap_or_default(); + let string = self.inner.emit_to_string(&self.source); + write!(f, "\nShader '{label}' parsing {string}") + } +} +impl fmt::Display for ShaderError<naga::WithSpan<naga::valid::ValidationError>> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files::SimpleFile, + term, + }; + + let label = self.label.as_deref().unwrap_or_default(); + let files = SimpleFile::new(label, &self.source); + let config = term::Config::default(); + let mut writer = term::termcolor::NoColor::new(Vec::new()); + + let diagnostic = Diagnostic::error().with_labels( + self.inner + .spans() + .map(|&(span, ref desc)| { + Label::primary((), span.to_range().unwrap()).with_message(desc.to_owned()) + }) + .collect(), + ); + + term::emit(&mut writer, &config, &files, &diagnostic).expect("cannot write error"); + + write!( + f, + "\nShader validation {}", + String::from_utf8_lossy(&writer.into_inner()) + ) + } +} +impl<E> Error for ShaderError<E> +where + ShaderError<E>: fmt::Display, + E: Error + 'static, +{ + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.inner) + } +} + +//Note: `Clone` would require `WithSpan: Clone`. +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum CreateShaderModuleError { + #[cfg(feature = "wgsl")] + #[error(transparent)] + Parsing(#[from] ShaderError<naga::front::wgsl::ParseError>), + #[error("Failed to generate the backend-specific code")] + Generation, + #[error(transparent)] + Device(#[from] DeviceError), + #[error(transparent)] + Validation(#[from] ShaderError<naga::WithSpan<naga::valid::ValidationError>>), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error( + "Shader global {bind:?} uses a group index {group} that exceeds the max_bind_groups limit of {limit}." + )] + InvalidGroupIndex { + bind: naga::ResourceBinding, + group: u32, + limit: u32, + }, +} + +impl CreateShaderModuleError { + pub fn location(&self, source: &str) -> Option<naga::SourceLocation> { + match *self { + #[cfg(feature = "wgsl")] + CreateShaderModuleError::Parsing(ref err) => err.inner.location(source), + CreateShaderModuleError::Validation(ref err) => err.inner.location(source), + _ => None, + } + } +} + +/// 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 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)] +#[non_exhaustive] +pub enum ImplicitLayoutError { + #[error("Missing IDs for deriving {0} bind groups")] + MissingIds(ImplicitBindGroupCount), + #[error("Unable to reflect the shader {0:?} interface")] + ReflectionError(wgt::ShaderStages), + #[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 stage: ProgrammableStageDescriptor<'a>, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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("Error matching shader requirements against the pipeline")] + Stage(#[from] validation::StageError), + #[error("Internal error: {0}")] + Internal(String), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +#[derive(Debug)] +pub struct ComputePipeline<A: hal::Api> { + pub(crate) raw: A::ComputePipeline, + pub(crate) layout_id: Stored<PipelineLayoutId>, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>, + pub(crate) life_guard: LifeGuard, +} + +impl<A: hal::Api> Resource for ComputePipeline<A> { + 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))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct VertexBufferLayout<'a> { + /// The stride, in bytes, between elements of this buffer. + pub array_stride: wgt::BufferAddress, + /// How often this vertex buffer is "stepped" forward. + pub step_mode: wgt::VertexStepMode, + /// The list of attributes which comprise a single vertex. + pub attributes: Cow<'a, [wgt::VertexAttribute]>, +} + +/// Describes the vertex process in a render pipeline. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub struct VertexState<'a> { + /// The compiled vertex stage and its entry point. + pub stage: ProgrammableStageDescriptor<'a>, + /// The format of any vertex buffers used with this pipeline. + pub buffers: Cow<'a, [VertexBufferLayout<'a>]>, +} + +/// Describes fragment processing in a render pipeline. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "trace", derive(serde::Serialize))] +#[cfg_attr(feature = "replay", derive(serde::Deserialize))] +pub struct FragmentState<'a> { + /// The compiled fragment stage and its entry point. + pub stage: ProgrammableStageDescriptor<'a>, + /// The effect of draw calls on the color aspect of the output target. + pub targets: Cow<'a, [Option<wgt::ColorTargetState>]>, +} + +/// 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 vertex processing state for this pipeline. + pub vertex: VertexState<'a>, + /// The properties of the pipeline at the primitive assembly and rasterization level. + #[cfg_attr(any(feature = "replay", feature = "trace"), serde(default))] + pub primitive: wgt::PrimitiveState, + /// The effect of draw calls on the depth and stencil aspects of the output target, if any. + #[cfg_attr(any(feature = "replay", feature = "trace"), serde(default))] + pub depth_stencil: Option<wgt::DepthStencilState>, + /// The multi-sampling properties of the pipeline. + #[cfg_attr(any(feature = "replay", feature = "trace"), serde(default))] + pub multisample: wgt::MultisampleState, + /// The fragment processing state for this pipeline. + pub fragment: Option<FragmentState<'a>>, + /// If the pipeline will be used with a multiview render pass, this indicates how many array + /// layers the attachments will have. + pub multiview: Option<NonZeroU32>, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ColorStateError { + #[error("Format {0:?} is not renderable")] + FormatNotRenderable(wgt::TextureFormat), + #[error("Format {0:?} is not blendable")] + FormatNotBlendable(wgt::TextureFormat), + #[error("Format {0:?} does not have a color aspect")] + FormatNotColor(wgt::TextureFormat), + #[error("Format {0:?} can't be multisampled")] + FormatNotMultisampled(wgt::TextureFormat), + #[error("Output format {pipeline} is incompatible with the shader {shader}")] + IncompatibleFormat { + pipeline: validation::NumericType, + shader: validation::NumericType, + }, + #[error("Blend factors for {0:?} must be `One`")] + InvalidMinMaxBlendFactors(wgt::BlendComponent), + #[error("Invalid write mask {0:?}")] + InvalidWriteMask(wgt::ColorWrites), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum DepthStencilStateError { + #[error("Format {0:?} is not renderable")] + FormatNotRenderable(wgt::TextureFormat), + #[error("Format {0:?} does not have a depth aspect, but depth test/write is enabled")] + FormatNotDepth(wgt::TextureFormat), + #[error("Format {0:?} does not have a stencil aspect, but stencil test/write is enabled")] + FormatNotStencil(wgt::TextureFormat), + #[error("Format {0:?} can't be multisampled")] + FormatNotMultisampled(wgt::TextureFormat), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateRenderPipelineError { + #[error(transparent)] + ColorAttachment(#[from] ColorAttachmentError), + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Pipeline layout is invalid")] + InvalidLayout, + #[error("Unable to derive an implicit layout")] + Implicit(#[from] ImplicitLayoutError), + #[error("Color state [{0}] is invalid")] + ColorState(u8, #[source] ColorStateError), + #[error("Depth/stencil state is invalid")] + DepthStencilState(#[from] DepthStencilStateError), + #[error("Invalid sample count {0}")] + InvalidSampleCount(u32), + #[error("The number of vertex buffers {given} exceeds the limit {limit}")] + TooManyVertexBuffers { given: u32, limit: u32 }, + #[error("The total number of vertex attributes {given} exceeds the limit {limit}")] + TooManyVertexAttributes { given: u32, limit: u32 }, + #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")] + VertexStrideTooLarge { index: u32, given: u32, limit: u32 }, + #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_STRIDE_ALIGNMENT`")] + UnalignedVertexStride { + index: u32, + stride: wgt::BufferAddress, + }, + #[error("Vertex attribute at location {location} has invalid offset {offset}")] + InvalidVertexAttributeOffset { + location: wgt::ShaderLocation, + offset: wgt::BufferAddress, + }, + #[error("Two or more vertex attributes were assigned to the same location in the shader: {0}")] + ShaderLocationClash(u32), + #[error("Strip index format was not set to None but to {strip_index_format:?} while using the non-strip topology {topology:?}")] + StripIndexFormatForNonStripTopology { + strip_index_format: Option<wgt::IndexFormat>, + topology: wgt::PrimitiveTopology, + }, + #[error("Conservative Rasterization is only supported for wgt::PolygonMode::Fill")] + ConservativeRasterizationNonFillPolygonMode, + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), + #[error("Error matching {stage:?} shader requirements against the pipeline")] + Stage { + stage: wgt::ShaderStages, + #[source] + error: validation::StageError, + }, + #[error("Internal error in {stage:?} shader: {error}")] + Internal { + stage: wgt::ShaderStages, + error: String, + }, + #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")] + UnalignedShader { group: u32, binding: u32, size: u64 }, +} + +bitflags::bitflags! { + #[repr(transparent)] + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub struct PipelineFlags: u32 { + const BLEND_CONSTANT = 1 << 0; + const STENCIL_REFERENCE = 1 << 1; + const WRITES_DEPTH = 1 << 2; + const WRITES_STENCIL = 1 << 3; + } +} + +/// How a render pipeline will retrieve attributes from a particular vertex buffer. +#[derive(Clone, Copy, Debug)] +pub struct VertexStep { + /// The byte stride in the buffer between one attribute value and the next. + pub stride: wgt::BufferAddress, + + /// Whether the buffer is indexed by vertex number or instance number. + pub mode: wgt::VertexStepMode, +} + +impl Default for VertexStep { + fn default() -> Self { + Self { + stride: 0, + mode: wgt::VertexStepMode::Vertex, + } + } +} + +#[derive(Debug)] +pub struct RenderPipeline<A: hal::Api> { + pub(crate) raw: A::RenderPipeline, + pub(crate) layout_id: Stored<PipelineLayoutId>, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) pass_context: RenderPassContext, + pub(crate) flags: PipelineFlags, + pub(crate) strip_index_format: Option<wgt::IndexFormat>, + pub(crate) vertex_steps: Vec<VertexStep>, + pub(crate) late_sized_buffer_groups: ArrayVec<LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }>, + pub(crate) life_guard: LifeGuard, +} + +impl<A: hal::Api> Resource for RenderPipeline<A> { + const TYPE: &'static str = "RenderPipeline"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} diff --git a/third_party/rust/wgpu-core/src/present.rs b/third_party/rust/wgpu-core/src/present.rs new file mode 100644 index 0000000000..f3989bdec6 --- /dev/null +++ b/third_party/rust/wgpu-core/src/present.rs @@ -0,0 +1,410 @@ +/*! Presentation. + +## Lifecycle + +Whenever a submission detects the use of any surface texture, it adds it to the device +tracker for the duration of the submission (temporarily, while recording). +It's added with `UNINITIALIZED` state and transitioned into `empty()` state. +When this texture is presented, we remove it from the device tracker as well as +extract it from the hub. +!*/ + +use std::borrow::Borrow; + +#[cfg(feature = "trace")] +use crate::device::trace::Action; +use crate::{ + conv, + device::{DeviceError, MissingDownlevelFlags}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Input, Token}, + id::{DeviceId, SurfaceId, TextureId, Valid}, + init_tracker::TextureInitTracker, + resource, track, LifeGuard, Stored, +}; + +use hal::{Queue as _, Surface as _}; +use thiserror::Error; +use wgt::SurfaceStatus as Status; + +const FRAME_TIMEOUT_MS: u32 = 1000; +pub const DESIRED_NUM_FRAMES: u32 = 3; + +#[derive(Debug)] +pub(crate) struct Presentation { + pub(crate) device_id: Stored<DeviceId>, + pub(crate) config: wgt::SurfaceConfiguration<Vec<wgt::TextureFormat>>, + #[allow(unused)] + pub(crate) num_frames: u32, + pub(crate) acquired_texture: Option<Stored<TextureId>>, +} + +impl Presentation { + pub(crate) fn backend(&self) -> wgt::Backend { + crate::id::TypedId::unzip(self.device_id.value.0).2 + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum SurfaceError { + #[error("Surface is invalid")] + Invalid, + #[error("Surface is not configured for presentation")] + NotConfigured, + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Surface image is already acquired")] + AlreadyAcquired, + #[error("Acquired frame is still referenced")] + StillReferenced, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum ConfigureSurfaceError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Invalid surface")] + InvalidSurface, + #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] + InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), + #[error("`SurfaceOutput` must be dropped before a new `Surface` is made")] + PreviousOutputExists, + #[error("Both `Surface` width and height must be non-zero. Wait to recreate the `Surface` until the window has non-zero area.")] + ZeroArea, + #[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: wgt::TextureFormat, + available: Vec<wgt::TextureFormat>, + }, + #[error("Requested present mode {requested:?} is not in the list of supported present modes: {available:?}")] + UnsupportedPresentMode { + requested: wgt::PresentMode, + available: Vec<wgt::PresentMode>, + }, + #[error("Requested alpha mode {requested:?} is not in the list of supported alpha modes: {available:?}")] + UnsupportedAlphaMode { + requested: wgt::CompositeAlphaMode, + available: Vec<wgt::CompositeAlphaMode>, + }, + #[error("Requested usage is not supported")] + UnsupportedUsage, +} + +#[repr(C)] +#[derive(Debug)] +pub struct SurfaceOutput { + pub status: Status, + pub texture_id: Option<TextureId>, +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + pub fn surface_get_current_texture<A: HalApi>( + &self, + surface_id: SurfaceId, + texture_id_in: Input<G, TextureId>, + ) -> Result<SurfaceOutput, SurfaceError> { + profiling::scope!("SwapChain::get_next_texture"); + + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.textures.prepare(texture_id_in); + + let (mut surface_guard, mut token) = self.surfaces.write(&mut token); + let surface = surface_guard + .get_mut(surface_id) + .map_err(|_| SurfaceError::Invalid)?; + let (device_guard, mut token) = hub.devices.read(&mut token); + + let (device, config) = match surface.presentation { + Some(ref present) => { + let device = &device_guard[present.device_id.value]; + (device, present.config.clone()) + } + None => return Err(SurfaceError::NotConfigured), + }; + + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(Action::GetSurfaceTexture { + id: fid.id(), + parent_id: surface_id, + }); + } + #[cfg(not(feature = "trace"))] + let _ = device; + + let suf = A::get_surface_mut(surface); + let (texture_id, status) = match unsafe { + suf.unwrap() + .raw + .acquire_texture(Some(std::time::Duration::from_millis( + FRAME_TIMEOUT_MS as u64, + ))) + } { + Ok(Some(ast)) => { + let clear_view_desc = hal::TextureViewDescriptor { + label: Some("(wgpu internal) clear surface texture view"), + format: config.format, + dimension: wgt::TextureViewDimension::D2, + usage: hal::TextureUses::COLOR_TARGET, + range: wgt::ImageSubresourceRange::default(), + }; + let mut clear_views = smallvec::SmallVec::new(); + clear_views.push( + unsafe { + hal::Device::create_texture_view( + &device.raw, + ast.texture.borrow(), + &clear_view_desc, + ) + } + .map_err(DeviceError::from)?, + ); + + let present = surface.presentation.as_mut().unwrap(); + let texture = resource::Texture { + inner: resource::TextureInner::Surface { + raw: ast.texture, + parent_id: Valid(surface_id), + has_work: false, + }, + device_id: present.device_id.clone(), + desc: wgt::TextureDescriptor { + label: (), + size: wgt::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + sample_count: 1, + mip_level_count: 1, + format: config.format, + dimension: wgt::TextureDimension::D2, + usage: config.usage, + view_formats: config.view_formats, + }, + hal_usage: conv::map_texture_usage(config.usage, config.format.into()), + format_features: wgt::TextureFormatFeatures { + allowed_usages: wgt::TextureUsages::RENDER_ATTACHMENT, + flags: wgt::TextureFormatFeatureFlags::MULTISAMPLE_X4 + | wgt::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE, + }, + initialization_status: TextureInitTracker::new(1, 1), + full_range: track::TextureSelector { + layers: 0..1, + mips: 0..1, + }, + life_guard: LifeGuard::new("<Surface>"), + clear_mode: resource::TextureClearMode::RenderPass { + clear_views, + is_color: true, + }, + }; + + let ref_count = texture.life_guard.add_ref(); + let id = fid.assign(texture, &mut token); + + { + // register it in the device tracker as uninitialized + let mut trackers = device.trackers.lock(); + trackers.textures.insert_single( + id.0, + ref_count.clone(), + hal::TextureUses::UNINITIALIZED, + ); + } + + if present.acquired_texture.is_some() { + return Err(SurfaceError::AlreadyAcquired); + } + present.acquired_texture = Some(Stored { + value: id, + ref_count, + }); + + let status = if ast.suboptimal { + Status::Suboptimal + } else { + Status::Good + }; + (Some(id.0), status) + } + Ok(None) => (None, Status::Timeout), + Err(err) => ( + None, + match err { + hal::SurfaceError::Lost => Status::Lost, + hal::SurfaceError::Device(err) => { + return Err(DeviceError::from(err).into()); + } + hal::SurfaceError::Outdated => Status::Outdated, + hal::SurfaceError::Other(msg) => { + log::error!("acquire error: {}", msg); + Status::Lost + } + }, + ), + }; + + Ok(SurfaceOutput { status, texture_id }) + } + + pub fn surface_present<A: HalApi>( + &self, + surface_id: SurfaceId, + ) -> Result<Status, SurfaceError> { + profiling::scope!("SwapChain::present"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut surface_guard, mut token) = self.surfaces.write(&mut token); + let surface = surface_guard + .get_mut(surface_id) + .map_err(|_| SurfaceError::Invalid)?; + let (mut device_guard, mut token) = hub.devices.write(&mut token); + + let present = match surface.presentation { + Some(ref mut present) => present, + None => return Err(SurfaceError::NotConfigured), + }; + + let device = &mut device_guard[present.device_id.value]; + + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(Action::Present(surface_id)); + } + + let result = { + let texture_id = present + .acquired_texture + .take() + .ok_or(SurfaceError::AlreadyAcquired)?; + + // The texture ID got added to the device tracker by `submit()`, + // and now we are moving it away. + log::debug!( + "Removing swapchain texture {:?} from the device tracker", + texture_id.value + ); + device.trackers.lock().textures.remove(texture_id.value); + + let (texture, _) = hub.textures.unregister(texture_id.value.0, &mut token); + if let Some(texture) = texture { + if let resource::TextureClearMode::RenderPass { clear_views, .. } = + texture.clear_mode + { + for clear_view in clear_views { + unsafe { + hal::Device::destroy_texture_view(&device.raw, clear_view); + } + } + } + + let suf = A::get_surface_mut(surface); + match texture.inner { + resource::TextureInner::Surface { + raw, + parent_id, + has_work, + } => { + if surface_id != parent_id.0 { + log::error!("Presented frame is from a different surface"); + Err(hal::SurfaceError::Lost) + } else if !has_work { + log::error!("No work has been submitted for this frame"); + unsafe { suf.unwrap().raw.discard_texture(raw) }; + Err(hal::SurfaceError::Outdated) + } else { + unsafe { device.queue.present(&mut suf.unwrap().raw, raw) } + } + } + resource::TextureInner::Native { .. } => unreachable!(), + } + } else { + Err(hal::SurfaceError::Outdated) //TODO? + } + }; + + log::debug!("Presented. End of Frame"); + + match result { + Ok(()) => Ok(Status::Good), + Err(err) => match err { + hal::SurfaceError::Lost => Ok(Status::Lost), + hal::SurfaceError::Device(err) => Err(SurfaceError::from(DeviceError::from(err))), + hal::SurfaceError::Outdated => Ok(Status::Outdated), + hal::SurfaceError::Other(msg) => { + log::error!("acquire error: {}", msg); + Err(SurfaceError::Invalid) + } + }, + } + } + + pub fn surface_texture_discard<A: HalApi>( + &self, + surface_id: SurfaceId, + ) -> Result<(), SurfaceError> { + profiling::scope!("SwapChain::discard"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut surface_guard, mut token) = self.surfaces.write(&mut token); + let surface = surface_guard + .get_mut(surface_id) + .map_err(|_| SurfaceError::Invalid)?; + let (mut device_guard, mut token) = hub.devices.write(&mut token); + + let present = match surface.presentation { + Some(ref mut present) => present, + None => return Err(SurfaceError::NotConfigured), + }; + + let device = &mut device_guard[present.device_id.value]; + + #[cfg(feature = "trace")] + if let Some(ref trace) = device.trace { + trace.lock().add(Action::DiscardSurfaceTexture(surface_id)); + } + + { + let texture_id = present + .acquired_texture + .take() + .ok_or(SurfaceError::AlreadyAcquired)?; + + // The texture ID got added to the device tracker by `submit()`, + // and now we are moving it away. + device.trackers.lock().textures.remove(texture_id.value); + + let (texture, _) = hub.textures.unregister(texture_id.value.0, &mut token); + if let Some(texture) = texture { + let suf = A::get_surface_mut(surface); + match texture.inner { + resource::TextureInner::Surface { + raw, + parent_id, + has_work: _, + } => { + if surface_id == parent_id.0 { + unsafe { suf.unwrap().raw.discard_texture(raw) }; + } else { + log::warn!("Surface texture is outdated"); + } + } + resource::TextureInner::Native { .. } => unreachable!(), + } + } + } + + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/resource.rs b/third_party/rust/wgpu-core/src/resource.rs new file mode 100644 index 0000000000..6ea7b9cce0 --- /dev/null +++ b/third_party/rust/wgpu-core/src/resource.rs @@ -0,0 +1,806 @@ +use crate::{ + device::{DeviceError, HostMap, MissingDownlevelFlags, MissingFeatures}, + hub::{Global, GlobalIdentityHandlerFactory, HalApi, Resource, Token}, + id::{AdapterId, DeviceId, SurfaceId, TextureId, Valid}, + init_tracker::{BufferInitTracker, TextureInitTracker}, + track::TextureSelector, + validation::MissingBufferUsageError, + Label, LifeGuard, RefCount, Stored, +}; + +use smallvec::SmallVec; +use thiserror::Error; + +use std::{borrow::Borrow, ops::Range, ptr::NonNull}; + +/// The status code provided to the buffer mapping callback. +/// +/// This is very similar to `BufferAccessResult`, except that this is FFI-friendly. +#[repr(C)] +#[derive(Debug)] +pub enum BufferMapAsyncStatus { + /// The Buffer is sucessfully mapped, `get_mapped_range` can be called. + /// + /// All other variants of this enum represent failures to map the buffer. + Success, + /// The buffer is already mapped. + /// + /// While this is treated as an error, it does not prevent mapped range from being accessed. + AlreadyMapped, + /// Mapping was already requested. + MapAlreadyPending, + /// An unknown error. + Error, + /// Mapping was aborted (by unmapping or destroying the buffer before mapping + /// happened). + Aborted, + /// The context is Lost. + ContextLost, + /// The buffer is in an invalid state. + Invalid, + /// The range isn't fully contained in the buffer. + InvalidRange, + /// The range isn't properly aligned. + InvalidAlignment, + /// Incompatible usage flags. + InvalidUsageFlags, +} + +pub(crate) enum BufferMapState<A: hal::Api> { + /// Mapped at creation. + Init { + ptr: NonNull<u8>, + stage_buffer: A::Buffer, + needs_flush: bool, + }, + /// Waiting for GPU to be done before mapping + Waiting(BufferPendingMapping), + /// Mapped + Active { + ptr: NonNull<u8>, + range: hal::MemoryRange, + host: HostMap, + }, + /// Not mapped + Idle, +} + +unsafe impl<A: hal::Api> Send for BufferMapState<A> {} +unsafe impl<A: hal::Api> Sync for BufferMapState<A> {} + +#[repr(C)] +pub struct BufferMapCallbackC { + pub callback: unsafe extern "C" fn(status: BufferMapAsyncStatus, user_data: *mut u8), + pub user_data: *mut u8, +} + +unsafe impl Send for BufferMapCallbackC {} + +pub struct BufferMapCallback { + // We wrap this so creating the enum in the C variant can be unsafe, + // allowing our call function to be safe. + inner: Option<BufferMapCallbackInner>, +} + +enum BufferMapCallbackInner { + Rust { + callback: Box<dyn FnOnce(BufferAccessResult) + Send + 'static>, + }, + C { + inner: BufferMapCallbackC, + }, +} + +impl BufferMapCallback { + pub fn from_rust(callback: Box<dyn FnOnce(BufferAccessResult) + Send + 'static>) -> Self { + Self { + inner: Some(BufferMapCallbackInner::Rust { callback }), + } + } + + /// # Safety + /// + /// - The callback pointer must be valid to call with the provided user_data + /// pointer. + /// + /// - Both pointers must point to valid memory until the callback is + /// invoked, which may happen at an unspecified time. + pub unsafe fn from_c(inner: BufferMapCallbackC) -> Self { + Self { + inner: Some(BufferMapCallbackInner::C { inner }), + } + } + + pub(crate) fn call(mut self, result: BufferAccessResult) { + match self.inner.take() { + Some(BufferMapCallbackInner::Rust { callback }) => { + callback(result); + } + // SAFETY: the contract of the call to from_c says that this unsafe is sound. + Some(BufferMapCallbackInner::C { inner }) => unsafe { + let status = match result { + Ok(()) => BufferMapAsyncStatus::Success, + Err(BufferAccessError::Device(_)) => BufferMapAsyncStatus::ContextLost, + Err(BufferAccessError::Invalid) | Err(BufferAccessError::Destroyed) => { + BufferMapAsyncStatus::Invalid + } + Err(BufferAccessError::AlreadyMapped) => BufferMapAsyncStatus::AlreadyMapped, + Err(BufferAccessError::MapAlreadyPending) => { + BufferMapAsyncStatus::MapAlreadyPending + } + Err(BufferAccessError::MissingBufferUsage(_)) => { + BufferMapAsyncStatus::InvalidUsageFlags + } + Err(BufferAccessError::UnalignedRange) + | Err(BufferAccessError::UnalignedRangeSize { .. }) + | Err(BufferAccessError::UnalignedOffset { .. }) => { + BufferMapAsyncStatus::InvalidAlignment + } + Err(BufferAccessError::OutOfBoundsUnderrun { .. }) + | Err(BufferAccessError::OutOfBoundsOverrun { .. }) + | Err(BufferAccessError::NegativeRange { .. }) => { + BufferMapAsyncStatus::InvalidRange + } + Err(_) => BufferMapAsyncStatus::Error, + }; + + (inner.callback)(status, inner.user_data); + }, + None => { + panic!("Map callback invoked twice"); + } + } + } +} + +impl Drop for BufferMapCallback { + fn drop(&mut self) { + if self.inner.is_some() { + panic!("Map callback was leaked"); + } + } +} + +pub struct BufferMapOperation { + pub host: HostMap, + pub callback: BufferMapCallback, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum BufferAccessError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Buffer map failed")] + Failed, + #[error("Buffer is invalid")] + Invalid, + #[error("Buffer is destroyed")] + Destroyed, + #[error("Buffer is already mapped")] + AlreadyMapped, + #[error("Buffer map is pending")] + MapAlreadyPending, + #[error(transparent)] + MissingBufferUsage(#[from] MissingBufferUsageError), + #[error("Buffer is not mapped")] + NotMapped, + #[error( + "Buffer map range must start aligned to `MAP_ALIGNMENT` and end to `COPY_BUFFER_ALIGNMENT`" + )] + UnalignedRange, + #[error("Buffer offset invalid: offset {offset} must be multiple of 8")] + UnalignedOffset { offset: wgt::BufferAddress }, + #[error("Buffer range size invalid: range_size {range_size} must be multiple of 4")] + UnalignedRangeSize { range_size: wgt::BufferAddress }, + #[error("Buffer access out of bounds: index {index} would underrun the buffer (limit: {min})")] + OutOfBoundsUnderrun { + index: wgt::BufferAddress, + min: wgt::BufferAddress, + }, + #[error( + "Buffer access out of bounds: last index {index} would overrun the buffer (limit: {max})" + )] + OutOfBoundsOverrun { + index: wgt::BufferAddress, + max: wgt::BufferAddress, + }, + #[error("Buffer map range start {start} is greater than end {end}")] + NegativeRange { + start: wgt::BufferAddress, + end: wgt::BufferAddress, + }, + #[error("Buffer map aborted")] + MapAborted, +} + +pub type BufferAccessResult = Result<(), BufferAccessError>; +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>>; + +pub struct Buffer<A: hal::Api> { + pub(crate) raw: Option<A::Buffer>, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) usage: wgt::BufferUsages, + pub(crate) size: wgt::BufferAddress, + pub(crate) initialization_status: BufferInitTracker, + pub(crate) sync_mapped_writes: Option<hal::MemoryRange>, + pub(crate) life_guard: LifeGuard, + pub(crate) map_state: BufferMapState<A>, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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("Invalid usage flags {0:?}")] + InvalidUsage(wgt::BufferUsages), + #[error("`MAP` usage can only be combined with the opposite `COPY`, requested {0:?}")] + UsageMismatch(wgt::BufferUsages), + #[error("Buffer size {requested} is greater than the maximum buffer size ({maximum})")] + MaxBufferSize { requested: u64, maximum: u64 }, + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +impl<A: hal::Api> Resource for Buffer<A> { + const TYPE: &'static str = "Buffer"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +/// A temporary buffer, consumed by the command that uses it. +/// +/// A [`StagingBuffer`] is designed for one-shot uploads of data to the GPU. It +/// is always created mapped, and the command that uses it destroys the buffer +/// when it is done. +/// +/// [`StagingBuffer`]s can be created with [`queue_create_staging_buffer`] and +/// used with [`queue_write_staging_buffer`]. They are also used internally by +/// operations like [`queue_write_texture`] that need to upload data to the GPU, +/// but that don't belong to any particular wgpu command buffer. +/// +/// Used `StagingBuffer`s are accumulated in [`Device::pending_writes`], to be +/// freed once their associated operation's queue submission has finished +/// execution. +/// +/// [`queue_create_staging_buffer`]: Global::queue_create_staging_buffer +/// [`queue_write_staging_buffer`]: Global::queue_write_staging_buffer +/// [`queue_write_texture`]: Global::queue_write_texture +/// [`Device::pending_writes`]: crate::device::Device +pub struct StagingBuffer<A: hal::Api> { + pub(crate) raw: A::Buffer, + pub(crate) size: wgt::BufferAddress, + pub(crate) is_coherent: bool, +} + +impl<A: hal::Api> Resource for StagingBuffer<A> { + const TYPE: &'static str = "StagingBuffer"; + + fn life_guard(&self) -> &LifeGuard { + unreachable!() + } + + fn label(&self) -> &str { + "<StagingBuffer>" + } +} + +pub type TextureDescriptor<'a> = wgt::TextureDescriptor<Label<'a>, Vec<wgt::TextureFormat>>; + +#[derive(Debug)] +pub(crate) enum TextureInner<A: hal::Api> { + Native { + raw: Option<A::Texture>, + }, + Surface { + raw: A::SurfaceTexture, + parent_id: Valid<SurfaceId>, + has_work: bool, + }, +} + +impl<A: hal::Api> TextureInner<A> { + pub fn as_raw(&self) -> Option<&A::Texture> { + match *self { + Self::Native { raw: Some(ref tex) } => Some(tex), + Self::Native { raw: None } => None, + Self::Surface { ref raw, .. } => Some(raw.borrow()), + } + } +} + +#[derive(Debug)] +pub enum TextureClearMode<A: hal::Api> { + BufferCopy, + // View for clear via RenderPass for every subsurface (mip/layer/slice) + RenderPass { + clear_views: SmallVec<[A::TextureView; 1]>, + is_color: bool, + }, + // Texture can't be cleared, attempting to do so will cause panic. + // (either because it is impossible for the type of texture or it is being destroyed) + None, +} + +#[derive(Debug)] +pub struct Texture<A: hal::Api> { + pub(crate) inner: TextureInner<A>, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) desc: wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>, + pub(crate) hal_usage: hal::TextureUses, + pub(crate) format_features: wgt::TextureFormatFeatures, + pub(crate) initialization_status: TextureInitTracker, + pub(crate) full_range: TextureSelector, + pub(crate) life_guard: LifeGuard, + pub(crate) clear_mode: TextureClearMode<A>, +} + +impl<A: hal::Api> Texture<A> { + pub(crate) fn get_clear_view(&self, mip_level: u32, depth_or_layer: u32) -> &A::TextureView { + match self.clear_mode { + TextureClearMode::BufferCopy => { + panic!("Given texture is cleared with buffer copies, not render passes") + } + TextureClearMode::None => { + panic!("Given texture can't be cleared") + } + TextureClearMode::RenderPass { + ref clear_views, .. + } => { + let index = if self.desc.dimension == wgt::TextureDimension::D3 { + (0..mip_level).fold(0, |acc, mip| { + acc + (self.desc.size.depth_or_array_layers >> mip).max(1) + }) + } else { + mip_level * self.desc.size.depth_or_array_layers + } + depth_or_layer; + &clear_views[index as usize] + } + } + } +} + +impl<G: GlobalIdentityHandlerFactory> Global<G> { + /// # Safety + /// + /// - The raw texture handle must not be manually destroyed + pub unsafe fn texture_as_hal<A: HalApi, F: FnOnce(Option<&A::Texture>)>( + &self, + id: TextureId, + hal_texture_callback: F, + ) { + profiling::scope!("Texture::as_hal"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (guard, _) = hub.textures.read(&mut token); + let texture = guard.try_get(id).ok().flatten(); + let hal_texture = texture.and_then(|tex| tex.inner.as_raw()); + + hal_texture_callback(hal_texture); + } + + /// # Safety + /// + /// - The raw adapter handle must not be manually destroyed + pub unsafe fn adapter_as_hal<A: HalApi, F: FnOnce(Option<&A::Adapter>) -> R, R>( + &self, + id: AdapterId, + hal_adapter_callback: F, + ) -> R { + profiling::scope!("Adapter::as_hal"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (guard, _) = hub.adapters.read(&mut token); + let adapter = guard.try_get(id).ok().flatten(); + let hal_adapter = adapter.map(|adapter| &adapter.raw.adapter); + + hal_adapter_callback(hal_adapter) + } + + /// # Safety + /// + /// - The raw device handle must not be manually destroyed + pub unsafe fn device_as_hal<A: HalApi, F: FnOnce(Option<&A::Device>) -> R, R>( + &self, + id: DeviceId, + hal_device_callback: F, + ) -> R { + profiling::scope!("Device::as_hal"); + + let hub = A::hub(self); + let mut token = Token::root(); + let (guard, _) = hub.devices.read(&mut token); + let device = guard.try_get(id).ok().flatten(); + let hal_device = device.map(|device| &device.raw); + + hal_device_callback(hal_device) + } + + /// # Safety + /// - The raw surface handle must not be manually destroyed + pub unsafe fn surface_as_hal_mut<A: HalApi, F: FnOnce(Option<&mut A::Surface>) -> R, R>( + &self, + id: SurfaceId, + hal_surface_callback: F, + ) -> R { + profiling::scope!("Surface::as_hal_mut"); + + let mut token = Token::root(); + let (mut guard, _) = self.surfaces.write(&mut token); + let surface = guard.get_mut(id).ok(); + let hal_surface = surface + .and_then(|surface| A::get_surface_mut(surface)) + .map(|surface| &mut surface.raw); + + hal_surface_callback(hal_surface) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum TextureErrorDimension { + X, + Y, + Z, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum TextureDimensionError { + #[error("Dimension {0:?} is zero")] + Zero(TextureErrorDimension), + #[error("Dimension {dim:?} value {given} exceeds the limit of {limit}")] + LimitExceeded { + dim: TextureErrorDimension, + given: u32, + limit: u32, + }, + #[error("Sample count {0} is invalid")] + InvalidSampleCount(u32), + #[error("Width {width} is not a multiple of {format:?}'s block width ({block_width})")] + NotMultipleOfBlockWidth { + width: u32, + block_width: u32, + format: wgt::TextureFormat, + }, + #[error("Height {height} is not a multiple of {format:?}'s block height ({block_height})")] + NotMultipleOfBlockHeight { + height: u32, + block_height: u32, + format: wgt::TextureFormat, + }, + #[error("Multisampled texture depth or array layers must be 1, got {0}")] + MultisampledDepthOrArrayLayer(u32), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateTextureError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Invalid usage flags {0:?}")] + InvalidUsage(wgt::TextureUsages), + #[error(transparent)] + InvalidDimension(#[from] TextureDimensionError), + #[error("Depth texture ({1:?}) can't be created as {0:?}")] + InvalidDepthDimension(wgt::TextureDimension, wgt::TextureFormat), + #[error("Compressed texture ({1:?}) can't be created as {0:?}")] + InvalidCompressedDimension(wgt::TextureDimension, wgt::TextureFormat), + #[error( + "Texture descriptor mip level count {requested} is invalid, maximum allowed is {maximum}" + )] + InvalidMipLevelCount { requested: u32, maximum: u32 }, + #[error( + "Texture usages {0:?} are not allowed on a texture of type {1:?}{}", + if *.2 { " due to downlevel restrictions" } else { "" } + )] + InvalidFormatUsages(wgt::TextureUsages, wgt::TextureFormat, bool), + #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] + InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), + #[error("Texture usages {0:?} are not allowed on a texture of dimensions {1:?}")] + InvalidDimensionUsages(wgt::TextureUsages, wgt::TextureDimension), + #[error("Texture usage STORAGE_BINDING is not allowed for multisampled textures")] + InvalidMultisampledStorageBinding, + #[error("Format {0:?} does not support multisampling")] + InvalidMultisampledFormat(wgt::TextureFormat), + #[error("Sample count {0} is not supported by format {1:?} on this device. It may be supported by your adapter through the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature.")] + InvalidSampleCount(u32, wgt::TextureFormat), + #[error("Multisampled textures must have RENDER_ATTACHMENT usage")] + MultisampledNotRenderAttachment, + #[error("Texture format {0:?} can't be used due to missing features")] + MissingFeatures(wgt::TextureFormat, #[source] MissingFeatures), + #[error(transparent)] + MissingDownlevelFlags(#[from] MissingDownlevelFlags), +} + +impl<A: hal::Api> Resource for Texture<A> { + const TYPE: &'static str = "Texture"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +impl<A: hal::Api> Borrow<TextureSelector> for Texture<A> { + fn borrow(&self) -> &TextureSelector { + &self.full_range + } +} + +/// Describes a [`TextureView`]. +#[derive(Clone, Debug, Default, Eq, 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 `D1`. + /// - For 2D textures it must be one of `D2`, `D2Array`, `Cube`, or `CubeArray`. + /// - For 3D textures it must be `D3`. + pub dimension: Option<wgt::TextureViewDimension>, + /// Range within the texture that is accessible via this view. + pub range: wgt::ImageSubresourceRange, +} + +#[derive(Debug)] +pub(crate) struct HalTextureViewDescriptor { + pub format: wgt::TextureFormat, + pub dimension: wgt::TextureViewDimension, + pub range: wgt::ImageSubresourceRange, +} + +impl HalTextureViewDescriptor { + pub fn aspects(&self) -> hal::FormatAspects { + hal::FormatAspects::new(self.format, self.range.aspect) + } +} + +#[derive(Debug, Copy, Clone, Error)] +pub enum TextureViewNotRenderableReason { + #[error("The texture this view references doesn't include the RENDER_ATTACHMENT usage. Provided usages: {0:?}")] + Usage(wgt::TextureUsages), + #[error("The dimension of this texture view is not 2D. View dimension: {0:?}")] + Dimension(wgt::TextureViewDimension), + #[error("This texture view has more than one mipmap level. View mipmap levels: {0:?}")] + MipLevelCount(u32), + #[error("This texture view has more than one array layer. View array layers: {0:?}")] + ArrayLayerCount(u32), + #[error( + "The aspects of this texture view are a subset of the aspects in the original texture. Aspects: {0:?}" + )] + Aspects(hal::FormatAspects), +} + +#[derive(Debug)] +pub struct TextureView<A: hal::Api> { + pub(crate) raw: A::TextureView, + // The parent's refcount is held alive, but the parent may still be deleted + // if it's a surface texture. TODO: make this cleaner. + pub(crate) parent_id: Stored<TextureId>, + pub(crate) device_id: Stored<DeviceId>, + //TODO: store device_id for quick access? + pub(crate) desc: HalTextureViewDescriptor, + pub(crate) format_features: wgt::TextureFormatFeatures, + /// This is `Err` only if the texture view is not renderable + pub(crate) render_extent: Result<wgt::Extent3d, TextureViewNotRenderableReason>, + pub(crate) samples: u32, + pub(crate) selector: TextureSelector, + pub(crate) life_guard: LifeGuard, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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 `{texture:?}`")] + InvalidTextureViewDimension { + view: wgt::TextureViewDimension, + texture: wgt::TextureDimension, + }, + #[error("Invalid texture view dimension `{0:?}` of a multisampled texture")] + InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension), + #[error("Invalid texture depth `{depth}` for texture view of dimension `Cubemap`. Cubemap views must use images of size 6.")] + InvalidCubemapTextureDepth { depth: u32 }, + #[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: u32 }, + #[error("Source texture width and height must be equal for a texture view of dimension `Cube`/`CubeArray`")] + InvalidCubeTextureViewSize, + #[error("Mip level count is 0")] + ZeroMipLevelCount, + #[error("Array layer count is 0")] + ZeroArrayLayerCount, + #[error( + "TextureView mip level count + base mip level {requested} must be <= Texture mip level count {total}" + )] + TooManyMipLevels { requested: u32, total: u32 }, + #[error("TextureView array layer count + base array layer {requested} must be <= Texture depth/array layer count {total}")] + TooManyArrayLayers { requested: u32, total: u32 }, + #[error("Requested array layer count {requested} is not valid for the target view dimension {dim:?}")] + InvalidArrayLayerCount { + requested: u32, + dim: wgt::TextureViewDimension, + }, + #[error("Aspect {requested_aspect:?} is not in the source texture format {texture_format:?}")] + InvalidAspect { + texture_format: wgt::TextureFormat, + requested_aspect: wgt::TextureAspect, + }, + #[error("Unable to view texture {texture:?} as {view:?}")] + FormatReinterpretation { + texture: wgt::TextureFormat, + view: wgt::TextureFormat, + }, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum TextureViewDestroyError {} + +impl<A: hal::Api> Resource for TextureView<A> { + const TYPE: &'static str = "TextureView"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +/// 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>, + /// Must be at least 1. If this is not 1, all filter modes must be linear. + pub anisotropy_clamp: u16, + /// Border color to use when address_mode is + /// [`AddressMode::ClampToBorder`](wgt::AddressMode::ClampToBorder) + pub border_color: Option<wgt::SamplerBorderColor>, +} + +#[derive(Debug)] +pub struct Sampler<A: hal::Api> { + pub(crate) raw: A::Sampler, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) life_guard: LifeGuard, + /// `true` if this is a comparison sampler + pub(crate) comparison: bool, + /// `true` if this is a filtering sampler + pub(crate) filtering: bool, +} + +#[derive(Copy, Clone)] +pub enum SamplerFilterErrorType { + MagFilter, + MinFilter, + MipmapFilter, +} + +impl std::fmt::Debug for SamplerFilterErrorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + SamplerFilterErrorType::MagFilter => write!(f, "magFilter"), + SamplerFilterErrorType::MinFilter => write!(f, "minFilter"), + SamplerFilterErrorType::MipmapFilter => write!(f, "mipmapFilter"), + } + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateSamplerError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("Invalid lodMinClamp: {0}. Must be greater or equal to 0.0")] + InvalidLodMinClamp(f32), + #[error("Invalid lodMaxClamp: {lod_max_clamp}. Must be greater or equal to lodMinClamp (which is {lod_min_clamp}).")] + InvalidLodMaxClamp { + lod_min_clamp: f32, + lod_max_clamp: f32, + }, + #[error("Invalid anisotropic clamp: {0}. Must be at least 1.")] + InvalidAnisotropy(u16), + #[error("Invalid filter mode for {filter_type:?}: {filter_mode:?}. When anistropic clamp is not 1 (it is {anisotropic_clamp}), all filter modes must be linear.")] + InvalidFilterModeWithAnisotropy { + filter_type: SamplerFilterErrorType, + filter_mode: wgt::FilterMode, + anisotropic_clamp: u16, + }, + #[error("Cannot create any more samplers")] + TooManyObjects, + /// AddressMode::ClampToBorder requires feature ADDRESS_MODE_CLAMP_TO_BORDER. + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), +} + +impl<A: hal::Api> Resource for Sampler<A> { + const TYPE: &'static str = "Sampler"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum CreateQuerySetError { + #[error(transparent)] + Device(#[from] DeviceError), + #[error("QuerySets cannot be made with zero queries")] + ZeroCount, + #[error("{count} is too many queries for a single QuerySet. QuerySets cannot be made more than {maximum} queries.")] + TooManyQueries { count: u32, maximum: u32 }, + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), +} + +pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor<Label<'a>>; + +#[derive(Debug)] +pub struct QuerySet<A: hal::Api> { + pub(crate) raw: A::QuerySet, + pub(crate) device_id: Stored<DeviceId>, + pub(crate) life_guard: LifeGuard, + pub(crate) desc: wgt::QuerySetDescriptor<()>, +} + +impl<A: hal::Api> Resource for QuerySet<A> { + const TYPE: &'static str = "QuerySet"; + + fn life_guard(&self) -> &LifeGuard { + &self.life_guard + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum DestroyError { + #[error("Resource is invalid")] + Invalid, + #[error("Resource is already destroyed")] + AlreadyDestroyed, +} diff --git a/third_party/rust/wgpu-core/src/track/buffer.rs b/third_party/rust/wgpu-core/src/track/buffer.rs new file mode 100644 index 0000000000..b7682968b2 --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/buffer.rs @@ -0,0 +1,791 @@ +/*! Buffer Trackers + * + * Buffers are represented by a single state for the whole resource, + * a 16 bit bitflag of buffer usages. Because there is only ever + * one subresource, they have no selector. +!*/ + +use std::{borrow::Cow, marker::PhantomData, vec::Drain}; + +use super::PendingTransition; +use crate::{ + hub, + id::{BufferId, TypedId, Valid}, + resource::Buffer, + track::{ + invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, + ResourceUses, UsageConflict, + }, + LifeGuard, RefCount, +}; +use hal::BufferUses; +use wgt::{strict_assert, strict_assert_eq}; + +impl ResourceUses for BufferUses { + const EXCLUSIVE: Self = Self::EXCLUSIVE; + + type Id = BufferId; + type Selector = (); + + fn bits(self) -> u16 { + Self::bits(&self) + } + + fn all_ordered(self) -> bool { + Self::ORDERED.contains(self) + } + + fn any_exclusive(self) -> bool { + self.intersects(Self::EXCLUSIVE) + } +} + +/// Stores all the buffers that a bind group stores. +pub(crate) struct BufferBindGroupState<A: hub::HalApi> { + buffers: Vec<(Valid<BufferId>, RefCount, BufferUses)>, + + _phantom: PhantomData<A>, +} +impl<A: hub::HalApi> BufferBindGroupState<A> { + pub fn new() -> Self { + Self { + buffers: Vec::new(), + + _phantom: PhantomData, + } + } + + /// Optimize the buffer bind group state by sorting it by ID. + /// + /// When this list of states is merged into a tracker, the memory + /// accesses will be in a constant assending order. + pub(crate) fn optimize(&mut self) { + self.buffers + .sort_unstable_by_key(|&(id, _, _)| id.0.unzip().0); + } + + /// Returns a list of all buffers tracked. May contain duplicates. + pub fn used(&self) -> impl Iterator<Item = Valid<BufferId>> + '_ { + self.buffers.iter().map(|&(id, _, _)| id) + } + + /// Adds the given resource with the given state. + pub fn add_single<'a>( + &mut self, + storage: &'a hub::Storage<Buffer<A>, BufferId>, + id: BufferId, + state: BufferUses, + ) -> Option<&'a Buffer<A>> { + let buffer = storage.get(id).ok()?; + + self.buffers + .push((Valid(id), buffer.life_guard.add_ref(), state)); + + Some(buffer) + } +} + +/// Stores all buffer state within a single usage scope. +#[derive(Debug)] +pub(crate) struct BufferUsageScope<A: hub::HalApi> { + state: Vec<BufferUses>, + + metadata: ResourceMetadata<A>, +} + +impl<A: hub::HalApi> BufferUsageScope<A> { + pub fn new() -> Self { + Self { + state: Vec::new(), + + metadata: ResourceMetadata::new(), + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.state.len()); + self.metadata.tracker_assert_in_bounds(index); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Buffer ID before + /// all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.state.resize(size, BufferUses::empty()); + self.metadata.set_size(size); + } + + /// Extend the vectors to let the given index be valid. + fn allow_index(&mut self, index: usize) { + if index >= self.state.len() { + self.set_size(index + 1); + } + } + + /// Returns a list of all buffers tracked. + pub fn used(&self) -> impl Iterator<Item = Valid<BufferId>> + '_ { + self.metadata.owned_ids() + } + + /// Merge the list of buffer states in the given bind group into this usage scope. + /// + /// If any of the resulting states is invalid, stops the merge and returns a usage + /// conflict with the details of the invalid state. + /// + /// Because bind groups do not check if the union of all their states is valid, + /// this method is allowed to return Err on the first bind group bound. + /// + /// # Safety + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + pub unsafe fn merge_bind_group( + &mut self, + bind_group: &BufferBindGroupState<A>, + ) -> Result<(), UsageConflict> { + for &(id, ref ref_count, state) in &bind_group.buffers { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + unsafe { + insert_or_merge( + None, + None, + &mut self.state, + &mut self.metadata, + index32, + index, + BufferStateProvider::Direct { state }, + ResourceMetadataProvider::Direct { + epoch, + ref_count: Cow::Borrowed(ref_count), + }, + )? + }; + } + + Ok(()) + } + + /// Merge the list of buffer states in the given usage scope into this UsageScope. + /// + /// If any of the resulting states is invalid, stops the merge and returns a usage + /// conflict with the details of the invalid state. + /// + /// If the given tracker uses IDs higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn merge_usage_scope(&mut self, scope: &Self) -> Result<(), UsageConflict> { + let incoming_size = scope.state.len(); + if incoming_size > self.state.len() { + self.set_size(incoming_size); + } + + for index in scope.metadata.owned_indices() { + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); + + unsafe { + insert_or_merge( + None, + None, + &mut self.state, + &mut self.metadata, + index as u32, + index, + BufferStateProvider::Indirect { + state: &scope.state, + }, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + )?; + }; + } + + Ok(()) + } + + /// Merge a single state into the UsageScope. + /// + /// If the resulting state is invalid, returns a usage + /// conflict with the details of the invalid state. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn merge_single<'a>( + &mut self, + storage: &'a hub::Storage<Buffer<A>, BufferId>, + id: BufferId, + new_state: BufferUses, + ) -> Result<&'a Buffer<A>, UsageConflict> { + let buffer = storage + .get(id) + .map_err(|_| UsageConflict::BufferInvalid { id })?; + + let (index32, epoch, _) = id.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + insert_or_merge( + Some(&buffer.life_guard), + None, + &mut self.state, + &mut self.metadata, + index32, + index, + BufferStateProvider::Direct { state: new_state }, + ResourceMetadataProvider::Resource { epoch }, + )?; + } + + Ok(buffer) + } +} + +/// Stores all buffer state within a command buffer or device. +pub(crate) struct BufferTracker<A: hub::HalApi> { + start: Vec<BufferUses>, + end: Vec<BufferUses>, + + metadata: ResourceMetadata<A>, + + temp: Vec<PendingTransition<BufferUses>>, +} +impl<A: hub::HalApi> BufferTracker<A> { + pub fn new() -> Self { + Self { + start: Vec::new(), + end: Vec::new(), + + metadata: ResourceMetadata::new(), + + temp: Vec::new(), + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.start.len()); + strict_assert!(index < self.end.len()); + self.metadata.tracker_assert_in_bounds(index); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Buffer ID before + /// all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.start.resize(size, BufferUses::empty()); + self.end.resize(size, BufferUses::empty()); + + self.metadata.set_size(size); + } + + /// Extend the vectors to let the given index be valid. + fn allow_index(&mut self, index: usize) { + if index >= self.start.len() { + self.set_size(index + 1); + } + } + + /// Returns a list of all buffers tracked. + pub fn used(&self) -> impl Iterator<Item = Valid<BufferId>> + '_ { + self.metadata.owned_ids() + } + + /// Drains all currently pending transitions. + pub fn drain(&mut self) -> Drain<'_, PendingTransition<BufferUses>> { + self.temp.drain(..) + } + + /// Inserts a single buffer and its state into the resource tracker. + /// + /// If the resource already exists in the tracker, this will panic. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn insert_single(&mut self, id: Valid<BufferId>, ref_count: RefCount, state: BufferUses) { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + let currently_owned = self.metadata.contains_unchecked(index); + + if currently_owned { + panic!("Tried to insert buffer already tracked"); + } + + insert( + None, + Some(&mut self.start), + &mut self.end, + &mut self.metadata, + index, + BufferStateProvider::Direct { state }, + None, + ResourceMetadataProvider::Direct { + epoch, + ref_count: Cow::Owned(ref_count), + }, + ) + } + } + + /// Sets the state of a single buffer. + /// + /// If a transition is needed to get the buffer into the given state, that transition + /// is returned. No more than one transition is needed. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_single<'a>( + &mut self, + storage: &'a hub::Storage<Buffer<A>, BufferId>, + id: BufferId, + state: BufferUses, + ) -> Option<(&'a Buffer<A>, Option<PendingTransition<BufferUses>>)> { + let value = storage.get(id).ok()?; + + let (index32, epoch, _) = id.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + insert_or_barrier_update( + Some(&value.life_guard), + Some(&mut self.start), + &mut self.end, + &mut self.metadata, + index32, + index, + BufferStateProvider::Direct { state }, + None, + ResourceMetadataProvider::Resource { epoch }, + &mut self.temp, + ) + }; + + strict_assert!(self.temp.len() <= 1); + + Some((value, self.temp.pop())) + } + + /// Sets the given state for all buffers in the given tracker. + /// + /// If a transition is needed to get the buffers into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_from_tracker(&mut self, tracker: &Self) { + let incoming_size = tracker.start.len(); + if incoming_size > self.start.len() { + self.set_size(incoming_size); + } + + for index in tracker.metadata.owned_indices() { + self.tracker_assert_in_bounds(index); + tracker.tracker_assert_in_bounds(index); + unsafe { + insert_or_barrier_update( + None, + Some(&mut self.start), + &mut self.end, + &mut self.metadata, + index as u32, + index, + BufferStateProvider::Indirect { + state: &tracker.start, + }, + Some(BufferStateProvider::Indirect { + state: &tracker.end, + }), + ResourceMetadataProvider::Indirect { + metadata: &tracker.metadata, + }, + &mut self.temp, + ) + } + } + } + + /// Sets the given state for all buffers in the given UsageScope. + /// + /// If a transition is needed to get the buffers into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_from_usage_scope(&mut self, scope: &BufferUsageScope<A>) { + let incoming_size = scope.state.len(); + if incoming_size > self.start.len() { + self.set_size(incoming_size); + } + + for index in scope.metadata.owned_indices() { + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); + unsafe { + insert_or_barrier_update( + None, + Some(&mut self.start), + &mut self.end, + &mut self.metadata, + index as u32, + index, + BufferStateProvider::Indirect { + state: &scope.state, + }, + None, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + &mut self.temp, + ) + } + } + } + + /// Iterates through all buffers in the given bind group and adopts + /// the state given for those buffers in the UsageScope. It also + /// removes all touched buffers from the usage scope. + /// + /// If a transition is needed to get the buffers into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// This is a really funky method used by Compute Passes to generate + /// barriers after a call to dispatch without needing to iterate + /// over all elements in the usage scope. We use each the + /// a given iterator of ids as a source of which IDs to look at. + /// All the IDs must have first been added to the usage scope. + /// + /// # Safety + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + pub unsafe fn set_and_remove_from_usage_scope_sparse( + &mut self, + scope: &mut BufferUsageScope<A>, + id_source: impl IntoIterator<Item = Valid<BufferId>>, + ) { + let incoming_size = scope.state.len(); + if incoming_size > self.start.len() { + self.set_size(incoming_size); + } + + for id in id_source { + let (index32, _, _) = id.0.unzip(); + let index = index32 as usize; + + scope.tracker_assert_in_bounds(index); + + if unsafe { !scope.metadata.contains_unchecked(index) } { + continue; + } + unsafe { + insert_or_barrier_update( + None, + Some(&mut self.start), + &mut self.end, + &mut self.metadata, + index as u32, + index, + BufferStateProvider::Indirect { + state: &scope.state, + }, + None, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + &mut self.temp, + ) + }; + + unsafe { scope.metadata.remove(index) }; + } + } + + /// Removes the given resource from the tracker iff we have the last reference to the + /// resource and the epoch matches. + /// + /// Returns true if the resource was removed. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + pub fn remove_abandoned(&mut self, id: Valid<BufferId>) -> bool { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_epoch = self.metadata.get_epoch_unchecked(index); + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + + if existing_epoch == epoch && existing_ref_count.load() == 1 { + self.metadata.remove(index); + return true; + } + } + } + + false + } +} + +/// Source of Buffer State. +#[derive(Debug, Clone)] +enum BufferStateProvider<'a> { + /// Get a state that was provided directly. + Direct { state: BufferUses }, + /// Get a state from an an array of states. + Indirect { state: &'a [BufferUses] }, +} +impl BufferStateProvider<'_> { + /// Gets the state from the provider, given a resource ID index. + /// + /// # Safety + /// + /// Index must be in bounds for the indirect source iff this is in the indirect state. + #[inline(always)] + unsafe fn get_state(&self, index: usize) -> BufferUses { + match *self { + BufferStateProvider::Direct { state } => state, + BufferStateProvider::Indirect { state } => { + strict_assert!(index < state.len()); + *unsafe { state.get_unchecked(index) } + } + } + } +} + +/// Does an insertion operation if the index isn't tracked +/// in the current metadata, otherwise merges the given state +/// with the current state. If the merging would cause +/// a conflict, returns that usage conflict. +/// +/// # Safety +/// +/// Indexes must be valid indexes into all arrays passed in +/// to this function, either directly or via metadata or provider structs. +#[inline(always)] +unsafe fn insert_or_merge<A: hub::HalApi>( + life_guard: Option<&LifeGuard>, + start_states: Option<&mut [BufferUses]>, + current_states: &mut [BufferUses], + resource_metadata: &mut ResourceMetadata<A>, + index32: u32, + index: usize, + state_provider: BufferStateProvider<'_>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) -> Result<(), UsageConflict> { + let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; + + if !currently_owned { + unsafe { + insert( + life_guard, + start_states, + current_states, + resource_metadata, + index, + state_provider, + None, + metadata_provider, + ) + }; + return Ok(()); + } + + unsafe { + merge( + current_states, + index32, + index, + state_provider, + metadata_provider, + ) + } +} + +/// If the resource isn't tracked +/// - Inserts the given resource. +/// - Uses the `start_state_provider` to populate `start_states` +/// - Uses either `end_state_provider` or `start_state_provider` +/// to populate `current_states`. +/// If the resource is tracked +/// - Inserts barriers from the state in `current_states` +/// to the state provided by `start_state_provider`. +/// - Updates the `current_states` with either the state from +/// `end_state_provider` or `start_state_provider`. +/// +/// Any barriers are added to the barrier vector. +/// +/// # Safety +/// +/// Indexes must be valid indexes into all arrays passed in +/// to this function, either directly or via metadata or provider structs. +#[inline(always)] +unsafe fn insert_or_barrier_update<A: hub::HalApi>( + life_guard: Option<&LifeGuard>, + start_states: Option<&mut [BufferUses]>, + current_states: &mut [BufferUses], + resource_metadata: &mut ResourceMetadata<A>, + index32: u32, + index: usize, + start_state_provider: BufferStateProvider<'_>, + end_state_provider: Option<BufferStateProvider<'_>>, + metadata_provider: ResourceMetadataProvider<'_, A>, + barriers: &mut Vec<PendingTransition<BufferUses>>, +) { + let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; + + if !currently_owned { + unsafe { + insert( + life_guard, + start_states, + current_states, + resource_metadata, + index, + start_state_provider, + end_state_provider, + metadata_provider, + ) + }; + return; + } + + let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); + unsafe { + barrier( + current_states, + index32, + index, + start_state_provider, + barriers, + ) + }; + + unsafe { update(current_states, index, update_state_provider) }; +} + +#[inline(always)] +unsafe fn insert<A: hub::HalApi>( + life_guard: Option<&LifeGuard>, + start_states: Option<&mut [BufferUses]>, + current_states: &mut [BufferUses], + resource_metadata: &mut ResourceMetadata<A>, + index: usize, + start_state_provider: BufferStateProvider<'_>, + end_state_provider: Option<BufferStateProvider<'_>>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) { + let new_start_state = unsafe { start_state_provider.get_state(index) }; + let new_end_state = + end_state_provider.map_or(new_start_state, |p| unsafe { p.get_state(index) }); + + // This should only ever happen with a wgpu bug, but let's just double + // check that resource states don't have any conflicts. + strict_assert_eq!(invalid_resource_state(new_start_state), false); + strict_assert_eq!(invalid_resource_state(new_end_state), false); + + log::trace!("\tbuf {index}: insert {new_start_state:?}..{new_end_state:?}"); + + unsafe { + if let Some(&mut ref mut start_state) = start_states { + *start_state.get_unchecked_mut(index) = new_start_state; + } + *current_states.get_unchecked_mut(index) = new_end_state; + + let (epoch, ref_count) = metadata_provider.get_own(life_guard, index); + resource_metadata.insert(index, epoch, ref_count); + } +} + +#[inline(always)] +unsafe fn merge<A: hub::HalApi>( + current_states: &mut [BufferUses], + index32: u32, + index: usize, + state_provider: BufferStateProvider<'_>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) -> Result<(), UsageConflict> { + let current_state = unsafe { current_states.get_unchecked_mut(index) }; + let new_state = unsafe { state_provider.get_state(index) }; + + let merged_state = *current_state | new_state; + + if invalid_resource_state(merged_state) { + return Err(UsageConflict::from_buffer( + BufferId::zip( + index32, + unsafe { metadata_provider.get_epoch(index) }, + A::VARIANT, + ), + *current_state, + new_state, + )); + } + + log::trace!("\tbuf {index32}: merge {current_state:?} + {new_state:?}"); + + *current_state = merged_state; + + Ok(()) +} + +#[inline(always)] +unsafe fn barrier( + current_states: &mut [BufferUses], + index32: u32, + index: usize, + state_provider: BufferStateProvider<'_>, + barriers: &mut Vec<PendingTransition<BufferUses>>, +) { + let current_state = unsafe { *current_states.get_unchecked(index) }; + let new_state = unsafe { state_provider.get_state(index) }; + + if skip_barrier(current_state, new_state) { + return; + } + + barriers.push(PendingTransition { + id: index32, + selector: (), + usage: current_state..new_state, + }); + + log::trace!("\tbuf {index32}: transition {current_state:?} -> {new_state:?}"); +} + +#[inline(always)] +unsafe fn update( + current_states: &mut [BufferUses], + index: usize, + state_provider: BufferStateProvider<'_>, +) { + let current_state = unsafe { current_states.get_unchecked_mut(index) }; + let new_state = unsafe { state_provider.get_state(index) }; + + *current_state = new_state; +} diff --git a/third_party/rust/wgpu-core/src/track/metadata.rs b/third_party/rust/wgpu-core/src/track/metadata.rs new file mode 100644 index 0000000000..73da0d6c5d --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/metadata.rs @@ -0,0 +1,269 @@ +//! The `ResourceMetadata` type. + +use crate::{ + hub, + id::{self, TypedId}, + Epoch, LifeGuard, RefCount, +}; +use bit_vec::BitVec; +use std::{borrow::Cow, marker::PhantomData, mem}; +use wgt::strict_assert; + +/// A set of resources, holding a [`RefCount`] and epoch for each member. +/// +/// Testing for membership is fast, and iterating over members is +/// reasonably fast in practice. Storage consumption is proportional +/// to the largest id index of any member, not to the number of +/// members, but a bit vector tracks occupancy, so iteration touches +/// only occupied elements. +#[derive(Debug)] +pub(super) struct ResourceMetadata<A: hub::HalApi> { + /// If the resource with index `i` is a member, `owned[i]` is `true`. + owned: BitVec<usize>, + + /// A vector parallel to `owned`, holding clones of members' `RefCount`s. + ref_counts: Vec<Option<RefCount>>, + + /// A vector parallel to `owned`, holding the epoch of each members' id. + epochs: Vec<Epoch>, + + /// This tells Rust that this type should be covariant with `A`. + _phantom: PhantomData<A>, +} + +impl<A: hub::HalApi> ResourceMetadata<A> { + pub(super) fn new() -> Self { + Self { + owned: BitVec::default(), + ref_counts: Vec::new(), + epochs: Vec::new(), + + _phantom: PhantomData, + } + } + + /// Returns the number of indices we can accommodate. + pub(super) fn size(&self) -> usize { + self.owned.len() + } + + pub(super) fn set_size(&mut self, size: usize) { + self.ref_counts.resize(size, None); + self.epochs.resize(size, u32::MAX); + + resize_bitvec(&mut self.owned, size); + } + + /// Ensures a given index is in bounds for all arrays and does + /// sanity checks of the presence of a refcount. + /// + /// In release mode this function is completely empty and is removed. + #[cfg_attr(not(feature = "strict_asserts"), allow(unused_variables))] + pub(super) fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.owned.len()); + strict_assert!(index < self.ref_counts.len()); + strict_assert!(index < self.epochs.len()); + + strict_assert!(if self.contains(index) { + self.ref_counts[index].is_some() + } else { + true + }); + } + + /// Returns true if the tracker owns no resources. + /// + /// This is a O(n) operation. + pub(super) fn is_empty(&self) -> bool { + !self.owned.any() + } + + /// Returns true if the set contains the resource with the given index. + pub(super) fn contains(&self, index: usize) -> bool { + self.owned[index] + } + + /// Returns true if the set contains the resource with the given index. + /// + /// # Safety + /// + /// The given `index` must be in bounds for this `ResourceMetadata`'s + /// existing tables. See `tracker_assert_in_bounds`. + #[inline(always)] + pub(super) unsafe fn contains_unchecked(&self, index: usize) -> bool { + unsafe { self.owned.get(index).unwrap_unchecked() } + } + + /// Insert a resource into the set. + /// + /// Add the resource with the given index, epoch, and reference count to the + /// set. + /// + /// # Safety + /// + /// The given `index` must be in bounds for this `ResourceMetadata`'s + /// existing tables. See `tracker_assert_in_bounds`. + #[inline(always)] + pub(super) unsafe fn insert(&mut self, index: usize, epoch: Epoch, ref_count: RefCount) { + self.owned.set(index, true); + unsafe { + *self.epochs.get_unchecked_mut(index) = epoch; + *self.ref_counts.get_unchecked_mut(index) = Some(ref_count); + } + } + + /// Get the [`RefCount`] of the resource with the given index. + /// + /// # Safety + /// + /// The given `index` must be in bounds for this `ResourceMetadata`'s + /// existing tables. See `tracker_assert_in_bounds`. + #[inline(always)] + pub(super) unsafe fn get_ref_count_unchecked(&self, index: usize) -> &RefCount { + unsafe { + self.ref_counts + .get_unchecked(index) + .as_ref() + .unwrap_unchecked() + } + } + + /// Get the [`Epoch`] of the id of the resource with the given index. + /// + /// # Safety + /// + /// The given `index` must be in bounds for this `ResourceMetadata`'s + /// existing tables. See `tracker_assert_in_bounds`. + #[inline(always)] + pub(super) unsafe fn get_epoch_unchecked(&self, index: usize) -> Epoch { + unsafe { *self.epochs.get_unchecked(index) } + } + + /// Returns an iterator over the ids for all resources owned by `self`. + pub(super) fn owned_ids<Id: TypedId>(&self) -> impl Iterator<Item = id::Valid<Id>> + '_ { + if !self.owned.is_empty() { + self.tracker_assert_in_bounds(self.owned.len() - 1) + }; + iterate_bitvec_indices(&self.owned).map(move |index| { + let epoch = unsafe { *self.epochs.get_unchecked(index) }; + id::Valid(Id::zip(index as u32, epoch, A::VARIANT)) + }) + } + + /// Returns an iterator over the indices of all resources owned by `self`. + pub(super) fn owned_indices(&self) -> impl Iterator<Item = usize> + '_ { + if !self.owned.is_empty() { + self.tracker_assert_in_bounds(self.owned.len() - 1) + }; + iterate_bitvec_indices(&self.owned) + } + + /// Remove the resource with the given index from the set. + pub(super) unsafe fn remove(&mut self, index: usize) { + unsafe { + *self.ref_counts.get_unchecked_mut(index) = None; + *self.epochs.get_unchecked_mut(index) = u32::MAX; + } + self.owned.set(index, false); + } +} + +/// A source of resource metadata. +/// +/// This is used to abstract over the various places +/// trackers can get new resource metadata from. +pub(super) enum ResourceMetadataProvider<'a, A: hub::HalApi> { + /// Comes directly from explicit values. + Direct { + epoch: Epoch, + ref_count: Cow<'a, RefCount>, + }, + /// Comes from another metadata tracker. + Indirect { metadata: &'a ResourceMetadata<A> }, + /// The epoch is given directly, but the life count comes from the resource itself. + Resource { epoch: Epoch }, +} +impl<A: hub::HalApi> ResourceMetadataProvider<'_, A> { + /// Get the epoch and an owned refcount from this. + /// + /// # Safety + /// + /// - The index must be in bounds of the metadata tracker if this uses an indirect source. + /// - life_guard must be Some if this uses a Resource source. + #[inline(always)] + pub(super) unsafe fn get_own( + self, + life_guard: Option<&LifeGuard>, + index: usize, + ) -> (Epoch, RefCount) { + match self { + ResourceMetadataProvider::Direct { epoch, ref_count } => { + (epoch, ref_count.into_owned()) + } + ResourceMetadataProvider::Indirect { metadata } => { + metadata.tracker_assert_in_bounds(index); + (unsafe { *metadata.epochs.get_unchecked(index) }, { + let ref_count = unsafe { metadata.ref_counts.get_unchecked(index) }; + unsafe { ref_count.clone().unwrap_unchecked() } + }) + } + ResourceMetadataProvider::Resource { epoch } => { + strict_assert!(life_guard.is_some()); + (epoch, unsafe { life_guard.unwrap_unchecked() }.add_ref()) + } + } + } + /// Get the epoch from this. + /// + /// # Safety + /// + /// - The index must be in bounds of the metadata tracker if this uses an indirect source. + #[inline(always)] + pub(super) unsafe fn get_epoch(self, index: usize) -> Epoch { + match self { + ResourceMetadataProvider::Direct { epoch, .. } + | ResourceMetadataProvider::Resource { epoch, .. } => epoch, + ResourceMetadataProvider::Indirect { metadata } => { + metadata.tracker_assert_in_bounds(index); + unsafe { *metadata.epochs.get_unchecked(index) } + } + } + } +} + +/// Resizes the given bitvec to the given size. I'm not sure why this is hard to do but it is. +fn resize_bitvec<B: bit_vec::BitBlock>(vec: &mut BitVec<B>, size: usize) { + let owned_size_to_grow = size.checked_sub(vec.len()); + if let Some(delta) = owned_size_to_grow { + if delta != 0 { + vec.grow(delta, false); + } + } else { + vec.truncate(size); + } +} + +/// Produces an iterator that yields the indexes of all bits that are set in the bitvec. +/// +/// Will skip entire usize's worth of bits if they are all false. +fn iterate_bitvec_indices(ownership: &BitVec<usize>) -> impl Iterator<Item = usize> + '_ { + const BITS_PER_BLOCK: usize = mem::size_of::<usize>() * 8; + + let size = ownership.len(); + + ownership + .blocks() + .enumerate() + .filter(|&(_, word)| word != 0) + .flat_map(move |(word_index, mut word)| { + let bit_start = word_index * BITS_PER_BLOCK; + let bit_end = (bit_start + BITS_PER_BLOCK).min(size); + + (bit_start..bit_end).filter(move |_| { + let active = word & 0b1 != 0; + word >>= 1; + + active + }) + }) +} diff --git a/third_party/rust/wgpu-core/src/track/mod.rs b/third_party/rust/wgpu-core/src/track/mod.rs new file mode 100644 index 0000000000..ad61e5e859 --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/mod.rs @@ -0,0 +1,610 @@ +/*! Resource State and Lifetime Trackers + +These structures are responsible for keeping track of resource state, +generating barriers where needed, and making sure resources are kept +alive until the trackers die. + +## General Architecture + +Tracking is some of the hottest code in the entire codebase, so the trackers +are designed to be as cache efficient as possible. They store resource state +in flat vectors, storing metadata SOA style, one vector per type of metadata. + +A lot of the tracker code is deeply unsafe, using unchecked accesses all over +to make performance as good as possible. However, for all unsafe accesses, there +is a corresponding debug assert the checks if that access is valid. This helps +get bugs caught fast, while still letting users not need to pay for the bounds +checks. + +In wgpu, resource IDs are allocated and re-used, so will always be as low +as reasonably possible. This allows us to use the ID as an index into an array. + +## Statefulness + +There are two main types of trackers, stateful and stateless. + +Stateful trackers are for buffers and textures. They both have +resource state attached to them which needs to be used to generate +automatic synchronization. Because of the different requirements of +buffers and textures, they have two separate tracking structures. + +Stateless trackers only store metadata and own the given resource. + +## Use Case + +Within each type of tracker, the trackers are further split into 3 different +use cases, Bind Group, Usage Scope, and a full Tracker. + +Bind Group trackers are just a list of different resources, their refcount, +and how they are used. Textures are used via a selector and a usage type. +Buffers by just a usage type. Stateless resources don't have a usage type. + +Usage Scope trackers are only for stateful resources. These trackers represent +a single [`UsageScope`] in the spec. When a use is added to a usage scope, +it is merged with all other uses of that resource in that scope. If there +is a usage conflict, merging will fail and an error will be reported. + +Full trackers represent a before and after state of a resource. These +are used for tracking on the device and on command buffers. The before +state represents the state the resource is first used as in the command buffer, +the after state is the state the command buffer leaves the resource in. +These double ended buffers can then be used to generate the needed transitions +between command buffers. + +## Dense Datastructure with Sparse Data + +This tracking system is based on having completely dense data, but trackers do +not always contain every resource. Some resources (or even most resources) go +unused in any given command buffer. So to help speed up the process of iterating +through possibly thousands of resources, we use a bit vector to represent if +a resource is in the buffer or not. This allows us extremely efficient memory +utilization, as well as being able to bail out of whole blocks of 32-64 resources +with a single usize comparison with zero. In practice this means that merging +partially resident buffers is extremely quick. + +The main advantage of this dense datastructure is that we can do merging +of trackers in an extremely efficient fashion that results in us doing linear +scans down a couple of buffers. CPUs and their caches absolutely eat this up. + +## Stateful Resource Operations + +All operations on stateful trackers boil down to one of four operations: +- `insert(tracker, new_state)` adds a resource with a given state to the tracker + for the first time. +- `merge(tracker, new_state)` merges this new state with the previous state, checking + for usage conflicts. +- `barrier(tracker, new_state)` compares the given state to the existing state and + generates the needed barriers. +- `update(tracker, new_state)` takes the given new state and overrides the old state. + +This allows us to compose the operations to form the various kinds of tracker merges +that need to happen in the codebase. For each resource in the given merger, the following +operation applies: + +```text +UsageScope <- Resource = insert(scope, usage) OR merge(scope, usage) +UsageScope <- UsageScope = insert(scope, scope) OR merge(scope, scope) +CommandBuffer <- UsageScope = insert(buffer.start, buffer.end, scope) + OR barrier(buffer.end, scope) + update(buffer.end, scope) +Device <- CommandBuffer = insert(device.start, device.end, buffer.start, buffer.end) + OR barrier(device.end, buffer.start) + update(device.end, buffer.end) +``` + +[`UsageScope`]: https://gpuweb.github.io/gpuweb/#programming-model-synchronization +*/ + +mod buffer; +mod metadata; +mod range; +mod stateless; +mod texture; + +use crate::{ + binding_model, command, conv, hub, + id::{self, TypedId}, + pipeline, resource, +}; + +use std::{fmt, ops}; +use thiserror::Error; + +pub(crate) use buffer::{BufferBindGroupState, BufferTracker, BufferUsageScope}; +use metadata::{ResourceMetadata, ResourceMetadataProvider}; +pub(crate) use stateless::{StatelessBindGroupSate, StatelessTracker}; +pub(crate) use texture::{ + TextureBindGroupState, TextureSelector, TextureTracker, TextureUsageScope, +}; +use wgt::strict_assert_ne; + +/// 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: ResourceUses> { + pub id: u32, + pub selector: S::Selector, + pub usage: ops::Range<S>, +} + +impl PendingTransition<hal::BufferUses> { + /// Produce the hal barrier corresponding to the transition. + pub fn into_hal<'a, A: hal::Api>( + self, + buf: &'a resource::Buffer<A>, + ) -> hal::BufferBarrier<'a, A> { + let buffer = buf.raw.as_ref().expect("Buffer is destroyed"); + hal::BufferBarrier { + buffer, + usage: self.usage, + } + } +} + +impl PendingTransition<hal::TextureUses> { + /// Produce the hal barrier corresponding to the transition. + pub fn into_hal<'a, A: hal::Api>( + self, + tex: &'a resource::Texture<A>, + ) -> hal::TextureBarrier<'a, A> { + let texture = tex.inner.as_raw().expect("Texture is destroyed"); + + // These showing up in a barrier is always a bug + strict_assert_ne!(self.usage.start, hal::TextureUses::UNKNOWN); + strict_assert_ne!(self.usage.end, hal::TextureUses::UNKNOWN); + + let mip_count = self.selector.mips.end - self.selector.mips.start; + strict_assert_ne!(mip_count, 0); + let layer_count = self.selector.layers.end - self.selector.layers.start; + strict_assert_ne!(layer_count, 0); + + hal::TextureBarrier { + texture, + range: wgt::ImageSubresourceRange { + aspect: wgt::TextureAspect::All, + base_mip_level: self.selector.mips.start, + mip_level_count: Some(mip_count), + base_array_layer: self.selector.layers.start, + array_layer_count: Some(layer_count), + }, + usage: self.usage, + } + } +} + +/// The uses that a resource or subresource can be in. +pub(crate) trait ResourceUses: + fmt::Debug + ops::BitAnd<Output = Self> + ops::BitOr<Output = Self> + PartialEq + Sized + Copy +{ + /// All flags that are exclusive. + const EXCLUSIVE: Self; + + /// The relevant resource ID type. + type Id: Copy + fmt::Debug + TypedId; + /// The selector used by this resource. + type Selector: fmt::Debug; + + /// Turn the resource into a pile of bits. + fn bits(self) -> u16; + /// Returns true if the all the uses are ordered. + fn all_ordered(self) -> bool; + /// Returns true if any of the uses are exclusive. + fn any_exclusive(self) -> bool; +} + +/// Returns true if the given states violates the usage scope rule +/// of any(inclusive) XOR one(exclusive) +fn invalid_resource_state<T: ResourceUses>(state: T) -> bool { + // Is power of two also means "is one bit set". We check for this as if + // we're in any exclusive state, we must only be in a single state. + state.any_exclusive() && !conv::is_power_of_two_u16(state.bits()) +} + +/// Returns true if the transition from one state to another does not require +/// a barrier. +fn skip_barrier<T: ResourceUses>(old_state: T, new_state: T) -> bool { + // If the state didn't change and all the usages are ordered, the hardware + // will guarentee the order of accesses, so we do not need to issue a barrier at all + old_state == new_state && old_state.all_ordered() +} + +#[derive(Clone, Debug, Error, Eq, PartialEq)] +pub enum UsageConflict { + #[error("Attempted to use invalid buffer")] + BufferInvalid { id: id::BufferId }, + #[error("Attempted to use invalid texture")] + TextureInvalid { id: id::TextureId }, + #[error("Attempted to use buffer with {invalid_use}.")] + Buffer { + id: id::BufferId, + invalid_use: InvalidUse<hal::BufferUses>, + }, + #[error("Attempted to use a texture (mips {mip_levels:?} layers {array_layers:?}) with {invalid_use}.")] + Texture { + id: id::TextureId, + mip_levels: ops::Range<u32>, + array_layers: ops::Range<u32>, + invalid_use: InvalidUse<hal::TextureUses>, + }, +} + +impl UsageConflict { + fn from_buffer( + id: id::BufferId, + current_state: hal::BufferUses, + new_state: hal::BufferUses, + ) -> Self { + Self::Buffer { + id, + invalid_use: InvalidUse { + current_state, + new_state, + }, + } + } + + fn from_texture( + id: id::TextureId, + selector: TextureSelector, + current_state: hal::TextureUses, + new_state: hal::TextureUses, + ) -> Self { + Self::Texture { + id, + mip_levels: selector.mips, + array_layers: selector.layers, + invalid_use: InvalidUse { + current_state, + new_state, + }, + } + } +} + +impl crate::error::PrettyError for UsageConflict { + fn fmt_pretty(&self, fmt: &mut crate::error::ErrorFormatter) { + fmt.error(self); + match *self { + Self::BufferInvalid { id } => { + fmt.buffer_label(&id); + } + Self::TextureInvalid { id } => { + fmt.texture_label(&id); + } + Self::Buffer { id, .. } => { + fmt.buffer_label(&id); + } + Self::Texture { id, .. } => { + fmt.texture_label(&id); + } + } + } +} + +/// Pretty print helper that shows helpful descriptions of a conflicting usage. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InvalidUse<T> { + current_state: T, + new_state: T, +} + +impl<T: ResourceUses> fmt::Display for InvalidUse<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let current = self.current_state; + let new = self.new_state; + + let current_exclusive = current & T::EXCLUSIVE; + let new_exclusive = new & T::EXCLUSIVE; + + let exclusive = current_exclusive | new_exclusive; + + // The text starts with "tried to use X resource with {self}" + write!( + f, + "conflicting usages. Current usage {current:?} and new usage {new:?}. \ + {exclusive:?} is an exclusive usage and cannot be used with any other \ + usages within the usage scope (renderpass or compute dispatch)" + ) + } +} + +/// All the usages that a bind group contains. The uses are not deduplicated in any way +/// and may include conflicting uses. This is fully compliant by the WebGPU spec. +/// +/// All bind group states are sorted by their ID so that when adding to a tracker, +/// they are added in the most efficient order possible (assending order). +pub(crate) struct BindGroupStates<A: hub::HalApi> { + pub buffers: BufferBindGroupState<A>, + pub textures: TextureBindGroupState<A>, + pub views: StatelessBindGroupSate<resource::TextureView<A>, id::TextureViewId>, + pub samplers: StatelessBindGroupSate<resource::Sampler<A>, id::SamplerId>, +} + +impl<A: hub::HalApi> BindGroupStates<A> { + pub fn new() -> Self { + Self { + buffers: BufferBindGroupState::new(), + textures: TextureBindGroupState::new(), + views: StatelessBindGroupSate::new(), + samplers: StatelessBindGroupSate::new(), + } + } + + /// Optimize the bind group states by sorting them by ID. + /// + /// When this list of states is merged into a tracker, the memory + /// accesses will be in a constant assending order. + pub fn optimize(&mut self) { + self.buffers.optimize(); + self.textures.optimize(); + self.views.optimize(); + self.samplers.optimize(); + } +} + +/// This is a render bundle specific usage scope. It includes stateless resources +/// that are not normally included in a usage scope, but are used by render bundles +/// and need to be owned by the render bundles. +pub(crate) struct RenderBundleScope<A: hub::HalApi> { + pub buffers: BufferUsageScope<A>, + pub textures: TextureUsageScope<A>, + // Don't need to track views and samplers, they are never used directly, only by bind groups. + pub bind_groups: StatelessTracker<A, binding_model::BindGroup<A>, id::BindGroupId>, + pub render_pipelines: StatelessTracker<A, pipeline::RenderPipeline<A>, id::RenderPipelineId>, + pub query_sets: StatelessTracker<A, resource::QuerySet<A>, id::QuerySetId>, +} + +impl<A: hub::HalApi> RenderBundleScope<A> { + /// Create the render bundle scope and pull the maximum IDs from the hubs. + pub fn new( + buffers: &hub::Storage<resource::Buffer<A>, id::BufferId>, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + bind_groups: &hub::Storage<binding_model::BindGroup<A>, id::BindGroupId>, + render_pipelines: &hub::Storage<pipeline::RenderPipeline<A>, id::RenderPipelineId>, + query_sets: &hub::Storage<resource::QuerySet<A>, id::QuerySetId>, + ) -> Self { + let mut value = Self { + buffers: BufferUsageScope::new(), + textures: TextureUsageScope::new(), + bind_groups: StatelessTracker::new(), + render_pipelines: StatelessTracker::new(), + query_sets: StatelessTracker::new(), + }; + + value.buffers.set_size(buffers.len()); + value.textures.set_size(textures.len()); + value.bind_groups.set_size(bind_groups.len()); + value.render_pipelines.set_size(render_pipelines.len()); + value.query_sets.set_size(query_sets.len()); + + value + } + + /// Merge the inner contents of a bind group into the render bundle tracker. + /// + /// Only stateful things are merged in here, all other resources are owned + /// indirectly by the bind group. + /// + /// # Safety + /// + /// The maximum ID given by each bind group resource must be less than the + /// length of the storage given at the call to `new`. + pub unsafe fn merge_bind_group( + &mut self, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + bind_group: &BindGroupStates<A>, + ) -> Result<(), UsageConflict> { + unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? }; + unsafe { + self.textures + .merge_bind_group(textures, &bind_group.textures)? + }; + + Ok(()) + } +} + +/// A usage scope tracker. Only needs to store stateful resources as stateless +/// resources cannot possibly have a usage conflict. +#[derive(Debug)] +pub(crate) struct UsageScope<A: hub::HalApi> { + pub buffers: BufferUsageScope<A>, + pub textures: TextureUsageScope<A>, +} + +impl<A: hub::HalApi> UsageScope<A> { + /// Create the render bundle scope and pull the maximum IDs from the hubs. + pub fn new( + buffers: &hub::Storage<resource::Buffer<A>, id::BufferId>, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + ) -> Self { + let mut value = Self { + buffers: BufferUsageScope::new(), + textures: TextureUsageScope::new(), + }; + + value.buffers.set_size(buffers.len()); + value.textures.set_size(textures.len()); + + value + } + + /// Merge the inner contents of a bind group into the usage scope. + /// + /// Only stateful things are merged in here, all other resources are owned + /// indirectly by the bind group. + /// + /// # Safety + /// + /// The maximum ID given by each bind group resource must be less than the + /// length of the storage given at the call to `new`. + pub unsafe fn merge_bind_group( + &mut self, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + bind_group: &BindGroupStates<A>, + ) -> Result<(), UsageConflict> { + unsafe { + self.buffers.merge_bind_group(&bind_group.buffers)?; + self.textures + .merge_bind_group(textures, &bind_group.textures)?; + } + + Ok(()) + } + + /// Merge the inner contents of a bind group into the usage scope. + /// + /// Only stateful things are merged in here, all other resources are owned + /// indirectly by a bind group or are merged directly into the command buffer tracker. + /// + /// # Safety + /// + /// The maximum ID given by each bind group resource must be less than the + /// length of the storage given at the call to `new`. + pub unsafe fn merge_render_bundle( + &mut self, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + render_bundle: &RenderBundleScope<A>, + ) -> Result<(), UsageConflict> { + self.buffers.merge_usage_scope(&render_bundle.buffers)?; + self.textures + .merge_usage_scope(textures, &render_bundle.textures)?; + + Ok(()) + } +} + +/// A full double sided tracker used by CommandBuffers and the Device. +pub(crate) struct Tracker<A: hub::HalApi> { + pub buffers: BufferTracker<A>, + pub textures: TextureTracker<A>, + pub views: StatelessTracker<A, resource::TextureView<A>, id::TextureViewId>, + pub samplers: StatelessTracker<A, resource::Sampler<A>, id::SamplerId>, + pub bind_groups: StatelessTracker<A, binding_model::BindGroup<A>, id::BindGroupId>, + pub compute_pipelines: StatelessTracker<A, pipeline::ComputePipeline<A>, id::ComputePipelineId>, + pub render_pipelines: StatelessTracker<A, pipeline::RenderPipeline<A>, id::RenderPipelineId>, + pub bundles: StatelessTracker<A, command::RenderBundle<A>, id::RenderBundleId>, + pub query_sets: StatelessTracker<A, resource::QuerySet<A>, id::QuerySetId>, +} + +impl<A: hub::HalApi> Tracker<A> { + pub fn new() -> Self { + Self { + buffers: BufferTracker::new(), + textures: TextureTracker::new(), + views: StatelessTracker::new(), + samplers: StatelessTracker::new(), + bind_groups: StatelessTracker::new(), + compute_pipelines: StatelessTracker::new(), + render_pipelines: StatelessTracker::new(), + bundles: StatelessTracker::new(), + query_sets: StatelessTracker::new(), + } + } + + /// Pull the maximum IDs from the hubs. + pub fn set_size( + &mut self, + buffers: Option<&hub::Storage<resource::Buffer<A>, id::BufferId>>, + textures: Option<&hub::Storage<resource::Texture<A>, id::TextureId>>, + views: Option<&hub::Storage<resource::TextureView<A>, id::TextureViewId>>, + samplers: Option<&hub::Storage<resource::Sampler<A>, id::SamplerId>>, + bind_groups: Option<&hub::Storage<binding_model::BindGroup<A>, id::BindGroupId>>, + compute_pipelines: Option< + &hub::Storage<pipeline::ComputePipeline<A>, id::ComputePipelineId>, + >, + render_pipelines: Option<&hub::Storage<pipeline::RenderPipeline<A>, id::RenderPipelineId>>, + bundles: Option<&hub::Storage<command::RenderBundle<A>, id::RenderBundleId>>, + query_sets: Option<&hub::Storage<resource::QuerySet<A>, id::QuerySetId>>, + ) { + if let Some(buffers) = buffers { + self.buffers.set_size(buffers.len()); + }; + if let Some(textures) = textures { + self.textures.set_size(textures.len()); + }; + if let Some(views) = views { + self.views.set_size(views.len()); + }; + if let Some(samplers) = samplers { + self.samplers.set_size(samplers.len()); + }; + if let Some(bind_groups) = bind_groups { + self.bind_groups.set_size(bind_groups.len()); + }; + if let Some(compute_pipelines) = compute_pipelines { + self.compute_pipelines.set_size(compute_pipelines.len()); + } + if let Some(render_pipelines) = render_pipelines { + self.render_pipelines.set_size(render_pipelines.len()); + }; + if let Some(bundles) = bundles { + self.bundles.set_size(bundles.len()); + }; + if let Some(query_sets) = query_sets { + self.query_sets.set_size(query_sets.len()); + }; + } + + /// Iterates through all resources in the given bind group and adopts + /// the state given for those resources in the UsageScope. It also + /// removes all touched resources from the usage scope. + /// + /// If a transition is needed to get the resources into the needed + /// state, those transitions are stored within the tracker. A + /// subsequent call to [`BufferTracker::drain`] or + /// [`TextureTracker::drain`] is needed to get those transitions. + /// + /// This is a really funky method used by Compute Passes to generate + /// barriers after a call to dispatch without needing to iterate + /// over all elements in the usage scope. We use each the + /// bind group as a source of which IDs to look at. The bind groups + /// must have first been added to the usage scope. + /// + /// Only stateful things are merged in here, all other resources are owned + /// indirectly by the bind group. + /// + /// # Safety + /// + /// The maximum ID given by each bind group resource must be less than the + /// value given to `set_size` + pub unsafe fn set_and_remove_from_usage_scope_sparse( + &mut self, + textures: &hub::Storage<resource::Texture<A>, id::TextureId>, + scope: &mut UsageScope<A>, + bind_group: &BindGroupStates<A>, + ) { + unsafe { + self.buffers.set_and_remove_from_usage_scope_sparse( + &mut scope.buffers, + bind_group.buffers.used(), + ) + }; + unsafe { + self.textures.set_and_remove_from_usage_scope_sparse( + textures, + &mut scope.textures, + &bind_group.textures, + ) + }; + } + + /// Tracks the stateless resources from the given renderbundle. It is expected + /// that the stateful resources will get merged into a usage scope first. + /// + /// # Safety + /// + /// The maximum ID given by each bind group resource must be less than the + /// value given to `set_size` + pub unsafe fn add_from_render_bundle( + &mut self, + render_bundle: &RenderBundleScope<A>, + ) -> Result<(), UsageConflict> { + self.bind_groups + .add_from_tracker(&render_bundle.bind_groups); + self.render_pipelines + .add_from_tracker(&render_bundle.render_pipelines); + self.query_sets.add_from_tracker(&render_bundle.query_sets); + + Ok(()) + } +} diff --git a/third_party/rust/wgpu-core/src/track/range.rs b/third_party/rust/wgpu-core/src/track/range.rs new file mode 100644 index 0000000000..12c527fb2a --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/range.rs @@ -0,0 +1,206 @@ +//Note: this could be the only place where we need `SmallVec`. +//TODO: consider getting rid of it. +use smallvec::SmallVec; + +use std::{fmt::Debug, iter, ops::Range}; + +/// 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(crate) 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 + Ord, T: Copy + PartialEq> RangedStates<I, T> { + 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(), + } + } + + pub fn iter(&self) -> impl Iterator<Item = &(Range<I>, T)> + Clone { + self.ranges.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (Range<I>, T)> { + self.ranges.iter_mut() + } + + /// 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); + } + } + + pub fn iter_filter<'a>( + &'a self, + range: &'a Range<I>, + ) -> impl Iterator<Item = (Range<I>, &T)> + 'a { + self.ranges + .iter() + .filter(move |&&(ref inner, ..)| inner.end > range.start && inner.start < range.end) + .map(move |&(ref inner, ref v)| { + let new_range = inner.start.max(range.start)..inner.end.min(range.end); + + (new_range, v) + }) + } + + /// 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 + } +} + +#[cfg(test)] +mod test { + //TODO: randomized/fuzzy testing + use super::RangedStates; + + #[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 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),] + ); + } +} diff --git a/third_party/rust/wgpu-core/src/track/stateless.rs b/third_party/rust/wgpu-core/src/track/stateless.rs new file mode 100644 index 0000000000..1d0fd5997a --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/stateless.rs @@ -0,0 +1,194 @@ +/*! Stateless Trackers + * + * Stateless trackers don't have any state, so make no + * distinction between a usage scope and a full tracker. +!*/ + +use std::marker::PhantomData; + +use crate::{ + hub, + id::{TypedId, Valid}, + track::ResourceMetadata, + RefCount, +}; + +/// Stores all the resources that a bind group stores. +pub(crate) struct StatelessBindGroupSate<T, Id: TypedId> { + resources: Vec<(Valid<Id>, RefCount)>, + + _phantom: PhantomData<T>, +} + +impl<T: hub::Resource, Id: TypedId> StatelessBindGroupSate<T, Id> { + pub fn new() -> Self { + Self { + resources: Vec::new(), + + _phantom: PhantomData, + } + } + + /// Optimize the buffer bind group state by sorting it by ID. + /// + /// When this list of states is merged into a tracker, the memory + /// accesses will be in a constant assending order. + pub(crate) fn optimize(&mut self) { + self.resources + .sort_unstable_by_key(|&(id, _)| id.0.unzip().0); + } + + /// Returns a list of all resources tracked. May contain duplicates. + pub fn used(&self) -> impl Iterator<Item = Valid<Id>> + '_ { + self.resources.iter().map(|&(id, _)| id) + } + + /// Adds the given resource. + pub fn add_single<'a>(&mut self, storage: &'a hub::Storage<T, Id>, id: Id) -> Option<&'a T> { + let resource = storage.get(id).ok()?; + + self.resources + .push((Valid(id), resource.life_guard().add_ref())); + + Some(resource) + } +} + +/// Stores all resource state within a command buffer or device. +pub(crate) struct StatelessTracker<A: hub::HalApi, T, Id: TypedId> { + metadata: ResourceMetadata<A>, + + _phantom: PhantomData<(T, Id)>, +} + +impl<A: hub::HalApi, T: hub::Resource, Id: TypedId> StatelessTracker<A, T, Id> { + pub fn new() -> Self { + Self { + metadata: ResourceMetadata::new(), + + _phantom: PhantomData, + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Resource ID of this type + /// before all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.metadata.set_size(size); + } + + /// Extend the vectors to let the given index be valid. + fn allow_index(&mut self, index: usize) { + if index >= self.metadata.size() { + self.set_size(index + 1); + } + } + + /// Returns a list of all resources tracked. + pub fn used(&self) -> impl Iterator<Item = Valid<Id>> + '_ { + self.metadata.owned_ids() + } + + /// Inserts a single resource into the resource tracker. + /// + /// If the resource already exists in the tracker, it will be overwritten. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn insert_single(&mut self, id: Valid<Id>, ref_count: RefCount) { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + self.metadata.insert(index, epoch, ref_count); + } + } + + /// Adds the given resource to the tracker. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn add_single<'a>(&mut self, storage: &'a hub::Storage<T, Id>, id: Id) -> Option<&'a T> { + let item = storage.get(id).ok()?; + + let (index32, epoch, _) = id.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + self.metadata + .insert(index, epoch, item.life_guard().add_ref()); + } + + Some(item) + } + + /// Adds the given resources from the given tracker. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn add_from_tracker(&mut self, other: &Self) { + let incoming_size = other.metadata.size(); + if incoming_size > self.metadata.size() { + self.set_size(incoming_size); + } + + for index in other.metadata.owned_indices() { + self.tracker_assert_in_bounds(index); + other.tracker_assert_in_bounds(index); + unsafe { + let previously_owned = self.metadata.contains_unchecked(index); + + if !previously_owned { + let epoch = other.metadata.get_epoch_unchecked(index); + let other_ref_count = other.metadata.get_ref_count_unchecked(index); + self.metadata.insert(index, epoch, other_ref_count.clone()); + } + } + } + } + + /// Removes the given resource from the tracker iff we have the last reference to the + /// resource and the epoch matches. + /// + /// Returns true if the resource was removed. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + pub fn remove_abandoned(&mut self, id: Valid<Id>) -> bool { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_epoch = self.metadata.get_epoch_unchecked(index); + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + + if existing_epoch == epoch && existing_ref_count.load() == 1 { + self.metadata.remove(index); + return true; + } + } + } + + false + } +} diff --git a/third_party/rust/wgpu-core/src/track/texture.rs b/third_party/rust/wgpu-core/src/track/texture.rs new file mode 100644 index 0000000000..6db2bab725 --- /dev/null +++ b/third_party/rust/wgpu-core/src/track/texture.rs @@ -0,0 +1,1551 @@ +/*! Texture Trackers + * + * Texture trackers are signifigantly more complicated than + * the buffer trackers because textures can be in a "complex" + * state where each individual subresource can potentially be + * in a different state from every other subtresource. These + * complex states are stored seperately from the simple states + * because they are signifignatly more difficult to track and + * most resources spend the vast majority of their lives in + * simple states. + * + * There are two special texture usages: `UNKNOWN` and `UNINITIALIZED`. + * - `UNKNOWN` is only used in complex states and is used to signify + * that the complex state does not know anything about those subresources. + * It cannot leak into transitions, it is invalid to transition into UNKNOWN + * state. + * - `UNINITIALIZED` is used in both simple and complex states to mean the texture + * is known to be in some undefined state. Any transition away from UNINITIALIZED + * will treat the contents as junk. +!*/ + +use super::{range::RangedStates, PendingTransition}; +use crate::{ + hub, + id::{TextureId, TypedId, Valid}, + resource::Texture, + track::{ + invalid_resource_state, skip_barrier, ResourceMetadata, ResourceMetadataProvider, + ResourceUses, UsageConflict, + }, + LifeGuard, RefCount, +}; +use hal::TextureUses; + +use arrayvec::ArrayVec; +use naga::FastHashMap; +use wgt::{strict_assert, strict_assert_eq}; + +use std::{borrow::Cow, iter, marker::PhantomData, ops::Range, vec::Drain}; + +/// Specifies a particular set of subresources in a texture. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TextureSelector { + pub mips: Range<u32>, + pub layers: Range<u32>, +} + +impl ResourceUses for TextureUses { + const EXCLUSIVE: Self = Self::EXCLUSIVE; + + type Id = TextureId; + type Selector = TextureSelector; + + fn bits(self) -> u16 { + Self::bits(&self) + } + + fn all_ordered(self) -> bool { + Self::ORDERED.contains(self) + } + + fn any_exclusive(self) -> bool { + self.intersects(Self::EXCLUSIVE) + } +} + +/// Represents the complex state of textures where every subresource is potentially +/// in a different state. +#[derive(Clone, Debug, Default, PartialEq)] +struct ComplexTextureState { + mips: ArrayVec<RangedStates<u32, TextureUses>, { hal::MAX_MIP_LEVELS as usize }>, +} + +impl ComplexTextureState { + /// Creates complex texture state for the given sizes. + /// + /// This state will be initialized with the UNKNOWN state, a special state + /// which means the trakcer knows nothing about the state. + fn new(mip_level_count: u32, array_layer_count: u32) -> Self { + Self { + mips: iter::repeat_with(|| { + RangedStates::from_range(0..array_layer_count, TextureUses::UNKNOWN) + }) + .take(mip_level_count as usize) + .collect(), + } + } + + /// Initialize a complex state from a selector representing the full size of the texture + /// and an iterator of a selector and a texture use, specifying a usage for a specific + /// set of subresources. + /// + /// [`Self::to_selector_state_iter`] can be used to create such an iterator. + /// + /// # Safety + /// + /// All selectors in the iterator must be inside of the full_range selector. + /// + /// The full range selector must have mips and layers start at 0. + unsafe fn from_selector_state_iter( + full_range: TextureSelector, + state_iter: impl Iterator<Item = (TextureSelector, TextureUses)>, + ) -> Self { + strict_assert_eq!(full_range.layers.start, 0); + strict_assert_eq!(full_range.mips.start, 0); + + let mut complex = + ComplexTextureState::new(full_range.mips.len() as u32, full_range.layers.len() as u32); + for (selector, desired_state) in state_iter { + strict_assert!(selector.layers.end <= full_range.layers.end); + strict_assert!(selector.mips.end <= full_range.mips.end); + + // This should only ever happen with a wgpu bug, but let's just double + // check that resource states don't have any conflicts. + strict_assert_eq!(invalid_resource_state(desired_state), false); + + let mips = selector.mips.start as usize..selector.mips.end as usize; + for mip in unsafe { complex.mips.get_unchecked_mut(mips) } { + for &mut (_, ref mut state) in mip.isolate(&selector.layers, TextureUses::UNKNOWN) { + *state = desired_state; + } + } + } + complex + } + + /// Convert a complex state into an iterator over all states stored. + /// + /// [`Self::from_selector_state_iter`] can be used to consume such an iterator. + fn to_selector_state_iter( + &self, + ) -> impl Iterator<Item = (TextureSelector, TextureUses)> + Clone + '_ { + self.mips.iter().enumerate().flat_map(|(mip, inner)| { + let mip = mip as u32; + { + inner.iter().map(move |&(ref layers, inner)| { + ( + TextureSelector { + mips: mip..mip + 1, + layers: layers.clone(), + }, + inner, + ) + }) + } + }) + } +} + +/// Stores all the textures that a bind group stores. +pub(crate) struct TextureBindGroupState<A: hub::HalApi> { + textures: Vec<( + Valid<TextureId>, + Option<TextureSelector>, + RefCount, + TextureUses, + )>, + + _phantom: PhantomData<A>, +} +impl<A: hub::HalApi> TextureBindGroupState<A> { + pub fn new() -> Self { + Self { + textures: Vec::new(), + + _phantom: PhantomData, + } + } + + /// Optimize the texture bind group state by sorting it by ID. + /// + /// When this list of states is merged into a tracker, the memory + /// accesses will be in a constant assending order. + pub(crate) fn optimize(&mut self) { + self.textures + .sort_unstable_by_key(|&(id, _, _, _)| id.0.unzip().0); + } + + /// Returns a list of all buffers tracked. May contain duplicates. + pub fn used(&self) -> impl Iterator<Item = Valid<TextureId>> + '_ { + self.textures.iter().map(|&(id, _, _, _)| id) + } + + /// Adds the given resource with the given state. + pub fn add_single<'a>( + &mut self, + storage: &'a hub::Storage<Texture<A>, TextureId>, + id: TextureId, + ref_count: RefCount, + selector: Option<TextureSelector>, + state: TextureUses, + ) -> Option<&'a Texture<A>> { + let value = storage.get(id).ok()?; + + self.textures.push((Valid(id), selector, ref_count, state)); + + Some(value) + } +} + +/// Container for corresponding simple and complex texture states. +#[derive(Debug)] +pub(crate) struct TextureStateSet { + simple: Vec<TextureUses>, + complex: FastHashMap<u32, ComplexTextureState>, +} +impl TextureStateSet { + fn new() -> Self { + Self { + simple: Vec::new(), + complex: FastHashMap::default(), + } + } + + fn set_size(&mut self, size: usize) { + self.simple.resize(size, TextureUses::UNINITIALIZED); + } +} + +/// Stores all texture state within a single usage scope. +#[derive(Debug)] +pub(crate) struct TextureUsageScope<A: hub::HalApi> { + set: TextureStateSet, + + metadata: ResourceMetadata<A>, +} + +impl<A: hub::HalApi> TextureUsageScope<A> { + pub fn new() -> Self { + Self { + set: TextureStateSet::new(), + + metadata: ResourceMetadata::new(), + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); + + strict_assert!(index < self.set.simple.len()); + + strict_assert!(if self.metadata.contains(index) + && self.set.simple[index] == TextureUses::COMPLEX + { + self.set.complex.contains_key(&(index as u32)) + } else { + true + }); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Texture ID before + /// all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.set.set_size(size); + self.metadata.set_size(size); + } + + /// Returns a list of all textures tracked. + pub fn used(&self) -> impl Iterator<Item = Valid<TextureId>> + '_ { + self.metadata.owned_ids() + } + + /// Returns true if the tracker owns no resources. + /// + /// This is a O(n) operation. + pub(crate) fn is_empty(&self) -> bool { + self.metadata.is_empty() + } + + /// Merge the list of texture states in the given usage scope into this UsageScope. + /// + /// If any of the resulting states is invalid, stops the merge and returns a usage + /// conflict with the details of the invalid state. + /// + /// If the given tracker uses IDs higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn merge_usage_scope( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + scope: &Self, + ) -> Result<(), UsageConflict> { + let incoming_size = scope.set.simple.len(); + if incoming_size > self.set.simple.len() { + self.set_size(incoming_size); + } + + for index in scope.metadata.owned_indices() { + let index32 = index as u32; + + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); + + let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + unsafe { + insert_or_merge( + texture_data, + &mut self.set, + &mut self.metadata, + index32, + index, + TextureStateProvider::TextureSet { set: &scope.set }, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + )? + }; + } + + Ok(()) + } + + /// Merge the list of texture states in the given bind group into this usage scope. + /// + /// If any of the resulting states is invalid, stops the merge and returns a usage + /// conflict with the details of the invalid state. + /// + /// Because bind groups do not check if the union of all their states is valid, + /// this method is allowed to return Err on the first bind group bound. + /// + /// # Safety + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + pub unsafe fn merge_bind_group( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + bind_group: &TextureBindGroupState<A>, + ) -> Result<(), UsageConflict> { + for &(id, ref selector, ref ref_count, state) in &bind_group.textures { + unsafe { self.merge_single(storage, id, selector.clone(), ref_count, state)? }; + } + + Ok(()) + } + + /// Merge a single state into the UsageScope. + /// + /// If the resulting state is invalid, returns a usage + /// conflict with the details of the invalid state. + /// + /// # Safety + /// + /// Unlike other trackers whose merge_single is safe, this method is only + /// called where there is already other unsafe tracking functions active, + /// so we can prove this unsafe "for free". + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + pub unsafe fn merge_single( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + id: Valid<TextureId>, + selector: Option<TextureSelector>, + ref_count: &RefCount, + new_state: TextureUses, + ) -> Result<(), UsageConflict> { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + self.tracker_assert_in_bounds(index); + + let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + unsafe { + insert_or_merge( + texture_data, + &mut self.set, + &mut self.metadata, + index32, + index, + TextureStateProvider::from_option(selector, new_state), + ResourceMetadataProvider::Direct { + epoch, + ref_count: Cow::Borrowed(ref_count), + }, + )? + }; + + Ok(()) + } +} + +/// Stores all texture state within a command buffer or device. +pub(crate) struct TextureTracker<A: hub::HalApi> { + start_set: TextureStateSet, + end_set: TextureStateSet, + + metadata: ResourceMetadata<A>, + + temp: Vec<PendingTransition<TextureUses>>, + + _phantom: PhantomData<A>, +} +impl<A: hub::HalApi> TextureTracker<A> { + pub fn new() -> Self { + Self { + start_set: TextureStateSet::new(), + end_set: TextureStateSet::new(), + + metadata: ResourceMetadata::new(), + + temp: Vec::new(), + + _phantom: PhantomData, + } + } + + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); + + strict_assert!(index < self.start_set.simple.len()); + strict_assert!(index < self.end_set.simple.len()); + + strict_assert!(if self.metadata.contains(index) + && self.start_set.simple[index] == TextureUses::COMPLEX + { + self.start_set.complex.contains_key(&(index as u32)) + } else { + true + }); + strict_assert!(if self.metadata.contains(index) + && self.end_set.simple[index] == TextureUses::COMPLEX + { + self.end_set.complex.contains_key(&(index as u32)) + } else { + true + }); + } + + /// Sets the size of all the vectors inside the tracker. + /// + /// Must be called with the highest possible Texture ID before + /// all unsafe functions are called. + pub fn set_size(&mut self, size: usize) { + self.start_set.set_size(size); + self.end_set.set_size(size); + + self.metadata.set_size(size); + } + + /// Extend the vectors to let the given index be valid. + fn allow_index(&mut self, index: usize) { + if index >= self.start_set.simple.len() { + self.set_size(index + 1); + } + } + + /// Returns a list of all textures tracked. + pub fn used(&self) -> impl Iterator<Item = Valid<TextureId>> + '_ { + self.metadata.owned_ids() + } + + /// Drains all currently pending transitions. + pub fn drain(&mut self) -> Drain<PendingTransition<TextureUses>> { + self.temp.drain(..) + } + + /// Get the refcount of the given resource. + /// + /// # Safety + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + /// + /// The resource must be tracked by this tracker. + pub unsafe fn get_ref_count(&self, id: Valid<TextureId>) -> &RefCount { + let (index32, _, _) = id.0.unzip(); + let index = index32 as usize; + + self.tracker_assert_in_bounds(index); + + unsafe { self.metadata.get_ref_count_unchecked(index) } + } + + /// Inserts a single texture and a state into the resource tracker. + /// + /// If the resource already exists in the tracker, this will panic. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn insert_single(&mut self, id: TextureId, ref_count: RefCount, usage: TextureUses) { + let (index32, epoch, _) = id.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + let currently_owned = self.metadata.contains_unchecked(index); + + if currently_owned { + panic!("Tried to insert texture already tracked"); + } + + insert( + None, + Some(&mut self.start_set), + &mut self.end_set, + &mut self.metadata, + index32, + index, + TextureStateProvider::KnownSingle { state: usage }, + None, + ResourceMetadataProvider::Direct { + epoch, + ref_count: Cow::Owned(ref_count), + }, + ) + }; + } + + /// Sets the state of a single texture. + /// + /// If a transition is needed to get the texture into the given state, that transition + /// is returned. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_single( + &mut self, + texture: &Texture<A>, + id: TextureId, + selector: TextureSelector, + new_state: TextureUses, + ) -> Option<Drain<'_, PendingTransition<TextureUses>>> { + let (index32, epoch, _) = id.unzip(); + let index = index32 as usize; + + self.allow_index(index); + + self.tracker_assert_in_bounds(index); + + unsafe { + insert_or_barrier_update( + (&texture.life_guard, &texture.full_range), + Some(&mut self.start_set), + &mut self.end_set, + &mut self.metadata, + index32, + index, + TextureStateProvider::Selector { + selector, + state: new_state, + }, + None, + ResourceMetadataProvider::Resource { epoch }, + &mut self.temp, + ) + } + + Some(self.temp.drain(..)) + } + + /// Sets the given state for all texture in the given tracker. + /// + /// If a transition is needed to get the texture into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_from_tracker( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + tracker: &Self, + ) { + let incoming_size = tracker.start_set.simple.len(); + if incoming_size > self.start_set.simple.len() { + self.set_size(incoming_size); + } + + for index in tracker.metadata.owned_indices() { + let index32 = index as u32; + + self.tracker_assert_in_bounds(index); + tracker.tracker_assert_in_bounds(index); + unsafe { + insert_or_barrier_update( + texture_data_from_texture(storage, index32), + Some(&mut self.start_set), + &mut self.end_set, + &mut self.metadata, + index32, + index, + TextureStateProvider::TextureSet { + set: &tracker.start_set, + }, + Some(TextureStateProvider::TextureSet { + set: &tracker.end_set, + }), + ResourceMetadataProvider::Indirect { + metadata: &tracker.metadata, + }, + &mut self.temp, + ); + } + } + } + + /// Sets the given state for all textures in the given UsageScope. + /// + /// If a transition is needed to get the textures into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// If the ID is higher than the length of internal vectors, + /// the vectors will be extended. A call to set_size is not needed. + pub fn set_from_usage_scope( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + scope: &TextureUsageScope<A>, + ) { + let incoming_size = scope.set.simple.len(); + if incoming_size > self.start_set.simple.len() { + self.set_size(incoming_size); + } + + for index in scope.metadata.owned_indices() { + let index32 = index as u32; + + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); + unsafe { + insert_or_barrier_update( + texture_data_from_texture(storage, index32), + Some(&mut self.start_set), + &mut self.end_set, + &mut self.metadata, + index32, + index, + TextureStateProvider::TextureSet { set: &scope.set }, + None, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + &mut self.temp, + ); + } + } + } + + /// Iterates through all textures in the given bind group and adopts + /// the state given for those textures in the UsageScope. It also + /// removes all touched textures from the usage scope. + /// + /// If a transition is needed to get the textures into the needed state, + /// those transitions are stored within the tracker. A subsequent + /// call to [`Self::drain`] is needed to get those transitions. + /// + /// This is a really funky method used by Compute Passes to generate + /// barriers after a call to dispatch without needing to iterate + /// over all elements in the usage scope. We use each the + /// bind group as a source of which IDs to look at. The bind groups + /// must have first been added to the usage scope. + /// + /// # Safety + /// + /// [`Self::set_size`] must be called with the maximum possible Buffer ID before this + /// method is called. + pub unsafe fn set_and_remove_from_usage_scope_sparse( + &mut self, + storage: &hub::Storage<Texture<A>, TextureId>, + scope: &mut TextureUsageScope<A>, + bind_group_state: &TextureBindGroupState<A>, + ) { + let incoming_size = scope.set.simple.len(); + if incoming_size > self.start_set.simple.len() { + self.set_size(incoming_size); + } + + for &(id, _, _, _) in bind_group_state.textures.iter() { + let (index32, _, _) = id.0.unzip(); + let index = index32 as usize; + scope.tracker_assert_in_bounds(index); + + if unsafe { !scope.metadata.contains_unchecked(index) } { + continue; + } + let texture_data = unsafe { texture_data_from_texture(storage, index32) }; + unsafe { + insert_or_barrier_update( + texture_data, + Some(&mut self.start_set), + &mut self.end_set, + &mut self.metadata, + index32, + index, + TextureStateProvider::TextureSet { set: &scope.set }, + None, + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, + }, + &mut self.temp, + ) + }; + + unsafe { scope.metadata.remove(index) }; + } + } + + /// Unconditionally removes the given resource from the tracker. + /// + /// Returns true if the resource was removed. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + pub fn remove(&mut self, id: Valid<TextureId>) -> bool { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_epoch = self.metadata.get_epoch_unchecked(index); + assert_eq!(existing_epoch, epoch); + + self.start_set.complex.remove(&index32); + self.end_set.complex.remove(&index32); + + self.metadata.remove(index); + + return true; + } + } + + false + } + + /// Removes the given resource from the tracker iff we have the last reference to the + /// resource and the epoch matches. + /// + /// Returns true if the resource was removed. + /// + /// If the ID is higher than the length of internal vectors, + /// false will be returned. + pub fn remove_abandoned(&mut self, id: Valid<TextureId>) -> bool { + let (index32, epoch, _) = id.0.unzip(); + let index = index32 as usize; + + if index > self.metadata.size() { + return false; + } + + self.tracker_assert_in_bounds(index); + + unsafe { + if self.metadata.contains_unchecked(index) { + let existing_epoch = self.metadata.get_epoch_unchecked(index); + let existing_ref_count = self.metadata.get_ref_count_unchecked(index); + + if existing_epoch == epoch && existing_ref_count.load() == 1 { + self.start_set.complex.remove(&index32); + self.end_set.complex.remove(&index32); + + self.metadata.remove(index); + + return true; + } + } + } + + false + } +} + +/// An iterator adapter that can store two different iterator types. +#[derive(Clone)] +enum EitherIter<L, R> { + Left(L), + Right(R), +} + +impl<L, R, D> Iterator for EitherIter<L, R> +where + L: Iterator<Item = D>, + R: Iterator<Item = D>, +{ + type Item = D; + + fn next(&mut self) -> Option<Self::Item> { + match *self { + EitherIter::Left(ref mut inner) => inner.next(), + EitherIter::Right(ref mut inner) => inner.next(), + } + } +} + +/// Container that signifies storing both different things +/// if there is a single state or many different states +/// involved in the operation. +#[derive(Debug, Clone)] +enum SingleOrManyStates<S, M> { + Single(S), + Many(M), +} + +/// A source of texture state. +#[derive(Clone)] +enum TextureStateProvider<'a> { + /// Comes directly from a single state. + KnownSingle { state: TextureUses }, + /// Comes from a selector and a single state. + Selector { + selector: TextureSelector, + state: TextureUses, + }, + /// Comes from another texture set. + TextureSet { set: &'a TextureStateSet }, +} +impl<'a> TextureStateProvider<'a> { + /// Convenience function turning `Option<Selector>` into this enum. + fn from_option(selector: Option<TextureSelector>, state: TextureUses) -> Self { + match selector { + Some(selector) => Self::Selector { selector, state }, + None => Self::KnownSingle { state }, + } + } + + /// Get the state provided by this. + /// + /// # Panics + /// + /// Panics if texture_data is None and this uses a Selector source. + /// + /// # Safety + /// + /// - The index must be in bounds of the state set if this uses an TextureSet source. + #[inline(always)] + unsafe fn get_state( + self, + texture_data: Option<(&LifeGuard, &TextureSelector)>, + index32: u32, + index: usize, + ) -> SingleOrManyStates< + TextureUses, + impl Iterator<Item = (TextureSelector, TextureUses)> + Clone + 'a, + > { + match self { + TextureStateProvider::KnownSingle { state } => SingleOrManyStates::Single(state), + TextureStateProvider::Selector { selector, state } => { + // We check if the selector given is actually for the full resource, + // and if it is we promote to a simple state. This allows upstream + // code to specify selectors willy nilly, and all that are really + // single states are promoted here. + if *texture_data.unwrap().1 == selector { + SingleOrManyStates::Single(state) + } else { + SingleOrManyStates::Many(EitherIter::Left(iter::once((selector, state)))) + } + } + TextureStateProvider::TextureSet { set } => { + let new_state = *unsafe { set.simple.get_unchecked(index) }; + + if new_state == TextureUses::COMPLEX { + let new_complex = unsafe { set.complex.get(&index32).unwrap_unchecked() }; + + SingleOrManyStates::Many(EitherIter::Right( + new_complex.to_selector_state_iter(), + )) + } else { + SingleOrManyStates::Single(new_state) + } + } + } + } +} + +/// Helper function that gets what is needed from the texture storage +/// out of the texture storage. +#[inline(always)] +unsafe fn texture_data_from_texture<A: hub::HalApi>( + storage: &hub::Storage<Texture<A>, TextureId>, + index32: u32, +) -> (&LifeGuard, &TextureSelector) { + let texture = unsafe { storage.get_unchecked(index32) }; + (&texture.life_guard, &texture.full_range) +} + +/// Does an insertion operation if the index isn't tracked +/// in the current metadata, otherwise merges the given state +/// with the current state. If the merging would cause +/// a conflict, returns that usage conflict. +/// +/// # Safety +/// +/// Indexes must be valid indexes into all arrays passed in +/// to this function, either directly or via metadata or provider structs. +#[inline(always)] +unsafe fn insert_or_merge<A: hub::HalApi>( + texture_data: (&LifeGuard, &TextureSelector), + current_state_set: &mut TextureStateSet, + resource_metadata: &mut ResourceMetadata<A>, + index32: u32, + index: usize, + state_provider: TextureStateProvider<'_>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) -> Result<(), UsageConflict> { + let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; + + if !currently_owned { + unsafe { + insert( + Some(texture_data), + None, + current_state_set, + resource_metadata, + index32, + index, + state_provider, + None, + metadata_provider, + ) + }; + return Ok(()); + } + + unsafe { + merge( + texture_data, + current_state_set, + index32, + index, + state_provider, + metadata_provider, + ) + } +} + +/// If the resource isn't tracked +/// - Inserts the given resource. +/// - Uses the `start_state_provider` to populate `start_states` +/// - Uses either `end_state_provider` or `start_state_provider` +/// to populate `current_states`. +/// If the resource is tracked +/// - Inserts barriers from the state in `current_states` +/// to the state provided by `start_state_provider`. +/// - Updates the `current_states` with either the state from +/// `end_state_provider` or `start_state_provider`. +/// +/// Any barriers are added to the barrier vector. +/// +/// # Safety +/// +/// Indexes must be valid indexes into all arrays passed in +/// to this function, either directly or via metadata or provider structs. +#[inline(always)] +unsafe fn insert_or_barrier_update<A: hub::HalApi>( + texture_data: (&LifeGuard, &TextureSelector), + start_state: Option<&mut TextureStateSet>, + current_state_set: &mut TextureStateSet, + resource_metadata: &mut ResourceMetadata<A>, + index32: u32, + index: usize, + start_state_provider: TextureStateProvider<'_>, + end_state_provider: Option<TextureStateProvider<'_>>, + metadata_provider: ResourceMetadataProvider<'_, A>, + barriers: &mut Vec<PendingTransition<TextureUses>>, +) { + let currently_owned = unsafe { resource_metadata.contains_unchecked(index) }; + + if !currently_owned { + unsafe { + insert( + Some(texture_data), + start_state, + current_state_set, + resource_metadata, + index32, + index, + start_state_provider, + end_state_provider, + metadata_provider, + ) + }; + return; + } + + let update_state_provider = end_state_provider.unwrap_or_else(|| start_state_provider.clone()); + unsafe { + barrier( + texture_data, + current_state_set, + index32, + index, + start_state_provider, + barriers, + ) + }; + + let start_state_set = start_state.unwrap(); + unsafe { + update( + texture_data, + start_state_set, + current_state_set, + index32, + index, + update_state_provider, + ) + }; +} + +#[inline(always)] +unsafe fn insert<A: hub::HalApi>( + texture_data: Option<(&LifeGuard, &TextureSelector)>, + start_state: Option<&mut TextureStateSet>, + end_state: &mut TextureStateSet, + resource_metadata: &mut ResourceMetadata<A>, + index32: u32, + index: usize, + start_state_provider: TextureStateProvider<'_>, + end_state_provider: Option<TextureStateProvider<'_>>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) { + let start_layers = unsafe { start_state_provider.get_state(texture_data, index32, index) }; + match start_layers { + SingleOrManyStates::Single(state) => { + // This should only ever happen with a wgpu bug, but let's just double + // check that resource states don't have any conflicts. + strict_assert_eq!(invalid_resource_state(state), false); + + log::trace!("\ttex {index32}: insert start {state:?}"); + + if let Some(start_state) = start_state { + unsafe { *start_state.simple.get_unchecked_mut(index) = state }; + } + + // We only need to insert ourselves the end state if there is no end state provider. + if end_state_provider.is_none() { + unsafe { *end_state.simple.get_unchecked_mut(index) = state }; + } + } + SingleOrManyStates::Many(state_iter) => { + let full_range = texture_data.unwrap().1.clone(); + + let complex = + unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; + + log::trace!("\ttex {index32}: insert start {complex:?}"); + + if let Some(start_state) = start_state { + unsafe { *start_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; + start_state.complex.insert(index32, complex.clone()); + } + + // We only need to insert ourselves the end state if there is no end state provider. + if end_state_provider.is_none() { + unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; + end_state.complex.insert(index32, complex); + } + } + } + + if let Some(end_state_provider) = end_state_provider { + match unsafe { end_state_provider.get_state(texture_data, index32, index) } { + SingleOrManyStates::Single(state) => { + // This should only ever happen with a wgpu bug, but let's just double + // check that resource states don't have any conflicts. + strict_assert_eq!(invalid_resource_state(state), false); + + log::trace!("\ttex {index32}: insert end {state:?}"); + + // We only need to insert into the end, as there is guarenteed to be + // a start state provider. + unsafe { *end_state.simple.get_unchecked_mut(index) = state }; + } + SingleOrManyStates::Many(state_iter) => { + let full_range = texture_data.unwrap().1.clone(); + + let complex = unsafe { + ComplexTextureState::from_selector_state_iter(full_range, state_iter) + }; + + log::trace!("\ttex {index32}: insert end {complex:?}"); + + // We only need to insert into the end, as there is guarenteed to be + // a start state provider. + unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; + end_state.complex.insert(index32, complex); + } + } + } + + unsafe { + let (epoch, ref_count) = + metadata_provider.get_own(texture_data.map(|(life_guard, _)| life_guard), index); + resource_metadata.insert(index, epoch, ref_count); + } +} + +#[inline(always)] +unsafe fn merge<A: hub::HalApi>( + texture_data: (&LifeGuard, &TextureSelector), + current_state_set: &mut TextureStateSet, + index32: u32, + index: usize, + state_provider: TextureStateProvider<'_>, + metadata_provider: ResourceMetadataProvider<'_, A>, +) -> Result<(), UsageConflict> { + let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; + let current_state = if *current_simple == TextureUses::COMPLEX { + SingleOrManyStates::Many(unsafe { + current_state_set + .complex + .get_mut(&index32) + .unwrap_unchecked() + }) + } else { + SingleOrManyStates::Single(current_simple) + }; + + let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + + match (current_state, new_state) { + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { + let merged_state = *current_simple | new_simple; + + log::trace!("\ttex {index32}: merge simple {current_simple:?} + {new_simple:?}"); + + if invalid_resource_state(merged_state) { + return Err(UsageConflict::from_texture( + TextureId::zip( + index32, + unsafe { metadata_provider.get_epoch(index) }, + A::VARIANT, + ), + texture_data.1.clone(), + *current_simple, + new_simple, + )); + } + + *current_simple = merged_state; + } + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { + // Because we are now demoting this simple state to a complex state, + // we actually need to make a whole new complex state for us to use + // as there wasn't one before. + let mut new_complex = unsafe { + ComplexTextureState::from_selector_state_iter( + texture_data.1.clone(), + iter::once((texture_data.1.clone(), *current_simple)), + ) + }; + + for (selector, new_state) in new_many { + let merged_state = *current_simple | new_state; + + log::trace!( + "\ttex {index32}: merge {selector:?} {current_simple:?} + {new_state:?}" + ); + + if invalid_resource_state(merged_state) { + return Err(UsageConflict::from_texture( + TextureId::zip( + index32, + unsafe { metadata_provider.get_epoch(index) }, + A::VARIANT, + ), + selector, + *current_simple, + new_state, + )); + } + + for mip in + &mut new_complex.mips[selector.mips.start as usize..selector.mips.end as usize] + { + for &mut (_, ref mut current_layer_state) in + mip.isolate(&selector.layers, TextureUses::UNKNOWN) + { + *current_layer_state = merged_state; + } + + mip.coalesce(); + } + } + + *current_simple = TextureUses::COMPLEX; + current_state_set.complex.insert(index32, new_complex); + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { + for (mip_id, mip) in current_complex.mips.iter_mut().enumerate() { + let mip_id = mip_id as u32; + + for &mut (ref layers, ref mut current_layer_state) in mip.iter_mut() { + let merged_state = *current_layer_state | new_simple; + + // Once we remove unknown, this will never be empty, as + // simple states are never unknown. + let merged_state = merged_state - TextureUses::UNKNOWN; + + log::trace!( + "\ttex {index32}: merge mip {mip_id} layers {layers:?} \ + {current_layer_state:?} + {new_simple:?}" + ); + + if invalid_resource_state(merged_state) { + return Err(UsageConflict::from_texture( + TextureId::zip( + index32, + unsafe { metadata_provider.get_epoch(index) }, + A::VARIANT, + ), + TextureSelector { + mips: mip_id..mip_id + 1, + layers: layers.clone(), + }, + *current_layer_state, + new_simple, + )); + } + + *current_layer_state = merged_state; + } + + mip.coalesce(); + } + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { + for (selector, new_state) in new_many { + for mip_id in selector.mips { + strict_assert!((mip_id as usize) < current_complex.mips.len()); + + let mip = unsafe { current_complex.mips.get_unchecked_mut(mip_id as usize) }; + + for &mut (ref layers, ref mut current_layer_state) in + mip.isolate(&selector.layers, TextureUses::UNKNOWN) + { + let merged_state = *current_layer_state | new_state; + let merged_state = merged_state - TextureUses::UNKNOWN; + + if merged_state.is_empty() { + // We know nothing about this state, lets just move on. + continue; + } + + log::trace!( + "\ttex {index32}: merge mip {mip_id} layers {layers:?} \ + {current_layer_state:?} + {new_state:?}" + ); + + if invalid_resource_state(merged_state) { + return Err(UsageConflict::from_texture( + TextureId::zip( + index32, + unsafe { metadata_provider.get_epoch(index) }, + A::VARIANT, + ), + TextureSelector { + mips: mip_id..mip_id + 1, + layers: layers.clone(), + }, + *current_layer_state, + new_state, + )); + } + *current_layer_state = merged_state; + } + + mip.coalesce(); + } + } + } + } + Ok(()) +} + +#[inline(always)] +unsafe fn barrier( + texture_data: (&LifeGuard, &TextureSelector), + current_state_set: &TextureStateSet, + index32: u32, + index: usize, + state_provider: TextureStateProvider<'_>, + barriers: &mut Vec<PendingTransition<TextureUses>>, +) { + let current_simple = unsafe { *current_state_set.simple.get_unchecked(index) }; + let current_state = if current_simple == TextureUses::COMPLEX { + SingleOrManyStates::Many(unsafe { + current_state_set.complex.get(&index32).unwrap_unchecked() + }) + } else { + SingleOrManyStates::Single(current_simple) + }; + + let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + + match (current_state, new_state) { + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { + if skip_barrier(current_simple, new_simple) { + return; + } + + log::trace!("\ttex {index32}: transition simple {current_simple:?} -> {new_simple:?}"); + + barriers.push(PendingTransition { + id: index32, + selector: texture_data.1.clone(), + usage: current_simple..new_simple, + }); + } + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { + for (selector, new_state) in new_many { + if new_state == TextureUses::UNKNOWN { + continue; + } + + if skip_barrier(current_simple, new_state) { + continue; + } + + log::trace!( + "\ttex {index32}: transition {selector:?} {current_simple:?} -> {new_state:?}" + ); + + barriers.push(PendingTransition { + id: index32, + selector, + usage: current_simple..new_state, + }); + } + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { + for (mip_id, mip) in current_complex.mips.iter().enumerate() { + let mip_id = mip_id as u32; + + for &(ref layers, current_layer_state) in mip.iter() { + if current_layer_state == TextureUses::UNKNOWN { + continue; + } + + if skip_barrier(current_layer_state, new_simple) { + continue; + } + + log::trace!( + "\ttex {index32}: transition mip {mip_id} layers {layers:?} \ + {current_layer_state:?} -> {new_simple:?}" + ); + + barriers.push(PendingTransition { + id: index32, + selector: TextureSelector { + mips: mip_id..mip_id + 1, + layers: layers.clone(), + }, + usage: current_layer_state..new_simple, + }); + } + } + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { + for (selector, new_state) in new_many { + for mip_id in selector.mips { + strict_assert!((mip_id as usize) < current_complex.mips.len()); + + let mip = unsafe { current_complex.mips.get_unchecked(mip_id as usize) }; + + for (layers, current_layer_state) in mip.iter_filter(&selector.layers) { + if *current_layer_state == TextureUses::UNKNOWN + || new_state == TextureUses::UNKNOWN + { + continue; + } + + if skip_barrier(*current_layer_state, new_state) { + continue; + } + + log::trace!( + "\ttex {index32}: transition mip {mip_id} layers {layers:?} \ + {current_layer_state:?} -> {new_state:?}" + ); + + barriers.push(PendingTransition { + id: index32, + selector: TextureSelector { + mips: mip_id..mip_id + 1, + layers, + }, + usage: *current_layer_state..new_state, + }); + } + } + } + } + } +} + +#[allow(clippy::needless_option_as_deref)] // we use this for reborrowing Option<&mut T> +#[inline(always)] +unsafe fn update( + texture_data: (&LifeGuard, &TextureSelector), + start_state_set: &mut TextureStateSet, + current_state_set: &mut TextureStateSet, + index32: u32, + index: usize, + state_provider: TextureStateProvider<'_>, +) { + let start_simple = unsafe { *start_state_set.simple.get_unchecked(index) }; + + // We only ever need to update the start state here if the state is complex. + // + // If the state is simple, the first insert to the tracker would cover it. + let mut start_complex = None; + if start_simple == TextureUses::COMPLEX { + start_complex = + Some(unsafe { start_state_set.complex.get_mut(&index32).unwrap_unchecked() }); + } + + let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; + let current_state = if *current_simple == TextureUses::COMPLEX { + SingleOrManyStates::Many(unsafe { + current_state_set + .complex + .get_mut(&index32) + .unwrap_unchecked() + }) + } else { + SingleOrManyStates::Single(current_simple) + }; + + let new_state = unsafe { state_provider.get_state(Some(texture_data), index32, index) }; + + match (current_state, new_state) { + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Single(new_simple)) => { + *current_simple = new_simple; + } + (SingleOrManyStates::Single(current_simple), SingleOrManyStates::Many(new_many)) => { + // Because we are now demoting this simple state to a complex state, + // we actually need to make a whole new complex state for us to use + // as there wasn't one before. + let mut new_complex = unsafe { + ComplexTextureState::from_selector_state_iter( + texture_data.1.clone(), + iter::once((texture_data.1.clone(), *current_simple)), + ) + }; + + for (selector, mut new_state) in new_many { + if new_state == TextureUses::UNKNOWN { + new_state = *current_simple; + } + for mip in + &mut new_complex.mips[selector.mips.start as usize..selector.mips.end as usize] + { + for &mut (_, ref mut current_layer_state) in + mip.isolate(&selector.layers, TextureUses::UNKNOWN) + { + *current_layer_state = new_state; + } + + mip.coalesce(); + } + } + + *current_simple = TextureUses::COMPLEX; + current_state_set.complex.insert(index32, new_complex); + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_single)) => { + for (mip_id, mip) in current_complex.mips.iter().enumerate() { + for &(ref layers, current_layer_state) in mip.iter() { + // If this state is unknown, that means that the start is _also_ unknown. + if current_layer_state == TextureUses::UNKNOWN { + if let Some(&mut ref mut start_complex) = start_complex { + strict_assert!(mip_id < start_complex.mips.len()); + + let start_mip = unsafe { start_complex.mips.get_unchecked_mut(mip_id) }; + + for &mut (_, ref mut current_start_state) in + start_mip.isolate(layers, TextureUses::UNKNOWN) + { + strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); + *current_start_state = new_single; + } + + start_mip.coalesce(); + } + } + } + } + + unsafe { *current_state_set.simple.get_unchecked_mut(index) = new_single }; + unsafe { + current_state_set + .complex + .remove(&index32) + .unwrap_unchecked() + }; + } + (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { + for (selector, new_state) in new_many { + if new_state == TextureUses::UNKNOWN { + // We know nothing new + continue; + } + + for mip_id in selector.mips { + let mip_id = mip_id as usize; + strict_assert!(mip_id < current_complex.mips.len()); + + let mip = unsafe { current_complex.mips.get_unchecked_mut(mip_id) }; + + for &mut (ref layers, ref mut current_layer_state) in + mip.isolate(&selector.layers, TextureUses::UNKNOWN) + { + if *current_layer_state == TextureUses::UNKNOWN + && new_state != TextureUses::UNKNOWN + { + // We now know something about this subresource that + // we didn't before so we should go back and update + // the start state. + // + // We know we must have starter state be complex, + // otherwise we would know about this state. + strict_assert!(start_complex.is_some()); + + let start_complex = + unsafe { start_complex.as_deref_mut().unwrap_unchecked() }; + + strict_assert!(mip_id < start_complex.mips.len()); + + let start_mip = unsafe { start_complex.mips.get_unchecked_mut(mip_id) }; + + for &mut (_, ref mut current_start_state) in + start_mip.isolate(layers, TextureUses::UNKNOWN) + { + strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); + *current_start_state = new_state; + } + + start_mip.coalesce(); + } + + *current_layer_state = new_state; + } + + mip.coalesce(); + } + } + } + } +} diff --git a/third_party/rust/wgpu-core/src/validation.rs b/third_party/rust/wgpu-core/src/validation.rs new file mode 100644 index 0000000000..eadc0005d0 --- /dev/null +++ b/third_party/rust/wgpu-core/src/validation.rs @@ -0,0 +1,1239 @@ +use crate::{binding_model::BindEntryMap, FastHashMap, FastHashSet}; +use naga::valid::GlobalUse; +use std::{collections::hash_map::Entry, fmt}; +use thiserror::Error; +use wgt::{BindGroupLayoutEntry, BindingType}; + +#[derive(Debug)] +enum ResourceType { + Buffer { + size: wgt::BufferSize, + }, + Texture { + dim: naga::ImageDimension, + arrayed: bool, + class: naga::ImageClass, + }, + Sampler { + comparison: bool, + }, +} + +#[derive(Debug)] +struct Resource { + #[allow(unused)] + name: Option<String>, + bind: naga::ResourceBinding, + ty: ResourceType, + class: naga::AddressSpace, +} + +#[derive(Clone, Copy, Debug)] +enum NumericDimension { + Scalar, + Vector(naga::VectorSize), + Matrix(naga::VectorSize, naga::VectorSize), +} + +impl fmt::Display for NumericDimension { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Scalar => write!(f, ""), + Self::Vector(size) => write!(f, "x{}", size as u8), + Self::Matrix(columns, rows) => write!(f, "x{}{}", columns as u8, rows as u8), + } + } +} + +impl NumericDimension { + fn num_components(&self) -> u32 { + match *self { + Self::Scalar => 1, + Self::Vector(size) => size as u32, + Self::Matrix(w, h) => w as u32 * h as u32, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NumericType { + dim: NumericDimension, + kind: naga::ScalarKind, + width: naga::Bytes, +} + +impl fmt::Display for NumericType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}{}{}", self.kind, self.width * 8, self.dim) + } +} + +#[derive(Clone, Debug)] +pub struct InterfaceVar { + pub ty: NumericType, + interpolation: Option<naga::Interpolation>, + sampling: Option<naga::Sampling>, +} + +impl InterfaceVar { + pub fn vertex_attribute(format: wgt::VertexFormat) -> Self { + InterfaceVar { + ty: NumericType::from_vertex_format(format), + interpolation: None, + sampling: None, + } + } +} + +impl fmt::Display for InterfaceVar { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{} interpolated as {:?} with sampling {:?}", + self.ty, self.interpolation, self.sampling + ) + } +} + +#[derive(Debug)] +enum Varying { + Local { location: u32, iv: InterfaceVar }, + BuiltIn(naga::BuiltIn), +} + +#[allow(unused)] +#[derive(Debug)] +struct SpecializationConstant { + id: u32, + ty: NumericType, +} + +#[derive(Debug, Default)] +struct EntryPoint { + inputs: Vec<Varying>, + outputs: Vec<Varying>, + resources: Vec<(naga::Handle<Resource>, GlobalUse)>, + #[allow(unused)] + spec_constants: Vec<SpecializationConstant>, + sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>, + workgroup_size: [u32; 3], +} + +#[derive(Debug)] +pub struct Interface { + features: wgt::Features, + limits: wgt::Limits, + resources: naga::Arena<Resource>, + entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>, +} + +#[derive(Clone, Debug, Error)] +#[error("Buffer usage is {actual:?} which does not contain required usage {expected:?}")] +pub struct MissingBufferUsageError { + pub(crate) actual: wgt::BufferUsages, + pub(crate) expected: wgt::BufferUsages, +} + +/// Checks that the given buffer usage contains the required buffer usage, +/// returns an error otherwise. +pub fn check_buffer_usage( + actual: wgt::BufferUsages, + expected: wgt::BufferUsages, +) -> 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::TextureUsages, + pub(crate) expected: wgt::TextureUsages, +} + +/// Checks that the given texture usage contains the required texture usage, +/// returns an error otherwise. +pub fn check_texture_usage( + actual: wgt::TextureUsages, + expected: wgt::TextureUsages, +) -> Result<(), MissingTextureUsageError> { + if !actual.contains(expected) { + Err(MissingTextureUsageError { actual, expected }) + } else { + Ok(()) + } +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum BindingError { + #[error("Binding is missing from the pipeline layout")] + Missing, + #[error("Visibility flags don't include the shader stage")] + Invisible, + #[error("The shader requires the load/store access flags {required:?} but only {allowed:?} is allowed")] + WrongUsage { + required: GlobalUse, + allowed: GlobalUse, + }, + #[error("Type on the shader side does not match the pipeline binding")] + WrongType, + #[error("Storage class {binding:?} doesn't match the shader {shader:?}")] + WrongAddressSpace { + binding: naga::AddressSpace, + shader: naga::AddressSpace, + }, + #[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::BufferSize), + #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")] + WrongTextureViewDimension { + dim: naga::ImageDimension, + is_array: bool, + binding: BindingType, + }, + #[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), + #[error( + "Storage texture usage {0:?} doesn't have a matching supported `StorageTextureAccess`" + )] + UnsupportedTextureStorageAccess(GlobalUse), +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum FilteringError { + #[error("Integer textures can't be sampled with a filtering sampler")] + Integer, + #[error("Non-filterable float textures can't be sampled with a filtering sampler")] + Float, +} + +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +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 {0}")] + WrongType(NumericType), + #[error("Input interpolation doesn't match provided {0:?}")] + InterpolationMismatch(Option<naga::Interpolation>), + #[error("Input sampling doesn't match provided {0:?}")] + SamplingMismatch(Option<naga::Sampling>), +} + +/// Errors produced when validating a programmable stage of a pipeline. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum StageError { + #[error("Shader module is invalid")] + InvalidModule, + #[error( + "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension limit {limit:?} and the total invocation limit {total}" + )] + InvalidWorkgroupSize { + current: [u32; 3], + current_total: u32, + limit: [u32; 3], + total: u32, + }, + #[error("Shader uses {used} inter-stage components above the limit of {limit}")] + TooManyVaryings { used: u32, limit: u32 }, + #[error("Unable to find entry point '{0}'")] + MissingEntryPoint(String), + #[error("Shader global {0:?} is not available in the layout pipeline layout")] + Binding(naga::ResourceBinding, #[source] BindingError), + #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")] + Filtering { + texture: naga::ResourceBinding, + sampler: naga::ResourceBinding, + #[source] + error: FilteringError, + }, + #[error("Location[{location}] {var} is not provided by the previous stage outputs")] + Input { + location: wgt::ShaderLocation, + var: InterfaceVar, + #[source] + error: InputError, + }, + #[error("Location[{location}] is provided by the previous stage output but is not consumed as input by this stage.")] + InputNotConsumed { location: wgt::ShaderLocation }, +} + +fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> { + use naga::StorageFormat as Sf; + use wgt::TextureFormat as Tf; + + Some(match format { + Tf::R8Unorm => Sf::R8Unorm, + Tf::R8Snorm => Sf::R8Snorm, + Tf::R8Uint => Sf::R8Uint, + Tf::R8Sint => Sf::R8Sint, + + Tf::R16Uint => Sf::R16Uint, + Tf::R16Sint => Sf::R16Sint, + Tf::R16Float => Sf::R16Float, + Tf::Rg8Unorm => Sf::Rg8Unorm, + Tf::Rg8Snorm => Sf::Rg8Snorm, + Tf::Rg8Uint => Sf::Rg8Uint, + Tf::Rg8Sint => Sf::Rg8Sint, + + Tf::R32Uint => Sf::R32Uint, + Tf::R32Sint => Sf::R32Sint, + Tf::R32Float => Sf::R32Float, + Tf::Rg16Uint => Sf::Rg16Uint, + Tf::Rg16Sint => Sf::Rg16Sint, + Tf::Rg16Float => Sf::Rg16Float, + Tf::Rgba8Unorm => Sf::Rgba8Unorm, + Tf::Rgba8Snorm => Sf::Rgba8Snorm, + Tf::Rgba8Uint => Sf::Rgba8Uint, + Tf::Rgba8Sint => Sf::Rgba8Sint, + + Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm, + Tf::Rg11b10Float => Sf::Rg11b10Float, + + 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, + + Tf::R16Unorm => Sf::R16Unorm, + Tf::R16Snorm => Sf::R16Snorm, + Tf::Rg16Unorm => Sf::Rg16Unorm, + Tf::Rg16Snorm => Sf::Rg16Snorm, + Tf::Rgba16Unorm => Sf::Rgba16Unorm, + Tf::Rgba16Snorm => Sf::Rgba16Snorm, + + _ => 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, + + Sf::R16Unorm => Tf::R16Unorm, + Sf::R16Snorm => Tf::R16Snorm, + Sf::Rg16Unorm => Tf::Rg16Unorm, + Sf::Rg16Snorm => Tf::Rg16Snorm, + Sf::Rgba16Unorm => Tf::Rgba16Unorm, + Sf::Rgba16Snorm => Tf::Rgba16Snorm, + } +} + +impl Resource { + fn check_binding_use( + &self, + entry: &BindGroupLayoutEntry, + shader_usage: GlobalUse, + ) -> Result<(), BindingError> { + let allowed_usage = match self.ty { + ResourceType::Buffer { size } => { + let (allowed_usage, min_size) = match entry.ty { + BindingType::Buffer { + ty, + has_dynamic_offset: _, + min_binding_size, + } => { + let (class, global_use) = match ty { + wgt::BufferBindingType::Uniform => { + (naga::AddressSpace::Uniform, GlobalUse::READ) + } + wgt::BufferBindingType::Storage { read_only } => { + let mut global_use = GlobalUse::READ | GlobalUse::QUERY; + global_use.set(GlobalUse::WRITE, !read_only); + let mut naga_access = naga::StorageAccess::LOAD; + naga_access.set(naga::StorageAccess::STORE, !read_only); + ( + naga::AddressSpace::Storage { + access: naga_access, + }, + global_use, + ) + } + }; + if self.class != class { + return Err(BindingError::WrongAddressSpace { + binding: class, + shader: self.class, + }); + } + (global_use, min_binding_size) + } + _ => return Err(BindingError::WrongType), + }; + match min_size { + Some(non_zero) if non_zero < size => { + return Err(BindingError::WrongBufferSize(size)) + } + _ => (), + } + allowed_usage + } + ResourceType::Sampler { comparison } => match entry.ty { + BindingType::Sampler(ty) => { + if (ty == wgt::SamplerBindingType::Comparison) == comparison { + GlobalUse::READ + } else { + return Err(BindingError::WrongSamplerComparison); + } + } + _ => return Err(BindingError::WrongType), + }, + ResourceType::Texture { + 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: false, + binding: entry.ty, + }) + } + }; + 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, + binding: entry.ty, + }) + } + } + } 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, + binding: entry.ty, + }) + } + } + } + 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 { multi }, + }; + (class, GlobalUse::READ | GlobalUse::QUERY) + } + BindingType::StorageTexture { + access, + format, + view_dimension: _, + } => { + let naga_format = map_storage_format_to_naga(format) + .ok_or(BindingError::BadStorageFormat(format))?; + let (naga_access, usage) = match access { + wgt::StorageTextureAccess::ReadOnly => ( + naga::StorageAccess::LOAD, + GlobalUse::READ | GlobalUse::QUERY, + ), + wgt::StorageTextureAccess::WriteOnly => ( + naga::StorageAccess::STORE, + GlobalUse::WRITE | GlobalUse::QUERY, + ), + wgt::StorageTextureAccess::ReadWrite => { + (naga::StorageAccess::all(), GlobalUse::all()) + } + }; + ( + naga::ImageClass::Storage { + format: naga_format, + access: naga_access, + }, + usage, + ) + } + _ => return Err(BindingError::WrongType), + }; + if class != expected_class { + return Err(BindingError::WrongTextureClass { + binding: expected_class, + shader: class, + }); + } + usage + } + }; + + if allowed_usage.contains(shader_usage) { + Ok(()) + } else { + Err(BindingError::WrongUsage { + required: shader_usage, + allowed: allowed_usage, + }) + } + } + + fn derive_binding_type( + &self, + shader_usage: GlobalUse, + features: wgt::Features, + ) -> Result<BindingType, BindingError> { + Ok(match self.ty { + ResourceType::Buffer { size } => BindingType::Buffer { + ty: match self.class { + naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform, + naga::AddressSpace::Storage { .. } => wgt::BufferBindingType::Storage { + read_only: !shader_usage.contains(GlobalUse::WRITE), + }, + _ => return Err(BindingError::WrongType), + }, + has_dynamic_offset: false, + min_binding_size: Some(size), + }, + ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison { + wgt::SamplerBindingType::Comparison + } else { + wgt::SamplerBindingType::Filtering + }), + ResourceType::Texture { + 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 { multi } => BindingType::Texture { + sample_type: wgt::TextureSampleType::Depth, + view_dimension, + multisampled: multi, + }, + naga::ImageClass::Storage { format, .. } => BindingType::StorageTexture { + access: if !shader_usage.contains(GlobalUse::READ) { + wgt::StorageTextureAccess::WriteOnly + } else if !features + .contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) + { + return Err(BindingError::UnsupportedTextureStorageAccess( + shader_usage, + )); + } else if shader_usage.contains(GlobalUse::WRITE) { + wgt::StorageTextureAccess::ReadWrite + } 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 + }, + }, + } + } + }) + } +} + +impl NumericType { + fn from_vertex_format(format: wgt::VertexFormat) -> Self { + use naga::{ScalarKind as Sk, VectorSize as Vs}; + use wgt::VertexFormat as Vf; + + let (dim, kind, width) = match format { + Vf::Uint32 => (NumericDimension::Scalar, Sk::Uint, 4), + Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => { + (NumericDimension::Vector(Vs::Bi), Sk::Uint, 4) + } + Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Uint, 4), + Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => { + (NumericDimension::Vector(Vs::Quad), Sk::Uint, 4) + } + Vf::Sint32 => (NumericDimension::Scalar, Sk::Sint, 4), + Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => { + (NumericDimension::Vector(Vs::Bi), Sk::Sint, 4) + } + Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Sint, 4), + Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => { + (NumericDimension::Vector(Vs::Quad), Sk::Sint, 4) + } + Vf::Float32 => (NumericDimension::Scalar, Sk::Float, 4), + Vf::Unorm8x2 + | Vf::Snorm8x2 + | Vf::Unorm16x2 + | Vf::Snorm16x2 + | Vf::Float16x2 + | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 4), + Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 4), + Vf::Unorm8x4 + | Vf::Snorm8x4 + | Vf::Unorm16x4 + | Vf::Snorm16x4 + | Vf::Float16x4 + | Vf::Float32x4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 4), + Vf::Float64 => (NumericDimension::Scalar, Sk::Float, 8), + Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 8), + Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 8), + Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 8), + }; + + NumericType { + dim, + kind, + //Note: Shader always sees data as int, uint, or float. + // It doesn't know if the original is normalized in a tighter form. + width, + } + } + + fn from_texture_format(format: wgt::TextureFormat) -> Self { + use naga::{ScalarKind as Sk, VectorSize as Vs}; + use wgt::TextureFormat as Tf; + + let (dim, kind) = match format { + Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => { + (NumericDimension::Scalar, Sk::Float) + } + Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Sk::Uint), + Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Sk::Sint), + Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => { + (NumericDimension::Vector(Vs::Bi), Sk::Float) + } + Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => { + (NumericDimension::Vector(Vs::Bi), Sk::Uint) + } + Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => { + (NumericDimension::Vector(Vs::Bi), Sk::Sint) + } + Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Sk::Float), + Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Sk::Float), + Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Sk::Float), + Tf::Rgba8Unorm + | Tf::Rgba8UnormSrgb + | Tf::Rgba8Snorm + | Tf::Bgra8Unorm + | Tf::Bgra8UnormSrgb + | Tf::Rgb10a2Unorm + | Tf::Rgba16Float + | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Sk::Float), + Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint => { + (NumericDimension::Vector(Vs::Quad), Sk::Uint) + } + Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => { + (NumericDimension::Vector(Vs::Quad), Sk::Sint) + } + Tf::Rg11b10Float => (NumericDimension::Vector(Vs::Tri), Sk::Float), + Tf::Stencil8 + | Tf::Depth16Unorm + | Tf::Depth32Float + | Tf::Depth32FloatStencil8 + | Tf::Depth24Plus + | Tf::Depth24PlusStencil8 => { + panic!("Unexpected depth format") + } + Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Sk::Float), + Tf::Bc1RgbaUnorm + | Tf::Bc1RgbaUnormSrgb + | Tf::Bc2RgbaUnorm + | Tf::Bc2RgbaUnormSrgb + | Tf::Bc3RgbaUnorm + | Tf::Bc3RgbaUnormSrgb + | Tf::Bc7RgbaUnorm + | Tf::Bc7RgbaUnormSrgb + | Tf::Etc2Rgb8A1Unorm + | Tf::Etc2Rgb8A1UnormSrgb + | Tf::Etc2Rgba8Unorm + | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Sk::Float), + Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => { + (NumericDimension::Scalar, Sk::Float) + } + Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => { + (NumericDimension::Vector(Vs::Bi), Sk::Float) + } + Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => { + (NumericDimension::Vector(Vs::Tri), Sk::Float) + } + Tf::Astc { + block: _, + channel: _, + } => (NumericDimension::Vector(Vs::Quad), Sk::Float), + }; + + NumericType { + dim, + kind, + //Note: Shader always sees data as int, uint, or float. + // It doesn't know if the original is normalized in a tighter form. + width: 4, + } + } + + fn is_subtype_of(&self, other: &NumericType) -> bool { + if self.width > other.width { + return false; + } + if self.kind != other.kind { + return false; + } + match (self.dim, other.dim) { + (NumericDimension::Scalar, NumericDimension::Scalar) => true, + (NumericDimension::Scalar, NumericDimension::Vector(_)) => true, + (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1, + (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => { + c0 == c1 && r0 == r1 + } + _ => false, + } + } + + fn is_compatible_with(&self, other: &NumericType) -> bool { + if self.kind != other.kind { + return false; + } + match (self.dim, other.dim) { + (NumericDimension::Scalar, NumericDimension::Scalar) => true, + (NumericDimension::Scalar, NumericDimension::Vector(_)) => true, + (NumericDimension::Vector(_), NumericDimension::Vector(_)) => true, + (NumericDimension::Matrix(..), NumericDimension::Matrix(..)) => true, + _ => false, + } + } +} + +/// Return true if the fragment `format` is covered by the provided `output`. +pub fn check_texture_format( + format: wgt::TextureFormat, + output: &NumericType, +) -> Result<(), NumericType> { + let nt = NumericType::from_texture_format(format); + if nt.is_subtype_of(output) { + Ok(()) + } else { + Err(nt) + } +} + +pub type StageIo = FastHashMap<wgt::ShaderLocation, InterfaceVar>; + +impl Interface { + fn populate( + list: &mut Vec<Varying>, + binding: Option<&naga::Binding>, + ty: naga::Handle<naga::Type>, + arena: &naga::UniqueArena<naga::Type>, + ) { + let numeric_ty = match arena[ty].inner { + naga::TypeInner::Scalar { kind, width } => NumericType { + dim: NumericDimension::Scalar, + kind, + width, + }, + naga::TypeInner::Vector { size, kind, width } => NumericType { + dim: NumericDimension::Vector(size), + kind, + width, + }, + naga::TypeInner::Matrix { + columns, + rows, + width, + } => NumericType { + dim: NumericDimension::Matrix(columns, rows), + kind: naga::ScalarKind::Float, + width, + }, + naga::TypeInner::Struct { ref members, .. } => { + for member in members { + Self::populate(list, member.binding.as_ref(), member.ty, arena); + } + return; + } + ref other => { + //Note: technically this should be at least `log::error`, but + // the reality is - every shader coming from `glslc` outputs an array + // of clip distances and hits this path :( + // So we lower it to `log::warn` to be less annoying. + log::warn!("Unexpected varying type: {:?}", other); + return; + } + }; + + let varying = match binding { + Some(&naga::Binding::Location { + location, + interpolation, + sampling, + }) => Varying::Local { + location, + iv: InterfaceVar { + ty: numeric_ty, + interpolation, + sampling, + }, + }, + Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in), + None => { + log::error!("Missing binding for a varying"); + return; + } + }; + list.push(varying); + } + + pub fn new( + module: &naga::Module, + info: &naga::valid::ModuleInfo, + features: wgt::Features, + limits: wgt::Limits, + ) -> Self { + let mut resources = naga::Arena::new(); + let mut resource_mapping = FastHashMap::default(); + for (var_handle, var) in module.global_variables.iter() { + let bind = match var.binding { + Some(ref br) => br.clone(), + _ => continue, + }; + let naga_ty = &module.types[var.ty].inner; + + let inner_ty = match *naga_ty { + naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner, + ref ty => ty, + }; + + let ty = match *inner_ty { + naga::TypeInner::Image { + dim, + arrayed, + class, + } => ResourceType::Texture { + dim, + arrayed, + class, + }, + naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison }, + naga::TypeInner::Array { stride, .. } => ResourceType::Buffer { + size: wgt::BufferSize::new(stride as u64).unwrap(), + }, + ref other => ResourceType::Buffer { + size: wgt::BufferSize::new(other.size(&module.constants) as u64).unwrap(), + }, + }; + let handle = resources.append( + Resource { + name: var.name.clone(), + bind, + ty, + class: var.space, + }, + Default::default(), + ); + resource_mapping.insert(var_handle, handle); + } + + let mut entry_points = FastHashMap::default(); + entry_points.reserve(module.entry_points.len()); + for (index, entry_point) in module.entry_points.iter().enumerate() { + let info = info.get_entry_point(index); + let mut ep = EntryPoint::default(); + for arg in entry_point.function.arguments.iter() { + Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types); + } + if let Some(ref result) = entry_point.function.result { + Self::populate( + &mut ep.outputs, + result.binding.as_ref(), + result.ty, + &module.types, + ); + } + + for (var_handle, var) in module.global_variables.iter() { + let usage = info[var_handle]; + if usage.is_empty() { + continue; + } + if var.binding.is_some() { + ep.resources.push((resource_mapping[&var_handle], usage)); + } + } + + for key in info.sampling_set.iter() { + ep.sampling_pairs + .insert((resource_mapping[&key.image], resource_mapping[&key.sampler])); + } + + ep.workgroup_size = entry_point.workgroup_size; + + entry_points.insert((entry_point.stage, entry_point.name.clone()), ep); + } + + Self { + features, + limits, + resources, + entry_points, + } + } + + pub fn check_stage( + &self, + given_layouts: Option<&[&BindEntryMap]>, + derived_layouts: &mut [BindEntryMap], + shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>, + entry_point_name: &str, + stage_bit: wgt::ShaderStages, + inputs: StageIo, + compare_function: Option<wgt::CompareFunction>, + ) -> Result<StageIo, 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::ShaderStages::VERTEX => naga::ShaderStage::Vertex, + wgt::ShaderStages::FRAGMENT => naga::ShaderStage::Fragment, + wgt::ShaderStages::COMPUTE => naga::ShaderStage::Compute, + _ => unreachable!(), + }; + let pair = (shader_stage, entry_point_name.to_string()); + let entry_point = self + .entry_points + .get(&pair) + .ok_or(StageError::MissingEntryPoint(pair.1))?; + + // check resources visibility + for &(handle, usage) in entry_point.resources.iter() { + let res = &self.resources[handle]; + let result = match given_layouts { + Some(layouts) => { + // update the required binding size for this buffer + if let ResourceType::Buffer { size } = res.ty { + match shader_binding_sizes.entry(res.bind.clone()) { + Entry::Occupied(e) => { + *e.into_mut() = size.max(*e.get()); + } + Entry::Vacant(e) => { + e.insert(size); + } + } + } + layouts + .get(res.bind.group as usize) + .and_then(|map| map.get(&res.bind.binding)) + .ok_or(BindingError::Missing) + .and_then(|entry| { + if entry.visibility.contains(stage_bit) { + Ok(entry) + } else { + Err(BindingError::Invisible) + } + }) + .and_then(|entry| res.check_binding_use(entry, usage)) + } + None => derived_layouts + .get_mut(res.bind.group as usize) + .ok_or(BindingError::Missing) + .and_then(|set| { + let ty = res.derive_binding_type(usage, self.features)?; + match set.entry(res.bind.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: res.bind.binding, + ty, + visibility: stage_bit, + count: None, + }); + } + } + Ok(()) + }), + }; + if let Err(error) = result { + return Err(StageError::Binding(res.bind.clone(), error)); + } + } + + // check the compatibility between textures and samplers + if let Some(layouts) = given_layouts { + for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() { + let texture_bind = &self.resources[texture_handle].bind; + let sampler_bind = &self.resources[sampler_handle].bind; + let texture_layout = &layouts[texture_bind.group as usize][&texture_bind.binding]; + let sampler_layout = &layouts[sampler_bind.group as usize][&sampler_bind.binding]; + assert!(texture_layout.visibility.contains(stage_bit)); + assert!(sampler_layout.visibility.contains(stage_bit)); + + let sampler_filtering = matches!( + sampler_layout.ty, + wgt::BindingType::Sampler(wgt::SamplerBindingType::Filtering) + ); + let texture_sample_type = match texture_layout.ty { + BindingType::Texture { sample_type, .. } => sample_type, + _ => unreachable!(), + }; + + let error = match (sampler_filtering, texture_sample_type) { + (true, wgt::TextureSampleType::Float { filterable: false }) => { + Some(FilteringError::Float) + } + (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer), + (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer), + _ => None, + }; + + if let Some(error) = error { + return Err(StageError::Filtering { + texture: texture_bind.clone(), + sampler: sampler_bind.clone(), + error, + }); + } + } + } + + // check workgroup size limits + if shader_stage == naga::ShaderStage::Compute { + let max_workgroup_size_limits = [ + self.limits.max_compute_workgroup_size_x, + self.limits.max_compute_workgroup_size_y, + self.limits.max_compute_workgroup_size_z, + ]; + let total_invocations = entry_point.workgroup_size.iter().product::<u32>(); + + if entry_point.workgroup_size.iter().any(|&s| s == 0) + || total_invocations > self.limits.max_compute_invocations_per_workgroup + || entry_point.workgroup_size[0] > max_workgroup_size_limits[0] + || entry_point.workgroup_size[1] > max_workgroup_size_limits[1] + || entry_point.workgroup_size[2] > max_workgroup_size_limits[2] + { + return Err(StageError::InvalidWorkgroupSize { + current: entry_point.workgroup_size, + current_total: total_invocations, + limit: max_workgroup_size_limits, + total: self.limits.max_compute_invocations_per_workgroup, + }); + } + } + + let mut inter_stage_components = 0; + + // check inputs compatibility + for input in entry_point.inputs.iter() { + match *input { + Varying::Local { location, ref iv } => { + let result = + inputs + .get(&location) + .ok_or(InputError::Missing) + .and_then(|provided| { + let (compatible, num_components) = match shader_stage { + // For vertex attributes, there are defaults filled out + // by the driver if data is not provided. + naga::ShaderStage::Vertex => { + // vertex inputs don't count towards inter-stage + (iv.ty.is_compatible_with(&provided.ty), 0) + } + naga::ShaderStage::Fragment => { + if iv.interpolation != provided.interpolation { + return Err(InputError::InterpolationMismatch( + provided.interpolation, + )); + } + if iv.sampling != provided.sampling { + return Err(InputError::SamplingMismatch( + provided.sampling, + )); + } + ( + iv.ty.is_subtype_of(&provided.ty), + iv.ty.dim.num_components(), + ) + } + naga::ShaderStage::Compute => (false, 0), + }; + if compatible { + Ok(num_components) + } else { + Err(InputError::WrongType(provided.ty)) + } + }); + match result { + Ok(num_components) => { + inter_stage_components += num_components; + } + Err(error) => { + return Err(StageError::Input { + location, + var: iv.clone(), + error, + }) + } + } + } + Varying::BuiltIn(_) => {} + } + } + + // Check all vertex outputs and make sure the fragment shader consumes them. + if shader_stage == naga::ShaderStage::Fragment { + for &index in inputs.keys() { + // This is a linear scan, but the count should be low enough + // that this should be fine. + let found = entry_point.inputs.iter().any(|v| match *v { + Varying::Local { location, .. } => location == index, + Varying::BuiltIn(_) => false, + }); + + if !found { + return Err(StageError::InputNotConsumed { location: index }); + } + } + } + + if shader_stage == naga::ShaderStage::Vertex { + for output in entry_point.outputs.iter() { + //TODO: count builtins towards the limit? + inter_stage_components += match *output { + Varying::Local { ref iv, .. } => iv.ty.dim.num_components(), + Varying::BuiltIn(_) => 0, + }; + + if let Some( + cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual, + ) = compare_function + { + if let Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) = *output + { + log::warn!( + "Vertex shader with entry point {entry_point_name} outputs a @builtin(position) without the @invariant \ + attribute and is used in a pipeline with {cmp:?}. On some machines, this can cause bad artifacting as {cmp:?} assumes \ + the values output from the vertex shader exactly match the value in the depth buffer. The @invariant attribute on the \ + @builtin(position) vertex output ensures that the exact same pixel depths are used every render." + ); + } + } + } + } + + if inter_stage_components > self.limits.max_inter_stage_shader_components { + return Err(StageError::TooManyVaryings { + used: inter_stage_components, + limit: self.limits.max_inter_stage_shader_components, + }); + } + + let outputs = entry_point + .outputs + .iter() + .filter_map(|output| match *output { + Varying::Local { location, ref iv } => Some((location, iv.clone())), + Varying::BuiltIn(_) => None, + }) + .collect(); + Ok(outputs) + } +} |