diff --git a/.cargo/config.in b/.cargo/config.in index 20b8c3fad8f5..c8ff48b1e308 100644 --- a/.cargo/config.in +++ b/.cargo/config.in @@ -85,12 +85,12 @@ rev = "3484d3e3ebdc8931493aa5df4d7ee9360a90e76b" [source."https://github.com/gfx-rs/wgpu"] git = "https://github.com/gfx-rs/wgpu" replace-with = "vendored-sources" -rev = "32af4f56" +rev = "b370b990" [source."https://github.com/gfx-rs/naga"] git = "https://github.com/gfx-rs/naga" replace-with = "vendored-sources" -rev = "571302e" +rev = "27d38aae" [source."https://github.com/gfx-rs/metal-rs"] git = "https://github.com/gfx-rs/metal-rs" diff --git a/Cargo.lock b/Cargo.lock index 586aaeafafac..8b630c49fd81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,15 @@ dependencies = [ "log", ] +[[package]] +name = "android_system_properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20ae67ce26261f218e2b3f2f0d01887a9818283ca6fb260fa7c67e253d61c92" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.57" @@ -3587,7 +3596,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664" [[package]] name = "naga" version = "0.8.0" -source = "git+https://github.com/gfx-rs/naga?rev=571302e#571302e3ff09cb856f63a3683da308159872b7cc" +source = "git+https://github.com/gfx-rs/naga?rev=27d38aae#27d38aae33fdbfa72197847038cb470720594cb1" dependencies = [ "bit-set", "bitflags", @@ -6095,7 +6104,7 @@ dependencies = [ [[package]] name = "wgpu-core" version = "0.12.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=32af4f56#32af4f56079fd2203c46c9c452cfe33fd60a5721" +source = "git+https://github.com/gfx-rs/wgpu?rev=b370b990#b370b990e543bd8c74c895a0f856604368806e5c" dependencies = [ "arrayvec", "bit-vec", @@ -6112,6 +6121,7 @@ dependencies = [ "serde", "smallvec", "thiserror", + "web-sys", "wgpu-hal", "wgpu-types", ] @@ -6119,8 +6129,9 @@ dependencies = [ [[package]] name = "wgpu-hal" version = "0.12.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=32af4f56#32af4f56079fd2203c46c9c452cfe33fd60a5721" +source = "git+https://github.com/gfx-rs/wgpu?rev=b370b990#b370b990e543bd8c74c895a0f856604368806e5c" dependencies = [ + "android_system_properties", "arrayvec", "ash", "bit-set", @@ -6156,7 +6167,7 @@ dependencies = [ [[package]] name = "wgpu-types" version = "0.12.0" -source = "git+https://github.com/gfx-rs/wgpu?rev=32af4f56#32af4f56079fd2203c46c9c452cfe33fd60a5721" +source = "git+https://github.com/gfx-rs/wgpu?rev=b370b990#b370b990e543bd8c74c895a0f856604368806e5c" dependencies = [ "bitflags", "bitflags_serde_shim", diff --git a/dom/webgpu/RenderPassEncoder.cpp b/dom/webgpu/RenderPassEncoder.cpp index 78eb9d9b8c59..c8612f0f87f2 100644 --- a/dom/webgpu/RenderPassEncoder.cpp +++ b/dom/webgpu/RenderPassEncoder.cpp @@ -84,13 +84,13 @@ ffi::WGPURenderPass* BeginRenderPass( desc.depth_stencil_attachment = &dsDesc; } - if (aDesc.mColorAttachments.Length() > WGPUMAX_COLOR_TARGETS) { + if (aDesc.mColorAttachments.Length() > WGPUMAX_COLOR_ATTACHMENTS) { aParent->GetDevice()->GenerateError(nsLiteralCString( "Too many color attachments in GPURenderPassDescriptor")); return nullptr; } - std::array + std::array colorDescs = {}; desc.color_attachments = colorDescs.data(); desc.color_attachments_length = aDesc.mColorAttachments.Length(); diff --git a/gfx/wgpu_bindings/Cargo.toml b/gfx/wgpu_bindings/Cargo.toml index af82fe402113..79af91f4b2d1 100644 --- a/gfx/wgpu_bindings/Cargo.toml +++ b/gfx/wgpu_bindings/Cargo.toml @@ -17,7 +17,7 @@ default = [] [dependencies.wgc] package = "wgpu-core" git = "https://github.com/gfx-rs/wgpu" -rev = "32af4f56" +rev = "b370b990" #Note: "replay" shouldn't ideally be needed, # but it allows us to serialize everything across IPC. features = ["replay", "trace", "serial-pass"] @@ -25,12 +25,12 @@ features = ["replay", "trace", "serial-pass"] [dependencies.wgt] package = "wgpu-types" git = "https://github.com/gfx-rs/wgpu" -rev = "32af4f56" +rev = "b370b990" [dependencies.wgh] package = "wgpu-hal" git = "https://github.com/gfx-rs/wgpu" -rev = "32af4f56" +rev = "b370b990" [dependencies] bincode = "1" diff --git a/gfx/wgpu_bindings/moz.yaml b/gfx/wgpu_bindings/moz.yaml index 6f8d95030635..638ade5265fe 100644 --- a/gfx/wgpu_bindings/moz.yaml +++ b/gfx/wgpu_bindings/moz.yaml @@ -20,11 +20,11 @@ origin: # Human-readable identifier for this version/release # Generally "version NNN", "tag SSS", "bookmark SSS" - release: commit 32af4f56 + release: commit b370b990 # Revision to pull in # Must be a long or short commit SHA (long preferred) - revision: 32af4f56 + revision: b370b990e543bd8c74c895a0f856604368806e5c license: ['MIT', 'Apache-2.0'] diff --git a/gfx/wgpu_bindings/src/client.rs b/gfx/wgpu_bindings/src/client.rs index 3e5d196890d5..b221c23aa3ca 100644 --- a/gfx/wgpu_bindings/src/client.rs +++ b/gfx/wgpu_bindings/src/client.rs @@ -114,11 +114,11 @@ impl FragmentState<'_> { fn to_wgpu(&self) -> wgc::pipeline::FragmentState { let color_targets = make_slice(self.targets, self.targets_length) .iter() - .map(|ct| wgt::ColorTargetState { + .map(|ct| Some(wgt::ColorTargetState { format: ct.format, blend: ct.blend.cloned(), write_mask: ct.write_mask, - }) + })) .collect(); wgc::pipeline::FragmentState { stage: self.stage.to_wgpu(), @@ -600,9 +600,13 @@ pub extern "C" fn wgpu_device_create_render_bundle_encoder( desc: &RenderBundleEncoderDescriptor, bb: &mut ByteBuf, ) -> *mut wgc::command::RenderBundleEncoder { + let color_formats: Vec<_> = make_slice(desc.color_formats, desc.color_formats_length) + .iter() + .map(|format| Some(format.clone())) + .collect(); let descriptor = wgc::command::RenderBundleEncoderDescriptor { label: cow_label(&desc.label), - color_formats: Cow::Borrowed(make_slice(desc.color_formats, desc.color_formats_length)), + color_formats: Cow::Owned(color_formats), depth_stencil: desc .depth_stencil_format .map(|&format| wgt::RenderBundleDepthStencil { @@ -701,14 +705,15 @@ pub unsafe extern "C" fn wgpu_command_encoder_begin_render_pass( encoder_id: id::CommandEncoderId, desc: &RenderPassDescriptor, ) -> *mut wgc::command::RenderPass { + let color_attachments: Vec<_> = make_slice(desc.color_attachments, desc.color_attachments_length) + .iter() + .map(|format| Some(format.clone())) + .collect(); let pass = wgc::command::RenderPass::new( encoder_id, &wgc::command::RenderPassDescriptor { label: cow_label(&desc.label), - color_attachments: Cow::Borrowed(make_slice( - desc.color_attachments, - desc.color_attachments_length, - )), + color_attachments: Cow::Owned(color_attachments), depth_stencil_attachment: desc.depth_stencil_attachment.as_ref(), }, ); diff --git a/third_party/rust/android_system_properties/.cargo-checksum.json b/third_party/rust/android_system_properties/.cargo-checksum.json new file mode 100644 index 000000000000..e2338386610c --- /dev/null +++ b/third_party/rust/android_system_properties/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CONTRIBUTING.md":"0834cb3b5e092977688d73d219a05bed23ae0ecb54b6d6e5d866ce07f6583b5e","Cargo.toml":"813fa9eacb5751a4d0a5654283e55a5252fb90e9ce12d5226cceba1c95fe8686","LICENSE-APACHE":"216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06","LICENSE-MIT":"80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e","README.md":"6a18a69fa94ca0a1a786d25e8df347605ba4200c47d3ac6926e235b15c9878e6","src/lib.rs":"704c78cd30205cb923e80bab8a8a9d7e2fbf667a4bb7d2c00c586f6dec244612"},"package":"a20ae67ce26261f218e2b3f2f0d01887a9818283ca6fb260fa7c67e253d61c92"} \ No newline at end of file diff --git a/third_party/rust/android_system_properties/CONTRIBUTING.md b/third_party/rust/android_system_properties/CONTRIBUTING.md new file mode 100644 index 000000000000..62436f59bbb9 --- /dev/null +++ b/third_party/rust/android_system_properties/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +Contributions are very much welcome. Here are the guidelines if you are thinking of helping us: + +## Contributions + +Contributions should be made in the form of GitHub pull requests. +Each pull request will be reviewed by a core contributor (someone with +permission to land patches) and either landed in the main tree or +given feedback for changes that would be required. + +Should you wish to work on an issue, please claim it first by commenting on +the GitHub issue that you want to work on it. This is to prevent duplicated +efforts from contributors on the same issue. + +## Pull Request Checklist + +- Branch from the master branch and, if needed, rebase to the current master + branch before submitting your pull request. If it doesn't merge cleanly with + master you may be asked to rebase your changes. + +- Commits should be as small as possible, while ensuring that each commit is + correct independently (i.e., each commit should compile and pass tests). + +- If your patch is not getting reviewed or you need a specific person to review + it, you can @-reply a reviewer asking for a review in the pull request or a + comment. + +- Whenever applicable, add tests relevant to the fixed bug or new feature. + +For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/Github-workflow). + +## Conduct + +We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html). +For escalation or moderation issues, please contact Nical (nical@fastmail.com) instead of the Rust moderation team. + +## License + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed dual MIT/Apache 2, without any additional terms or conditions. diff --git a/third_party/rust/android_system_properties/Cargo.toml b/third_party/rust/android_system_properties/Cargo.toml new file mode 100644 index 000000000000..6981644e4a6c --- /dev/null +++ b/third_party/rust/android_system_properties/Cargo.toml @@ -0,0 +1,35 @@ +# 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 = "2018" +name = "android_system_properties" +version = "0.1.2" +authors = ["Nicolas Silva "] +description = "Minimal Android system properties wrapper" +homepage = "https://github.com/nical/android_system_properties" +documentation = "https://docs.rs/android_system_properties" +readme = "README.md" +keywords = ["android"] +license = "MIT/Apache-2.0" +repository = "https://github.com/nical/android_system_properties" + +[package.metadata.docs.rs] +targets = [ + "arm-linux-androideabi", + "armv7-linux-androideabi", + "aarch64-linux-android", + "i686-linux-android", + "x86_64-unknown-linux-gnu", +] + +[dependencies.libc] +version = "0.2.126" diff --git a/third_party/rust/android_system_properties/LICENSE-APACHE b/third_party/rust/android_system_properties/LICENSE-APACHE new file mode 100644 index 000000000000..47f4fd0e8982 --- /dev/null +++ b/third_party/rust/android_system_properties/LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2016 Nicolas Silva + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/android_system_properties/LICENSE-MIT b/third_party/rust/android_system_properties/LICENSE-MIT new file mode 100644 index 000000000000..e6d4b179949c --- /dev/null +++ b/third_party/rust/android_system_properties/LICENSE-MIT @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Nicolas Silva + +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/android_system_properties/README.md b/third_party/rust/android_system_properties/README.md new file mode 100644 index 000000000000..6a90e2203229 --- /dev/null +++ b/third_party/rust/android_system_properties/README.md @@ -0,0 +1,37 @@ +# android_system_properties + +A minimal rust wrapper over android system properties + + +This crate is similar to the `android-properties` crate with the exception that +the necessary Android libc symbols are loaded dynamically instead of linked +statically. In practice this means that the same binary will work with old and +new versions of Android, even though the API for reading system properties changed +around Android L. + +## Example + +```rust +use android_system_properties::AndroidSystemProperties; + +let properties = AndroidSystemProperties::new(); + +if let Some(value) = properties.get("persist.sys.timezone") { + println!("{}", value); +} +``` + +## Listing and setting properties + +For the sake of simplicity this crate currently only contains what's needed by wgpu. +The implementations for listing and setting properties can be added back if anyone needs +them (let me know by filing an issue). + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/third_party/rust/android_system_properties/src/lib.rs b/third_party/rust/android_system_properties/src/lib.rs new file mode 100644 index 000000000000..dd4e0f2ac42c --- /dev/null +++ b/third_party/rust/android_system_properties/src/lib.rs @@ -0,0 +1,162 @@ +//! A thin rust wrapper for Andoird system properties. +//! +//! This crate is similar to the `android-properties` crate with the exception that +//! the necessary Android libc symbols are loaded dynamically instead of linked +//! statically. In practice this means that the same binary will work with old and +//! new versions of Android, even though the API for reading system properties changed +//! around Android L. +use std::{ + ffi::{CStr, CString}, + os::raw::{c_char, c_int, c_void}, +}; + +#[cfg(target_os = "android")] +use std::mem; + +unsafe fn property_callback(payload: *mut String, _name: *const c_char, value: *const c_char, _serial: u32) { + let cvalue = CStr::from_ptr(value); + (*payload) = cvalue.to_str().unwrap().to_string(); +} + +type Callback = unsafe fn(*mut String, *const c_char, *const c_char, u32); + +type SystemPropertyGetFn = unsafe extern "C" fn(*const c_char, *mut c_char) -> c_int; +type SystemPropertyFindFn = unsafe extern "C" fn(*const c_char) -> *const c_void; +type SystemPropertyReadCallbackFn = unsafe extern "C" fn(*const c_void, Callback, *mut String) -> *const c_void; + +#[derive(Debug)] +/// An object that can retrieve android system properties. +/// +/// ## Example +/// +/// ``` +/// use android_system_properties::AndroidSystemProperties; +/// +/// let properties = AndroidSystemProperties::new(); +/// +/// if let Some(value) = properties.get("persist.sys.timezone") { +/// println!("{}", value); +/// } +/// ``` +pub struct AndroidSystemProperties { + libc_so: *mut c_void, + get_fn: Option, + find_fn: Option, + read_callback_fn: Option, +} + +impl AndroidSystemProperties { + #[cfg(not(target_os = "android"))] + /// Create an entry point for accessing Android properties. + pub fn new() -> Self { + AndroidSystemProperties { + libc_so: std::ptr::null_mut(), + find_fn: None, + read_callback_fn: None, + get_fn: None, + } + } + + #[cfg(target_os = "android")] + /// Create an entry point for accessing Android properties. + pub fn new() -> Self { + let libc_name = CString::new("libc.so").unwrap(); + let libc_so = unsafe { libc::dlopen(libc_name.as_ptr(), libc::RTLD_NOLOAD) }; + + let mut properties = AndroidSystemProperties { + libc_so, + find_fn: None, + read_callback_fn: None, + get_fn: None, + }; + + if libc_so.is_null() { + return properties; + } + + + unsafe fn load_fn(libc_so: *mut c_void, name: &str) -> Option<*const c_void> { + let cname = CString::new(name).unwrap(); + let fn_ptr = libc::dlsym(libc_so, cname.as_ptr()); + + if fn_ptr.is_null() { + return None; + } + + Some(fn_ptr) + } + + unsafe { + properties.read_callback_fn = load_fn(libc_so, "__system_property_read_callback") + .map(|raw| mem::transmute::<*const c_void, SystemPropertyReadCallbackFn>(raw)); + + properties.find_fn = load_fn(libc_so, "__system_property_find") + .map(|raw| mem::transmute::<*const c_void, SystemPropertyFindFn>(raw)); + + // Fallback for old versions of Android. + if properties.read_callback_fn.is_none() || properties.find_fn.is_none() { + properties.get_fn = load_fn(libc_so, "__system_property_get") + .map(|raw| mem::transmute::<*const c_void, SystemPropertyGetFn>(raw)); + } + } + + properties + } + + /// Retrieve a system property. + /// + /// Returns None if the operation fails. + pub fn get(&self, name: &str) -> Option { + let cname = CString::new(name).unwrap(); + + // If available, use the recommended approach to accessing properties (Android L and onward). + if let (Some(find_fn), Some(read_callback_fn)) = (self.find_fn, self.read_callback_fn) { + let info = unsafe { (find_fn)(cname.as_ptr()) }; + + if info.is_null() { + return None; + } + + let mut result = String::new(); + + unsafe { + (read_callback_fn)(info, property_callback, &mut result); + } + + return Some(result); + } + + // Fall back to the older approach. + if let Some(get_fn) = self.get_fn { + // The constant is PROP_VALUE_MAX in Android's libc/include/sys/system_properties.h + const PROPERTY_VALUE_MAX: usize = 92; + let cvalue = CString::new(Vec::with_capacity(PROPERTY_VALUE_MAX)).unwrap(); + let raw = cvalue.into_raw(); + + let len = unsafe { (get_fn)(cname.as_ptr(), raw) }; + + let bytes = unsafe { + let raw: *mut u8 = std::mem::transmute(raw); // Cast from *mut i8. + Vec::from_raw_parts(raw, len as usize, PROPERTY_VALUE_MAX) + }; + + if len > 0 { + String::from_utf8(bytes).ok() + } else { + None + } + } else { + None + } + } +} + +impl Drop for AndroidSystemProperties { + fn drop(&mut self) { + if !self.libc_so.is_null() { + unsafe { + libc::dlclose(self.libc_so); + } + } + } +} diff --git a/third_party/rust/naga/.cargo-checksum.json b/third_party/rust/naga/.cargo-checksum.json index 0640e9c892ef..ac11fd43f4a5 100644 --- a/third_party/rust/naga/.cargo-checksum.json +++ b/third_party/rust/naga/.cargo-checksum.json @@ -1 +1 @@ -{"files":{".github/workflows/lazy.yml":"efffd9aafa5e1fbe8c1746035e31523c5819348116a6b982ab6ab39a8c887c78",".github/workflows/pipeline.yml":"53cfa6363710bea4a97360a9667b34b507cc9212623b8a068cb34ec60bce275d",".github/workflows/validation-linux.yml":"797389222960f54d3da8d58b017c44be1b5f45033d8f635c17172bd79a975dbd",".github/workflows/validation-macos.yml":"ace910e819b4b7f4c3bcef0f6b8109bdf9fa817806b125605bd5f860c375d77e",".github/workflows/validation-windows.yml":"3717d69c8c21b379a40a6ff5a19dff18f06c56b767b3884565ecda0ddbe54493","CHANGELOG.md":"42278a1a5df5d79308713e5a30798bb9eb32bd3f2301e3993fd7b9474b4dac5b","Cargo.toml":"8c06729a81401a5eeebd71e363af9205c7df3a41ea02b8047e547d4e6a8efb7d","LICENSE-APACHE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","LICENSE-MIT":"ca3be8518f5ef097669cea882643fc532025f29972def4fda49df885565a0480","Makefile":"f4fce2a22f5d285e2682b95c863a26e64915eee856c221a1274462d1fe0774e7","README.md":"d34eadc118913060bf258383a4ee35a284a5fbc9753e60c5e3d4d1b9e81c7304","benches/criterion.rs":"470d09d115eabb7f4761f73365aec9cf417f3445193aba124cec3704dda45730","src/arena.rs":"88e6357932288b7d26ecfc9435a6c98847c63587dde8bb0675f25da627cf8a15","src/back/dot/mod.rs":"59a44df6776e48e6695065da517a1b5bfdd50b4b031ca3c4a0a9b25860122198","src/back/glsl/features.rs":"017ae911a94d3067ee9b9ba47558bd46a2b1f2d3e8e68099bfc0d2f13f66ebe8","src/back/glsl/keywords.rs":"3f23b1e63e99a7056c3b223524d5d37000ef7316ae9df25532a726a1157d1dcd","src/back/glsl/mod.rs":"525dda15520ba47df2d3823137778aa59f0bd6b638aa1d5b627e2f6b8a181599","src/back/hlsl/conv.rs":"46097319c63a0ae04ed4d6e86c5d1321c45d53bdf324440fa1588ec4990b2385","src/back/hlsl/help.rs":"1c00fef402c8244ffee0a0839006fd5446edcdd6d1b339ca57d5a87c8aa4ac80","src/back/hlsl/keywords.rs":"a158c52bf9a05514b5a6e29658c2872c1cf2a6f295e8094a6ac4a37f76a761ad","src/back/hlsl/mod.rs":"385078b0f10e2a502800c30a5335b8eac3c7df3cb122540ccc41ed37a8c462f5","src/back/hlsl/storage.rs":"a3942cc99ccfbaa7f3c9db55daff6af805167acc993a2f8d6065dba683256d09","src/back/hlsl/writer.rs":"d420d811cbbe3e8774cdfcc042f3e6882e2c601fcc5fc6fc68f8836dcfb5cc67","src/back/mod.rs":"719cae1c096a71196fc1485943fd5ea6d8ebffbd1af47ed8663ce87bf54cd3bf","src/back/msl/keywords.rs":"e1c2bf804b1a3a56639bedb9c1b3b78fbf920724ef65f91fe2e9fe476ee3fbbf","src/back/msl/mod.rs":"be992959cc7aa58a8a30ab936ce65e432de92ab9bfba4bf2b5368bdfe63bcb27","src/back/msl/sampler.rs":"210cf28143ac17d6c56a9adf4fc0e3b85b1b16bb8b94771f9304f2a43b7ce78e","src/back/msl/writer.rs":"47103b226936063a2cd63f3017f7b3b6255e028836ff797e4a534d034e0d63c5","src/back/spv/block.rs":"33cf662b1f0152c18f0ac74d9c61826c8b13fa1b6ffb8f65a777d76497bc22b5","src/back/spv/helpers.rs":"0ec3381f3ba5856a14d876559f77e21fd5d2f342cb005343a3df2ff58443e4fb","src/back/spv/image.rs":"32e6bf0a23fd0383c12bece8ffb1983a68ebc2d5421224f44b74d136981cf5a4","src/back/spv/index.rs":"7a9815bdb1f323a780d41092371582f6d4210f305bec5c74e882dbb6d2d49bc3","src/back/spv/instructions.rs":"42eb1afad17cd311d89cbf49813171848a417543195df571ba0f3de1633efc8b","src/back/spv/layout.rs":"e263de53cd2f9a03ad94b82b434ce636609bc1ed435a2d1132951663bfaa8ebd","src/back/spv/mod.rs":"0f5a5a5c10c5a301fbbcb30857156bd6b963d3a8752ea02050446cd711a5c1d6","src/back/spv/recyclable.rs":"9448eedf9766ae2b1916282b5945b218017c6067dceac2da1415580ee5796c73","src/back/spv/selection.rs":"81e404abfa0a977f7c1f76ccb37a78d13ccadbda229048dad53cc67687cc39db","src/back/spv/writer.rs":"57b61ad31a9ececb9cec0b13e64100164ead9c40dfbe3ec4c903b22722c9d952","src/back/wgsl/mod.rs":"2dd12bbea9ace835850192bb68c5760953da6bac6a636073d1eca19381c0c0b6","src/back/wgsl/writer.rs":"de6518bedf340bf5720bdacafc80516469e96c2da258db14a8fed775177c606f","src/block.rs":"89c061206f608365ef3ce03c0d205d7205ef9d892035dd3dfb8d252003159f14","src/front/glsl/ast.rs":"839aa81cf60aeafe8c192c98864c009ecf5c6f32e60866e7a788867121c37bcf","src/front/glsl/builtins.rs":"7fbb50e468f8b1e540bf80366f90fd2af2411ddefc1c107eb3f47882cda0f09d","src/front/glsl/constants.rs":"f08fd19633c80d54a3a4234e501070a1127eaef7cf3de92d69130aa6fc9c1e51","src/front/glsl/context.rs":"d68e5db51d6445766cd7e50426c63d68e56f480a050413750d3e0347ffc08b1b","src/front/glsl/error.rs":"6e049db79e59cffcd79bce3ac259f781062ab72e752f95d42f94c5a72efa1710","src/front/glsl/functions.rs":"7b5fe425e2cfa02b8285918e1f41eb88c99817395385fb81f3156a9bde3cf3c8","src/front/glsl/lex.rs":"08736ae8beb955da5b0e6e3e0f45995a824995f7096d516a2910417e9c7afa32","src/front/glsl/mod.rs":"2aaffb60fedbdf27b1e86fa8d65c637142d7efe61e4ffcb463455dc17d5676cb","src/front/glsl/offset.rs":"098f37b97112931517521c88825497a4ea6435c3ddcb5608be04a2b4f5498900","src/front/glsl/parser.rs":"4ca425ded0e19dcf9fabc74a0e7833c9493198ab796a7cba02ead1431cff77be","src/front/glsl/parser/declarations.rs":"f177b239f0ca9189458c2feb69219b94f2777a837b240ff4cc8b278b0291a311","src/front/glsl/parser/expressions.rs":"9b9979e5a32bfb825871dbb5d01b3e6e64062f30096f0140e01035283353998b","src/front/glsl/parser/functions.rs":"3ae5110825bc867ad8ec2cd2ca35afad2874e7999142915cb4a17746851b91d4","src/front/glsl/parser/types.rs":"ab09449ec40f3b5c24b16488b23dbf3b147d132beb6188ccb2068a54c0128bb8","src/front/glsl/parser_tests.rs":"4256fdce5e5de7234a173ecd0dc77eef20052932b0d298c1227b40bd6a7f6a28","src/front/glsl/token.rs":"42325adbef5bfc9e6f9e40e7ed3cbfa7ef498c05799fdb7694b123e6450410e1","src/front/glsl/types.rs":"d043b50192ac427455177059c1a6a7472392673fc8bf9be61d6d457f1587372f","src/front/glsl/variables.rs":"35055f3618e2738224b29eef1c43f4f10a92e8d0c37263998268e0ed484c73e9","src/front/interpolator.rs":"1f19f8634c639981d83aa8aa132ca5634bdc99830212eec3eac74c856bd25274","src/front/mod.rs":"30a1d62f05d79fb5e6afc1e5ebb4aec84eed1c1a60b991fae9b0eb6e361ccca0","src/front/spv/convert.rs":"fa61b1bb53f1e39ce93be91b5ac63eb01bcd5e087a6cb46b478466ce60c833b6","src/front/spv/error.rs":"1d0acaf179e2b71df87b7eee9aa9cc78e60fda86866969795f9b79111748124c","src/front/spv/function.rs":"4bc19456976ae38a91b32bd3d89d098998d4f516f706a389d6ea588d1ed2a3e6","src/front/spv/image.rs":"88a041f0cb1e26a5ca90f7419ebf712149689560fd5806f313dfeda580466203","src/front/spv/mod.rs":"9e28202589f00073e1c7929e10e7157306639bd6bba706392c2ec28d74dc0176","src/front/spv/null.rs":"93e364cd1db48e7a6344e8c7a9aa14a18812352c9011c471f305b6657acf9b7c","src/front/wgsl/construction.rs":"fe9545c16d3171747f3592934b6289ca574de295b3356b803783ecdd34822a82","src/front/wgsl/conv.rs":"6ce908d5f3562b50750afd0b082627613f0336cc8cb2f8b58441ddee0e30f130","src/front/wgsl/lexer.rs":"4d8221bf7f624aa1dca1dab43d93901875a6bb9afa417c64b3ac59eab9b363a9","src/front/wgsl/mod.rs":"8f9b9f905eb4a09f6bf7ded6dca7924ebdef11d91af74408d103ef024cb611b5","src/front/wgsl/number_literals.rs":"7d0790ae9704c679230c4a1961a3bb101a7270aab0c54ddc5b17c5c4ed77f395","src/front/wgsl/tests.rs":"67579b7374771fa79c50feb83415d62febbd64ff0b0a5797ff2f701b27efe111","src/keywords/mod.rs":"0138f3931f8af0b0a05174549d0fd2152945b027dc3febefc1bbd676581d2e45","src/keywords/wgsl.rs":"b3c330c8134107aaac721224fb11a42c743a3c546b874314256c5c0f13e9ec1f","src/lib.rs":"92ccd5e69830d8de63d6e2494f9468279600bbc694c1ce77e5aa6e1ddaeeef44","src/proc/index.rs":"9f1be774ac7e47b899066116d292604faed0acae5751628869c7767b919b71aa","src/proc/layouter.rs":"bd2b64a8f1f4113faee356ff1df53f7d44743679b4046659748ede60d638265d","src/proc/mod.rs":"e7719689f9efcc97dbe81e4938c675aa87d1e036311237f183c30e12b56eb23b","src/proc/namer.rs":"12d946cbc5ed44f6607f9406f1cecbf24a3bda97c3de12b8dd51d0a95e17ac6e","src/proc/terminator.rs":"40d22f4c821d24e972403a31ded5dae6078d3dc33b52a9b0993396bdf77f8d1d","src/proc/typifier.rs":"3582068c0e1801d34414be7709238b8860ade0017435fb118dc25de85ae7776f","src/span.rs":"ff1f43d8a1373db770c16ffe8f628861e47d14faaf981c85de5321be8bacdf57","src/valid/analyzer.rs":"b06a71b11abecbcc2ec83b0cf90320fded804281fe18810ffd773dbb5565d9c1","src/valid/compose.rs":"e4c029db8d07ca5e11d4e7e59d8885634856872b595a166e266a3ce0342ccdcd","src/valid/expression.rs":"ba5c4e3ee582c5ccde716b49b36c0844374080c0ae569e0b8d10b0034088270b","src/valid/function.rs":"dde9745837655ddc84017eddb758af17a7c9a9a196e071ca528c7fbac836c567","src/valid/interface.rs":"af1b1e7eca72976b316912b01ddec702d5d8c25f21159962d7a35efa035e60ee","src/valid/mod.rs":"00dab13c754bac27def55d6714135dfe531c593fd7b55a416c23f04289279e1a","src/valid/type.rs":"cea1602864775c39e155eff1ab52ac59c517c289f7d76793983ea8f876486798"},"package":null} \ No newline at end of file +{"files":{".github/workflows/lazy.yml":"efffd9aafa5e1fbe8c1746035e31523c5819348116a6b982ab6ab39a8c887c78",".github/workflows/pipeline.yml":"1fe4fe78102cd2e05d9f975284f1b106146e44c6c840d8c9ea4a61c8501fb63f",".github/workflows/validation-linux.yml":"797389222960f54d3da8d58b017c44be1b5f45033d8f635c17172bd79a975dbd",".github/workflows/validation-macos.yml":"ace910e819b4b7f4c3bcef0f6b8109bdf9fa817806b125605bd5f860c375d77e",".github/workflows/validation-windows.yml":"ac592928b89536ce890d20fd2f1af5789337acf64ea25319728bb9495238fc04","CHANGELOG.md":"b8eb3667595583dfb36d5394d68546fc451f25e4d514ae19cc17f2c9f96cec76","Cargo.toml":"5de0a9536c1c56ffdaa85fdbe4d83405a8551d052cfb13b3ba4c59fddec9f3a5","LICENSE-APACHE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","LICENSE-MIT":"ca3be8518f5ef097669cea882643fc532025f29972def4fda49df885565a0480","Makefile":"734abcbabafa8eeba088c4210fee187310843e1b6d424eada1ca75b1c957b304","README.md":"535d4002f3f358e49c48ca57e52e37045cacf9ee0db864d63ede5d01c8470d11","benches/criterion.rs":"d30f0d67aef47632015db7992ca24d0729acc4625c90acc937b9a5ee8f162b94","src/arena.rs":"88e6357932288b7d26ecfc9435a6c98847c63587dde8bb0675f25da627cf8a15","src/back/dot/mod.rs":"367f4fa7be6acca5d5d486220adabf863d0e87e83474acb6c5417d8297ca6607","src/back/glsl/features.rs":"1aeeb1d915491b72a98660916c75720e0c3bdfe11922f04567b9df0b4619f25c","src/back/glsl/keywords.rs":"3f23b1e63e99a7056c3b223524d5d37000ef7316ae9df25532a726a1157d1dcd","src/back/glsl/mod.rs":"34495af13b1989b4d0f4b112513a12803e0295f99ee3605a7e9ae0ccfca3a9a1","src/back/hlsl/conv.rs":"7cf174f9e0f1878cc9978d8cf5ba520c51ad01a15591cd820b7dbb146eb98a38","src/back/hlsl/help.rs":"a859bd6c73a6ac75149c2873dc8f01ded9c491f0a710aba29312fb6a46704258","src/back/hlsl/keywords.rs":"a158c52bf9a05514b5a6e29658c2872c1cf2a6f295e8094a6ac4a37f76a761ad","src/back/hlsl/mod.rs":"fcfd023e43f9144edaf7709eebd1ae0b2876de09cc0121028378fa2bde4227a5","src/back/hlsl/storage.rs":"fad78a919399b774a0b50e1ea4be581a5f7dca06d8df8e1b8d360edf2a8a6f5d","src/back/hlsl/writer.rs":"8a8f72455f56683014a70f24b549f6521d6dc82c69f07c6ba996dbd701dfe92a","src/back/mod.rs":"719cae1c096a71196fc1485943fd5ea6d8ebffbd1af47ed8663ce87bf54cd3bf","src/back/msl/keywords.rs":"e1c2bf804b1a3a56639bedb9c1b3b78fbf920724ef65f91fe2e9fe476ee3fbbf","src/back/msl/mod.rs":"be992959cc7aa58a8a30ab936ce65e432de92ab9bfba4bf2b5368bdfe63bcb27","src/back/msl/sampler.rs":"210cf28143ac17d6c56a9adf4fc0e3b85b1b16bb8b94771f9304f2a43b7ce78e","src/back/msl/writer.rs":"0ded3371103bc2029fe6bcd9bbdf07543f871ca7ab14ad1ff810a7acef342ebe","src/back/spv/block.rs":"e2a52c8da1abcc9d782a8a6509837676155e0445fb507c2240ab5b93f3e2f88f","src/back/spv/helpers.rs":"0ec3381f3ba5856a14d876559f77e21fd5d2f342cb005343a3df2ff58443e4fb","src/back/spv/image.rs":"32e6bf0a23fd0383c12bece8ffb1983a68ebc2d5421224f44b74d136981cf5a4","src/back/spv/index.rs":"7a9815bdb1f323a780d41092371582f6d4210f305bec5c74e882dbb6d2d49bc3","src/back/spv/instructions.rs":"42eb1afad17cd311d89cbf49813171848a417543195df571ba0f3de1633efc8b","src/back/spv/layout.rs":"e263de53cd2f9a03ad94b82b434ce636609bc1ed435a2d1132951663bfaa8ebd","src/back/spv/mod.rs":"0f5a5a5c10c5a301fbbcb30857156bd6b963d3a8752ea02050446cd711a5c1d6","src/back/spv/recyclable.rs":"9448eedf9766ae2b1916282b5945b218017c6067dceac2da1415580ee5796c73","src/back/spv/selection.rs":"81e404abfa0a977f7c1f76ccb37a78d13ccadbda229048dad53cc67687cc39db","src/back/spv/writer.rs":"8ba791b6038d294c63e46a987ff49888f408795b33ab441cfea03d1a6f90ea76","src/back/wgsl/mod.rs":"2dd12bbea9ace835850192bb68c5760953da6bac6a636073d1eca19381c0c0b6","src/back/wgsl/writer.rs":"19f9f95a6956bd4728bbe8c4ddd10ca68806007263d36c67e6874fe55f12752b","src/block.rs":"89c061206f608365ef3ce03c0d205d7205ef9d892035dd3dfb8d252003159f14","src/front/glsl/ast.rs":"839aa81cf60aeafe8c192c98864c009ecf5c6f32e60866e7a788867121c37bcf","src/front/glsl/builtins.rs":"b1d71fc2da328eb89d33aee862acd882a23a22b751d8e073f0c6a5d4057a8a0e","src/front/glsl/constants.rs":"f08fd19633c80d54a3a4234e501070a1127eaef7cf3de92d69130aa6fc9c1e51","src/front/glsl/context.rs":"708ae20eacbc14405b26fa064830430d80130f334ac8547d1277c42b71e81811","src/front/glsl/error.rs":"6e049db79e59cffcd79bce3ac259f781062ab72e752f95d42f94c5a72efa1710","src/front/glsl/functions.rs":"9a58e35a2b54786690074e0c85a23d4d73e725bd985401dbd3adbd8599db06ae","src/front/glsl/lex.rs":"08736ae8beb955da5b0e6e3e0f45995a824995f7096d516a2910417e9c7afa32","src/front/glsl/mod.rs":"2aaffb60fedbdf27b1e86fa8d65c637142d7efe61e4ffcb463455dc17d5676cb","src/front/glsl/offset.rs":"84ed5a0add0ded719036aba862a3161310f9e7165ecb667bb201789ae15726fd","src/front/glsl/parser.rs":"4ca425ded0e19dcf9fabc74a0e7833c9493198ab796a7cba02ead1431cff77be","src/front/glsl/parser/declarations.rs":"7421debcb1ba35aa45ec9ac40ce1864f513efc01d0aacc34dc661a0e0e97ae53","src/front/glsl/parser/expressions.rs":"9b9979e5a32bfb825871dbb5d01b3e6e64062f30096f0140e01035283353998b","src/front/glsl/parser/functions.rs":"609b73c0c97b77a2bd6083232f52c9251c4bdcb52fff25744b9b7b06e7e4c48c","src/front/glsl/parser/types.rs":"ab09449ec40f3b5c24b16488b23dbf3b147d132beb6188ccb2068a54c0128bb8","src/front/glsl/parser_tests.rs":"4256fdce5e5de7234a173ecd0dc77eef20052932b0d298c1227b40bd6a7f6a28","src/front/glsl/token.rs":"42325adbef5bfc9e6f9e40e7ed3cbfa7ef498c05799fdb7694b123e6450410e1","src/front/glsl/types.rs":"d043b50192ac427455177059c1a6a7472392673fc8bf9be61d6d457f1587372f","src/front/glsl/variables.rs":"35055f3618e2738224b29eef1c43f4f10a92e8d0c37263998268e0ed484c73e9","src/front/interpolator.rs":"1f19f8634c639981d83aa8aa132ca5634bdc99830212eec3eac74c856bd25274","src/front/mod.rs":"b88d2a8b2ed68e93865943e8c6d4a54a9a8db97b4eb1bda9f5225daa880bd0f4","src/front/spv/convert.rs":"fa61b1bb53f1e39ce93be91b5ac63eb01bcd5e087a6cb46b478466ce60c833b6","src/front/spv/error.rs":"1d0acaf179e2b71df87b7eee9aa9cc78e60fda86866969795f9b79111748124c","src/front/spv/function.rs":"07edfde70aac02544ef175ff881a2ad5097e1584c52e48ddb5690365a33a4487","src/front/spv/image.rs":"88a041f0cb1e26a5ca90f7419ebf712149689560fd5806f313dfeda580466203","src/front/spv/mod.rs":"59c8db3286f917db71b6f4ce47f4efea9b65a6ee274a06a6d8809379fad5d85a","src/front/spv/null.rs":"93e364cd1db48e7a6344e8c7a9aa14a18812352c9011c471f305b6657acf9b7c","src/front/wgsl/construction.rs":"a0c333458782ff3e748bde5cb94e11f6e7ccc3e81b4d606b0d2df40763af49fb","src/front/wgsl/conv.rs":"6ce908d5f3562b50750afd0b082627613f0336cc8cb2f8b58441ddee0e30f130","src/front/wgsl/lexer.rs":"4ce8934bf3b2450a0a5d8f346c2476ba3f8ea524840fee9d3d7900e358006042","src/front/wgsl/mod.rs":"ef26fe4726d266c72f9b7366db578565d7ffabec6a29e88b39117c047912bda4","src/front/wgsl/number.rs":"ea121881dcc112ad037864bf6366ebf30c5676552ef514d2954ae53e9bb6c53d","src/front/wgsl/tests.rs":"a2528d1db810c1f330503483048275de889d686768bf7e3e5323dd860969a2a7","src/keywords/mod.rs":"0138f3931f8af0b0a05174549d0fd2152945b027dc3febefc1bbd676581d2e45","src/keywords/wgsl.rs":"b3c330c8134107aaac721224fb11a42c743a3c546b874314256c5c0f13e9ec1f","src/lib.rs":"0410a6b513cc9142c8b72c2414d75cbcc46d65cd3c5e2df81a57a2d0a826f6ac","src/proc/index.rs":"9f1be774ac7e47b899066116d292604faed0acae5751628869c7767b919b71aa","src/proc/layouter.rs":"296d6af052b916981bb3b7aa3d710c6a5cd344cb64c1d34baa3561f8ddd0e858","src/proc/mod.rs":"4752aec5c5120247fbe174fc857e70384d97bb97101c1a8f00a02911c8135db1","src/proc/namer.rs":"12d946cbc5ed44f6607f9406f1cecbf24a3bda97c3de12b8dd51d0a95e17ac6e","src/proc/terminator.rs":"40d22f4c821d24e972403a31ded5dae6078d3dc33b52a9b0993396bdf77f8d1d","src/proc/typifier.rs":"3582068c0e1801d34414be7709238b8860ade0017435fb118dc25de85ae7776f","src/span.rs":"65f841b82dd190fd8e8dc538bb950fc9967a959a74a7d27f7da753d8d4436786","src/valid/analyzer.rs":"997819e0060ab97ef3202f94ffa6e39e3b95bdbe8502eff4aed9ed9ba4455745","src/valid/compose.rs":"e4c029db8d07ca5e11d4e7e59d8885634856872b595a166e266a3ce0342ccdcd","src/valid/expression.rs":"ba5c4e3ee582c5ccde716b49b36c0844374080c0ae569e0b8d10b0034088270b","src/valid/function.rs":"03002cd17290bd66e836a81877865ca1319b72a403d714848cabd698b447defc","src/valid/interface.rs":"7e928a8149a54f8b9a46d110a55b9c5ec3f10214f61bcd62b8086d578d7d6e8e","src/valid/mod.rs":"d5c2373f537b9702f040db333890d604cc99091c19a1e3af31b14e0a6bd093d8","src/valid/type.rs":"a5d4618b4c9c7aac8060102e286a4fa95f7d87f66fda9699faabdd5943b9353b"},"package":null} \ No newline at end of file diff --git a/third_party/rust/naga/.github/workflows/pipeline.yml b/third_party/rust/naga/.github/workflows/pipeline.yml index a4b4bc18618f..46c427ee0f38 100644 --- a/third_party/rust/naga/.github/workflows/pipeline.yml +++ b/third_party/rust/naga/.github/workflows/pipeline.yml @@ -48,6 +48,11 @@ jobs: args: --all-features --workspace - name: Check snapshots run: git diff --exit-code -- tests/out + - uses: actions-rs/cargo@v1 + name: Check benchmarks + with: + command: check + args: --benches clippy: name: Clippy runs-on: ubuntu-latest @@ -75,7 +80,6 @@ jobs: profile: minimal toolchain: stable override: true - - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: doc diff --git a/third_party/rust/naga/.github/workflows/validation-windows.yml b/third_party/rust/naga/.github/workflows/validation-windows.yml index c9bd5ae1febd..31f7ee368674 100644 --- a/third_party/rust/naga/.github/workflows/validation-windows.yml +++ b/third_party/rust/naga/.github/workflows/validation-windows.yml @@ -5,12 +5,28 @@ on: - 'tests/out/hlsl/*.hlsl' jobs: - validate-windows: - name: HLSL + validate-windows-dxc: + name: HLSL via DXC runs-on: windows-latest steps: - uses: actions/checkout@v2 - name: Add DirectXShaderCompiler uses: napokue/setup-dxc@v1.0.0 - - run: make validate-hlsl + - run: make validate-hlsl-dxc shell: sh + + validate-windows-fxc: + name: HLSL via FXC + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Add fxc bin to PATH + run: | + Get-Childitem -Path "C:\Program Files (x86)\Windows Kits\10\bin\**\x64\fxc.exe" ` + | Sort-Object -Property LastWriteTime -Descending ` + | Select-Object -First 1 ` + | Split-Path -Parent ` + | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf8 -Append + shell: powershell + - run: make validate-hlsl-fxc + shell: sh \ No newline at end of file diff --git a/third_party/rust/naga/CHANGELOG.md b/third_party/rust/naga/CHANGELOG.md index aef49154e259..d1959c357642 100644 --- a/third_party/rust/naga/CHANGELOG.md +++ b/third_party/rust/naga/CHANGELOG.md @@ -1,16 +1,288 @@ # Change Log ## v0.9 (TBD) - - WGSL: - - semicolon after struct declaration are now optional - - commas are used to separate struct members instead of semicolons - - attributes are declared with `@attrib` instead of `[[attrib]]` - - `stride` attribute is removed - - block comments are supported - - renames: - - `findLsb` -> `firstTrailingBit` - - `findMsb` -> `firstLeadingBit` - - `smoothStep` -> `smoothstep` + +- Fix minimal-versions of dependencies ([#1840](https://github.com/gfx-rs/naga/pull/1840)) **@teoxoy** +- Update MSRV to 1.56 ([#1838](https://github.com/gfx-rs/naga/pull/1838)) **@teoxoy** + +API + +- Rename `TypeFlags` `INTERFACE`/`HOST_SHARED` to `IO_SHARED`/`HOST_SHAREABLE` ([#1872](https://github.com/gfx-rs/naga/pull/1872)) **@jimblandy** +- Expose more error information ([#1827](https://github.com/gfx-rs/naga/pull/1827), [#1937](https://github.com/gfx-rs/naga/pull/1937)) **@jakobhellermann** **@nical** **@jimblandy** +- Do not unconditionally make error output colorful ([#1707](https://github.com/gfx-rs/naga/pull/1707)) **@rhysd** +- Rename `StorageClass` to `AddressSpace` ([#1699](https://github.com/gfx-rs/naga/pull/1699)) **@kvark** +- Add a way to emit errors to a path ([#1640](https://github.com/gfx-rs/naga/pull/1640)) **@laptou** + +CLI + +- Add `bincode` representation ([#1729](https://github.com/gfx-rs/naga/pull/1729)) **@kvark** +- Include file path in WGSL parse error ([#1708](https://github.com/gfx-rs/naga/pull/1708)) **@rhysd** +- Add `--version` flag ([#1706](https://github.com/gfx-rs/naga/pull/1706)) **@rhysd** +- Support reading input from stdin via `--stdin-file-path` ([#1701](https://github.com/gfx-rs/naga/pull/1701)) **@rhysd** +- Use `panic = "abort"` ([#1597](https://github.com/gfx-rs/naga/pull/1597)) **@jrmuizel** + +DOCS + +- Standardize some docs ([#1660](https://github.com/gfx-rs/naga/pull/1660)) **@NoelTautges** +- Document `TypeInner::BindingArray` ([#1859](https://github.com/gfx-rs/naga/pull/1859)) **@jimblandy** +- Clarify accepted types for `Expression::AccessIndex` ([#1862](https://github.com/gfx-rs/naga/pull/1862)) **@NoelTautges** +- Document `proc::layouter` ([#1693](https://github.com/gfx-rs/naga/pull/1693)) **@jimblandy** +- Document Naga's promises around validation and panics ([#1828](https://github.com/gfx-rs/naga/pull/1828)) **@jimblandy** +- `FunctionInfo` doc fixes ([#1726](https://github.com/gfx-rs/naga/pull/1726)) **@jimblandy** + +VALIDATOR + +- Forbid returning pointers and atomics from functions ([#911](https://github.com/gfx-rs/naga/pull/911)) **@jimblandy** +- Let validation check for more unsupported builtins ([#1962](https://github.com/gfx-rs/naga/pull/1962)) **@jimblandy** +- Fix `Capabilities::SAMPLER_NON_UNIFORM_INDEXING` bitflag ([#1915](https://github.com/gfx-rs/naga/pull/1915)) **@cwfitzgerald** +- Properly check that user-defined IO uses IO-shareable types ([#912](https://github.com/gfx-rs/naga/pull/912)) **@jimblandy** +- Validate `ValuePointer` exactly like a `Pointer` to a `Scalar` ([#1875](https://github.com/gfx-rs/naga/pull/1875)) **@jimblandy** +- Reject empty structs ([#1826](https://github.com/gfx-rs/naga/pull/1826)) **@jimblandy** +- Validate uniform address space layout constraints ([#1812](https://github.com/gfx-rs/naga/pull/1812)) **@teoxoy** +- Improve `AddressSpace` related error messages ([#1710](https://github.com/gfx-rs/naga/pull/1710)) **@kvark** + +WGSL-IN + +Main breaking changes + +- Commas to separate struct members (comma after last member is optional) + - `struct S { a: f32; b: i32; }` -> `struct S { a: f32, b: i32 }` +- Attribute syntax + - `[[binding(0), group(0)]]` -> `@binding(0) @group(0)` +- Entry point stage attributes + - `@stage(vertex)` -> `@vertex` + - `@stage(fragment)` -> `@fragment` + - `@stage(compute)` -> `@compute` +- Function renames + - `smoothStep` -> `smoothstep` + - `findLsb` -> `firstTrailingBit` + - `findMsb` -> `firstLeadingBit` + +Specification Changes (relavant changes have also been applied to the WGSL backend) + +- Update number literal format ([#1863](https://github.com/gfx-rs/naga/pull/1863)) **@teoxoy** +- Allow non-ascii characters in identifiers ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** +- Update reserved keywords ([#1847](https://github.com/gfx-rs/naga/pull/1847), [#1870](https://github.com/gfx-rs/naga/pull/1870), [#1905](https://github.com/gfx-rs/naga/pull/1905)) **@teoxoy** **@Gordon-F** +- Update entry point stage attributes ([#1833](https://github.com/gfx-rs/naga/pull/1833)) **@Gordon-F** +- Make colon in case optional ([#1801](https://github.com/gfx-rs/naga/pull/1801)) **@Gordon-F** +- Rename `smoothStep` to `smoothstep` ([#1800](https://github.com/gfx-rs/naga/pull/1800)) **@Gordon-F** +- Make semicolon after struct declaration optional ([#1791](https://github.com/gfx-rs/naga/pull/1791)) **@stshine** +- Use commas to separate struct members instead of semicolons ([#1773](https://github.com/gfx-rs/naga/pull/1773)) **@Gordon-F** +- Rename `findLsb`/`findMsb` to `firstTrailingBit`/`firstLeadingBit` ([#1735](https://github.com/gfx-rs/naga/pull/1735)) **@kvark** +- Make parenthesis optional for `if` and `switch` statements ([#1725](https://github.com/gfx-rs/naga/pull/1725)) **@Gordon-F** +- Declare attribtues with `@attrib` instead of `[[attrib]]` ([#1676](https://github.com/gfx-rs/naga/pull/1676)) **@kvark** +- Allow non-structure buffer types ([#1682](https://github.com/gfx-rs/naga/pull/1682)) **@kvark** +- Remove `stride` attribute ([#1681](https://github.com/gfx-rs/naga/pull/1681)) **@kvark** + +Improvements + +- Implement `firstTrailingBit`/`firstLeadingBit` u32 overloads ([#1865](https://github.com/gfx-rs/naga/pull/1865)) **@teoxoy** +- Add error for non-floating-point matrix ([#1917](https://github.com/gfx-rs/naga/pull/1917)) **@grovesNL** +- Implement partial vector & matrix identity constructors ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Implement phony assignment ([#1866](https://github.com/gfx-rs/naga/pull/1866), [#1869](https://github.com/gfx-rs/naga/pull/1869)) **@teoxoy** +- Fix being able to match `~=` as LogicalOperation ([#1849](https://github.com/gfx-rs/naga/pull/1849)) **@teoxoy** +- Implement Binding Arrays ([#1845](https://github.com/gfx-rs/naga/pull/1845)) **@cwfitzgerald** +- Implement unary vector operators ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Implement zero value constructors and constructors that infer their type from their parameters ([#1790](https://github.com/gfx-rs/naga/pull/1790)) **@teoxoy** +- Implement invariant attribute ([#1789](https://github.com/gfx-rs/naga/pull/1789), [#1822](https://github.com/gfx-rs/naga/pull/1822)) **@teoxoy** **@jimblandy** +- Implement increment and decrement statements ([#1788](https://github.com/gfx-rs/naga/pull/1788), [#1912](https://github.com/gfx-rs/naga/pull/1912)) **@teoxoy** +- Implement `while` loop ([#1787](https://github.com/gfx-rs/naga/pull/1787)) **@teoxoy** +- Fix array size on globals ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** +- Implement integer vector overloads for `dot` function ([#1689](https://github.com/gfx-rs/naga/pull/1689)) **@francesco-cattoglio** +- Implement block comments ([#1675](https://github.com/gfx-rs/naga/pull/1675)) **@kocsis1david** +- Implement assignment binary operators ([#1662](https://github.com/gfx-rs/naga/pull/1662)) **@kvark** +- Implement `radians`/`degrees` builtin functions ([#1627](https://github.com/gfx-rs/naga/pull/1627)) **@encounter** +- Implement `findLsb`/`findMsb` builtin functions ([#1473](https://github.com/gfx-rs/naga/pull/1473)) **@fintelia** +- Implement `textureGather`/`textureGatherCompare` builtin functions ([#1596](https://github.com/gfx-rs/naga/pull/1596)) **@kvark** + +SPV-IN + +- Implement `OpBitReverse` and `OpBitCount` ([#1954](https://github.com/gfx-rs/naga/pull/1954)) **@JCapucho** +- Add `MultiView` to `SUPPORTED_CAPABILITIES` ([#1934](https://github.com/gfx-rs/naga/pull/1934)) **@expenses** +- Translate `OpSMod` and `OpFMod` correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Error on unsupported `MatrixStride` ([#1805](https://github.com/gfx-rs/naga/pull/1805)) **@teoxoy** +- Align array stride for undecorated arrays ([#1724](https://github.com/gfx-rs/naga/pull/1724)) **@JCapucho** + +GLSL-IN + +- Fix matrix multiplication check ([#1953](https://github.com/gfx-rs/naga/pull/1953)) **@JCapucho** +- Fix panic (stop emitter in conditional) ([#1952](https://github.com/gfx-rs/naga/pull/1952)) **@JCapucho** +- Translate `mod` fn correctly ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Make the ternary operator behave as an if ([#1877](https://github.com/gfx-rs/naga/pull/1877)) **@JCapucho** +- Add support for `clamp` function ([#1502](https://github.com/gfx-rs/naga/pull/1502)) **@sjinno** +- Better errors for bad constant expression ([#1501](https://github.com/gfx-rs/naga/pull/1501)) **@sjinno** +- Error on a `matCx2` used with the `std140` layout ([#1806](https://github.com/gfx-rs/naga/pull/1806)) **@teoxoy** +- Allow nested accesses in lhs positions ([#1794](https://github.com/gfx-rs/naga/pull/1794)) **@JCapucho** +- Use forced conversions for vector/matrix constructors ([#1796](https://github.com/gfx-rs/naga/pull/1796)) **@JCapucho** +- Add support for `barrier` function ([#1793](https://github.com/gfx-rs/naga/pull/1793)) **@fintelia** +- Fix panic (resume expression emit after `imageStore`) ([#1795](https://github.com/gfx-rs/naga/pull/1795)) **@JCapucho** +- Allow multiple array specifiers ([#1780](https://github.com/gfx-rs/naga/pull/1780)) **@JCapucho** +- Fix memory qualifiers being inverted ([#1779](https://github.com/gfx-rs/naga/pull/1779)) **@JCapucho** +- Support arrays as input/output types ([#1759](https://github.com/gfx-rs/naga/pull/1759)) **@JCapucho** +- Fix freestanding constructor parsing ([#1758](https://github.com/gfx-rs/naga/pull/1758)) **@JCapucho** +- Fix matrix - scalar operations ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Fix matrix - matrix division ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Fix matrix comparisons ([#1757](https://github.com/gfx-rs/naga/pull/1757)) **@JCapucho** +- Add support for `texelFetchOffset` ([#1746](https://github.com/gfx-rs/naga/pull/1746)) **@JCapucho** +- Inject `sampler2DMSArray` builtins on use ([#1737](https://github.com/gfx-rs/naga/pull/1737)) **@JCapucho** +- Inject `samplerCubeArray` builtins on use ([#1736](https://github.com/gfx-rs/naga/pull/1736)) **@JCapucho** +- Add support for image builtin functions ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** +- Add support for image declarations ([#1723](https://github.com/gfx-rs/naga/pull/1723)) **@JCapucho** +- Texture builtins fixes ([#1719](https://github.com/gfx-rs/naga/pull/1719)) **@JCapucho** +- Type qualifiers rework ([#1713](https://github.com/gfx-rs/naga/pull/1713)) **@JCapucho** +- `texelFetch` accept multisampled textures ([#1715](https://github.com/gfx-rs/naga/pull/1715)) **@JCapucho** +- Fix panic when culling nested block ([#1714](https://github.com/gfx-rs/naga/pull/1714)) **@JCapucho** +- Fix composite constructors ([#1631](https://github.com/gfx-rs/naga/pull/1631)) **@JCapucho** +- Fix using swizzle as out arguments ([#1632](https://github.com/gfx-rs/naga/pull/1632)) **@JCapucho** + +SPV-OUT + +- Implement `reverseBits` and `countOneBits` ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** +- Use `OpCopyObject` for matrix identity casts ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Use `OpCopyObject` for bool - bool conversion due to `OpBitcast` not being feasible for booleans ([#1916](https://github.com/gfx-rs/naga/pull/1916)) **@teoxoy** +- Zero init variables in function and private address spaces ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** +- Use `SRem` instead of `SMod` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Add support for integer vector - scalar multiplication ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Add support for matrix addition and subtraction ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Emit required decorations on wrapper struct types ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** +- Decorate array and struct type layouts unconditionally ([#1815](https://github.com/gfx-rs/naga/pull/1815)) **@jimblandy** +- Fix wrong `MatrixStride` for `matCx2` and `mat2xR` ([#1781](https://github.com/gfx-rs/naga/pull/1781)) **@teoxoy** +- Use `OpImageQuerySize` for MS images ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** + +MSL-OUT + +- Fix pointers to private or workgroup address spaces possibly being read only ([#1901](https://github.com/gfx-rs/naga/pull/1901)) **@teoxoy** +- Zero init variables in function address space ([#1871](https://github.com/gfx-rs/naga/pull/1871)) **@teoxoy** +- Make binding arrays play nice with bounds checks ([#1855](https://github.com/gfx-rs/naga/pull/1855)) **@cwfitzgerald** +- Permit `invariant` qualifier on vertex shader outputs ([#1821](https://github.com/gfx-rs/naga/pull/1821)) **@jimblandy** +- Fix packed `vec3` stores ([#1816](https://github.com/gfx-rs/naga/pull/1816)) **@teoxoy** +- Actually test push constants to be used ([#1767](https://github.com/gfx-rs/naga/pull/1767)) **@kvark** +- Properly rename entry point arguments for struct members ([#1766](https://github.com/gfx-rs/naga/pull/1766)) **@jimblandy** +- Qualify read-only storage with const ([#1763](https://github.com/gfx-rs/naga/pull/1763)) **@kvark** +- Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** +- Add bounds checks for `ImageLoad` and `ImageStore` ([#1730](https://github.com/gfx-rs/naga/pull/1730)) **@jimblandy** +- Fix resource bindings for non-structures ([#1718](https://github.com/gfx-rs/naga/pull/1718)) **@kvark** +- Always check whether _buffer_sizes arg is needed ([#1717](https://github.com/gfx-rs/naga/pull/1717)) **@jimblandy** +- WGSL storage address space should always correspond to MSL device address space ([#1711](https://github.com/gfx-rs/naga/pull/1711)) **@wtholliday** +- Mitigation for MSL atomic bounds check ([#1703](https://github.com/gfx-rs/naga/pull/1703)) **@glalonde** + +HLSL-OUT + +- Fix fallthrough in switch statements ([#1920](https://github.com/gfx-rs/naga/pull/1920)) **@teoxoy** +- Fix missing break statements ([#1919](https://github.com/gfx-rs/naga/pull/1919)) **@teoxoy** +- Fix `countOneBits` and `reverseBits` for signed integers ([#1928](https://github.com/gfx-rs/naga/pull/1928)) **@hasali19** +- Fix array constructor return type ([#1914](https://github.com/gfx-rs/naga/pull/1914)) **@teoxoy** +- Fix hlsl output for writes to scalar/vector storage buffer ([#1903](https://github.com/gfx-rs/naga/pull/1903)) **@hasali19** +- Use `fmod` instead of `%` ([#1867](https://github.com/gfx-rs/naga/pull/1867)) **@teoxoy** +- Use wrapped constructors when loading from storage address space ([#1893](https://github.com/gfx-rs/naga/pull/1893)) **@teoxoy** +- Zero init struct constructor ([#1890](https://github.com/gfx-rs/naga/pull/1890)) **@teoxoy** +- Flesh out matrix handling documentation ([#1850](https://github.com/gfx-rs/naga/pull/1850)) **@jimblandy** +- Emit `row_major` qualifier on matrix uniform globals ([#1846](https://github.com/gfx-rs/naga/pull/1846)) **@jimblandy** +- Fix bool splat ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Add more padding when necessary ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Support multidimensional arrays ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Don't output interpolation modifier if it's the default ([#1809](https://github.com/gfx-rs/naga/pull/1809)) **@NoelTautges** +- Fix `matCx2` translation for uniform buffers ([#1802](https://github.com/gfx-rs/naga/pull/1802)) **@teoxoy** +- Fix modifiers not being written in the vertex output and fragment input structs ([#1789](https://github.com/gfx-rs/naga/pull/1789)) **@teoxoy** +- Fix matrix not being declared as transposed ([#1784](https://github.com/gfx-rs/naga/pull/1784)) **@teoxoy** +- Insert padding between struct members ([#1786](https://github.com/gfx-rs/naga/pull/1786)) **@teoxoy** +- Fix not unary operator for integer scalars ([#1760](https://github.com/gfx-rs/naga/pull/1760)) **@vincentisambart** + +GLSL-OUT + +- Fix type error for `countOneBits` implementation ([#1897](https://github.com/gfx-rs/naga/pull/1897)) **@hasali19** +- Fix storage format for `Rgba8Unorm` ([#1955](https://github.com/gfx-rs/naga/pull/1955)) **@JCapucho** +- Implement bounds checks for `ImageLoad` ([#1889](https://github.com/gfx-rs/naga/pull/1889)) **@JCapucho** +- Fix feature search in expressions ([#1887](https://github.com/gfx-rs/naga/pull/1887)) **@JCapucho** +- Emit globals of any type ([#1823](https://github.com/gfx-rs/naga/pull/1823)) **@jimblandy** +- Add support for boolean vector `~`, `|` and `&` ops ([#1820](https://github.com/gfx-rs/naga/pull/1820)) **@teoxoy** +- Fix array function arguments ([#1814](https://github.com/gfx-rs/naga/pull/1814)) **@teoxoy** +- Write constant sized array type for uniform ([#1768](https://github.com/gfx-rs/naga/pull/1768)) **@hatoo** +- Texture function fixes ([#1742](https://github.com/gfx-rs/naga/pull/1742)) **@JCapucho** +- Push constants use anonymous uniforms ([#1683](https://github.com/gfx-rs/naga/pull/1683)) **@JCapucho** +- Add support for push constant emulation ([#1672](https://github.com/gfx-rs/naga/pull/1672)) **@JCapucho** +- Skip unsized types if unused ([#1649](https://github.com/gfx-rs/naga/pull/1649)) **@kvark** +- Write struct and array initializers ([#1644](https://github.com/gfx-rs/naga/pull/1644)) **@JCapucho** + + +## v0.8.5 (2022-01-25) + +MSL-OUT + +- Make VS-output positions invariant on even more systems ([#1697](https://github.com/gfx-rs/naga/pull/1697)) **@cwfitzgerald** +- Improve support for point primitives ([#1696](https://github.com/gfx-rs/naga/pull/1696)) **@kvark** + + +## v0.8.4 (2022-01-24) + +MSL-OUT + +- Make VS-output positions invariant if possible ([#1687](https://github.com/gfx-rs/naga/pull/1687)) **@kvark** + +GLSL-OUT + +- Fix `floatBitsToUint` spelling ([#1688](https://github.com/gfx-rs/naga/pull/1688)) **@cwfitzgerald** +- Call proper memory barrier functions ([#1680](https://github.com/gfx-rs/naga/pull/1680)) **@francesco-cattoglio** + + +## v0.8.3 (2022-01-20) + +- Don't pin `indexmap` version ([#1666](https://github.com/gfx-rs/naga/pull/1666)) **@a1phyr** + +MSL-OUT + +- Fix support for point primitives ([#1674](https://github.com/gfx-rs/naga/pull/1674)) **@kvark** + +GLSL-OUT + +- Fix sampler association ([#1671](https://github.com/gfx-rs/naga/pull/1671)) **@JCapucho** + + +## v0.8.2 (2022-01-11) + +VALIDATOR + +- Check structure resource types ([#1639](https://github.com/gfx-rs/naga/pull/1639)) **@kvark** + +WGSL-IN + +- Improve type mismatch errors ([#1658](https://github.com/gfx-rs/naga/pull/1658)) **@Gordon-F** + +SPV-IN + +- Implement more sign agnostic operations ([#1651](https://github.com/gfx-rs/naga/pull/1651), [#1650](https://github.com/gfx-rs/naga/pull/1650)) **@JCapucho** + +SPV-OUT + +- Fix modulo operator (use `OpFRem` instead of `OpFMod`) ([#1653](https://github.com/gfx-rs/naga/pull/1653)) **@JCapucho** + +MSL-OUT + +- Fix `texture1d` accesses ([#1647](https://github.com/gfx-rs/naga/pull/1647)) **@jimblandy** +- Fix data packing functions ([#1637](https://github.com/gfx-rs/naga/pull/1637)) **@phoekz** + + +## v0.8.1 (2021-12-29) + +API + +- Make `WithSpan` clonable ([#1620](https://github.com/gfx-rs/naga/pull/1620)) **@jakobhellermann** + +MSL-OUT + +- Fix packed vec access ([#1634](https://github.com/gfx-rs/naga/pull/1634)) **@kvark** +- Fix packed float support ([#1630](https://github.com/gfx-rs/naga/pull/1630)) **@kvark** + +HLSL-OUT + +- Support arrays of matrices ([#1629](https://github.com/gfx-rs/naga/pull/1629)) **@kvark** +- Use `mad` instead of `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** + +GLSL-OUT + +- Fix conflicting names for globals ([#1616](https://github.com/gfx-rs/naga/pull/1616)) **@Gordon-F** +- Fix `fma` function ([#1580](https://github.com/gfx-rs/naga/pull/1580)) **@parasyte** + ## v0.8 (2021-12-18) - development release for wgpu-0.12 @@ -26,7 +298,7 @@ - MSL-out: - full out-of-bounds checking -### v0.7.3 (2021-12-14) +## v0.7.3 (2021-12-14) - API: - `view_index` builtin - GLSL-out: @@ -34,7 +306,7 @@ - SPV-out: - fix incorrect pack/unpack -### v0.7.2 (2021-12-01) +## v0.7.2 (2021-12-01) - validator: - check stores for proper pointer class - HLSL-out: @@ -50,7 +322,7 @@ - GLSL-in: - don't panic on invalid integer operations -### v0.7.1 (2021-10-12) +## v0.7.1 (2021-10-12) - implement casts from and to booleans in the backends ## v0.7 (2021-10-07) @@ -83,7 +355,7 @@ - option to emit point size - option to clamp output depth -### v0.6.3 (2021-09-08) +## v0.6.3 (2021-09-08) - Reduced heap allocations when generating WGSL, HLSL, and GLSL - WGSL-in: - support module-scope `let` type inference @@ -96,7 +368,7 @@ - SPV-out: - allow working around Adreno issue with `OpName` -### v0.6.2 (2021-09-01) +## v0.6.2 (2021-09-01) - SPV-out fixes: - requested capabilities for 1D and cube images, storage formats - handling `break` and `continue` in a `switch` statement @@ -110,7 +382,7 @@ - GLSL-in fixes: - avoid infinite loop on invalid statements -### v0.6.1 (2021-08-24) +## v0.6.1 (2021-08-24) - HLSL-out fixes: - array arguments - pointers to array arguments @@ -165,7 +437,7 @@ - multisampling on GLES - WGSL is vastly improved and now usable -### v0.4.2 (2021-05-28) +## v0.4.2 (2021-05-28) - SPIR-V frontend: - fix image stores - fix matrix stride check @@ -175,7 +447,7 @@ - support sample interpolation - write out swizzled vector accesses -### v0.4.1 (2021-05-14) +## v0.4.1 (2021-05-14) - numerous additions and improvements to SPIR-V frontend: - int8, in16, int64 - null constant initializers for structs and matrices @@ -218,7 +490,7 @@ - `convert` example is transformed into the default binary target named `naga` - lots of frontend and backend fixes -### v0.3.2 (2021-02-15) +## v0.3.2 (2021-02-15) - fix logical expression types - fix _FragDepth_ semantics - spv-in: @@ -227,7 +499,7 @@ - add lots of missing math functions - implement discard -### v0.3.1 (2021-01-31) +## v0.3.1 (2021-01-31) - wgsl: - support constant array sizes - spv-out: diff --git a/third_party/rust/naga/Cargo.toml b/third_party/rust/naga/Cargo.toml index 1476eb9cf8e9..1b2fb838b557 100644 --- a/third_party/rust/naga/Cargo.toml +++ b/third_party/rust/naga/Cargo.toml @@ -67,7 +67,10 @@ unicode-xid = { version = "0.2.3", optional = true } bincode = "1" criterion = { version = "0.3", features = [] } diff = "0.1" -ron = "0.7" +# Require at least version 0.7.1 of ron, this version changed how floating points are +# serialized by forcing them to always have the decimal part, this makes it backwards +# incompatible with our tests because we do a syntatic diff and not a semantic one. +ron = "~0.7.1" serde = { version = "1.0", features = ["derive"] } spirv = { version = "0.2", features = ["deserialize"] } rspirv = "0.11" diff --git a/third_party/rust/naga/Makefile b/third_party/rust/naga/Makefile index 4622fdbb5df2..a491722af2ac 100644 --- a/third_party/rust/naga/Makefile +++ b/third_party/rust/naga/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean validate-spv validate-msl validate-glsl validate-dot validate-wgsl validate-hlsl +.PHONY: all clean validate-spv validate-msl validate-glsl validate-dot validate-wgsl validate-hlsl-dxc validate-hlsl-fxc .SECONDARY: boids.metal quad.metal SNAPSHOTS_BASE_IN=tests/in SNAPSHOTS_BASE_OUT=tests/out @@ -69,10 +69,10 @@ validate-wgsl: $(SNAPSHOTS_BASE_OUT)/wgsl/*.wgsl cargo run $${file}; \ done -validate-hlsl: SHELL:=/bin/bash # required because config files uses arrays -validate-hlsl: $(SNAPSHOTS_BASE_OUT)/hlsl/*.hlsl +validate-hlsl-dxc: SHELL:=/bin/bash # required because config files uses arrays +validate-hlsl-dxc: $(SNAPSHOTS_BASE_OUT)/hlsl/*.hlsl @set -e && for file in $^ ; do \ - DXC_PARAMS="-Wno-parentheses-equality -Zi -Qembed_debug"; \ + DXC_PARAMS="-Wno-parentheses-equality -Zi -Qembed_debug -Od"; \ echo "Validating" $${file#"$(SNAPSHOTS_BASE_OUT)/"}; \ config="$$(dirname $${file})/$$(basename $${file}).config"; \ . $${config}; \ @@ -93,3 +93,37 @@ validate-hlsl: $(SNAPSHOTS_BASE_OUT)/hlsl/*.hlsl done; \ echo "======================"; \ done + +validate-hlsl-fxc: SHELL:=/bin/bash # required because config files uses arrays +validate-hlsl-fxc: $(SNAPSHOTS_BASE_OUT)/hlsl/*.hlsl + @set -e && for file in $^ ; do \ + FXC_PARAMS="-Zi -Od"; \ + echo "Validating" $${file#"$(SNAPSHOTS_BASE_OUT)/"}; \ + config="$$(dirname $${file})/$$(basename $${file}).config"; \ + . $${config}; \ + for (( i=0; i<$${#vertex[@]}; i++ )); do \ + name=`echo $${vertex[i]} | cut -d \: -f 1`; \ + profile=`echo $${vertex[i]} | cut -d \: -f 2`; \ + sm=`echo $${profile} | cut -d \_ -f 2`; \ + if (( sm < 6 )); then \ + (set -x; fxc $${file} -T $${profile} -E $${name} $${FXC_PARAMS} > /dev/null); \ + fi \ + done; \ + for (( i=0; i<$${#fragment[@]}; i++ )); do \ + name=`echo $${fragment[i]} | cut -d \: -f 1`; \ + profile=`echo $${fragment[i]} | cut -d \: -f 2`; \ + sm=`echo $${profile} | cut -d \_ -f 2`; \ + if (( sm < 6 )); then \ + (set -x; fxc $${file} -T $${profile} -E $${name} $${FXC_PARAMS} > /dev/null); \ + fi \ + done; \ + for (( i=0; i<$${#compute[@]}; i++ )); do \ + name=`echo $${compute[i]} | cut -d \: -f 1`; \ + profile=`echo $${compute[i]} | cut -d \: -f 2`; \ + sm=`echo $${profile} | cut -d \_ -f 2`; \ + if (( sm < 6 )); then \ + (set -x; fxc $${file} -T $${profile} -E $${name} $${FXC_PARAMS} > /dev/null); \ + fi \ + done; \ + echo "======================"; \ + done diff --git a/third_party/rust/naga/README.md b/third_party/rust/naga/README.md index fbaa063d7d85..1e407a959e30 100644 --- a/third_party/rust/naga/README.md +++ b/third_party/rust/naga/README.md @@ -15,7 +15,7 @@ Front-end | Status | Feature | Notes | --------------- | ------------------ | ------- | ----- | SPIR-V (binary) | :white_check_mark: | spv-in | | WGSL | :white_check_mark: | wgsl-in | Fully validated | -GLSL | :ok: | glsl-in | GLSL 440+ | +GLSL | :ok: | glsl-in | GLSL 440+ and Vulkan semantics only | Back-end | Status | Feature | Notes | --------------- | ------------------ | -------- | ----- | @@ -81,5 +81,7 @@ make validate-msl # for Metal shaders, requires XCode command-line tools install make validate-glsl # for OpenGL shaders, requires GLSLang installed make validate-dot # for dot files, requires GraphViz installed make validate-wgsl # for WGSL shaders -make validate-hlsl # for HLSL shaders. Note: this Make target makes use of the "sh" shell. This is not the default shell in Windows. +make validate-hlsl-dxc # for HLSL shaders via DXC +make validate-hlsl-fxc # for HLSL shaders via FXC +# Note: HLSL Make targets make use of the "sh" shell. This is not the default shell in Windows. ``` diff --git a/third_party/rust/naga/benches/criterion.rs b/third_party/rust/naga/benches/criterion.rs index f239948e88fd..1175528458c2 100644 --- a/third_party/rust/naga/benches/criterion.rs +++ b/third_party/rust/naga/benches/criterion.rs @@ -255,6 +255,7 @@ fn backends(c: &mut Criterion) { info, &options, &pipeline_options, + naga::proc::BoundsCheckPolicies::default(), ) { Ok(mut writer) => { let _ = writer.write(); // can error if unsupported diff --git a/third_party/rust/naga/src/back/dot/mod.rs b/third_party/rust/naga/src/back/dot/mod.rs index 523e48ce5fca..265e400758e8 100644 --- a/third_party/rust/naga/src/back/dot/mod.rs +++ b/third_party/rust/naga/src/back/dot/mod.rs @@ -81,11 +81,15 @@ impl StatementGraph { S::Loop { ref body, ref continuing, + break_if, } => { let body_id = self.add(body); self.flow.push((id, body_id, "body")); let continuing_id = self.add(continuing); self.flow.push((body_id, continuing_id, "continuing")); + if let Some(expr) = break_if { + self.dependencies.push((id, expr, "break if")); + } "Loop" } S::Return { value } => { diff --git a/third_party/rust/naga/src/back/glsl/features.rs b/third_party/rust/naga/src/back/glsl/features.rs index d3f117045fa5..82718b040e42 100644 --- a/third_party/rust/naga/src/back/glsl/features.rs +++ b/third_party/rust/naga/src/back/glsl/features.rs @@ -38,6 +38,10 @@ bitflags::bitflags! { const FMA = 1 << 18; /// Texture samples query const TEXTURE_SAMPLES = 1 << 19; + /// Texture levels query + const TEXTURE_LEVELS = 1 << 20; + /// Image size query + const IMAGE_SIZE = 1 << 21; } } @@ -104,9 +108,11 @@ impl FeaturesManager { check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310); check_feature!(MULTI_VIEW, 140, 310); // Only available on glsl core, this means that opengl es can't query the number - // of samples in a image and neither do bound checks on the sample argument - // of texelFecth + // of samples nor levels in a image and neither do bound checks on the sample nor + // the level argument of texelFecth check_feature!(TEXTURE_SAMPLES, 150); + check_feature!(TEXTURE_LEVELS, 130); + check_feature!(IMAGE_SIZE, 430, 310); // Return an error if there are missing features if missing.is_empty() { @@ -223,6 +229,11 @@ impl FeaturesManager { )?; } + if self.0.contains(Features::TEXTURE_LEVELS) && version < Version::Desktop(430) { + // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt + writeln!(out, "#extension GL_ARB_texture_query_levels : require")?; + } + Ok(()) } } @@ -376,27 +387,75 @@ impl<'a, W> Writer<'a, W> { } } - // Loop trough all expressions in both functions and entry points + // We will need to pass some of the members to a closure, so we need + // to separate them otherwise the borrow checker will complain, this + // shouldn't be needed in rust 2021 + let &mut Self { + module, + info, + ref mut features, + entry_point, + entry_point_idx, + ref policies, + .. + } = self; + + // Loop trough all expressions in both functions and the entry point // to check for needed features - for (_, expr) in self - .module + for (expressions, info) in module .functions .iter() - .flat_map(|(_, f)| f.expressions.iter()) - .chain(self.entry_point.function.expressions.iter()) + .map(|(h, f)| (&f.expressions, &info[h])) + .chain(std::iter::once(( + &entry_point.function.expressions, + info.get_entry_point(entry_point_idx as usize), + ))) { - match *expr { + for (_, expr) in expressions.iter() { + match *expr { // Check for fused multiply add use Expression::Math { fun, .. } if fun == MathFunction::Fma => { - self.features.request(Features::FMA) + features.request(Features::FMA) } - // Check for samples query + // Check for queries that neeed aditonal features Expression::ImageQuery { - query: crate::ImageQuery::NumSamples, + image, + query, .. - } => self.features.request(Features::TEXTURE_SAMPLES), + } => match query { + // Storage images use `imageSize` which is only available + // in glsl > 420 + // + // layers queries are also implemented as size queries + crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => { + if let TypeInner::Image { + class: crate::ImageClass::Storage { .. }, .. + } = *info[image].ty.inner_with(&module.types) { + features.request(Features::IMAGE_SIZE) + } + }, + crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS), + crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES), + } + , + // Check for image loads that needs bound checking on the sample + // or level argument since this requires a feature + Expression::ImageLoad { + sample, level, .. + } => { + if policies.image != crate::proc::BoundsCheckPolicy::Unchecked { + if sample.is_some() { + features.request(Features::TEXTURE_SAMPLES) + } + + if level.is_some() { + features.request(Features::TEXTURE_LEVELS) + } + } + } _ => {} } + } } self.features.check_availability(self.options.version) diff --git a/third_party/rust/naga/src/back/glsl/mod.rs b/third_party/rust/naga/src/back/glsl/mod.rs index 5b7700ba9d52..a59a0b9d76b0 100644 --- a/third_party/rust/naga/src/back/glsl/mod.rs +++ b/third_party/rust/naga/src/back/glsl/mod.rs @@ -12,7 +12,6 @@ to output a [`Module`](crate::Module) into glsl - 420 - 430 - 450 -- 460 ### ES - 300 @@ -69,6 +68,10 @@ pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[330, 400, 410, 420, 430, 440, 450] /// List of supported `es` GLSL versions. pub const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310, 320]; +/// The suffix of the variable that will hold the calculated clamped level +/// of detail for bounds checking in `ImageLoad` +const CLAMPED_LOD_SUFFIX: &str = "_clamped_lod"; + /// Mapping between resources and bindings. pub type BindingMap = std::collections::BTreeMap; @@ -375,6 +378,8 @@ pub struct Writer<'a, W> { out: W, /// User defined configuration to be used. options: &'a Options, + /// The bound checking policies to be used + policies: proc::BoundsCheckPolicies, // Internal State /// Features manager used to store all the needed features and write them. @@ -410,6 +415,7 @@ impl<'a, W: Write> Writer<'a, W> { info: &'a valid::ModuleInfo, options: &'a Options, pipeline_options: &'a PipelineOptions, + policies: proc::BoundsCheckPolicies, ) -> Result { // Check if the requested version is supported if !options.version.is_supported() { @@ -437,6 +443,8 @@ impl<'a, W: Write> Writer<'a, W> { info, out, options, + policies, + namer, features: FeaturesManager::new(), names, @@ -1518,20 +1526,30 @@ impl<'a, W: Write> Writer<'a, W> { arg: Handle, arg1: Handle, size: usize, + ctx: &back::FunctionCtx<'_>, ) -> BackendResult { + // Write parantheses around the dot product expression to prevent operators + // with different precedences from applying earlier. write!(self.out, "(")?; - let arg0_name = &self.named_expressions[&arg]; - let arg1_name = &self.named_expressions[&arg1]; - - // This will print an extra '+' at the beginning but that is fine in glsl + // Cycle trough all the components of the vector for index in 0..size { let component = back::COMPONENTS[index]; - write!( - self.out, - " + {}.{} * {}.{}", - arg0_name, component, arg1_name, component - )?; + // Write the addition to the previous product + // This will print an extra '+' at the beginning but that is fine in glsl + write!(self.out, " + ")?; + // Write the first vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg, ctx)?; + // Access the current component on the first vector + write!(self.out, ".{} * ", component)?; + // Write the second vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.write_expr(arg1, ctx)?; + // Access the current component on the second vector + write!(self.out, ".{}", component)?; } write!(self.out, ")")?; @@ -1635,6 +1653,27 @@ impl<'a, W: Write> Writer<'a, W> { None }; + // If we are going to write an `ImageLoad` next and the target image + // is sampled and we are using the `Restrict` policy for bounds + // checking images we need to write a local holding the clamped lod. + if let crate::Expression::ImageLoad { + image, + level: Some(level_expr), + .. + } = ctx.expressions[handle] + { + if let TypeInner::Image { + class: crate::ImageClass::Sampled { .. }, + .. + } = *ctx.info[image].ty.inner_with(&self.module.types) + { + if let proc::BoundsCheckPolicy::Restrict = self.policies.image { + write!(self.out, "{}", level)?; + self.write_clamped_lod(ctx, handle, image, level_expr)? + } + } + } + if let Some(name) = expr_name { write!(self.out, "{}", level)?; self.write_named_expr(handle, name, ctx)?; @@ -1761,16 +1800,26 @@ impl<'a, W: Write> Writer<'a, W> { Statement::Loop { ref body, ref continuing, + break_if, } => { - if !continuing.is_empty() { + if !continuing.is_empty() || break_if.is_some() { let gate_name = self.namer.call("loop_init"); writeln!(self.out, "{}bool {} = true;", level, gate_name)?; writeln!(self.out, "{}while(true) {{", level)?; - writeln!(self.out, "{}if (!{}) {{", level.next(), gate_name)?; + let l2 = level.next(); + let l3 = l2.next(); + writeln!(self.out, "{}if (!{}) {{", l2, gate_name)?; for sta in continuing { - self.write_stmt(sta, ctx, level.next())?; + self.write_stmt(sta, ctx, l3)?; } - writeln!(self.out, "{}}}", level.next())?; + if let Some(condition) = break_if { + write!(self.out, "{}if (", l3)?; + self.write_expr(condition, ctx)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", l3.next())?; + writeln!(self.out, "{}}}", l3)?; + } + writeln!(self.out, "{}}}", l2)?; writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; } else { writeln!(self.out, "{}while(true) {{", level)?; @@ -1933,19 +1982,7 @@ impl<'a, W: Write> Writer<'a, W> { value, } => { write!(self.out, "{}", level)?; - // This will only panic if the module is invalid - let dim = match *ctx.info[image].ty.inner_with(&self.module.types) { - TypeInner::Image { dim, .. } => dim, - _ => unreachable!(), - }; - - write!(self.out, "imageStore(")?; - self.write_expr(image, ctx)?; - write!(self.out, ", ")?; - self.write_texture_coordinates(coordinate, array_index, dim, ctx)?; - write!(self.out, ", ")?; - self.write_expr(value, ctx)?; - writeln!(self.out, ");")?; + self.write_image_store(ctx, image, coordinate, array_index, value)? } // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list Statement::Call { @@ -2320,51 +2357,13 @@ impl<'a, W: Write> Writer<'a, W> { // End the function write!(self.out, ")")? } - // `ImageLoad` is also a bit complicated. - // There are two functions one for sampled - // images another for storage images, the former uses `texelFetch` and the latter uses - // `imageLoad`. - // Furthermore we have `index` which is always `Some` for sampled images - // and `None` for storage images, so we end up with two functions: - // `texelFetch(image, coordinate, index)` - for sampled images - // `imageLoad(image, coordinate)` - for storage images Expression::ImageLoad { image, coordinate, array_index, sample, level, - } => { - // This will only panic if the module is invalid - let (dim, class) = match *ctx.info[image].ty.inner_with(&self.module.types) { - TypeInner::Image { - dim, - arrayed: _, - class, - } => (dim, class), - _ => unreachable!(), - }; - - let fun_name = match class { - crate::ImageClass::Sampled { .. } => "texelFetch", - crate::ImageClass::Storage { .. } => "imageLoad", - // TODO: Is there even a function for this? - crate::ImageClass::Depth { multi: _ } => { - return Err(Error::Custom("TODO: depth sample loads".to_string())) - } - }; - - write!(self.out, "{}(", fun_name)?; - self.write_expr(image, ctx)?; - write!(self.out, ", ")?; - self.write_texture_coordinates(coordinate, array_index, dim, ctx)?; - - if let Some(sample_or_level) = sample.or(level) { - write!(self.out, ", ")?; - self.write_expr(sample_or_level, ctx)?; - } - write!(self.out, ")")?; - } + } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?, // Query translates into one of the: // - textureSize/imageSize // - textureQueryLevels @@ -2747,7 +2746,7 @@ impl<'a, W: Write> Writer<'a, W> { .. } => "dot", crate::TypeInner::Vector { size, .. } => { - return self.write_dot_product(arg, arg1.unwrap(), size as usize) + return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx) } _ => unreachable!( "Correct TypeInner for dot product should be already validated" @@ -2819,32 +2818,59 @@ impl<'a, W: Write> Writer<'a, W> { let extract_bits = fun == Mf::ExtractBits; let insert_bits = fun == Mf::InsertBits; - // we might need to cast to unsigned integers since - // GLSL's findLSB / findMSB always return signed integers - let need_extra_paren = { - (fun == Mf::FindLsb || fun == Mf::FindMsb || fun == Mf::CountOneBits) - && match *ctx.info[arg].ty.inner_with(&self.module.types) { - crate::TypeInner::Scalar { - kind: crate::ScalarKind::Uint, - .. - } => { - write!(self.out, "uint(")?; - true - } - crate::TypeInner::Vector { - kind: crate::ScalarKind::Uint, - size, - .. - } => { - write!(self.out, "uvec{}(", size as u8)?; - true - } - _ => false, - } + // Some GLSL functions always return signed integers (like findMSB), + // so they need to be cast to uint if the argument is also an uint. + let ret_might_need_int_to_uint = + matches!(fun, Mf::FindLsb | Mf::FindMsb | Mf::CountOneBits | Mf::Abs); + + // Some GLSL functions only accept signed integers (like abs), + // so they need their argument cast from uint to int. + let arg_might_need_uint_to_int = matches!(fun, Mf::Abs); + + // Check if the argument is an unsigned integer and return the vector size + // in case it's a vector + let maybe_uint_size = match *ctx.info[arg].ty.inner_with(&self.module.types) { + crate::TypeInner::Scalar { + kind: crate::ScalarKind::Uint, + .. + } => Some(None), + crate::TypeInner::Vector { + kind: crate::ScalarKind::Uint, + size, + .. + } => Some(Some(size)), + _ => None, }; + // Cast to uint if the function needs it + if ret_might_need_int_to_uint { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "uvec{}(", size as u8)?, + None => write!(self.out, "uint(")?, + } + } + } + write!(self.out, "{}(", fun_name)?; + + // Cast to int if the function needs it + if arg_might_need_uint_to_int { + if let Some(maybe_size) = maybe_uint_size { + match maybe_size { + Some(size) => write!(self.out, "ivec{}(", size as u8)?, + None => write!(self.out, "int(")?, + } + } + } + self.write_expr(arg, ctx)?; + + // Close the cast from uint to int + if arg_might_need_uint_to_int && maybe_uint_size.is_some() { + write!(self.out, ")")? + } + if let Some(arg) = arg1 { write!(self.out, ", ")?; if extract_bits { @@ -2877,7 +2903,8 @@ impl<'a, W: Write> Writer<'a, W> { } write!(self.out, ")")?; - if need_extra_paren { + // Close the cast from int to uint + if ret_might_need_int_to_uint && maybe_uint_size.is_some() { write!(self.out, ")")? } } @@ -2913,38 +2940,50 @@ impl<'a, W: Write> Writer<'a, W> { None => { use crate::ScalarKind as Sk; - let source_kind = inner.scalar_kind().unwrap(); - let conv_op = match (source_kind, target_kind) { - (Sk::Float, Sk::Sint) => "floatBitsToInt", - (Sk::Float, Sk::Uint) => "floatBitsToUint", - (Sk::Sint, Sk::Float) => "intBitsToFloat", - (Sk::Uint, Sk::Float) => "uintBitsToFloat", - // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion - (Sk::Uint, Sk::Sint) => "int", - (Sk::Sint, Sk::Uint) => "uint", - - (Sk::Bool, Sk::Sint) => "int", - (Sk::Bool, Sk::Uint) => "uint", - (Sk::Bool, Sk::Float) => "float", - - (Sk::Sint, Sk::Bool) => "bool", - (Sk::Uint, Sk::Bool) => "bool", - (Sk::Float, Sk::Bool) => "bool", - - // No conversion needed - (Sk::Sint, Sk::Sint) => "", - (Sk::Uint, Sk::Uint) => "", - (Sk::Float, Sk::Float) => "", - (Sk::Bool, Sk::Bool) => "", + let target_vector_type = match *inner { + TypeInner::Vector { size, width, .. } => Some(TypeInner::Vector { + size, + width, + kind: target_kind, + }), + _ => None, }; - write!(self.out, "{}", conv_op)?; - if !conv_op.is_empty() { - write!(self.out, "(")?; - } + + let source_kind = inner.scalar_kind().unwrap(); + + match (source_kind, target_kind, target_vector_type) { + // No conversion needed + (Sk::Sint, Sk::Sint, _) + | (Sk::Uint, Sk::Uint, _) + | (Sk::Float, Sk::Float, _) + | (Sk::Bool, Sk::Bool, _) => { + self.write_expr(expr, ctx)?; + return Ok(()); + } + + // Cast to/from floats + (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?, + (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?, + (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?, + (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?, + + // Cast between vector types + (_, _, Some(vector)) => { + self.write_value_type(&vector)?; + } + + // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion + (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?, + (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?, + (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?, + (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => { + write!(self.out, "bool")? + } + }; + + write!(self.out, "(")?; self.write_expr(expr, ctx)?; - if !conv_op.is_empty() { - write!(self.out, ")")? - } + write!(self.out, ")")?; } } } @@ -2961,25 +3000,71 @@ impl<'a, W: Write> Writer<'a, W> { Ok(()) } - fn write_texture_coordinates( + /// Helper function to write the local holding the clamped lod + fn write_clamped_lod( &mut self, + ctx: &back::FunctionCtx, + expr: Handle, + image: Handle, + level_expr: Handle, + ) -> Result<(), Error> { + // Define our local and start a call to `clamp` + write!( + self.out, + "int {}{}{} = clamp(", + back::BAKE_PREFIX, + expr.index(), + CLAMPED_LOD_SUFFIX + )?; + // Write the lod that will be clamped + self.write_expr(level_expr, ctx)?; + // Set the min value to 0 and start a call to `textureQueryLevels` to get + // the maximum value + write!(self.out, ", 0, textureQueryLevels(")?; + // Write the target image as an argument to `textureQueryLevels` + self.write_expr(image, ctx)?; + // Close the call to `textureQueryLevels` subtract 1 from it since + // the lod argument is 0 based, close the `clamp` call and end the + // local declaration statement. + writeln!(self.out, ") - 1);")?; + + Ok(()) + } + + // Helper method used to retrieve how many elements a coordinate vector + // for the images operations need. + fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 { + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es(); + // Get how many components the coordinate vector needs for the dimensions only + let tex_coord_size = match dim { + crate::ImageDimension::D1 => 1, + crate::ImageDimension::D2 => 2, + crate::ImageDimension::D3 => 3, + crate::ImageDimension::Cube => 2, + }; + // Calculate the true size of the coordinate vector by adding 1 for arrayed images + // and another 1 if we need to workaround 1D images by making them 2D + tex_coord_size + tex_1d_hack as u8 + arrayed as u8 + } + + /// Helper method to write the coordinate vector for image operations + fn write_texture_coord( + &mut self, + ctx: &back::FunctionCtx, + vector_size: u8, coordinate: Handle, array_index: Option>, - dim: crate::ImageDimension, - ctx: &back::FunctionCtx, + // Emulate 1D images as 2D for profiles that don't support it (glsl es) + tex_1d_hack: bool, ) -> Result<(), Error> { - use crate::ImageDimension as IDim; - - let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); match array_index { + // If the image needs an array indice we need to add it to the end of our + // coordinate vector, to do so we will use the `ivec(ivec, scalar)` + // constructor notation (NOTE: the inner `ivec` can also be a scalar, this + // is important for 1D arrayed images). Some(layer_expr) => { - let tex_coord_size = match dim { - IDim::D1 => 2, - IDim::D2 => 3, - IDim::D3 => 4, - IDim::Cube => 4, - }; - write!(self.out, "ivec{}(", tex_coord_size + tex_1d_hack as u8)?; + write!(self.out, "ivec{}(", vector_size)?; self.write_expr(coordinate, ctx)?; write!(self.out, ", ")?; // If we are replacing sampler1D with sampler2D we also need @@ -2990,16 +3075,326 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(layer_expr, ctx)?; write!(self.out, ")")?; } + // Otherwise write just the expression (and the 1D hack if needed) None => { if tex_1d_hack { write!(self.out, "ivec2(")?; } self.write_expr(coordinate, ctx)?; if tex_1d_hack { - write!(self.out, ", 0.0)")?; + write!(self.out, ", 0)")?; } } } + + Ok(()) + } + + /// Helper method to write the `ImageStore` statement + fn write_image_store( + &mut self, + ctx: &back::FunctionCtx, + image: Handle, + coordinate: Handle, + array_index: Option>, + value: Handle, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // NOTE: openGL requires that `imageStore`s have no effets when the texel is invalid + // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20) + + // This will only panic if the module is invalid + let dim = match *ctx.info[image].ty.inner_with(&self.module.types) { + TypeInner::Image { dim, .. } => dim, + _ => unreachable!(), + }; + + // Begin our call to `imageStore` + write!(self.out, "imageStore(")?; + self.write_expr(image, ctx)?; + // Separate the image argument from the coordinates + write!(self.out, ", ")?; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Write the coordinate vector + self.write_texture_coord( + ctx, + // Get the size of the coordinate vector + self.get_coordinate_vector_size(dim, array_index.is_some()), + coordinate, + array_index, + tex_1d_hack, + )?; + + // Separate the coordinate from the value to write and write the expression + // of the value to write. + write!(self.out, ", ")?; + self.write_expr(value, ctx)?; + // End the call to `imageStore` and the statement. + writeln!(self.out, ");")?; + + Ok(()) + } + + /// Helper method for writing an `ImageLoad` expression. + #[allow(clippy::too_many_arguments)] + fn write_image_load( + &mut self, + handle: Handle, + ctx: &back::FunctionCtx, + image: Handle, + coordinate: Handle, + array_index: Option>, + sample: Option>, + level: Option>, + ) -> Result<(), Error> { + use crate::ImageDimension as IDim; + + // `ImageLoad` is a bit complicated. + // There are two functions one for sampled + // images another for storage images, the former uses `texelFetch` and the + // latter uses `imageLoad`. + // + // Furthermore we have `level` which is always `Some` for sampled images + // and `None` for storage images, so we end up with two functions: + // - `texelFetch(image, coordinate, level)` for sampled images + // - `imageLoad(image, coordinate)` for storage images + // + // Finally we also have to consider bounds checking, for storage images + // this is easy since openGL requires that invalid texels always return + // 0, for sampled images we need to either verify that all arguments are + // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`). + + // This will only panic if the module is invalid + let (dim, class) = match *ctx.info[image].ty.inner_with(&self.module.types) { + TypeInner::Image { + dim, + arrayed: _, + class, + } => (dim, class), + _ => unreachable!(), + }; + + // Get the name of the function to be used for the load operation + // and the policy to be used with it. + let (fun_name, policy) = match class { + // Sampled images inherit the policy from the user passed policies + crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image), + crate::ImageClass::Storage { .. } => { + // OpenGL 4.2 Core §3.9.20 defines that out of bounds texels in `imageLoad`s + // always return zero values so we don't need to generate bounds checks + ("imageLoad", proc::BoundsCheckPolicy::Unchecked) + } + // TODO: Is there even a function for this? + crate::ImageClass::Depth { multi: _ } => { + return Err(Error::Custom( + "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(), + )) + } + }; + + // openGL es doesn't have 1D images so we need workaround it + let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es(); + // Get the size of the coordinate vector + let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some()); + + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // To write the bounds checks for `ReadZeroSkipWrite` we will use a + // ternary operator since we are in the middle of an expression and + // need to return a value. + // + // NOTE: glsl does short circuit when evaluating logical + // expressions so we can be sure that after we test a + // condition it will be true for the next ones + + // Write parantheses around the ternary operator to prevent problems with + // expressions emitted before or after it having more precedence + write!(self.out, "(",)?; + + // The lod check needs to precede the size check since we need + // to use the lod to get the size of the image at that level. + if let Some(level_expr) = level { + self.write_expr(level_expr, ctx)?; + write!(self.out, " < textureQueryLevels(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // Check that the sample arguments doesn't exceed the number of samples + if let Some(sample_expr) = sample { + self.write_expr(sample_expr, ctx)?; + write!(self.out, " < textureSamples(",)?; + self.write_expr(image, ctx)?; + // Chain the next check + write!(self.out, ") && ")?; + } + + // We now need to write the size checks for the coordinates and array index + // first we write the comparation function in case the image is 1D non arrayed + // (and no 1D to 2D hack was needed) we are comparing scalars so the less than + // operator will suffice, but otherwise we'll be comparing two vectors so we'll + // need to use the `lessThan` function but it returns a vector of booleans (one + // for each comparison) so we need to fold it all in one scalar boolean, since + // we want all comparisons to pass we use the `all` function which will only + // return `true` if all the elements of the boolean vector are also `true`. + // + // So we'll end with one of the following forms + // - `coord < textureSize(image, lod)` for 1D images + // - `all(lessThan(coord, textureSize(image, lod)))` for normal images + // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))` + // for arrayed images + // - `all(lessThan(coord, textureSize(image)))` for multi sampled images + + if vector_size != 1 { + write!(self.out, "all(lessThan(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + if vector_size != 1 { + // If we used the `lessThan` function we need to separate the + // coordinates from the image size. + write!(self.out, ", ")?; + } else { + // If we didn't use it (ie. 1D images) we perform the comparsion + // using the less than operator. + write!(self.out, " < ")?; + } + + // Call `textureSize` to get our image size + write!(self.out, "textureSize(")?; + self.write_expr(image, ctx)?; + // `textureSize` uses the lod as a second argument for mipmapped images + if let Some(level_expr) = level { + // Separate the image from the lod + write!(self.out, ", ")?; + self.write_expr(level_expr, ctx)?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + if vector_size != 1 { + // Close the `all` and `lessThan` calls + write!(self.out, "))")?; + } + + // Finally end the condition part of the ternary operator + write!(self.out, " ? ")?; + } + + // Begin the call to the function used to load the texel + write!(self.out, "{}(", fun_name)?; + self.write_expr(image, ctx)?; + write!(self.out, ", ")?; + + // If we are using `Restrict` bounds checking we need to pass valid texel + // coordinates, to do so we use the `clamp` function to get a value between + // 0 and the image size - 1 (indexing begins at 0) + if let proc::BoundsCheckPolicy::Restrict = policy { + write!(self.out, "clamp(")?; + } + + // Write the coordinate vector + self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?; + + // If we are using `Restrict` bounds checking we need to write the rest of the + // clamp we initiated before writing the coordinates. + if let proc::BoundsCheckPolicy::Restrict = policy { + // Write the min value 0 + if vector_size == 1 { + write!(self.out, ", 0")?; + } else { + write!(self.out, ", ivec{}(0)", vector_size)?; + } + // Start the `textureSize` call to use as the max value. + write!(self.out, ", textureSize(")?; + self.write_expr(image, ctx)?; + // If the image is mipmapped we need to add the lod argument to the + // `textureSize` call, but this needs to be the clamped lod, this should + // have been generated earlier and put in a local. + if class.is_mipmapped() { + write!( + self.out, + ", {}{}{}", + back::BAKE_PREFIX, + handle.index(), + CLAMPED_LOD_SUFFIX + )?; + } + // Close the `textureSize` call + write!(self.out, ")")?; + + // Subtract 1 from the `textureSize` call since the coordinates are zero based. + if vector_size == 1 { + write!(self.out, " - 1")?; + } else { + write!(self.out, " - ivec{}(1)", vector_size)?; + } + + // Close the `clamp` call + write!(self.out, ")")?; + + // Add the clamped lod (if present) as the second argument to the + // image load function. + if level.is_some() { + write!( + self.out, + ", {}{}{}", + back::BAKE_PREFIX, + handle.index(), + CLAMPED_LOD_SUFFIX + )?; + } + + // If a sample argument is needed we need to clamp it between 0 and + // the number of samples the image has. + if let Some(sample_expr) = sample { + write!(self.out, ", clamp(")?; + self.write_expr(sample_expr, ctx)?; + // Set the min value to 0 and start the call to `textureSamples` + write!(self.out, ", 0, textureSamples(")?; + self.write_expr(image, ctx)?; + // Close the `textureSamples` call, subtract 1 from it since the sample + // argument is zero based, and close the `clamp` call + writeln!(self.out, ") - 1)")?; + } + } else if let Some(sample_or_level) = sample.or(level) { + // If no bounds checking is need just add the sample or level argument + // after the coordinates + write!(self.out, ", ")?; + self.write_expr(sample_or_level, ctx)?; + } + + // Close the image load function. + write!(self.out, ")")?; + + // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch + // (which is taken if the condition is `true`) with a colon (`:`) and write the + // second branch which is just a 0 value. + if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy { + // Get the kind of the output value. + let kind = match class { + // Only sampled images can reach here since storage images + // don't need bounds checks and depth images aren't implmented + crate::ImageClass::Sampled { kind, .. } => kind, + _ => unreachable!(), + }; + + // End the first branch + write!(self.out, " : ")?; + // Write the 0 value + write!(self.out, "{}vec4(", glsl_scalar(kind, 4)?.prefix,)?; + self.write_zero_init_scalar(kind)?; + // Close the zero value constructor + write!(self.out, ")")?; + // Close the parantheses surrounding our ternary + write!(self.out, ")")?; + } + Ok(()) } @@ -3347,7 +3742,7 @@ const fn glsl_storage_format(format: crate::StorageFormat) -> &'static str { Sf::Rg16Uint => "rg16ui", Sf::Rg16Sint => "rg16i", Sf::Rg16Float => "rg16f", - Sf::Rgba8Unorm => "rgba8ui", + Sf::Rgba8Unorm => "rgba8", Sf::Rgba8Snorm => "rgba8_snorm", Sf::Rgba8Uint => "rgba8ui", Sf::Rgba8Sint => "rgba8i", diff --git a/third_party/rust/naga/src/back/hlsl/conv.rs b/third_party/rust/naga/src/back/hlsl/conv.rs index b63d635775ff..039bfcce3040 100644 --- a/third_party/rust/naga/src/back/hlsl/conv.rs +++ b/third_party/rust/naga/src/back/hlsl/conv.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use crate::proc::Alignment; + use super::Error; impl crate::ScalarKind { @@ -49,8 +51,7 @@ impl crate::TypeInner { rows, width, } => { - let aligned_rows = if rows > crate::VectorSize::Bi { 4 } else { 2 }; - let stride = aligned_rows * width as u32; + let stride = Alignment::from(rows) * width as u32; let last_row_size = rows as u32 * width as u32; ((columns as u32 - 1) * stride) + last_row_size } diff --git a/third_party/rust/naga/src/back/hlsl/help.rs b/third_party/rust/naga/src/back/hlsl/help.rs index d71675f6a199..ec913ba66de9 100644 --- a/third_party/rust/naga/src/back/hlsl/help.rs +++ b/third_party/rust/naga/src/back/hlsl/help.rs @@ -54,6 +54,11 @@ pub(super) struct WrappedStructMatrixAccess { pub(super) index: u32, } +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub(super) struct WrappedMatCx2 { + pub(super) columns: crate::VectorSize, +} + /// HLSL backend requires its own `ImageQuery` enum. /// /// It is used inside `WrappedImageQuery` and should be unique per ImageQuery function. @@ -461,12 +466,36 @@ impl<'a, W: Write> super::Writer<'a, W> { )?; } } - _ => { - writeln!( - self.out, - "{}{}.{} = {}{};", - INDENT, RETURN_VARIABLE_NAME, field_name, ARGUMENT_VARIABLE_NAME, i, - )?; + ref other => { + // We cast arrays of native HLSL `floatCx2`s to arrays of `matCx2`s + // (where the inner matrix is represented by a struct with C `float2` members). + // See the module-level block comment in mod.rs for details. + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, member.ty) + { + write!( + self.out, + "{}{}.{} = (__mat{}x2", + INDENT, RETURN_VARIABLE_NAME, field_name, columns as u8 + )?; + if let crate::TypeInner::Array { base, size, .. } = *other { + self.write_array_size(module, base, size)?; + } + writeln!(self.out, "){}{};", ARGUMENT_VARIABLE_NAME, i,)?; + } else { + writeln!( + self.out, + "{}{}.{} = {}{};", + INDENT, + RETURN_VARIABLE_NAME, + field_name, + ARGUMENT_VARIABLE_NAME, + i, + )?; + } } } } @@ -715,7 +744,7 @@ impl<'a, W: Write> super::Writer<'a, W> { for i in 0..columns as u8 { writeln!( self.out, - "{}case {}: {}.{}_{} = {};", + "{}case {}: {{ {}.{}_{} = {}; break; }}", INDENT, i, STRUCT_ARGUMENT_VARIABLE_NAME, @@ -809,7 +838,7 @@ impl<'a, W: Write> super::Writer<'a, W> { for i in 0..columns as u8 { writeln!( self.out, - "{}case {}: {}.{}_{}[{}] = {};", + "{}case {}: {{ {}.{}_{}[{}] = {}; break; }}", INDENT, i, STRUCT_ARGUMENT_VARIABLE_NAME, @@ -1050,4 +1079,117 @@ impl<'a, W: Write> super::Writer<'a, W> { } Ok(()) } + + pub(super) fn write_mat_cx2_typedef_and_functions( + &mut self, + WrappedMatCx2 { columns }: WrappedMatCx2, + ) -> BackendResult { + use crate::back::INDENT; + + // typedef + write!(self.out, "typedef struct {{ ")?; + for i in 0..columns as u8 { + write!(self.out, "float2 _{}; ", i)?; + } + writeln!(self.out, "}} __mat{}x2;", columns as u8)?; + + // __get_col_of_mat + writeln!( + self.out, + "float2 __get_col_of_mat{}x2(__mat{}x2 mat, uint idx) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{}switch(idx) {{", INDENT)?; + for i in 0..columns as u8 { + writeln!(self.out, "{}case {}: {{ return mat._{}; }}", INDENT, i, i)?; + } + writeln!(self.out, "{}default: {{ return (float2)0; }}", INDENT)?; + writeln!(self.out, "{}}}", INDENT)?; + writeln!(self.out, "}}")?; + + // __set_col_of_mat + writeln!( + self.out, + "void __set_col_of_mat{}x2(__mat{}x2 mat, uint idx, float2 value) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{}switch(idx) {{", INDENT)?; + for i in 0..columns as u8 { + writeln!( + self.out, + "{}case {}: {{ mat._{} = value; break; }}", + INDENT, i, i + )?; + } + writeln!(self.out, "{}}}", INDENT)?; + writeln!(self.out, "}}")?; + + // __set_el_of_mat + writeln!( + self.out, + "void __set_el_of_mat{}x2(__mat{}x2 mat, uint idx, uint vec_idx, float value) {{", + columns as u8, columns as u8 + )?; + writeln!(self.out, "{}switch(idx) {{", INDENT)?; + for i in 0..columns as u8 { + writeln!( + self.out, + "{}case {}: {{ mat._{}[vec_idx] = value; break; }}", + INDENT, i, i + )?; + } + writeln!(self.out, "{}}}", INDENT)?; + writeln!(self.out, "}}")?; + + writeln!(self.out)?; + + Ok(()) + } + + pub(super) fn write_all_mat_cx2_typedefs_and_functions( + &mut self, + module: &crate::Module, + ) -> BackendResult { + for (handle, _) in module.global_variables.iter() { + let global = &module.global_variables[handle]; + + if global.space == crate::AddressSpace::Uniform { + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, global.ty) + { + let entry = WrappedMatCx2 { columns }; + if !self.wrapped.mat_cx2s.contains(&entry) { + self.write_mat_cx2_typedef_and_functions(entry)?; + self.wrapped.mat_cx2s.insert(entry); + } + } + } + } + + for (_, ty) in module.types.iter() { + if let crate::TypeInner::Struct { ref members, .. } = ty.inner { + for member in members.iter() { + if let crate::TypeInner::Array { .. } = module.types[member.ty].inner { + if let Some(super::writer::MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = super::writer::get_inner_matrix_data(module, member.ty) + { + let entry = WrappedMatCx2 { columns }; + if !self.wrapped.mat_cx2s.contains(&entry) { + self.write_mat_cx2_typedef_and_functions(entry)?; + self.wrapped.mat_cx2s.insert(entry); + } + } + } + } + } + } + + Ok(()) + } } diff --git a/third_party/rust/naga/src/back/hlsl/mod.rs b/third_party/rust/naga/src/back/hlsl/mod.rs index db027f7769ee..76abb2978316 100644 --- a/third_party/rust/naga/src/back/hlsl/mod.rs +++ b/third_party/rust/naga/src/back/hlsl/mod.rs @@ -57,8 +57,8 @@ that the columns of a `matKx2` need only be [aligned as required for `vec2`][ilov], which is [eight-byte alignment][8bb]. To compensate for this, any time a `matKx2` appears in a WGSL -`uniform` variable, whether directly as the variable's type or as a -struct member, we actually emit `K` separate `float2` members, and +`uniform` variable, whether directly as the variable's type or as part +of a struct/array, we actually emit `K` separate `float2` members, and assemble/disassemble the matrix from its columns (in WGSL; rows in HLSL) upon load and store. @@ -92,14 +92,10 @@ float3x2 GetMatmOnBaz(Baz obj) { We also emit an analogous `Set` function, as well as functions for accessing individual columns by dynamic index. -At present, we do not generate correct HLSL when `matCx2` us used -directly as the type of a WGSL `uniform` global ([#1837]). - [hlsl]: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl [ilov]: https://gpuweb.github.io/gpuweb/wgsl/#internal-value-layout [16bb]: https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing#constant-buffer-packing [8bb]: https://gpuweb.github.io/gpuweb/wgsl/#alignment-and-size -[#1837]: https://github.com/gfx-rs/naga/issues/1837 */ mod conv; @@ -253,6 +249,7 @@ struct Wrapped { image_queries: crate::FastHashSet, constructors: crate::FastHashSet, struct_matrix_access: crate::FastHashSet, + mat_cx2s: crate::FastHashSet, } impl Wrapped { @@ -261,6 +258,7 @@ impl Wrapped { self.image_queries.clear(); self.constructors.clear(); self.struct_matrix_access.clear(); + self.mat_cx2s.clear(); } } diff --git a/third_party/rust/naga/src/back/hlsl/storage.rs b/third_party/rust/naga/src/back/hlsl/storage.rs index 637b05d4bb95..65edc92022b6 100644 --- a/third_party/rust/naga/src/back/hlsl/storage.rs +++ b/third_party/rust/naga/src/back/hlsl/storage.rs @@ -6,7 +6,7 @@ HLSL backend uses byte address buffers for all storage buffers in IR. use super::{super::FunctionCtx, BackendResult, Error}; use crate::{ - proc::{NameKey, TypeResolution}, + proc::{Alignment, NameKey, TypeResolution}, Handle, }; @@ -130,11 +130,7 @@ impl super::Writer<'_, W> { )?; // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. - let padded_rows = match rows { - crate::VectorSize::Tri => 4, - rows => rows as u32, - }; - let row_stride = width as u32 * padded_rows; + let row_stride = Alignment::from(rows) * width as u32; let iter = (0..columns as u32).map(|i| { let ty_inner = crate::TypeInner::Vector { size: rows, @@ -277,11 +273,7 @@ impl super::Writer<'_, W> { writeln!(self.out, ";")?; // Note: Matrices containing vec3s, due to padding, act like they contain vec4s. - let padded_rows = match rows { - crate::VectorSize::Tri => 4, - rows => rows as u32, - }; - let row_stride = width as u32 * padded_rows; + let row_stride = Alignment::from(rows) * width as u32; // then iterate the stores for i in 0..columns as u32 { @@ -409,12 +401,7 @@ impl super::Writer<'_, W> { stride: width as u32, }, crate::TypeInner::Matrix { columns, width, .. } => Parent::Array { - stride: width as u32 - * if columns > crate::VectorSize::Bi { - 4 - } else { - 2 - }, + stride: Alignment::from(columns) * width as u32, }, _ => unreachable!(), }, diff --git a/third_party/rust/naga/src/back/hlsl/writer.rs b/third_party/rust/naga/src/back/hlsl/writer.rs index e713c2f5dd19..b582cfa4f20e 100644 --- a/third_party/rust/naga/src/back/hlsl/writer.rs +++ b/third_party/rust/naga/src/back/hlsl/writer.rs @@ -150,6 +150,8 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { .map(|ep| (ep.stage, ep.function.result.clone())) .collect::)>>(); + self.write_all_mat_cx2_typedefs_and_functions(module)?; + // Write all structs for (handle, ty) in module.types.iter() { if let TypeInner::Struct { ref members, span } = ty.inner { @@ -661,19 +663,41 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { if global.space == crate::AddressSpace::Uniform { write!(self.out, " {{ ")?; - // Even though Naga IR matrices are column-major, we must describe - // matrices passed from the CPU as being in row-major order. See - // the module-level comments for details. - if let TypeInner::Matrix { .. } = module.types[global.ty].inner { - write!(self.out, "row_major ")?; + + let matrix_data = get_inner_matrix_data(module, global.ty); + + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = matrix_data + { + write!( + self.out, + "__mat{}x2 {}", + columns as u8, + &self.names[&NameKey::GlobalVariable(handle)] + )?; + } else { + // Even though Naga IR matrices are column-major, we must describe + // matrices passed from the CPU as being in row-major order. + // See the module-level block comment in mod.rs for details. + if matrix_data.is_some() { + write!(self.out, "row_major ")?; + } + + self.write_type(module, global.ty)?; + let sub_name = &self.names[&NameKey::GlobalVariable(handle)]; + write!(self.out, " {}", sub_name)?; } - self.write_type(module, global.ty)?; - let sub_name = &self.names[&NameKey::GlobalVariable(handle)]; - write!(self.out, " {}", sub_name)?; + // need to write the array size if the type was emitted with `write_type` if let TypeInner::Array { base, size, .. } = module.types[global.ty].inner { self.write_array_size(module, base, size)?; } + writeln!(self.out, "; }}")?; } else { writeln!(self.out, ";")?; @@ -801,16 +825,31 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { write!(self.out, "{}", back::INDENT)?; match module.types[member.ty].inner { - TypeInner::Array { - base, - size, - stride: _, - } => { + TypeInner::Array { base, size, .. } => { // HLSL arrays are written as `type name[size]` - if let TypeInner::Matrix { .. } = module.types[base].inner { - write!(self.out, "row_major ")?; + + let matrix_data = get_inner_matrix_data(module, member.ty); + + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = matrix_data + { + write!(self.out, "__mat{}x2", columns as u8)?; + } else { + // Even though Naga IR matrices are column-major, we must describe + // matrices passed from the CPU as being in row-major order. + // See the module-level block comment in mod.rs for details. + if matrix_data.is_some() { + write!(self.out, "row_major ")?; + } + + self.write_type(module, base)?; } - self.write_type(module, base)?; + // Write `name` write!( self.out, @@ -820,8 +859,8 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { // Write [size] self.write_array_size(module, base, size)?; } - // We treat matrices of the form `matCx2` as a sequence of C `vec2`s - // (see top level module docs for details). + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. TypeInner::Matrix { rows, columns, @@ -848,6 +887,9 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_modifier(binding)?; } + // Even though Naga IR matrices are column-major, we must describe + // matrices passed from the CPU as being in row-major order. + // See the module-level block comment in mod.rs for details. if let TypeInner::Matrix { .. } = module.types[member.ty].inner { write!(self.out, "row_major ")?; } @@ -1285,17 +1327,6 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { } Statement::Store { pointer, value } => { let ty_inner = func_ctx.info[pointer].ty.inner_with(&module.types); - let array_info = match *ty_inner { - TypeInner::Pointer { base, .. } => match module.types[base].inner { - crate::TypeInner::Array { - size: crate::ArraySize::Constant(ch), - .. - } => Some((ch, base)), - _ => None, - }, - _ => None, - }; - if let Some(crate::AddressSpace::Storage { .. }) = ty_inner.pointer_space() { let var_handle = self.fill_access_chain(module, pointer, func_ctx)?; self.write_storage_store( @@ -1305,26 +1336,9 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { func_ctx, level, )?; - } else if let Some((const_handle, base_ty)) = array_info { - let size = module.constants[const_handle].to_array_length().unwrap(); - writeln!(self.out, "{}{{", level)?; - write!(self.out, "{}", level.next())?; - self.write_type(module, base_ty)?; - write!(self.out, " _result[{}]=", size)?; - self.write_expr(module, value, func_ctx)?; - writeln!(self.out, ";")?; - write!( - self.out, - "{}for(int _i=0; _i<{}; ++_i) ", - level.next(), - size - )?; - self.write_expr(module, pointer, func_ctx)?; - writeln!(self.out, "[_i] = _result[_i];")?; - writeln!(self.out, "{}}}", level)?; } else { - // We treat matrices of the form `matCx2` as a sequence of C `vec2`s - // (see top level module docs for details). + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. // // We handle matrix Stores here directly (including sub accesses for Vectors and Scalars). // Loads are handled by `Expression::AccessIndex` (since sub accesses work fine for Loads). @@ -1487,28 +1501,159 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { writeln!(self.out, ");")?; } } else { - self.write_expr(module, pointer, func_ctx)?; - write!(self.out, " = ")?; - self.write_expr(module, value, func_ctx)?; - writeln!(self.out, ";")? + // We handle `Store`s to __matCx2 column vectors and scalar elements via + // the previously injected functions __set_col_of_matCx2 / __set_el_of_matCx2. + struct MatrixData { + columns: crate::VectorSize, + base: Handle, + } + + enum Index { + Expression(Handle), + Static(u32), + } + + let mut matrix = None; + let mut vector = None; + let mut scalar = None; + + let mut current_expr = pointer; + for _ in 0..3 { + let resolved = func_ctx.info[current_expr].ty.inner_with(&module.types); + match (resolved, &func_ctx.expressions[current_expr]) { + ( + &TypeInner::ValuePointer { + size: Some(crate::VectorSize::Bi), + .. + }, + &crate::Expression::Access { base, index }, + ) => { + vector = Some(index); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::Access { base, index }, + ) => { + scalar = Some(Index::Expression(index)); + current_expr = base; + } + ( + &TypeInner::ValuePointer { size: None, .. }, + &crate::Expression::AccessIndex { base, index }, + ) => { + scalar = Some(Index::Static(index)); + current_expr = base; + } + _ => { + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member( + module, + current_expr, + func_ctx, + true, + ) { + matrix = Some(MatrixData { + columns, + base: current_expr, + }); + } + + break; + } + } + } + + if let (Some(MatrixData { columns, base }), Some(vec_index)) = + (matrix, vector) + { + if scalar.is_some() { + write!(self.out, "__set_el_of_mat{}x2", columns as u8)?; + } else { + write!(self.out, "__set_col_of_mat{}x2", columns as u8)?; + } + write!(self.out, "(")?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, vec_index, func_ctx)?; + + if let Some(scalar_index) = scalar { + write!(self.out, ", ")?; + match scalar_index { + Index::Static(index) => { + write!(self.out, "{}", index)?; + } + Index::Expression(index) => { + self.write_expr(module, index, func_ctx)?; + } + } + } + + write!(self.out, ", ")?; + self.write_expr(module, value, func_ctx)?; + + writeln!(self.out, ");")?; + } else { + self.write_expr(module, pointer, func_ctx)?; + write!(self.out, " = ")?; + + // We cast the RHS of this store in cases where the LHS + // is a struct member with type: + // - matCx2 or + // - a (possibly nested) array of matCx2's + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member( + module, pointer, func_ctx, false, + ) { + let mut resolved = + func_ctx.info[pointer].ty.inner_with(&module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + } + + write!(self.out, "(__mat{}x2", columns as u8)?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_array_size(module, base, size)?; + } + write!(self.out, ")")?; + } + + self.write_expr(module, value, func_ctx)?; + writeln!(self.out, ";")? + } } } } Statement::Loop { ref body, ref continuing, + break_if, } => { let l2 = level.next(); - if !continuing.is_empty() { + if !continuing.is_empty() || break_if.is_some() { let gate_name = self.namer.call("loop_init"); writeln!(self.out, "{}bool {} = true;", level, gate_name)?; writeln!(self.out, "{}while(true) {{", level)?; writeln!(self.out, "{}if (!{}) {{", l2, gate_name)?; + let l3 = l2.next(); for sta in continuing.iter() { - self.write_stmt(module, sta, func_ctx, l2)?; + self.write_stmt(module, sta, func_ctx, l3)?; } - writeln!(self.out, "{}}}", level.next())?; - writeln!(self.out, "{}{} = false;", level.next(), gate_name)?; + if let Some(condition) = break_if { + write!(self.out, "{}if (", l3)?; + self.write_expr(module, condition, func_ctx)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", l3.next())?; + writeln!(self.out, "{}}}", l3)?; + } + writeln!(self.out, "{}}}", l2)?; + writeln!(self.out, "{}{} = false;", l2, gate_name)?; } else { writeln!(self.out, "{}while(true) {{", level)?; } @@ -1651,7 +1796,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { let indent_level_1 = level.next(); let indent_level_2 = indent_level_1.next(); - for case in cases { + for (i, case) in cases.iter().enumerate() { match case.value { crate::SwitchValue::Integer(value) => writeln!( self.out, @@ -1663,25 +1808,35 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { } } + // FXC doesn't support fallthrough so we duplicate the body of the following case blocks if case.fall_through { - // Generate each fallthrough case statement in a new block. This is done to - // prevent symbol collision of variables declared in these cases statements. - writeln!(self.out, "{}/* fallthrough */", indent_level_2)?; - writeln!(self.out, "{}{{", indent_level_2)?; - } - for sta in case.body.iter() { - self.write_stmt( - module, - sta, - func_ctx, - back::Level(indent_level_2.0 + usize::from(case.fall_through)), - )?; - } + let curr_len = i + 1; + let end_case_idx = curr_len + + cases + .iter() + .skip(curr_len) + .position(|case| !case.fall_through) + .unwrap(); + let indent_level_3 = indent_level_2.next(); + for case in &cases[i..=end_case_idx] { + writeln!(self.out, "{}{{", indent_level_2)?; + for sta in case.body.iter() { + self.write_stmt(module, sta, func_ctx, indent_level_3)?; + } + writeln!(self.out, "{}}}", indent_level_2)?; + } - if case.fall_through { - writeln!(self.out, "{}}}", indent_level_2)?; - } else if case.body.last().map_or(true, |s| !s.is_terminator()) { - writeln!(self.out, "{}break;", indent_level_2)?; + let last_case = &cases[end_case_idx]; + if last_case.body.last().map_or(true, |s| !s.is_terminator()) { + writeln!(self.out, "{}break;", indent_level_2)?; + } + } else { + for sta in case.body.iter() { + self.write_stmt(module, sta, func_ctx, indent_level_2)?; + } + if case.body.last().map_or(true, |s| !s.is_terminator()) { + writeln!(self.out, "{}break;", indent_level_2)?; + } } writeln!(self.out, "{}}}", indent_level_1)?; @@ -1844,6 +1999,26 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { { // do nothing, the chain is written on `Load`/`Store` } else { + // We use the function __get_col_of_matCx2 here in cases + // where `base`s type resolves to a matCx2 and is part of a + // struct member with type of (possibly nested) array of matCx2's. + // + // Note that this only works for `Load`s and we handle + // `Store`s differently in `Statement::Store`. + if let Some(MatrixType { + columns, + rows: crate::VectorSize::Bi, + width: 4, + }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) + { + write!(self.out, "__get_col_of_mat{}x2(", columns as u8)?; + self.write_expr(module, base, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, index, func_ctx)?; + write!(self.out, ")")?; + return Ok(()); + } + let base_ty_res = &func_ctx.info[base].ty; let resolved = base_ty_res.inner_with(&module.types); @@ -1876,18 +2051,64 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { { // do nothing, the chain is written on `Load`/`Store` } else { + fn write_access( + writer: &mut super::Writer<'_, W>, + resolved: &TypeInner, + base_ty_handle: Option>, + index: u32, + ) -> BackendResult { + match *resolved { + TypeInner::Vector { .. } => { + // Write vector access as a swizzle + write!(writer.out, ".{}", back::COMPONENTS[index as usize])? + } + TypeInner::Matrix { .. } + | TypeInner::Array { .. } + | TypeInner::BindingArray { .. } + | TypeInner::ValuePointer { .. } => write!(writer.out, "[{}]", index)?, + TypeInner::Struct { .. } => { + // This will never panic in case the type is a `Struct`, this is not true + // for other types so we can only check while inside this match arm + let ty = base_ty_handle.unwrap(); + + write!( + writer.out, + ".{}", + &writer.names[&NameKey::StructMember(ty, index)] + )? + } + ref other => { + return Err(Error::Custom(format!("Cannot index {:?}", other))) + } + } + Ok(()) + } + + // We write the matrix column access in a special way since + // the type of `base` is our special __matCx2 struct. + if let Some(MatrixType { + rows: crate::VectorSize::Bi, + width: 4, + .. + }) = get_inner_matrix_of_struct_array_member(module, base, func_ctx, true) + { + self.write_expr(module, base, func_ctx)?; + write!(self.out, "._{}", index)?; + return Ok(()); + } + let base_ty_res = &func_ctx.info[base].ty; let mut resolved = base_ty_res.inner_with(&module.types); let base_ty_handle = match *resolved { - TypeInner::Pointer { base, space: _ } => { + TypeInner::Pointer { base, .. } => { resolved = &module.types[base].inner; Some(base) } _ => base_ty_res.handle(), }; - // We treat matrices of the form `matCx2` as a sequence of C `vec2`s - // (see top level module docs for details). + // We treat matrices of the form `matCx2` as a sequence of C `vec2`s. + // See the module-level block comment in mod.rs for details. // // We handle matrix reconstruction here for Loads. // Stores are handled directly by `Statement::Store`. @@ -1910,34 +2131,10 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { } _ => {} } - }; + } self.write_expr(module, base, func_ctx)?; - - match *resolved { - TypeInner::Vector { .. } => { - // Write vector access as a swizzle - write!(self.out, ".{}", back::COMPONENTS[index as usize])? - } - TypeInner::Matrix { .. } - | TypeInner::Array { .. } - | TypeInner::BindingArray { .. } - | TypeInner::ValuePointer { .. } => write!(self.out, "[{}]", index)?, - TypeInner::Struct { .. } => { - // This will never panic in case the type is a `Struct`, this is not true - // for other types so we can only check while inside this match arm - let ty = base_ty_handle.unwrap(); - - write!( - self.out, - ".{}", - &self.names[&NameKey::StructMember(ty, index)] - )? - } - ref other => { - return Err(Error::Custom(format!("Cannot index {:?}", other))) - } - } + write_access(self, resolved, base_ty_handle, index)?; } } Expression::FunctionArgument(pos) => { @@ -2108,7 +2305,42 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_storage_load(module, var_handle, result_ty, func_ctx)?; } _ => { + let mut close_paren = false; + + // We cast the value loaded to a native HLSL floatCx2 + // in cases where it is of type: + // - __matCx2 or + // - a (possibly nested) array of __matCx2's + if let Some(MatrixType { + rows: crate::VectorSize::Bi, + width: 4, + .. + }) = get_inner_matrix_of_struct_array_member( + module, pointer, func_ctx, false, + ) + .or_else(|| get_inner_matrix_of_global_uniform(module, pointer, func_ctx)) + { + let mut resolved = func_ctx.info[pointer].ty.inner_with(&module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + } + + write!(self.out, "((")?; + if let TypeInner::Array { base, size, .. } = *resolved { + self.write_type(module, base)?; + self.write_array_size(module, base, size)?; + } else { + self.write_value_type(module, resolved)?; + } + write!(self.out, ")")?; + close_paren = true; + } + self.write_expr(module, pointer, func_ctx)?; + + if close_paren { + write!(self.out, ")")?; + } } } } @@ -2567,3 +2799,139 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { Ok(()) } } + +pub(super) struct MatrixType { + pub(super) columns: crate::VectorSize, + pub(super) rows: crate::VectorSize, + pub(super) width: crate::Bytes, +} + +pub(super) fn get_inner_matrix_data( + module: &Module, + handle: Handle, +) -> Option { + match module.types[handle].inner { + TypeInner::Matrix { + columns, + rows, + width, + } => Some(MatrixType { + columns, + rows, + width, + }), + TypeInner::Array { base, .. } => get_inner_matrix_data(module, base), + _ => None, + } +} + +/// Returns the matrix data if the access chain starting at `base`: +/// - starts with an expression with resolved type of [`TypeInner::Matrix`] if `direct = true` +/// - contains one or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] +/// - ends at an expression with resolved type of [`TypeInner::Struct`] +pub(super) fn get_inner_matrix_of_struct_array_member( + module: &Module, + base: Handle, + func_ctx: &back::FunctionCtx<'_>, + direct: bool, +) -> Option { + let mut mat_data = None; + let mut array_base = None; + + let mut current_base = base; + loop { + let mut resolved = func_ctx.info[current_base].ty.inner_with(&module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + }; + + match *resolved { + TypeInner::Matrix { + columns, + rows, + width, + } => { + mat_data = Some(MatrixType { + columns, + rows, + width, + }) + } + TypeInner::Array { base, .. } => { + array_base = Some(base); + } + TypeInner::Struct { .. } => { + if let Some(array_base) = array_base { + if direct { + return mat_data; + } else { + return get_inner_matrix_data(module, array_base); + } + } + + break; + } + _ => break, + } + + current_base = match func_ctx.expressions[current_base] { + crate::Expression::Access { base, .. } => base, + crate::Expression::AccessIndex { base, .. } => base, + _ => break, + }; + } + None +} + +/// Returns the matrix data if the access chain starting at `base`: +/// - starts with an expression with resolved type of [`TypeInner::Matrix`] +/// - contains zero or more expressions with resolved type of [`TypeInner::Array`] of [`TypeInner::Matrix`] +/// - ends with an [`Expression::GlobalVariable`](crate::Expression::GlobalVariable) in [`AddressSpace::Uniform`](crate::AddressSpace::Uniform) +fn get_inner_matrix_of_global_uniform( + module: &Module, + base: Handle, + func_ctx: &back::FunctionCtx<'_>, +) -> Option { + let mut mat_data = None; + let mut array_base = None; + + let mut current_base = base; + loop { + let mut resolved = func_ctx.info[current_base].ty.inner_with(&module.types); + if let TypeInner::Pointer { base, .. } = *resolved { + resolved = &module.types[base].inner; + }; + + match *resolved { + TypeInner::Matrix { + columns, + rows, + width, + } => { + mat_data = Some(MatrixType { + columns, + rows, + width, + }) + } + TypeInner::Array { base, .. } => { + array_base = Some(base); + } + _ => break, + } + + current_base = match func_ctx.expressions[current_base] { + crate::Expression::Access { base, .. } => base, + crate::Expression::AccessIndex { base, .. } => base, + crate::Expression::GlobalVariable(handle) + if module.global_variables[handle].space == crate::AddressSpace::Uniform => + { + return mat_data.or_else(|| { + array_base.and_then(|array_base| get_inner_matrix_data(module, array_base)) + }) + } + _ => break, + }; + } + None +} diff --git a/third_party/rust/naga/src/back/msl/writer.rs b/third_party/rust/naga/src/back/msl/writer.rs index 8294626a80c2..c35786f7ad5c 100644 --- a/third_party/rust/naga/src/back/msl/writer.rs +++ b/third_party/rust/naga/src/back/msl/writer.rs @@ -364,6 +364,8 @@ pub struct Writer { put_expression_stack_pointers: FastHashSet<*const ()>, #[cfg(test)] put_block_stack_pointers: FastHashSet<*const ()>, + /// Set of (struct type, struct field index) denoting which fields require + /// padding inserted **before** them (i.e. between fields at index - 1 and index) struct_member_pads: FastHashSet<(Handle, u32)>, } @@ -647,6 +649,8 @@ impl Writer { } /// Finishes writing and returns the output. + // See https://github.com/rust-lang/rust-clippy/issues/4979. + #[allow(clippy::missing_const_for_fn)] pub fn finish(self) -> W { self.out } @@ -1221,20 +1225,30 @@ impl Writer { arg: Handle, arg1: Handle, size: usize, + context: &ExpressionContext, ) -> BackendResult { + // Write parantheses around the dot product expression to prevent operators + // with different precedences from applying earlier. write!(self.out, "(")?; - let arg0_name = &self.named_expressions[&arg]; - let arg1_name = &self.named_expressions[&arg1]; - - // This will print an extra '+' at the beginning but that is fine in msl + // Cycle trough all the components of the vector for index in 0..size { let component = back::COMPONENTS[index]; - write!( - self.out, - " + {}.{} * {}.{}", - arg0_name, component, arg1_name, component - )?; + // Write the addition to the previous product + // This will print an extra '+' at the beginning but that is fine in msl + write!(self.out, " + ")?; + // Write the first vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.put_expression(arg, context, true)?; + // Access the current component on the first vector + write!(self.out, ".{} * ", component)?; + // Write the second vector expression, this expression is marked to be + // cached so unless it can't be cached (for example, it's a Constant) + // it shouldn't produce large expressions. + self.put_expression(arg1, context, true)?; + // Access the current component on the second vector + write!(self.out, ".{}", component)?; } write!(self.out, ")")?; @@ -1652,7 +1666,7 @@ impl Writer { .. } => "dot", crate::TypeInner::Vector { size, .. } => { - return self.put_dot_product(arg, arg1.unwrap(), size as usize) + return self.put_dot_product(arg, arg1.unwrap(), size as usize, context) } _ => unreachable!( "Correct TypeInner for dot product should be already validated" @@ -2538,14 +2552,23 @@ impl Writer { crate::Statement::Loop { ref body, ref continuing, + break_if, } => { - if !continuing.is_empty() { + if !continuing.is_empty() || break_if.is_some() { let gate_name = self.namer.call("loop_init"); writeln!(self.out, "{}bool {} = true;", level, gate_name)?; writeln!(self.out, "{}while(true) {{", level)?; let lif = level.next(); + let lcontinuing = lif.next(); writeln!(self.out, "{}if (!{}) {{", lif, gate_name)?; - self.put_block(lif.next(), continuing, context)?; + self.put_block(lcontinuing, continuing, context)?; + if let Some(condition) = break_if { + write!(self.out, "{}if (", lcontinuing)?; + self.put_expression(condition, &context.expression, true)?; + writeln!(self.out, ") {{")?; + writeln!(self.out, "{}break;", lcontinuing.next())?; + writeln!(self.out, "{}}}", lcontinuing)?; + } writeln!(self.out, "{}}}", lif)?; writeln!(self.out, "{}{} = false;", lif, gate_name)?; } else { @@ -3080,6 +3103,10 @@ impl Writer { }; write!(self.out, "constant {} {} = {{", ty_name, name,)?; for (i, &sub_handle) in components.iter().enumerate() { + // insert padding initialization, if needed + if self.struct_member_pads.contains(&(ty, i as u32)) { + write!(self.out, ", {{}}")?; + } let separator = if i != 0 { ", " } else { "" }; let coco = ConstantContext { handle: sub_handle, diff --git a/third_party/rust/naga/src/back/spv/block.rs b/third_party/rust/naga/src/back/spv/block.rs index 413f4df5d327..1c475e49b02d 100644 --- a/third_party/rust/naga/src/back/spv/block.rs +++ b/third_party/rust/naga/src/back/spv/block.rs @@ -37,6 +37,28 @@ enum ExpressionPointer { }, } +/// The termination statement to be added to the end of the block +pub enum BlockExit { + /// Generates an OpReturn (void return) + Return, + /// Generates an OpBranch to the specified block + Branch { + /// The branch target block + target: Word, + }, + /// Translates a loop `break if` into an `OpBranchConditional` to the + /// merge block if true (the merge block is passed through [`LoopContext::break_id`] + /// or else to the loop header (passed through [`preamble_id`]) + /// + /// [`preamble_id`]: Self::BreakIf::preamble_id + BreakIf { + /// The condition of the `break if` + condition: Handle, + /// The loop header block id + preamble_id: Word, + }, +} + impl Writer { // Flip Y coordinate to adjust for coordinate space difference // between SPIR-V and our IR. @@ -1491,7 +1513,7 @@ impl<'w> BlockContext<'w> { &mut self, label_id: Word, statements: &[crate::Statement], - exit_id: Option, + exit: BlockExit, loop_context: LoopContext, ) -> Result<(), Error> { let mut block = Block::new(label_id); @@ -1508,7 +1530,12 @@ impl<'w> BlockContext<'w> { self.function.consume(block, Instruction::branch(scope_id)); let merge_id = self.gen_id(); - self.write_block(scope_id, block_statements, Some(merge_id), loop_context)?; + self.write_block( + scope_id, + block_statements, + BlockExit::Branch { target: merge_id }, + loop_context, + )?; block = Block::new(merge_id); } @@ -1546,10 +1573,20 @@ impl<'w> BlockContext<'w> { ); if let Some(block_id) = accept_id { - self.write_block(block_id, accept, Some(merge_id), loop_context)?; + self.write_block( + block_id, + accept, + BlockExit::Branch { target: merge_id }, + loop_context, + )?; } if let Some(block_id) = reject_id { - self.write_block(block_id, reject, Some(merge_id), loop_context)?; + self.write_block( + block_id, + reject, + BlockExit::Branch { target: merge_id }, + loop_context, + )?; } block = Block::new(merge_id); @@ -1611,7 +1648,9 @@ impl<'w> BlockContext<'w> { self.write_block( *label_id, &case.body, - Some(case_finish_id), + BlockExit::Branch { + target: case_finish_id, + }, inner_context, )?; } @@ -1619,7 +1658,12 @@ impl<'w> BlockContext<'w> { // If no default was encountered write a empty block to satisfy the presence of // a block the default label if !reached_default { - self.write_block(default_id, &[], Some(merge_id), inner_context)?; + self.write_block( + default_id, + &[], + BlockExit::Branch { target: merge_id }, + inner_context, + )?; } block = Block::new(merge_id); @@ -1627,6 +1671,7 @@ impl<'w> BlockContext<'w> { crate::Statement::Loop { ref body, ref continuing, + break_if, } => { let preamble_id = self.gen_id(); self.function @@ -1649,17 +1694,29 @@ impl<'w> BlockContext<'w> { self.write_block( body_id, body, - Some(continuing_id), + BlockExit::Branch { + target: continuing_id, + }, LoopContext { continuing_id: Some(continuing_id), break_id: Some(merge_id), }, )?; + let exit = match break_if { + Some(condition) => BlockExit::BreakIf { + condition, + preamble_id, + }, + None => BlockExit::Branch { + target: preamble_id, + }, + }; + self.write_block( continuing_id, continuing, - Some(preamble_id), + exit, LoopContext { continuing_id: None, break_id: Some(merge_id), @@ -1955,12 +2012,10 @@ impl<'w> BlockContext<'w> { } } - let termination = match exit_id { - Some(id) => Instruction::branch(id), - // This can happen if the last branch had all the paths - // leading out of the graph (i.e. returning). - // Or it may be the end of the self.function. - None => match self.ir_function.result { + let termination = match exit { + // We're generating code for the top-level Block of the function, so we + // need to end it with some kind of return instruction. + BlockExit::Return => match self.ir_function.result { Some(ref result) if self.function.entry_point_context.is_none() => { let type_id = self.get_type_id(LookupType::Handle(result.ty)); let null_id = self.writer.write_constant_null(type_id); @@ -1968,6 +2023,19 @@ impl<'w> BlockContext<'w> { } _ => Instruction::return_void(), }, + BlockExit::Branch { target } => Instruction::branch(target), + BlockExit::BreakIf { + condition, + preamble_id, + } => { + let condition_id = self.cached[condition]; + + Instruction::branch_conditional( + condition_id, + loop_context.break_id.unwrap(), + preamble_id, + ) + } }; self.function.consume(block, termination); diff --git a/third_party/rust/naga/src/back/spv/writer.rs b/third_party/rust/naga/src/back/spv/writer.rs index f4d9e0e60356..6e8758f0c46d 100644 --- a/third_party/rust/naga/src/back/spv/writer.rs +++ b/third_party/rust/naga/src/back/spv/writer.rs @@ -8,7 +8,7 @@ use super::{ use crate::{ arena::{Handle, UniqueArena}, back::spv::BindingInfo, - proc::TypeResolution, + proc::{Alignment, TypeResolution}, valid::{FunctionInfo, ModuleInfo}, }; use spirv::Word; @@ -574,7 +574,12 @@ impl Writer { context .function .consume(prelude, Instruction::branch(main_id)); - context.write_block(main_id, &ir_function.body, None, LoopContext::default())?; + context.write_block( + main_id, + &ir_function.body, + super::block::BlockExit::Return, + LoopContext::default(), + )?; // Consume the `BlockContext`, ending its borrows and letting the // `Writer` steal back its cached expression table and temp_list. @@ -1379,10 +1384,7 @@ impl Writer { width, } = *member_array_subty_inner { - let byte_stride = match rows { - crate::VectorSize::Bi => 2 * width, - crate::VectorSize::Tri | crate::VectorSize::Quad => 4 * width, - }; + let byte_stride = Alignment::from(rows) * width as u32; self.annotations.push(Instruction::member_decorate( struct_id, index as u32, @@ -1393,7 +1395,7 @@ impl Writer { struct_id, index as u32, Decoration::MatrixStride, - &[byte_stride as u32], + &[byte_stride], )); } diff --git a/third_party/rust/naga/src/back/wgsl/writer.rs b/third_party/rust/naga/src/back/wgsl/writer.rs index 487ea40d8abf..8a7a71cdc23b 100644 --- a/third_party/rust/naga/src/back/wgsl/writer.rs +++ b/third_party/rust/naga/src/back/wgsl/writer.rs @@ -908,6 +908,7 @@ impl Writer { Statement::Loop { ref body, ref continuing, + break_if, } => { write!(self.out, "{}", level)?; writeln!(self.out, "loop {{")?; @@ -917,11 +918,26 @@ impl Writer { self.write_stmt(module, sta, func_ctx, l2)?; } - if !continuing.is_empty() { + // The continuing is optional so we don't need to write it if + // it is empty, but the `break if` counts as a continuing statement + // so even if `continuing` is empty we must generate it if a + // `break if` exists + if !continuing.is_empty() || break_if.is_some() { writeln!(self.out, "{}continuing {{", l2)?; for sta in continuing.iter() { self.write_stmt(module, sta, func_ctx, l2.next())?; } + + // The `break if` is always the last + // statement of the `continuing` block + if let Some(condition) = break_if { + // The trailing space is important + write!(self.out, "{}break if ", l2.next())?; + self.write_expr(module, condition, func_ctx)?; + // Close the `break if` statement + writeln!(self.out, ";")?; + } + writeln!(self.out, "{}}}", l2)?; } @@ -1859,6 +1875,8 @@ impl Writer { Ok(()) } + // See https://github.com/rust-lang/rust-clippy/issues/4979. + #[allow(clippy::missing_const_for_fn)] pub fn finish(self) -> W { self.out } diff --git a/third_party/rust/naga/src/front/glsl/builtins.rs b/third_party/rust/naga/src/front/glsl/builtins.rs index 287f66d6f1b8..ea75ba2a2409 100644 --- a/third_party/rust/naga/src/front/glsl/builtins.rs +++ b/third_party/rust/naga/src/front/glsl/builtins.rs @@ -1556,7 +1556,7 @@ fn inject_common_builtin( }; declaration.overloads.push(module.add_builtin( vec![ty(), ty(), base_ty()], - MacroCall::MathFunction(MathFunction::SmoothStep), + MacroCall::SmoothStep { splatted: size }, )) } } @@ -1604,6 +1604,12 @@ pub enum MacroCall { BitCast(Sk), Derivate(DerivativeAxis), Barrier, + /// SmoothStep needs a separate variant because it might need it's inputs + /// to be splatted depending on the overload + SmoothStep { + /// The size of the splat operation if some + splatted: Option, + }, } impl MacroCall { @@ -2072,6 +2078,22 @@ impl MacroCall { body.push(crate::Statement::Barrier(crate::Barrier::all()), meta); return Ok(None); } + MacroCall::SmoothStep { splatted } => { + ctx.implicit_splat(parser, &mut args[0], meta, splatted)?; + ctx.implicit_splat(parser, &mut args[1], meta, splatted)?; + + ctx.add_expression( + Expression::Math { + fun: MathFunction::SmoothStep, + arg: args[0], + arg1: args.get(1).copied(), + arg2: args.get(2).copied(), + arg3: None, + }, + Span::default(), + body, + ) + } })) } } @@ -2237,20 +2259,26 @@ pub fn sampled_to_depth( meta: Span, errors: &mut Vec, ) { + // Get the a mutable type handle of the underlying image storage let ty = match ctx[image] { Expression::GlobalVariable(handle) => &mut module.global_variables.get_mut(handle).ty, Expression::FunctionArgument(i) => { + // Mark the function argument as carrying a depth texture ctx.parameters_info[i as usize].depth = true; + // NOTE: We need to later also change the parameter type &mut ctx.arguments[i as usize].ty } _ => { + // Only globals and function arguments are allowed to carry an image return errors.push(Error { kind: ErrorKind::SemanticError("Not a valid texture expression".into()), meta, - }) + }); } }; + match module.types[*ty].inner { + // Update the image class to depth in case it already isn't TypeInner::Image { class, dim, @@ -2270,6 +2298,7 @@ pub fn sampled_to_depth( ) } ImageClass::Depth { .. } => {} + // Other image classes aren't allowed to be transformed to depth _ => errors.push(Error { kind: ErrorKind::SemanticError("Not a texture".into()), meta, @@ -2280,6 +2309,15 @@ pub fn sampled_to_depth( meta, }), }; + + // Copy the handle to allow borrowing the `ctx` again + let ty = *ty; + + // If the image was passed trough a function argument we also need to change + // the corresponding parameter + if let Expression::FunctionArgument(i) = ctx[image] { + ctx.parameters[i as usize] = ty; + } } bitflags::bitflags! { diff --git a/third_party/rust/naga/src/front/glsl/context.rs b/third_party/rust/naga/src/front/glsl/context.rs index fe20df410177..13eb5d180add 100644 --- a/third_party/rust/naga/src/front/glsl/context.rs +++ b/third_party/rust/naga/src/front/glsl/context.rs @@ -617,11 +617,14 @@ impl Context { width: right_width, }, ) => { + let dimensions_ok = if op == BinaryOperator::Multiply { + left_columns == right_rows + } else { + left_columns == right_columns && left_rows == right_rows + }; + // Check that the two arguments have the same dimensions - if left_columns != right_columns - || left_rows != right_rows - || left_width != right_width - { + if !dimensions_ok || left_width != right_width { parser.errors.push(Error { kind: ErrorKind::SemanticError( format!( @@ -1174,6 +1177,10 @@ impl Context { self.emit_end(&mut reject_body); } } + } else { + // Technically there's nothing to flush but later we will need to + // add some expressions that must not be emitted. + self.emit_end(body) } // We need to get the type of the resulting expression to create the local, diff --git a/third_party/rust/naga/src/front/glsl/functions.rs b/third_party/rust/naga/src/front/glsl/functions.rs index 325f4d8abecb..2c0445bdf252 100644 --- a/third_party/rust/naga/src/front/glsl/functions.rs +++ b/third_party/rust/naga/src/front/glsl/functions.rs @@ -671,6 +671,13 @@ impl Parser { let overload_param_ty = &self.module.types[*overload_parameter].inner; let call_arg_ty = self.resolve_type(ctx, call_argument.0, call_argument.1)?; + log::trace!( + "Testing parameter {}\n\tOverload = {:?}\n\tCall = {:?}", + i, + overload_param_ty, + call_arg_ty + ); + // Storage images cannot be directly compared since while the access is part of the // type in naga's IR, in glsl they are a qualifier and don't enter in the match as // long as the access needed is satisfied. diff --git a/third_party/rust/naga/src/front/glsl/offset.rs b/third_party/rust/naga/src/front/glsl/offset.rs index 9c89b5d2c337..d7950489cb84 100644 --- a/third_party/rust/naga/src/front/glsl/offset.rs +++ b/third_party/rust/naga/src/front/glsl/offset.rs @@ -16,7 +16,7 @@ use super::{ error::{Error, ErrorKind}, Span, }; -use crate::{front::align_up, Arena, Constant, Handle, Type, TypeInner, UniqueArena}; +use crate::{proc::Alignment, Arena, Constant, Handle, Type, TypeInner, UniqueArena}; /// Struct with information needed for defining a struct member. /// @@ -28,7 +28,7 @@ pub struct TypeAlignSpan { /// with a different stride set. pub ty: Handle, /// The alignment required by the type. - pub align: u32, + pub align: Alignment, /// The size of the type. pub span: u32, } @@ -54,15 +54,15 @@ pub fn calculate_offset( let (align, span) = match types[ty].inner { // 1. If the member is a scalar consuming N basic machine units, // the base alignment is N. - TypeInner::Scalar { width, .. } => (width as u32, width as u32), + TypeInner::Scalar { width, .. } => (Alignment::from_width(width), width as u32), // 2. If the member is a two- or four-component vector with components // consuming N basic machine units, the base alignment is 2N or 4N, respectively. // 3. If the member is a three-component vector with components consuming N // basic machine units, the base alignment is 4N. - TypeInner::Vector { size, width, .. } => match size { - crate::VectorSize::Tri => (4 * width as u32, 3 * width as u32), - _ => (size as u32 * width as u32, size as u32 * width as u32), - }, + TypeInner::Vector { size, width, .. } => ( + Alignment::from(size) * Alignment::from_width(width), + size as u32 * width as u32, + ), // 4. If the member is an array of scalars or vectors, the base alignment and array // stride are set to match the base alignment of a single array element, according // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. @@ -71,14 +71,14 @@ pub fn calculate_offset( let info = calculate_offset(base, meta, layout, types, constants, errors); let name = types[ty].name.clone(); - let mut align = info.align; - let mut stride = (align).max(info.span); // See comment at the beginning of the function - if StructLayout::Std430 != layout { - stride = align_up(stride, 16); - align = align_up(align, 16); - } + let (align, stride) = if StructLayout::Std430 == layout { + (info.align, info.align.round_up(info.span)) + } else { + let align = info.align.max(Alignment::MIN_UNIFORM); + (align, align.round_up(info.span)) + }; let span = match size { crate::ArraySize::Constant(s) => { @@ -111,14 +111,11 @@ pub fn calculate_offset( rows, width, } => { - let mut align = match rows { - crate::VectorSize::Tri => (4 * width as u32), - _ => (rows as u32 * width as u32), - }; + let mut align = Alignment::from(rows) * Alignment::from_width(width); // See comment at the beginning of the function if StructLayout::Std430 != layout { - align = align_up(align, 16); + align = align.max(Alignment::MIN_UNIFORM); } // See comment on the error kind @@ -133,15 +130,16 @@ pub fn calculate_offset( } TypeInner::Struct { ref members, .. } => { let mut span = 0; - let mut align = 0; + let mut align = Alignment::ONE; let mut members = members.clone(); let name = types[ty].name.clone(); for member in members.iter_mut() { let info = calculate_offset(member.ty, meta, layout, types, constants, errors); - span = align_up(span, info.align); - align = align.max(info.align); + let member_alignment = info.align; + span = member_alignment.round_up(span); + align = member_alignment.max(align); member.ty = info.ty; member.offset = span; @@ -149,7 +147,7 @@ pub fn calculate_offset( span += info.span; } - span = align_up(span, align); + span = align.round_up(span); let ty_span = types.get_span(ty); ty = types.insert( @@ -167,7 +165,7 @@ pub fn calculate_offset( kind: ErrorKind::SemanticError("Invalid struct member type".into()), meta, }); - (1, 0) + (Alignment::ONE, 0) } }; diff --git a/third_party/rust/naga/src/front/glsl/parser/declarations.rs b/third_party/rust/naga/src/front/glsl/parser/declarations.rs index 18a6ceb2e3a9..a43f2fe77242 100644 --- a/third_party/rust/naga/src/front/glsl/parser/declarations.rs +++ b/third_party/rust/naga/src/front/glsl/parser/declarations.rs @@ -12,6 +12,7 @@ use crate::{ variables::{GlobalOrConstant, VarDeclaration}, Error, ErrorKind, Parser, Span, }, + proc::Alignment, AddressSpace, Block, Expression, FunctionResult, Handle, ScalarKind, Statement, StructMember, Type, TypeInner, }; @@ -570,7 +571,7 @@ impl<'source> ParsingContext<'source> { layout: StructLayout, ) -> Result { let mut span = 0; - let mut align = 0; + let mut align = Alignment::ONE; loop { // TODO: type_qualifier @@ -593,8 +594,9 @@ impl<'source> ParsingContext<'source> { &mut parser.errors, ); - span = crate::front::align_up(span, info.align); - align = align.max(info.align); + let member_alignment = info.align; + span = member_alignment.round_up(span); + align = member_alignment.max(align); members.push(StructMember { name: Some(name), @@ -610,7 +612,7 @@ impl<'source> ParsingContext<'source> { } } - span = crate::front::align_up(span, align); + span = align.round_up(span); Ok(span) } diff --git a/third_party/rust/naga/src/front/glsl/parser/functions.rs b/third_party/rust/naga/src/front/glsl/parser/functions.rs index 8a9487917beb..6cb9bf017bb4 100644 --- a/third_party/rust/naga/src/front/glsl/parser/functions.rs +++ b/third_party/rust/naga/src/front/glsl/parser/functions.rs @@ -177,6 +177,8 @@ impl<'source> ParsingContext<'source> { ctx.emit_restart(body); let mut cases = Vec::new(); + // Track if any default case is present in the switch statement. + let mut default_present = false; self.expect(parser, TokenValue::LeftBrace)?; loop { @@ -215,6 +217,7 @@ impl<'source> ParsingContext<'source> { } TokenValue::Default => { self.bump(parser)?; + default_present = true; crate::SwitchValue::Default } TokenValue::RightBrace => { @@ -273,6 +276,40 @@ impl<'source> ParsingContext<'source> { meta.subsume(end_meta); + // NOTE: do not unwrap here since a switch statement isn't required + // to have any cases. + if let Some(case) = cases.last_mut() { + // GLSL requires that the last case not be empty, so we check + // that here and produce an error otherwise (fall_trough must + // also be checked because `break`s count as statements but + // they aren't added to the body) + if case.body.is_empty() && case.fall_through { + parser.errors.push(Error { + kind: ErrorKind::SemanticError( + "last case/default label must be followed by statements".into(), + ), + meta, + }) + } + + // GLSL allows the last case to not have any `break` statement, + // this would mark it as fall trough but naga's IR requires that + // the last case must not be fall trough, so we mark need to mark + // the last case as not fall trough always. + case.fall_through = false; + } + + // Add an empty default case in case non was present, this is needed because + // naga's IR requires that all switch statements must have a default case but + // GLSL doesn't require that, so we might need to add an empty default case. + if !default_present { + cases.push(SwitchCase { + value: crate::SwitchValue::Default, + body: Block::new(), + fall_through: false, + }) + } + body.push(Statement::Switch { selector, cases }, meta); meta @@ -321,6 +358,7 @@ impl<'source> ParsingContext<'source> { Statement::Loop { body: loop_body, continuing: Block::new(), + break_if: None, }, meta, ); @@ -374,6 +412,7 @@ impl<'source> ParsingContext<'source> { Statement::Loop { body: loop_body, continuing: Block::new(), + break_if: None, }, meta, ); @@ -476,6 +515,7 @@ impl<'source> ParsingContext<'source> { Statement::Loop { body: block, continuing, + break_if: None, }, meta, ); diff --git a/third_party/rust/naga/src/front/mod.rs b/third_party/rust/naga/src/front/mod.rs index fb6bef75e63e..bfc99f1f9669 100644 --- a/third_party/rust/naga/src/front/mod.rs +++ b/third_party/rust/naga/src/front/mod.rs @@ -133,20 +133,3 @@ impl ops::Index> for Typifier { &self.resolutions[handle.index()] } } - -/// Helper function used for aligning `value` to the next multiple of `align` -/// -/// # Notes: -/// - `align` must be a power of two. -/// - The returned value will be greater or equal to `value`. -/// # Examples: -/// ```ignore -/// assert_eq!(0, align_up(0, 16)); -/// assert_eq!(16, align_up(1, 16)); -/// assert_eq!(16, align_up(16, 16)); -/// assert_eq!(334, align_up(333, 2)); -/// assert_eq!(384, align_up(257, 128)); -/// ``` -pub const fn align_up(value: u32, align: u32) -> u32 { - ((value.wrapping_sub(1)) & !(align - 1)).wrapping_add(align) -} diff --git a/third_party/rust/naga/src/front/spv/function.rs b/third_party/rust/naga/src/front/spv/function.rs index c57695601f31..956f93cf9c55 100644 --- a/third_party/rust/naga/src/front/spv/function.rs +++ b/third_party/rust/naga/src/front/spv/function.rs @@ -556,7 +556,11 @@ impl<'function> BlockContext<'function> { let continuing = lower_impl(blocks, bodies, continuing); block.push( - crate::Statement::Loop { body, continuing }, + crate::Statement::Loop { + body, + continuing, + break_if: None, + }, crate::Span::default(), ) } diff --git a/third_party/rust/naga/src/front/spv/mod.rs b/third_party/rust/naga/src/front/spv/mod.rs index cf4f50d1d765..9f5d710e1b2c 100644 --- a/third_party/rust/naga/src/front/spv/mod.rs +++ b/third_party/rust/naga/src/front/spv/mod.rs @@ -39,7 +39,7 @@ use function::*; use crate::{ arena::{Arena, Handle, UniqueArena}, - proc::Layouter, + proc::{Alignment, Layouter}, FastHashMap, FastHashSet, }; @@ -71,6 +71,7 @@ pub const SUPPORTED_CAPABILITIES: &[spirv::Capability] = &[ spirv::Capability::Float16, spirv::Capability::Float64, spirv::Capability::Geometry, + spirv::Capability::MultiView, // tricky ones spirv::Capability::UniformBufferArrayDynamicIndexing, spirv::Capability::StorageBufferArrayDynamicIndexing, @@ -305,7 +306,8 @@ struct LookupExpression { /// /// Note that, while a SPIR-V result id can be used in any block dominated /// by its definition, a Naga `Expression` is only in scope for the rest of - /// its subtree. `Parser::get_expr_handle` takes care of + /// its subtree. `Parser::get_expr_handle` takes care of spilling the result + /// to a `LocalVariable` which can then be used anywhere. handle: Handle, /// The SPIR-V type of this result. @@ -2391,6 +2393,34 @@ impl> Parser { }, ); } + Op::BitReverse | Op::BitCount => { + inst.expect(4)?; + + let result_type_id = self.next()?; + let result_id = self.next()?; + let base_id = self.next()?; + let base_lexp = self.lookup_expression.lookup(base_id)?; + let base_handle = get_expr_handle!(base_id, base_lexp); + let expr = crate::Expression::Math { + fun: match inst.op { + Op::BitReverse => crate::MathFunction::ReverseBits, + Op::BitCount => crate::MathFunction::CountOneBits, + _ => unreachable!(), + }, + arg: base_handle, + arg1: None, + arg2: None, + arg3: None, + }; + self.lookup_expression.insert( + result_id, + LookupExpression { + handle: ctx.expressions.append(expr, span), + type_id: result_type_id, + block_id, + }, + ); + } Op::OuterProduct => { inst.expect(5)?; @@ -3535,6 +3565,7 @@ impl> Parser { S::Loop { ref mut body, ref mut continuing, + break_if: _, } => { self.patch_statements(body, expressions, fun_parameter_sampling)?; self.patch_statements(continuing, expressions, fun_parameter_sampling)?; @@ -4347,7 +4378,7 @@ impl> Parser { let mut member_lookups = Vec::with_capacity(members.capacity()); let mut storage_access = crate::StorageAccess::empty(); let mut span = 0; - let mut alignment = 1; + let mut alignment = Alignment::ONE; for i in 0..u32::from(inst.wc) - 2 { let type_id = self.next()?; let ty = self.lookup_type.lookup(type_id)?.handle; @@ -4363,8 +4394,9 @@ impl> Parser { row_major: decor.matrix_major == Some(Majority::Row), }); - span = crate::front::align_up(span, self.layouter[ty].alignment.get()); - alignment = self.layouter[ty].alignment.get().max(alignment); + let member_alignment = self.layouter[ty].alignment; + span = member_alignment.round_up(span); + alignment = member_alignment.max(alignment); let mut binding = decor.io_binding().ok(); if let Some(offset) = decor.offset { @@ -4382,8 +4414,7 @@ impl> Parser { } = *inner { if let Some(stride) = decor.matrix_stride { - let aligned_rows = if rows > crate::VectorSize::Bi { 4 } else { 2 }; - let expected_stride = aligned_rows * width as u32; + let expected_stride = Alignment::from(rows) * width as u32; if stride.get() != expected_stride { return Err(Error::UnsupportedMatrixStride { stride: stride.get(), @@ -4406,7 +4437,7 @@ impl> Parser { }); } - span = crate::front::align_up(span, alignment); + span = alignment.round_up(span); let inner = crate::TypeInner::Struct { span, members }; diff --git a/third_party/rust/naga/src/front/wgsl/construction.rs b/third_party/rust/naga/src/front/wgsl/construction.rs index ca43894e2b86..7039bb5dd8a7 100644 --- a/third_party/rust/naga/src/front/wgsl/construction.rs +++ b/third_party/rust/naga/src/front/wgsl/construction.rs @@ -196,9 +196,7 @@ fn parse_constructor_type<'a>( } (Token::Paren('<'), ConstructorType::PartialArray) => { lexer.expect_generic_paren('<')?; - let base = parser - .parse_type_decl(lexer, None, type_arena, const_arena)? - .0; + let base = parser.parse_type_decl(lexer, None, type_arena, const_arena)?; let size = if lexer.skip(Token::Separator(',')) { let const_handle = parser.parse_const_expression(lexer, type_arena, const_arena)?; ArraySize::Constant(const_handle) diff --git a/third_party/rust/naga/src/front/wgsl/lexer.rs b/third_party/rust/naga/src/front/wgsl/lexer.rs index 212e41f2bf67..1d019e94fedf 100644 --- a/third_party/rust/naga/src/front/wgsl/lexer.rs +++ b/third_party/rust/naga/src/front/wgsl/lexer.rs @@ -1,269 +1,10 @@ -use super::{conv, Error, ExpectedToken, NumberType, Span, Token, TokenSpan}; +use super::{conv, number::consume_number, Error, ExpectedToken, Span, Token, TokenSpan}; fn consume_any(input: &str, what: impl Fn(char) -> bool) -> (&str, &str) { let pos = input.find(|c| !what(c)).unwrap_or(input.len()); input.split_at(pos) } -/// Tries to skip a given prefix in the input string. -/// Returns whether the prefix was present and could therefore be skipped, -/// the remaining str and the number of *bytes* skipped. -pub fn try_skip_prefix<'a, 'b>(input: &'a str, prefix: &'b str) -> (bool, &'a str, usize) { - if let Some(rem) = input.strip_prefix(prefix) { - (true, rem, prefix.len()) - } else { - (false, input, 0) - } -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum NLDigitState { - Nothing, - LeadingZero, - DigitBeforeDot, - OnlyDot, - DigitsThenDot, - DigitAfterDot, - Exponent, - SignAfterExponent, - DigitAfterExponent, -} - -struct NumberLexerState { - _minus: bool, - hex: bool, - leading_zeros: usize, - digit_state: NLDigitState, - uint_suffix: bool, -} - -impl NumberLexerState { - // TODO: add proper error reporting, possibly through try_into_token function returning Result - - pub fn _is_valid_number(&self) -> bool { - match *self { - Self { - _minus: false, // No negative zero for integers. - hex, - leading_zeros, - digit_state: NLDigitState::LeadingZero, - .. - } => hex || leading_zeros == 1, // No leading zeros allowed in non-hex integers, "0" is always allowed. - Self { - _minus: minus, - hex, - leading_zeros, - digit_state: NLDigitState::DigitBeforeDot, - uint_suffix, - } => { - (hex || leading_zeros == 0) // No leading zeros allowed in non-hex integers. - // In this state the number has non-zero digits, - // i.e. it is not just "0". - && (minus ^ uint_suffix) // Either a negative number, or and unsigned integer, not both. - } - _ => self.is_float(), - } - } - - pub fn is_float(&self) -> bool { - !self.uint_suffix - && (self.digit_state == NLDigitState::DigitsThenDot - || self.digit_state == NLDigitState::DigitAfterDot - || self.digit_state == NLDigitState::DigitAfterExponent) - } -} - -fn consume_number(input: &str) -> (Token, &str) { - let (minus, working_substr, minus_offset) = try_skip_prefix(input, "-"); - - let (hex, working_substr, hex_offset) = try_skip_prefix(working_substr, "0x"); - - let mut state = NumberLexerState { - _minus: minus, - hex, - leading_zeros: 0, - digit_state: NLDigitState::Nothing, - uint_suffix: false, - }; - - let mut what = |c| { - match state { - NumberLexerState { - uint_suffix: true, .. - } => return false, // Scanning is done once we've reached a type suffix. - NumberLexerState { - hex, - digit_state: NLDigitState::Nothing, - .. - } => match c { - '0' => { - state.digit_state = NLDigitState::LeadingZero; - state.leading_zeros += 1; - } - '1'..='9' => { - state.digit_state = NLDigitState::DigitBeforeDot; - } - 'a'..='f' | 'A'..='F' if hex => { - state.digit_state = NLDigitState::DigitBeforeDot; - } - '.' => { - state.digit_state = NLDigitState::OnlyDot; - } - _ => return false, - }, - - NumberLexerState { - hex, - digit_state: NLDigitState::LeadingZero, - .. - } => match c { - '0' => { - // We stay in NLDigitState::LeadingZero. - state.leading_zeros += 1; - } - '1'..='9' => { - state.digit_state = NLDigitState::DigitBeforeDot; - } - 'a'..='f' | 'A'..='F' if hex => { - state.digit_state = NLDigitState::DigitBeforeDot; - } - '.' => { - state.digit_state = NLDigitState::DigitsThenDot; - } - 'e' | 'E' if !hex => { - state.digit_state = NLDigitState::Exponent; - } - 'p' | 'P' if hex => { - state.digit_state = NLDigitState::Exponent; - } - 'u' => { - // We stay in NLDigitState::LeadingZero. - state.uint_suffix = true; - } - _ => return false, - }, - - NumberLexerState { - hex, - digit_state: NLDigitState::DigitBeforeDot, - .. - } => match c { - '0'..='9' => { - // We stay in NLDigitState::DigitBeforeDot. - } - 'a'..='f' | 'A'..='F' if hex => { - // We stay in NLDigitState::DigitBeforeDot. - } - '.' => { - state.digit_state = NLDigitState::DigitsThenDot; - } - 'e' | 'E' if !hex => { - state.digit_state = NLDigitState::Exponent; - } - 'p' | 'P' if hex => { - state.digit_state = NLDigitState::Exponent; - } - 'u' => { - // We stay in NLDigitState::DigitBeforeDot. - state.uint_suffix = true; - } - _ => return false, - }, - - NumberLexerState { - hex, - digit_state: NLDigitState::OnlyDot, - .. - } => match c { - '0'..='9' => { - state.digit_state = NLDigitState::DigitAfterDot; - } - 'a'..='f' | 'A'..='F' if hex => { - state.digit_state = NLDigitState::DigitAfterDot; - } - _ => return false, - }, - - NumberLexerState { - hex, - digit_state: NLDigitState::DigitsThenDot | NLDigitState::DigitAfterDot, - .. - } => match c { - '0'..='9' => { - state.digit_state = NLDigitState::DigitAfterDot; - } - 'a'..='f' | 'A'..='F' if hex => { - state.digit_state = NLDigitState::DigitAfterDot; - } - 'e' | 'E' if !hex => { - state.digit_state = NLDigitState::Exponent; - } - 'p' | 'P' if hex => { - state.digit_state = NLDigitState::Exponent; - } - _ => return false, - }, - - NumberLexerState { - digit_state: NLDigitState::Exponent, - .. - } => match c { - '0'..='9' => { - state.digit_state = NLDigitState::DigitAfterExponent; - } - '-' | '+' => { - state.digit_state = NLDigitState::SignAfterExponent; - } - _ => return false, - }, - - NumberLexerState { - digit_state: NLDigitState::SignAfterExponent | NLDigitState::DigitAfterExponent, - .. - } => match c { - '0'..='9' => { - state.digit_state = NLDigitState::DigitAfterExponent; - } - _ => return false, - }, - } - - // No match branch has rejected this yet, so we are still in a number literal - true - }; - - let pos = working_substr - .find(|c| !what(c)) - .unwrap_or(working_substr.len()); - let (value, rest) = input.split_at(pos + minus_offset + hex_offset); - - // NOTE: This code can use string slicing, - // because number literals are exclusively ASCII. - // This means all relevant characters fit into one byte - // and using string slicing (which slices UTF-8 bytes) works for us. - - // TODO: A syntax error can already be recognized here, possibly report it at this stage. - - // Return possibly knowably incorrect (given !state.is_valid_number()) token for now. - ( - Token::Number { - value: if state.uint_suffix { - &value[..value.len() - 1] - } else { - value - }, - ty: if state.uint_suffix { - NumberType::Uint - } else if state.is_float() { - NumberType::Float - } else { - NumberType::Sint - }, - }, - rest, - ) -} - fn consume_token(input: &str, generic: bool) -> (Token<'_>, &str) { let mut chars = input.chars(); let cur = match chars.next() { @@ -632,6 +373,9 @@ impl<'a> Lexer<'a> { } } +#[cfg(test)] +use super::{number::Number, NumberError}; + #[cfg(test)] fn sub_test(source: &str, expected_tokens: &[Token]) { let mut lex = Lexer::new(source); @@ -641,41 +385,195 @@ fn sub_test(source: &str, expected_tokens: &[Token]) { assert_eq!(lex.next().0, Token::End); } +#[test] +fn test_numbers() { + // WGSL spec examples // + + // decimal integer + sub_test( + "0x123 0X123u 1u 123 0 0i 0x3f", + &[ + Token::Number(Ok(Number::I32(291))), + Token::Number(Ok(Number::U32(291))), + Token::Number(Ok(Number::U32(1))), + Token::Number(Ok(Number::I32(123))), + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(0))), + Token::Number(Ok(Number::I32(63))), + ], + ); + // decimal floating point + sub_test( + "0.e+4f 01. .01 12.34 .0f 0h 1e-3 0xa.fp+2 0x1P+4f 0X.3 0x3p+2h 0X1.fp-4 0x3.2p+2h", + &[ + Token::Number(Ok(Number::F32(0.))), + Token::Number(Ok(Number::F32(1.))), + Token::Number(Ok(Number::F32(0.01))), + Token::Number(Ok(Number::F32(12.34))), + Token::Number(Ok(Number::F32(0.))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::F32(0.001))), + Token::Number(Ok(Number::F32(43.75))), + Token::Number(Ok(Number::F32(16.))), + Token::Number(Ok(Number::F32(0.1875))), + Token::Number(Err(NumberError::UnimplementedF16)), + Token::Number(Ok(Number::F32(0.12109375))), + Token::Number(Err(NumberError::UnimplementedF16)), + ], + ); + + // MIN / MAX // + + // min / max decimal signed integer + sub_test( + "-2147483648i 2147483647i -2147483649i 2147483648i", + &[ + Token::Number(Ok(Number::I32(i32::MIN))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max decimal unsigned integer + sub_test( + "0u 4294967295u -1u 4294967296u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + // min / max hexadecimal signed integer + sub_test( + "-0x80000000i 0x7FFFFFFFi -0x80000001i 0x80000000i", + &[ + Token::Number(Ok(Number::I32(i32::MIN))), + Token::Number(Ok(Number::I32(i32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + // min / max hexadecimal unsigned integer + sub_test( + "0x0u 0xFFFFFFFFu -0x1u 0x100000000u", + &[ + Token::Number(Ok(Number::U32(u32::MIN))), + Token::Number(Ok(Number::U32(u32::MAX))), + Token::Number(Err(NumberError::NotRepresentable)), + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); + + /// ≈ 2^-126 * 2^−23 (= 2^−149) + const SMALLEST_POSITIVE_SUBNORMAL_F32: f32 = 1e-45; + /// ≈ 2^-126 * (1 − 2^−23) + const LARGEST_SUBNORMAL_F32: f32 = 1.1754942e-38; + /// ≈ 2^-126 + const SMALLEST_POSITIVE_NORMAL_F32: f32 = f32::MIN_POSITIVE; + /// ≈ 1 − 2^−24 + const LARGEST_F32_LESS_THAN_ONE: f32 = 0.99999994; + /// ≈ 1 + 2^−23 + const SMALLEST_F32_LARGER_THAN_ONE: f32 = 1.0000001; + /// ≈ -(2^127 * (2 − 2^−23)) + const SMALLEST_NORMAL_F32: f32 = f32::MIN; + /// ≈ 2^127 * (2 − 2^−23) + const LARGEST_NORMAL_F32: f32 = f32::MAX; + + // decimal floating point + sub_test( + "1e-45f 1.1754942e-38f 1.17549435e-38f 0.99999994f 1.0000001f -3.40282347e+38f 3.40282347e+38f", + &[ + Token::Number(Ok(Number::F32( + SMALLEST_POSITIVE_SUBNORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_SUBNORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_POSITIVE_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_F32_LESS_THAN_ONE, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_F32_LARGER_THAN_ONE, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_NORMAL_F32, + ))), + ], + ); + sub_test( + "-3.40282367e+38f 3.40282367e+38f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // ≈ -2^128 + Token::Number(Err(NumberError::NotRepresentable)), // ≈ 2^128 + ], + ); + + // hexadecimal floating point + sub_test( + "0x1p-149f 0x7FFFFFp-149f 0x1p-126f 0xFFFFFFp-24f 0x800001p-23f -0xFFFFFFp+104f 0xFFFFFFp+104f", + &[ + Token::Number(Ok(Number::F32( + SMALLEST_POSITIVE_SUBNORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_SUBNORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_POSITIVE_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_F32_LESS_THAN_ONE, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_F32_LARGER_THAN_ONE, + ))), + Token::Number(Ok(Number::F32( + SMALLEST_NORMAL_F32, + ))), + Token::Number(Ok(Number::F32( + LARGEST_NORMAL_F32, + ))), + ], + ); + sub_test( + "-0x1p128f 0x1p128f 0x1.000001p0f", + &[ + Token::Number(Err(NumberError::NotRepresentable)), // = -2^128 + Token::Number(Err(NumberError::NotRepresentable)), // = 2^128 + Token::Number(Err(NumberError::NotRepresentable)), + ], + ); +} + #[test] fn test_tokens() { sub_test("id123_OK", &[Token::Word("id123_OK")]); sub_test( "92No", - &[ - Token::Number { - value: "92", - ty: NumberType::Sint, - }, - Token::Word("No"), - ], + &[Token::Number(Ok(Number::I32(92))), Token::Word("No")], ); sub_test( "2u3o", &[ - Token::Number { - value: "2", - ty: NumberType::Uint, - }, - Token::Number { - value: "3", - ty: NumberType::Sint, - }, + Token::Number(Ok(Number::U32(2))), + Token::Number(Ok(Number::I32(3))), Token::Word("o"), ], ); sub_test( "2.4f44po", &[ - Token::Number { - value: "2.4", - ty: NumberType::Float, - }, - Token::Word("f44po"), + Token::Number(Ok(Number::F32(2.4))), + Token::Number(Ok(Number::I32(44))), + Token::Word("po"), ], ); sub_test( @@ -715,10 +613,7 @@ fn test_variable_decl() { Token::Attribute, Token::Word("group"), Token::Paren('('), - Token::Number { - value: "0", - ty: NumberType::Sint, - }, + Token::Number(Ok(Number::I32(0))), Token::Paren(')'), Token::Word("var"), Token::Paren('<'), diff --git a/third_party/rust/naga/src/front/wgsl/mod.rs b/third_party/rust/naga/src/front/wgsl/mod.rs index 7379b51fd6b3..2f68534cf275 100644 --- a/third_party/rust/naga/src/front/wgsl/mod.rs +++ b/third_party/rust/naga/src/front/wgsl/mod.rs @@ -7,7 +7,7 @@ Frontend for [WGSL][wgsl] (WebGPU Shading Language). mod construction; mod conv; mod lexer; -mod number_literals; +mod number; #[cfg(test)] mod tests; @@ -16,31 +16,24 @@ use crate::{ proc::{ ensure_block_returns, Alignment, Layouter, ResolveContext, ResolveError, TypeResolution, }, + span::SourceLocation, span::Span as NagaSpan, - Bytes, ConstantInner, FastHashMap, ScalarValue, + ConstantInner, FastHashMap, ScalarValue, }; -use self::{ - lexer::Lexer, - number_literals::{ - get_f32_literal, get_i32_literal, get_u32_literal, parse_generic_non_negative_int_literal, - parse_non_negative_sint_literal, - }, -}; +use self::{lexer::Lexer, number::Number}; use codespan_reporting::{ diagnostic::{Diagnostic, Label}, - files::{Files, SimpleFile}, + files::SimpleFile, term::{ self, termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}, }, }; -use hexf_parse::ParseHexfError; use std::{ borrow::Cow, convert::TryFrom, io::{self, Write}, - num::{NonZeroU32, ParseFloatError, ParseIntError}, ops, }; use thiserror::Error; @@ -48,19 +41,12 @@ use thiserror::Error; type Span = ops::Range; type TokenSpan<'a> = (Token<'a>, Span); -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum NumberType { - Sint, - Uint, - Float, -} - #[derive(Copy, Clone, Debug, PartialEq)] pub enum Token<'a> { Separator(char), Paren(char), Attribute, - Number { value: &'a str, ty: NumberType }, + Number(Result), Word(&'a str), Operation(char), LogicalOperation(char), @@ -74,14 +60,18 @@ pub enum Token<'a> { End, } +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum NumberType { + I32, + U32, + F32, +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum ExpectedToken<'a> { Token(Token<'a>), Identifier, - Number { - ty: Option, - width: Option, - }, + Number(NumberType), Integer, Constant, /// Expected: constant, parenthesized expression, identifier @@ -100,36 +90,25 @@ pub enum ExpectedToken<'a> { GlobalItem, } -#[derive(Clone, Debug, Error)] -pub enum BadIntError { - #[error(transparent)] - ParseIntError(#[from] ParseIntError), - #[error("non-hex negative zero integer literals are not allowed")] - NegativeZero, - #[error("leading zeros for non-hex integer literals are not allowed")] - LeadingZeros, -} - -#[derive(Clone, Debug, Error)] -pub enum BadFloatError { - #[error(transparent)] - ParseFloatError(#[from] ParseFloatError), - #[error(transparent)] - ParseHexfError(#[from] ParseHexfError), +#[derive(Clone, Copy, Debug, Error, PartialEq)] +pub enum NumberError { + #[error("invalid numeric literal format")] + Invalid, + #[error("numeric literal not representable by target type")] + NotRepresentable, + #[error("unimplemented f16 type")] + UnimplementedF16, } #[derive(Clone, Debug)] pub enum Error<'a> { Unexpected(TokenSpan<'a>, ExpectedToken<'a>), UnexpectedComponents(Span), - BadU32(Span, BadIntError), - BadI32(Span, BadIntError), + BadNumber(Span, NumberError), /// A negative signed integer literal where both signed and unsigned, /// but only non-negative literals are allowed. NegativeInt(Span), - BadFloat(Span, BadFloatError), BadU32Constant(Span), - BadScalarWidth(Span, Bytes), BadMatrixScalarKind(Span, crate::ScalarKind, u8), BadAccessor(Span), BadTexture(Span), @@ -146,7 +125,9 @@ pub enum Error<'a> { BadIncrDecrReferenceType(Span), InvalidResolve(ResolveError), InvalidForInitializer(Span), - InvalidGatherComponent(Span, i32), + /// A break if appeared outside of a continuing block + InvalidBreakIf(Span), + InvalidGatherComponent(Span, u32), InvalidConstructorComponentType(Span, i32), InvalidIdentifierUnderscore(Span), ReservedIdentifierPrefix(Span), @@ -160,7 +141,9 @@ pub enum Error<'a> { UnknownType(Span), UnknownStorageFormat(Span), UnknownConservativeDepth(Span), - ZeroSizeOrAlign(Span), + SizeAttributeTooLow(Span, u32), + AlignAttributeTooLow(Span, Alignment), + NonPowerOfTwoAlignAttribute(Span), InconsistentBinding(Span), UnknownLocalFunction(Span), TypeNotConstructible(Span), @@ -191,9 +174,7 @@ impl<'a> Error<'a> { Token::Separator(c) => format!("'{}'", c), Token::Paren(c) => format!("'{}'", c), Token::Attribute => "@".to_string(), - Token::Number { value, .. } => { - format!("number ({})", value) - } + Token::Number(_) => "number".to_string(), Token::Word(s) => s.to_string(), Token::Operation(c) => format!("operation ('{}')", c), Token::LogicalOperation(c) => format!("logical operation ('{}')", c), @@ -209,25 +190,12 @@ impl<'a> Error<'a> { } } ExpectedToken::Identifier => "identifier".to_string(), - ExpectedToken::Number { ty, width } => { - let literal_ty_str = match ty { - Some(NumberType::Float) => "floating-point", - Some(NumberType::Uint) => "unsigned integer", - Some(NumberType::Sint) => "signed integer", - None => "arbitrary number", - }; - if let Some(width) = width { - format!( - "{} literal of {}-bit width", - literal_ty_str, - width as u32 * 8, - ) - } else { - format!( - "{} literal of arbitrary width", - literal_ty_str, - ) - } + ExpectedToken::Number(ty) => { + match ty { + NumberType::I32 => "32-bit signed integer literal", + NumberType::U32 => "32-bit unsigned integer literal", + NumberType::F32 => "32-bit floating-point literal", + }.to_string() }, ExpectedToken::Integer => "unsigned/signed integer literal".to_string(), ExpectedToken::Constant => "constant".to_string(), @@ -257,21 +225,13 @@ impl<'a> Error<'a> { labels: vec![(bad_span.clone(), "unexpected components".into())], notes: vec![], }, - Error::BadU32(ref bad_span, ref err) => ParseError { + Error::BadNumber(ref bad_span, ref err) => ParseError { message: format!( - "expected unsigned integer literal, found `{}`", - &source[bad_span.clone()], + "{}: `{}`", + err,&source[bad_span.clone()], ), - labels: vec![(bad_span.clone(), "expected unsigned integer".into())], - notes: vec![err.to_string()], - }, - Error::BadI32(ref bad_span, ref err) => ParseError { - message: format!( - "expected integer literal, found `{}`", - &source[bad_span.clone()], - ), - labels: vec![(bad_span.clone(), "expected signed integer".into())], - notes: vec![err.to_string()], + labels: vec![(bad_span.clone(), err.to_string().into())], + notes: vec![], }, Error::NegativeInt(ref bad_span) => ParseError { message: format!( @@ -281,14 +241,6 @@ impl<'a> Error<'a> { labels: vec![(bad_span.clone(), "expected non-negative integer".into())], notes: vec![], }, - Error::BadFloat(ref bad_span, ref err) => ParseError { - message: format!( - "expected floating-point literal, found `{}`", - &source[bad_span.clone()], - ), - labels: vec![(bad_span.clone(), "expected floating-point literal".into())], - notes: vec![err.to_string()], - }, Error::BadU32Constant(ref bad_span) => ParseError { message: format!( "expected unsigned integer constant expression, found `{}`", @@ -297,11 +249,6 @@ impl<'a> Error<'a> { labels: vec![(bad_span.clone(), "expected unsigned integer".into())], notes: vec![], }, - Error::BadScalarWidth(ref bad_span, width) => ParseError { - message: format!("invalid width of `{}` bits for literal", width as u32 * 8,), - labels: vec![(bad_span.clone(), "invalid width".into())], - notes: vec!["the only valid width is 32 for now".to_string()], - }, Error::BadMatrixScalarKind( ref span, kind, @@ -362,6 +309,11 @@ impl<'a> Error<'a> { labels: vec![(bad_span.clone(), "not an assignment or function call".into())], notes: vec![], }, + Error::InvalidBreakIf(ref bad_span) => ParseError { + message: "A break if is only allowed in a continuing block".to_string(), + labels: vec![(bad_span.clone(), "not in a continuing block".into())], + notes: vec![], + }, Error::InvalidGatherComponent(ref bad_span, component) => ParseError { message: format!("textureGather component {} doesn't exist, must be 0, 1, 2, or 3", component), labels: vec![(bad_span.clone(), "invalid component".into())], @@ -422,9 +374,19 @@ impl<'a> Error<'a> { labels: vec![(bad_span.clone(), "unknown type".into())], notes: vec![], }, - Error::ZeroSizeOrAlign(ref bad_span) => ParseError { - message: "struct member size or alignment must not be 0".to_string(), - labels: vec![(bad_span.clone(), "struct member size or alignment must not be 0".into())], + Error::SizeAttributeTooLow(ref bad_span, min_size) => ParseError { + message: format!("struct member size must be at least {}", min_size), + labels: vec![(bad_span.clone(), format!("must be at least {}", min_size).into())], + notes: vec![], + }, + Error::AlignAttributeTooLow(ref bad_span, min_align) => ParseError { + message: format!("struct member alignment must be at least {}", min_align), + labels: vec![(bad_span.clone(), format!("must be at least {}", min_align).into())], + notes: vec![], + }, + Error::NonPowerOfTwoAlignAttribute(ref bad_span) => ParseError { + message: "struct member alignment must be a power of 2".to_string(), + labels: vec![(bad_span.clone(), "must be a power of 2".into())], notes: vec![], }, Error::InconsistentBinding(ref span) => ParseError { @@ -1193,8 +1155,8 @@ impl Composition { #[derive(Default)] struct TypeAttributes { // Although WGSL nas no type attributes at the moment, it had them in the past -// (`[[stride]]`) and may as well acquire some again in the future. -// Therefore, we are leaving the plumbing in for now. + // (`[[stride]]`) and may as well acquire some again in the future. + // Therefore, we are leaving the plumbing in for now. } #[derive(Clone, Debug, PartialEq)] @@ -1234,7 +1196,7 @@ impl BindingParser { match name { "location" => { lexer.expect(Token::Paren('('))?; - self.location = Some(parse_non_negative_sint_literal(lexer, 4)?); + self.location = Some(Parser::parse_non_negative_i32_literal(lexer)?); lexer.expect(Token::Paren(')'))?; } "builtin" => { @@ -1365,19 +1327,11 @@ impl ParseError { writer.into_string() } - /// Returns the 1-based line number and column of the first label in the - /// error message. - pub fn location(&self, source: &str) -> (usize, usize) { - let files = SimpleFile::new("wgsl", source); - match self.labels.get(0) { - Some(label) => { - let location = files - .location((), label.0.start) - .expect("invalid span location"); - (location.line_number, location.column_number) - } - None => (1, 1), - } + /// Returns a [`SourceLocation`] for the first label in the error message. + pub fn location(&self, source: &str) -> Option { + self.labels + .get(0) + .map(|label| NagaSpan::new(label.0.start as u32, label.0.end as u32).location(source)) } } @@ -1431,38 +1385,45 @@ impl Parser { lexer.span_from(initial) } - fn get_constant_inner<'a>( - word: &'a str, - ty: NumberType, - token_span: TokenSpan<'a>, - ) -> Result> { - let span = token_span.1; - - let value = match ty { - NumberType::Sint => { - get_i32_literal(word, span).map(|val| crate::ScalarValue::Sint(val as i64))? - } - NumberType::Uint => { - get_u32_literal(word, span).map(|val| crate::ScalarValue::Uint(val as u64))? - } - NumberType::Float => { - get_f32_literal(word, span).map(|val| crate::ScalarValue::Float(val as f64))? - } - }; - - Ok(crate::ConstantInner::Scalar { value, width: 4 }) - } - fn parse_switch_value<'a>(lexer: &mut Lexer<'a>, uint: bool) -> Result> { let token_span = lexer.next(); - let word = match token_span.0 { - Token::Number { value, .. } => value, - _ => return Err(Error::Unexpected(token_span, ExpectedToken::Integer)), - }; + match token_span.0 { + Token::Number(Ok(Number::U32(num))) if uint => Ok(num as i32), + Token::Number(Ok(Number::I32(num))) if !uint => Ok(num), + Token::Number(Err(e)) => Err(Error::BadNumber(token_span.1, e)), + _ => Err(Error::Unexpected(token_span, ExpectedToken::Integer)), + } + } - match uint { - true => get_u32_literal(word, token_span.1).map(|v| v as i32), - false => get_i32_literal(word, token_span.1), + /// Parse a non-negative signed integer literal. + /// This is for attributes like `size`, `location` and others. + fn parse_non_negative_i32_literal<'a>(lexer: &mut Lexer<'a>) -> Result> { + match lexer.next() { + (Token::Number(Ok(Number::I32(num))), span) => { + u32::try_from(num).map_err(|_| Error::NegativeInt(span)) + } + (Token::Number(Err(e)), span) => Err(Error::BadNumber(span, e)), + other => Err(Error::Unexpected( + other, + ExpectedToken::Number(NumberType::I32), + )), + } + } + + /// Parse a non-negative integer literal that may be either signed or unsigned. + /// This is for the `workgroup_size` attribute and array lengths. + /// Note: these values should be no larger than [`i32::MAX`], but this is not checked here. + fn parse_generic_non_negative_int_literal<'a>(lexer: &mut Lexer<'a>) -> Result> { + match lexer.next() { + (Token::Number(Ok(Number::I32(num))), span) => { + u32::try_from(num).map_err(|_| Error::NegativeInt(span)) + } + (Token::Number(Ok(Number::U32(num))), _) => Ok(num), + (Token::Number(Err(e)), span) => Err(Error::BadNumber(span, e)), + other => Err(Error::Unexpected( + other, + ExpectedToken::Number(NumberType::I32), + )), } } @@ -1615,7 +1576,7 @@ impl Parser { "bitcast" => { let _ = lexer.next(); lexer.expect_generic_paren('<')?; - let ((ty, _access), type_span) = lexer.capture_span(|lexer| { + let (ty, type_span) = lexer.capture_span(|lexer| { self.parse_type_decl(lexer, None, ctx.types, ctx.constants) })?; lexer.expect_generic_paren('>')?; @@ -2006,17 +1967,9 @@ impl Parser { "textureGather" => { let _ = lexer.next(); lexer.open_arguments()?; - let component = if let ( - Token::Number { - value, - ty: NumberType::Sint, - }, - span, - ) = lexer.peek() - { - let _ = lexer.next(); + let component = if let (Token::Number(..), span) = lexer.peek() { + let index = Self::parse_non_negative_i32_literal(lexer)?; lexer.expect(Token::Separator(','))?; - let index = get_i32_literal(value, span.clone())?; *crate::SwizzleComponent::XYZW .get(index as usize) .ok_or(Error::InvalidGatherComponent(span, index))? @@ -2219,9 +2172,22 @@ impl Parser { let inner = match first_token_span { (Token::Word("true"), _) => crate::ConstantInner::boolean(true), (Token::Word("false"), _) => crate::ConstantInner::boolean(false), - (Token::Number { value, ty }, _) => { - Self::get_constant_inner(value, ty, first_token_span)? - } + (Token::Number(num), _) => match num { + Ok(Number::I32(num)) => crate::ConstantInner::Scalar { + value: crate::ScalarValue::Sint(num as i64), + width: 4, + }, + Ok(Number::U32(num)) => crate::ConstantInner::Scalar { + value: crate::ScalarValue::Uint(num as u64), + width: 4, + }, + Ok(Number::F32(num)) => crate::ConstantInner::Scalar { + value: crate::ScalarValue::Float(num as f64), + width: 4, + }, + Ok(Number::AbstractInt(_) | Number::AbstractFloat(_)) => unreachable!(), + Err(e) => return Err(Error::BadNumber(first_token_span.1, e)), + }, (Token::Word(name), name_span) => { // look for an existing constant first for (handle, var) in const_arena.iter() { @@ -2312,10 +2278,8 @@ impl Parser { self.pop_scope(lexer); expr } - token @ (Token::Word("true" | "false") | Token::Number { .. }, _) => { - let _ = lexer.next(); - let const_handle = - self.parse_const_expression_impl(token, lexer, None, ctx.types, ctx.constants)?; + (Token::Word("true" | "false") | Token::Number(..), _) => { + let const_handle = self.parse_const_expression(lexer, ctx.types, ctx.constants)?; let span = NagaSpan::from(self.pop_scope(lexer)); TypedExpression::non_reference( ctx.interrupt_emitter(crate::Expression::Constant(const_handle), span), @@ -2781,11 +2745,11 @@ impl Parser { lexer: &mut Lexer<'a>, type_arena: &mut UniqueArena, const_arena: &mut Arena, - ) -> Result<(&'a str, Span, Handle, crate::StorageAccess), Error<'a>> { + ) -> Result<(&'a str, Span, Handle), Error<'a>> { let (name, name_span) = lexer.next_ident_with_span()?; lexer.expect(Token::Separator(':'))?; - let (ty, access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; - Ok((name, name_span, ty, access)) + let ty = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + Ok((name, name_span, ty)) } fn parse_variable_decl<'a>( @@ -2815,7 +2779,7 @@ impl Parser { } let name = lexer.next_ident()?; lexer.expect(Token::Separator(':'))?; - let (ty, _access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + let ty = self.parse_type_decl(lexer, None, type_arena, const_arena)?; let init = if lexer.skip(Token::Operation('=')) { let handle = self.parse_const_expression(lexer, type_arena, const_arena)?; @@ -2841,7 +2805,7 @@ impl Parser { const_arena: &mut Arena, ) -> Result<(Vec, u32), Error<'a>> { let mut offset = 0; - let mut alignment = Alignment::new(1).unwrap(); + let mut struct_alignment = Alignment::ONE; let mut members = Vec::new(); lexer.expect(Token::Paren('{'))?; @@ -2853,30 +2817,32 @@ impl Parser { ExpectedToken::Token(Token::Separator(',')), )); } - let (mut size, mut align) = (None, None); + let (mut size_attr, mut align_attr) = (None, None); self.push_scope(Scope::Attribute, lexer); let mut bind_parser = BindingParser::default(); while lexer.skip(Token::Attribute) { match lexer.next_ident_with_span()? { ("size", _) => { lexer.expect(Token::Paren('('))?; - let (value, span) = lexer - .capture_span(|lexer| parse_non_negative_sint_literal(lexer, 4))?; + let (value, span) = + lexer.capture_span(Self::parse_non_negative_i32_literal)?; lexer.expect(Token::Paren(')'))?; - size = Some(NonZeroU32::new(value).ok_or(Error::ZeroSizeOrAlign(span))?); + size_attr = Some((value, span)); } ("align", _) => { lexer.expect(Token::Paren('('))?; - let (value, span) = lexer - .capture_span(|lexer| parse_non_negative_sint_literal(lexer, 4))?; + let (value, span) = + lexer.capture_span(Self::parse_non_negative_i32_literal)?; lexer.expect(Token::Paren(')'))?; - align = Some(NonZeroU32::new(value).ok_or(Error::ZeroSizeOrAlign(span))?); + align_attr = Some((value, span)); } (word, word_span) => bind_parser.parse(lexer, word, word_span)?, } } let bind_span = self.pop_scope(lexer); + let mut binding = bind_parser.finish(bind_span)?; + let (name, span) = match lexer.next() { (Token::Word(word), span) => (word, span), other => return Err(Error::Unexpected(other, ExpectedToken::FieldName)), @@ -2885,29 +2851,57 @@ impl Parser { return Err(Error::ReservedKeyword(span)); } lexer.expect(Token::Separator(':'))?; - let (ty, _access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + let ty = self.parse_type_decl(lexer, None, type_arena, const_arena)?; ready = lexer.skip(Token::Separator(',')); self.layouter.update(type_arena, const_arena).unwrap(); - let (range, align) = self.layouter.member_placement(offset, ty, align, size); - alignment = alignment.max(align); - offset = range.end; + let member_min_size = self.layouter[ty].size; + let member_min_alignment = self.layouter[ty].alignment; + + let member_size = if let Some((size, span)) = size_attr { + if size < member_min_size { + return Err(Error::SizeAttributeTooLow(span, member_min_size)); + } else { + size + } + } else { + member_min_size + }; + + let member_alignment = if let Some((align, span)) = align_attr { + if let Some(alignment) = Alignment::new(align) { + if alignment < member_min_alignment { + return Err(Error::AlignAttributeTooLow(span, member_min_alignment)); + } else { + alignment + } + } else { + return Err(Error::NonPowerOfTwoAlignAttribute(span)); + } + } else { + member_min_alignment + }; + + offset = member_alignment.round_up(offset); + struct_alignment = struct_alignment.max(member_alignment); - let mut binding = bind_parser.finish(bind_span)?; if let Some(ref mut binding) = binding { binding.apply_default_interpolation(&type_arena[ty].inner); } + members.push(crate::StructMember { name: Some(name.to_owned()), ty, binding, - offset: range.start, + offset, }); + + offset += member_size; } - let span = Layouter::round_up(alignment, offset); - Ok((members, span)) + let struct_size = struct_alignment.round_up(offset); + Ok((members, struct_size)) } fn parse_matrix_scalar_type<'a>( @@ -3012,7 +3006,7 @@ impl Parser { let (ident, span) = lexer.next_ident_with_span()?; let mut space = conv::map_address_space(ident, span)?; lexer.expect(Token::Separator(','))?; - let (base, _access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + let base = self.parse_type_decl(lexer, None, type_arena, const_arena)?; if let crate::AddressSpace::Storage { ref mut access } = space { *access = if lexer.skip(Token::Separator(',')) { lexer.next_storage_access()? @@ -3025,7 +3019,7 @@ impl Parser { } "array" => { lexer.expect_generic_paren('<')?; - let (base, _access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + let base = self.parse_type_decl(lexer, None, type_arena, const_arena)?; let size = if lexer.skip(Token::Separator(',')) { let const_handle = self.parse_const_expression(lexer, type_arena, const_arena)?; @@ -3043,7 +3037,7 @@ impl Parser { } "binding_array" => { lexer.expect_generic_paren('<')?; - let (base, _access) = self.parse_type_decl(lexer, None, type_arena, const_arena)?; + let base = self.parse_type_decl(lexer, None, type_arena, const_arena)?; let size = if lexer.skip(Token::Separator(',')) { let const_handle = self.parse_const_expression(lexer, type_arena, const_arena)?; @@ -3258,7 +3252,7 @@ impl Parser { debug_name: Option<&'a str>, type_arena: &mut UniqueArena, const_arena: &mut Arena, - ) -> Result<(Handle, crate::StorageAccess), Error<'a>> { + ) -> Result, Error<'a>> { self.push_scope(Scope::TypeDecl, lexer); let attribute = TypeAttributes::default(); @@ -3267,7 +3261,6 @@ impl Parser { return Err(Error::Unexpected(other, ExpectedToken::TypeAttribute)); } - let storage_access = crate::StorageAccess::default(); let (name, name_span) = lexer.next_ident_with_span()?; let handle = self.parse_type_decl_name( lexer, @@ -3282,7 +3275,7 @@ impl Parser { // Only set span if it's the first occurrence of the type. // Type spans therefore should only be used for errors in type declarations; // use variable spans/expression spans/etc. otherwise - Ok((handle, storage_access)) + Ok(handle) } /// Parse an assignment statement (will also parse increment and decrement statements) @@ -3510,7 +3503,7 @@ impl Parser { return Err(Error::ReservedKeyword(name_span)); } let given_ty = if lexer.skip(Token::Separator(':')) { - let (ty, _access) = self.parse_type_decl( + let ty = self.parse_type_decl( lexer, None, context.types, @@ -3571,7 +3564,7 @@ impl Parser { return Err(Error::ReservedKeyword(name_span)); } let given_ty = if lexer.skip(Token::Separator(':')) { - let (ty, _access) = self.parse_type_decl( + let ty = self.parse_type_decl( lexer, None, context.types, @@ -3825,26 +3818,7 @@ impl Parser { Some(crate::Statement::Switch { selector, cases }) } - "loop" => { - let _ = lexer.next(); - let mut body = crate::Block::new(); - let mut continuing = crate::Block::new(); - lexer.expect(Token::Paren('{'))?; - - loop { - if lexer.skip(Token::Word("continuing")) { - continuing = self.parse_block(lexer, context.reborrow(), false)?; - lexer.expect(Token::Paren('}'))?; - break; - } - if lexer.skip(Token::Paren('}')) { - break; - } - self.parse_statement(lexer, context.reborrow(), &mut body, false)?; - } - - Some(crate::Statement::Loop { body, continuing }) - } + "loop" => Some(self.parse_loop(lexer, context.reborrow(), &mut emitter)?), "while" => { let _ = lexer.next(); let mut body = crate::Block::new(); @@ -3877,6 +3851,7 @@ impl Parser { Some(crate::Statement::Loop { body, continuing: crate::Block::new(), + break_if: None, }) } "for" => { @@ -3949,10 +3924,22 @@ impl Parser { self.parse_statement(lexer, context.reborrow(), &mut body, false)?; } - Some(crate::Statement::Loop { body, continuing }) + Some(crate::Statement::Loop { + body, + continuing, + break_if: None, + }) } "break" => { - let _ = lexer.next(); + let (_, mut span) = lexer.next(); + // Check if the next token is an `if`, this indicates + // that the user tried to type out a `break if` which + // is illegal in this position. + let (peeked_token, peeked_span) = lexer.peek(); + if let Token::Word("if") = peeked_token { + span.end = peeked_span.end; + return Err(Error::InvalidBreakIf(span)); + } Some(crate::Statement::Break) } "continue" => { @@ -4055,6 +4042,84 @@ impl Parser { Ok(()) } + fn parse_loop<'a>( + &mut self, + lexer: &mut Lexer<'a>, + mut context: StatementContext<'a, '_, '_>, + emitter: &mut super::Emitter, + ) -> Result> { + let _ = lexer.next(); + let mut body = crate::Block::new(); + let mut continuing = crate::Block::new(); + let mut break_if = None; + lexer.expect(Token::Paren('{'))?; + + loop { + if lexer.skip(Token::Word("continuing")) { + // Branch for the `continuing` block, this must be + // the last thing in the loop body + + // Expect a opening brace to start the continuing block + lexer.expect(Token::Paren('{'))?; + loop { + if lexer.skip(Token::Word("break")) { + // Branch for the `break if` statement, this statement + // has the form `break if ;` and must be the last + // statement in a continuing block + + // The break must be followed by an `if` to form + // the break if + lexer.expect(Token::Word("if"))?; + + // Start the emitter to begin parsing an expression + emitter.start(context.expressions); + let condition = self.parse_general_expression( + lexer, + context.as_expression(&mut body, emitter), + )?; + // Add all emits to the continuing body + continuing.extend(emitter.finish(context.expressions)); + // Set the condition of the break if to the newly parsed + // expression + break_if = Some(condition); + + // Expext a semicolon to close the statement + lexer.expect(Token::Separator(';'))?; + // Expect a closing brace to close the continuing block, + // since the break if must be the last statement + lexer.expect(Token::Paren('}'))?; + // Stop parsing the continuing block + break; + } else if lexer.skip(Token::Paren('}')) { + // If we encounter a closing brace it means we have reached + // the end of the continuing block and should stop processing + break; + } else { + // Otherwise try to parse a statement + self.parse_statement(lexer, context.reborrow(), &mut continuing, false)?; + } + } + // Since the continuing block must be the last part of the loop body, + // we expect to see a closing brace to end the loop body + lexer.expect(Token::Paren('}'))?; + break; + } + if lexer.skip(Token::Paren('}')) { + // If we encounter a closing brace it means we have reached + // the end of the loop body and should stop processing + break; + } + // Otherwise try to parse a statement + self.parse_statement(lexer, context.reborrow(), &mut body, false)?; + } + + Ok(crate::Statement::Loop { + body, + continuing, + break_if, + }) + } + fn parse_block<'a>( &mut self, lexer: &mut Lexer<'a>, @@ -4146,7 +4211,7 @@ impl Parser { )); } let mut binding = self.parse_varying_binding(lexer)?; - let (param_name, param_name_span, param_type, _access) = + let (param_name, param_name_span, param_type) = self.parse_variable_ident_decl(lexer, &mut module.types, &mut module.constants)?; if crate::keywords::wgsl::RESERVED.contains(¶m_name) { return Err(Error::ReservedKeyword(param_name_span)); @@ -4176,8 +4241,7 @@ impl Parser { // read return type let result = if lexer.skip(Token::Arrow) && !lexer.skip(Token::Word("void")) { let mut binding = self.parse_varying_binding(lexer)?; - let (ty, _access) = - self.parse_type_decl(lexer, None, &mut module.types, &mut module.constants)?; + let ty = self.parse_type_decl(lexer, None, &mut module.types, &mut module.constants)?; if let Some(ref mut binding) = binding { binding.apply_default_interpolation(&module.types[ty].inner); } @@ -4244,12 +4308,12 @@ impl Parser { match lexer.next_ident_with_span()? { ("binding", _) => { lexer.expect(Token::Paren('('))?; - bind_index = Some(parse_non_negative_sint_literal(lexer, 4)?); + bind_index = Some(Self::parse_non_negative_i32_literal(lexer)?); lexer.expect(Token::Paren(')'))?; } ("group", _) => { lexer.expect(Token::Paren('('))?; - bind_group = Some(parse_non_negative_sint_literal(lexer, 4)?); + bind_group = Some(Self::parse_non_negative_i32_literal(lexer)?); lexer.expect(Token::Paren(')'))?; } ("vertex", _) => { @@ -4263,8 +4327,9 @@ impl Parser { } ("workgroup_size", _) => { lexer.expect(Token::Paren('('))?; + workgroup_size = [1u32; 3]; for (i, size) in workgroup_size.iter_mut().enumerate() { - *size = parse_generic_non_negative_int_literal(lexer, 4)?; + *size = Self::parse_generic_non_negative_int_literal(lexer)?; match lexer.next() { (Token::Paren(')'), _) => break, (Token::Separator(','), _) if i != 2 => (), @@ -4276,11 +4341,6 @@ impl Parser { } } } - for size in workgroup_size.iter_mut() { - if *size == 0 { - *size = 1; - } - } } ("early_depth_test", _) => { let conservative = if lexer.skip(Token::Paren('(')) { @@ -4334,7 +4394,7 @@ impl Parser { (Token::Word("type"), _) => { let name = lexer.next_ident()?; lexer.expect(Token::Operation('='))?; - let (ty, _access) = self.parse_type_decl( + let ty = self.parse_type_decl( lexer, Some(name), &mut module.types, @@ -4358,7 +4418,7 @@ impl Parser { }); } let given_ty = if lexer.skip(Token::Separator(':')) { - let (ty, _access) = self.parse_type_decl( + let ty = self.parse_type_decl( lexer, None, &mut module.types, diff --git a/third_party/rust/naga/src/front/wgsl/number.rs b/third_party/rust/naga/src/front/wgsl/number.rs new file mode 100644 index 000000000000..d7e8801b09e2 --- /dev/null +++ b/third_party/rust/naga/src/front/wgsl/number.rs @@ -0,0 +1,445 @@ +use std::borrow::Cow; + +use super::{NumberError, Token}; + +/// When using this type assume no Abstract Int/Float for now +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Number { + /// Abstract Int (-2^63 ≤ i < 2^63) + AbstractInt(i64), + /// Abstract Float (IEEE-754 binary64) + AbstractFloat(f64), + /// Concrete i32 + I32(i32), + /// Concrete u32 + U32(u32), + /// Concrete f32 + F32(f32), +} + +impl Number { + /// Convert abstract numbers to a plausible concrete counterpart. + /// + /// Return concrete numbers unchanged. If the conversion would be + /// lossy, return an error. + fn abstract_to_concrete(self) -> Result { + match self { + Number::AbstractInt(num) => { + use std::convert::TryFrom; + i32::try_from(num) + .map(Number::I32) + .map_err(|_| NumberError::NotRepresentable) + } + Number::AbstractFloat(num) => { + let num = num as f32; + if num.is_finite() { + Ok(Number::F32(num)) + } else { + Err(NumberError::NotRepresentable) + } + } + num => Ok(num), + } + } +} + +// TODO: when implementing Creation-Time Expressions, remove the ability to match the minus sign + +pub(super) fn consume_number(input: &str) -> (Token<'_>, &str) { + let (result, rest) = parse(input); + ( + Token::Number(result.and_then(Number::abstract_to_concrete)), + rest, + ) +} + +enum Kind { + Int(IntKind), + Float(FloatKind), +} + +enum IntKind { + I32, + U32, +} + +enum FloatKind { + F32, + F16, +} + +// The following regexes (from the WGSL spec) will be matched: + +// int_literal: +// | / 0 [iu]? / +// | / [1-9][0-9]* [iu]? / +// | / 0[xX][0-9a-fA-F]+ [iu]? / + +// decimal_float_literal: +// | / 0 [fh] / +// | / [1-9][0-9]* [fh] / +// | / [0-9]* \.[0-9]+ ([eE][+-]?[0-9]+)? [fh]? / +// | / [0-9]+ \.[0-9]* ([eE][+-]?[0-9]+)? [fh]? / +// | / [0-9]+ [eE][+-]?[0-9]+ [fh]? / + +// hex_float_literal: +// | / 0[xX][0-9a-fA-F]* \.[0-9a-fA-F]+ ([pP][+-]?[0-9]+ [fh]?)? / +// | / 0[xX][0-9a-fA-F]+ \.[0-9a-fA-F]* ([pP][+-]?[0-9]+ [fh]?)? / +// | / 0[xX][0-9a-fA-F]+ [pP][+-]?[0-9]+ [fh]? / + +// You could visualize the regex below via https://debuggex.com to get a rough idea what `parse` is doing +// -?(?:0[xX](?:([0-9a-fA-F]+\.[0-9a-fA-F]*|[0-9a-fA-F]*\.[0-9a-fA-F]+)(?:([pP][+-]?[0-9]+)([fh]?))?|([0-9a-fA-F]+)([pP][+-]?[0-9]+)([fh]?)|([0-9a-fA-F]+)([iu]?))|((?:[0-9]+[eE][+-]?[0-9]+|(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?))([fh]?)|((?:[0-9]|[1-9][0-9]+))([iufh]?)) + +fn parse(input: &str) -> (Result, &str) { + /// returns `true` and consumes `X` bytes from the given byte buffer + /// if the given `X` nr of patterns are found at the start of the buffer + macro_rules! consume { + ($bytes:ident, $($($pattern:pat)|*),*) => { + match $bytes { + &[$($($pattern)|*),*, ref rest @ ..] => { $bytes = rest; true }, + _ => false, + } + }; + } + + /// consumes one byte from the given byte buffer + /// if one of the given patterns are found at the start of the buffer + /// returning the corresponding expr for the matched pattern + macro_rules! consume_map { + ($bytes:ident, [$($($pattern:pat)|* => $to:expr),*]) => { + match $bytes { + $( &[$($pattern)|*, ref rest @ ..] => { $bytes = rest; Some($to) }, )* + _ => None, + } + }; + } + + /// consumes all consecutive bytes matched by the `0-9` pattern from the given byte buffer + /// returning the number of consumed bytes + macro_rules! consume_dec_digits { + ($bytes:ident) => {{ + let start_len = $bytes.len(); + while let &[b'0'..=b'9', ref rest @ ..] = $bytes { + $bytes = rest; + } + start_len - $bytes.len() + }}; + } + + /// consumes all consecutive bytes matched by the `0-9 | a-f | A-F` pattern from the given byte buffer + /// returning the number of consumed bytes + macro_rules! consume_hex_digits { + ($bytes:ident) => {{ + let start_len = $bytes.len(); + while let &[b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F', ref rest @ ..] = $bytes { + $bytes = rest; + } + start_len - $bytes.len() + }}; + } + + /// maps the given `&[u8]` (tail of the initial `input: &str`) to a `&str` + macro_rules! rest_to_str { + ($bytes:ident) => { + &input[input.len() - $bytes.len()..] + }; + } + + struct ExtractSubStr<'a>(&'a str); + + impl<'a> ExtractSubStr<'a> { + /// given an `input` and a `start` (tail of the `input`) + /// creates a new [ExtractSubStr] + fn start(input: &'a str, start: &'a [u8]) -> Self { + let start = input.len() - start.len(); + Self(&input[start..]) + } + /// given an `end` (tail of the initial `input`) + /// returns a substring of `input` + fn end(&self, end: &'a [u8]) -> &'a str { + let end = self.0.len() - end.len(); + &self.0[..end] + } + } + + let mut bytes = input.as_bytes(); + + let general_extract = ExtractSubStr::start(input, bytes); + + let is_negative = consume!(bytes, b'-'); + + if consume!(bytes, b'0', b'x' | b'X') { + let digits_extract = ExtractSubStr::start(input, bytes); + + let consumed = consume_hex_digits!(bytes); + + if consume!(bytes, b'.') { + let consumed_after_period = consume_hex_digits!(bytes); + + if consumed + consumed_after_period == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let significand = general_extract.end(bytes); + + if consume!(bytes, b'p' | b'P') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let number = general_extract.end(bytes); + + let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (parse_hex_float(number, kind), rest_to_str!(bytes)) + } else { + ( + parse_hex_float_missing_exponent(significand, None), + rest_to_str!(bytes), + ) + } + } else { + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let significand = general_extract.end(bytes); + let digits = digits_extract.end(bytes); + + let exp_extract = ExtractSubStr::start(input, bytes); + + if consume!(bytes, b'p' | b'P') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let exponent = exp_extract.end(bytes); + + let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + ( + parse_hex_float_missing_period(significand, exponent, kind), + rest_to_str!(bytes), + ) + } else { + let kind = consume_map!(bytes, [b'i' => IntKind::I32, b'u' => IntKind::U32]); + + ( + parse_hex_int(is_negative, digits, kind), + rest_to_str!(bytes), + ) + } + } + } else { + let is_first_zero = bytes.first() == Some(&b'0'); + + let consumed = consume_dec_digits!(bytes); + + if consume!(bytes, b'.') { + let consumed_after_period = consume_dec_digits!(bytes); + + if consumed + consumed_after_period == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + if consume!(bytes, b'e' | b'E') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + } + + let number = general_extract.end(bytes); + + let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (parse_dec_float(number, kind), rest_to_str!(bytes)) + } else { + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + if consume!(bytes, b'e' | b'E') { + consume!(bytes, b'+' | b'-'); + let consumed = consume_dec_digits!(bytes); + + if consumed == 0 { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let number = general_extract.end(bytes); + + let kind = consume_map!(bytes, [b'f' => FloatKind::F32, b'h' => FloatKind::F16]); + + (parse_dec_float(number, kind), rest_to_str!(bytes)) + } else { + // make sure the multi-digit numbers don't start with zero + if consumed > 1 && is_first_zero { + return (Err(NumberError::Invalid), rest_to_str!(bytes)); + } + + let digits_with_sign = general_extract.end(bytes); + + let kind = consume_map!(bytes, [ + b'i' => Kind::Int(IntKind::I32), + b'u' => Kind::Int(IntKind::U32), + b'f' => Kind::Float(FloatKind::F32), + b'h' => Kind::Float(FloatKind::F16) + ]); + + ( + parse_dec(is_negative, digits_with_sign, kind), + rest_to_str!(bytes), + ) + } + } + } +} + +fn parse_hex_float_missing_exponent( + // format: -?0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) + significand: &str, + kind: Option, +) -> Result { + let hexf_input = format!("{}{}", significand, "p0"); + parse_hex_float(&hexf_input, kind) +} + +fn parse_hex_float_missing_period( + // format: -?0[xX] [0-9a-fA-F]+ + significand: &str, + // format: [pP][+-]?[0-9]+ + exponent: &str, + kind: Option, +) -> Result { + let hexf_input = format!("{}.{}", significand, exponent); + parse_hex_float(&hexf_input, kind) +} + +fn parse_hex_int( + is_negative: bool, + // format: [0-9a-fA-F]+ + digits: &str, + kind: Option, +) -> Result { + let digits_with_sign = if is_negative { + Cow::Owned(format!("-{}", digits)) + } else { + Cow::Borrowed(digits) + }; + parse_int(&digits_with_sign, kind, 16, is_negative) +} + +fn parse_dec( + is_negative: bool, + // format: -? ( [0-9] | [1-9][0-9]+ ) + digits_with_sign: &str, + kind: Option, +) -> Result { + match kind { + None => parse_int(digits_with_sign, None, 10, is_negative), + Some(Kind::Int(kind)) => parse_int(digits_with_sign, Some(kind), 10, is_negative), + Some(Kind::Float(kind)) => parse_dec_float(digits_with_sign, Some(kind)), + } +} + +// Float parsing notes + +// The following chapters of IEEE 754-2019 are relevant: +// +// 7.4 Overflow (largest finite number is exceeded by what would have been +// the rounded floating-point result were the exponent range unbounded) +// +// 7.5 Underflow (tiny non-zero result is detected; +// for decimal formats tininess is detected before rounding when a non-zero result +// computed as though both the exponent range and the precision were unbounded +// would lie strictly between 2^−126) +// +// 7.6 Inexact (rounded result differs from what would have been computed +// were both exponent range and precision unbounded) + +// The WGSL spec requires us to error: +// on overflow for decimal floating point literals +// on overflow and inexact for hexadecimal floating point literals +// (underflow is not mentioned) + +// hexf_parse errors on overflow, underflow, inexact +// rust std lib float from str handles overflow, underflow, inexact transparently (rounds and will not error) + +// Therefore we only check for overflow manually for decimal floating point literals + +// input format: -?0[xX] ( [0-9a-fA-F]+\.[0-9a-fA-F]* | [0-9a-fA-F]*\.[0-9a-fA-F]+ ) [pP][+-]?[0-9]+ +fn parse_hex_float(input: &str, kind: Option) -> Result { + match kind { + None => match hexf_parse::parse_hexf64(input, false) { + Ok(num) => Ok(Number::AbstractFloat(num)), + // can only be ParseHexfErrorKind::Inexact but we can't check since it's private + _ => Err(NumberError::NotRepresentable), + }, + Some(FloatKind::F32) => match hexf_parse::parse_hexf32(input, false) { + Ok(num) => Ok(Number::F32(num)), + // can only be ParseHexfErrorKind::Inexact but we can't check since it's private + _ => Err(NumberError::NotRepresentable), + }, + Some(FloatKind::F16) => Err(NumberError::UnimplementedF16), + } +} + +// input format: -? ( [0-9]+\.[0-9]* | [0-9]*\.[0-9]+ ) ([eE][+-]?[0-9]+)? +// | -? [0-9]+ [eE][+-]?[0-9]+ +fn parse_dec_float(input: &str, kind: Option) -> Result { + match kind { + None => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then(|| Number::AbstractFloat(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F32) => { + let num = input.parse::().unwrap(); // will never fail + num.is_finite() + .then(|| Number::F32(num)) + .ok_or(NumberError::NotRepresentable) + } + Some(FloatKind::F16) => Err(NumberError::UnimplementedF16), + } +} + +fn parse_int( + input: &str, + kind: Option, + radix: u32, + is_negative: bool, +) -> Result { + fn map_err(e: core::num::ParseIntError) -> NumberError { + match *e.kind() { + core::num::IntErrorKind::PosOverflow | core::num::IntErrorKind::NegOverflow => { + NumberError::NotRepresentable + } + _ => unreachable!(), + } + } + match kind { + None => match i64::from_str_radix(input, radix) { + Ok(num) => Ok(Number::AbstractInt(num)), + Err(e) => Err(map_err(e)), + }, + Some(IntKind::I32) => match i32::from_str_radix(input, radix) { + Ok(num) => Ok(Number::I32(num)), + Err(e) => Err(map_err(e)), + }, + Some(IntKind::U32) if is_negative => Err(NumberError::NotRepresentable), + Some(IntKind::U32) => match u32::from_str_radix(input, radix) { + Ok(num) => Ok(Number::U32(num)), + Err(e) => Err(map_err(e)), + }, + } +} diff --git a/third_party/rust/naga/src/front/wgsl/number_literals.rs b/third_party/rust/naga/src/front/wgsl/number_literals.rs deleted file mode 100644 index 6691ab2d05f0..000000000000 --- a/third_party/rust/naga/src/front/wgsl/number_literals.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::convert::TryFrom; - -use hexf_parse::parse_hexf32; - -use crate::Bytes; - -use super::{ - lexer::{try_skip_prefix, Lexer}, - BadFloatError, BadIntError, Error, ExpectedToken, NumberType, Span, Token, -}; - -fn check_int_literal(word_without_minus: &str, minus: bool, hex: bool) -> Result<(), BadIntError> { - let leading_zeros = word_without_minus - .bytes() - .take_while(|&b| b == b'0') - .count(); - - if word_without_minus == "0" && minus { - Err(BadIntError::NegativeZero) - } else if word_without_minus != "0" && !hex && leading_zeros != 0 { - Err(BadIntError::LeadingZeros) - } else { - Ok(()) - } -} - -pub fn get_i32_literal(word: &str, span: Span) -> Result> { - let (minus, word_without_minus, _) = try_skip_prefix(word, "-"); - let (hex, word_without_minus_and_0x, _) = try_skip_prefix(word_without_minus, "0x"); - - check_int_literal(word_without_minus, minus, hex) - .map_err(|e| Error::BadI32(span.clone(), e))?; - - let parsed_val = match (hex, minus) { - (true, true) => i32::from_str_radix(&format!("-{}", word_without_minus_and_0x), 16), - (true, false) => i32::from_str_radix(word_without_minus_and_0x, 16), - (false, _) => word.parse(), - }; - - parsed_val.map_err(|e| Error::BadI32(span, e.into())) -} - -pub fn get_u32_literal(word: &str, span: Span) -> Result> { - let (minus, word_without_minus, _) = try_skip_prefix(word, "-"); - let (hex, word_without_minus_and_0x, _) = try_skip_prefix(word_without_minus, "0x"); - - check_int_literal(word_without_minus, minus, hex) - .map_err(|e| Error::BadU32(span.clone(), e))?; - - // We need to add a minus here as well, since the lexer also accepts syntactically incorrect negative uints - let parsed_val = match (hex, minus) { - (true, true) => u32::from_str_radix(&format!("-{}", word_without_minus_and_0x), 16), - (true, false) => u32::from_str_radix(word_without_minus_and_0x, 16), - (false, _) => word.parse(), - }; - - parsed_val.map_err(|e| Error::BadU32(span, e.into())) -} - -pub fn get_f32_literal(word: &str, span: Span) -> Result> { - let hex = word.starts_with("0x") || word.starts_with("-0x"); - - let parsed_val = if hex { - parse_hexf32(word, false).map_err(BadFloatError::ParseHexfError) - } else { - word.parse::().map_err(BadFloatError::ParseFloatError) - }; - - parsed_val.map_err(|e| Error::BadFloat(span, e)) -} - -pub(super) fn _parse_uint_literal<'a>( - lexer: &mut Lexer<'a>, - width: Bytes, -) -> Result> { - let token_span = lexer.next(); - - if width != 4 { - // Only 32-bit literals supported by the spec and naga for now! - return Err(Error::BadScalarWidth(token_span.1, width)); - } - - match token_span { - ( - Token::Number { - value, - ty: NumberType::Uint, - }, - span, - ) => get_u32_literal(value, span), - other => Err(Error::Unexpected( - other, - ExpectedToken::Number { - ty: Some(NumberType::Uint), - width: Some(width), - }, - )), - } -} - -/// Parse a non-negative signed integer literal. -/// This is for attributes like `size`, `location` and others. -pub(super) fn parse_non_negative_sint_literal<'a>( - lexer: &mut Lexer<'a>, - width: Bytes, -) -> Result> { - let token_span = lexer.next(); - - if width != 4 { - // Only 32-bit literals supported by the spec and naga for now! - return Err(Error::BadScalarWidth(token_span.1, width)); - } - - match token_span { - ( - Token::Number { - value, - ty: NumberType::Sint, - }, - span, - ) => { - let i32_val = get_i32_literal(value, span.clone())?; - u32::try_from(i32_val).map_err(|_| Error::NegativeInt(span)) - } - other => Err(Error::Unexpected( - other, - ExpectedToken::Number { - ty: Some(NumberType::Sint), - width: Some(width), - }, - )), - } -} - -/// Parse a non-negative integer literal that may be either signed or unsigned. -/// This is for the `workgroup_size` attribute and array lengths. -/// Note: these values should be no larger than [`i32::MAX`], but this is not checked here. -pub(super) fn parse_generic_non_negative_int_literal<'a>( - lexer: &mut Lexer<'a>, - width: Bytes, -) -> Result> { - let token_span = lexer.next(); - - if width != 4 { - // Only 32-bit literals supported by the spec and naga for now! - return Err(Error::BadScalarWidth(token_span.1, width)); - } - - match token_span { - ( - Token::Number { - value, - ty: NumberType::Sint, - }, - span, - ) => { - let i32_val = get_i32_literal(value, span.clone())?; - u32::try_from(i32_val).map_err(|_| Error::NegativeInt(span)) - } - ( - Token::Number { - value, - ty: NumberType::Uint, - }, - span, - ) => get_u32_literal(value, span), - other => Err(Error::Unexpected( - other, - ExpectedToken::Number { - ty: Some(NumberType::Sint), - width: Some(width), - }, - )), - } -} - -pub(super) fn _parse_float_literal<'a>( - lexer: &mut Lexer<'a>, - width: Bytes, -) -> Result> { - let token_span = lexer.next(); - - if width != 4 { - // Only 32-bit literals supported by the spec and naga for now! - return Err(Error::BadScalarWidth(token_span.1, width)); - } - - match token_span { - ( - Token::Number { - value, - ty: NumberType::Float, - }, - span, - ) => get_f32_literal(value, span), - other => Err(Error::Unexpected( - other, - ExpectedToken::Number { - ty: Some(NumberType::Float), - width: Some(width), - }, - )), - } -} diff --git a/third_party/rust/naga/src/front/wgsl/tests.rs b/third_party/rust/naga/src/front/wgsl/tests.rs index 1f7e451f94ad..33fc541acba5 100644 --- a/third_party/rust/naga/src/front/wgsl/tests.rs +++ b/third_party/rust/naga/src/front/wgsl/tests.rs @@ -14,76 +14,6 @@ fn parse_comment() { .unwrap(); } -// Regexes for the literals are taken from the working draft at -// https://www.w3.org/TR/2021/WD-WGSL-20210806/#literals - -#[test] -fn parse_decimal_floats() { - // /^(-?[0-9]*\.[0-9]+|-?[0-9]+\.[0-9]*)((e|E)(\+|-)?[0-9]+)?$/ - parse_str("let a : f32 = -1.;").unwrap(); - parse_str("let a : f32 = -.1;").unwrap(); - parse_str("let a : f32 = 42.1234;").unwrap(); - parse_str("let a : f32 = -1.E3;").unwrap(); - parse_str("let a : f32 = -.1e-5;").unwrap(); - parse_str("let a : f32 = 2.3e+55;").unwrap(); - - assert!(parse_str("let a : f32 = 42.1234f;").is_err()); - assert!(parse_str("let a : f32 = 42.1234f32;").is_err()); -} - -#[test] -fn parse_hex_floats() { - // /^-?0x([0-9a-fA-F]*\.?[0-9a-fA-F]+|[0-9a-fA-F]+\.[0-9a-fA-F]*)(p|P)(\+|-)?[0-9]+$/ - parse_str("let a : f32 = -0xa.p1;").unwrap(); - parse_str("let a : f32 = -0x.fp9;").unwrap(); - parse_str("let a : f32 = 0x2a.4D2P4;").unwrap(); - parse_str("let a : f32 = -0x.1p-5;").unwrap(); - parse_str("let a : f32 = 0xC.8p+55;").unwrap(); - parse_str("let a : f32 = 0x1p1;").unwrap(); - - assert!(parse_str("let a : f32 = 0x1p1f;").is_err()); - assert!(parse_str("let a : f32 = 0x1p1f32;").is_err()); -} - -#[test] -fn parse_decimal_ints() { - // i32 /^-?0x[0-9a-fA-F]+|0|-?[1-9][0-9]*$/ - parse_str("let a : i32 = 0;").unwrap(); - parse_str("let a : i32 = 1092;").unwrap(); - parse_str("let a : i32 = -9923;").unwrap(); - - assert!(parse_str("let a : i32 = -0;").is_err()); - assert!(parse_str("let a : i32 = 01;").is_err()); - assert!(parse_str("let a : i32 = 1.0;").is_err()); - assert!(parse_str("let a : i32 = 1i;").is_err()); - assert!(parse_str("let a : i32 = 1i32;").is_err()); - - // u32 /^0x[0-9a-fA-F]+u|0u|[1-9][0-9]*u$/ - parse_str("let a : u32 = 0u;").unwrap(); - parse_str("let a : u32 = 1092u;").unwrap(); - - assert!(parse_str("let a : u32 = -0u;").is_err()); - assert!(parse_str("let a : u32 = 01u;").is_err()); - assert!(parse_str("let a : u32 = 1.0u;").is_err()); - assert!(parse_str("let a : u32 = 1u32;").is_err()); -} - -#[test] -fn parse_hex_ints() { - // i32 /^-?0x[0-9a-fA-F]+|0|-?[1-9][0-9]*$/ - parse_str("let a : i32 = -0x0;").unwrap(); - parse_str("let a : i32 = 0x2a4D2;").unwrap(); - - assert!(parse_str("let a : i32 = 0x2a4D2i;").is_err()); - assert!(parse_str("let a : i32 = 0x2a4D2i32;").is_err()); - - // u32 /^0x[0-9a-fA-F]+u|0u|[1-9][0-9]*u$/ - parse_str("let a : u32 = 0x0u;").unwrap(); - parse_str("let a : u32 = 0x2a4D2u;").unwrap(); - - assert!(parse_str("let a : u32 = 0x2a4D2u32;").is_err()); -} - #[test] fn parse_types() { parse_str("let a : i32 = 2;").unwrap(); @@ -161,7 +91,7 @@ fn parse_struct() { struct Bar { @size(16) x: vec2, @align(16) y: f32, - @size(32) @align(8) z: vec3, + @size(32) @align(128) z: vec3, }; struct Empty {} var s: Foo; diff --git a/third_party/rust/naga/src/lib.rs b/third_party/rust/naga/src/lib.rs index 80e7e6f78921..29bcbeaf4a6b 100644 --- a/third_party/rust/naga/src/lib.rs +++ b/third_party/rust/naga/src/lib.rs @@ -211,7 +211,7 @@ pub mod valid; pub use crate::arena::{Arena, Handle, Range, UniqueArena}; -pub use crate::span::{Span, SpanContext, WithSpan}; +pub use crate::span::{SourceLocation, Span, SpanContext, WithSpan}; #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; #[cfg(feature = "deserialize")] @@ -1439,11 +1439,23 @@ pub enum Statement { /// this loop. (It may have `Break` and `Continue` statements targeting /// loops or switches nested within the `continuing` block.) /// + /// If present, `break_if` is an expression which is evaluated after the + /// continuing block. If its value is true, control continues after the + /// `Loop` statement, rather than branching back to the top of body as + /// usual. The `break_if` expression corresponds to a "break if" statement + /// in WGSL, or a loop whose back edge is an `OpBranchConditional` + /// instruction in SPIR-V. + /// /// [`Break`]: Statement::Break /// [`Continue`]: Statement::Continue /// [`Kill`]: Statement::Kill /// [`Return`]: Statement::Return - Loop { body: Block, continuing: Block }, + /// [`break if`]: Self::Loop::break_if + Loop { + body: Block, + continuing: Block, + break_if: Option>, + }, /// Exits the innermost enclosing [`Loop`] or [`Switch`]. /// diff --git a/third_party/rust/naga/src/proc/layouter.rs b/third_party/rust/naga/src/proc/layouter.rs index 598afba92098..0c3a00db154e 100644 --- a/third_party/rust/naga/src/proc/layouter.rs +++ b/third_party/rust/naga/src/proc/layouter.rs @@ -1,7 +1,86 @@ use crate::arena::{Arena, BadHandle, Handle, UniqueArena}; -use std::{num::NonZeroU32, ops}; +use std::{fmt::Display, num::NonZeroU32, ops}; -pub type Alignment = NonZeroU32; +/// A newtype struct where its only valid values are powers of 2 +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +pub struct Alignment(NonZeroU32); + +impl Alignment { + pub const ONE: Self = Self(unsafe { NonZeroU32::new_unchecked(1) }); + pub const TWO: Self = Self(unsafe { NonZeroU32::new_unchecked(2) }); + pub const FOUR: Self = Self(unsafe { NonZeroU32::new_unchecked(4) }); + pub const EIGHT: Self = Self(unsafe { NonZeroU32::new_unchecked(8) }); + pub const SIXTEEN: Self = Self(unsafe { NonZeroU32::new_unchecked(16) }); + + pub const MIN_UNIFORM: Self = Self::SIXTEEN; + + pub const fn new(n: u32) -> Option { + if n.is_power_of_two() { + // SAFETY: value can't be 0 since we just checked if it's a power of 2 + Some(Self(unsafe { NonZeroU32::new_unchecked(n) })) + } else { + None + } + } + + /// # Panics + /// If `width` is not a power of 2 + pub fn from_width(width: u8) -> Self { + Self::new(width as u32).unwrap() + } + + /// Returns whether or not `n` is a multiple of this alignment. + pub const fn is_aligned(&self, n: u32) -> bool { + // equivalent to: `n % self.0.get() == 0` but much faster + n & (self.0.get() - 1) == 0 + } + + /// Round `n` up to the nearest alignment boundary. + pub const fn round_up(&self, n: u32) -> u32 { + // equivalent to: + // match n % self.0.get() { + // 0 => n, + // rem => n + (self.0.get() - rem), + // } + let mask = self.0.get() - 1; + (n + mask) & !mask + } +} + +impl Display for Alignment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.get().fmt(f) + } +} + +impl ops::Mul for Alignment { + type Output = u32; + + fn mul(self, rhs: u32) -> Self::Output { + self.0.get() * rhs + } +} + +impl ops::Mul for Alignment { + type Output = Alignment; + + fn mul(self, rhs: Alignment) -> Self::Output { + // SAFETY: both lhs and rhs are powers of 2, the result will be a power of 2 + Self(unsafe { NonZeroU32::new_unchecked(self.0.get() * rhs.0.get()) }) + } +} + +impl From for Alignment { + fn from(size: crate::VectorSize) -> Self { + match size { + crate::VectorSize::Bi => Alignment::TWO, + crate::VectorSize::Tri => Alignment::FOUR, + crate::VectorSize::Quad => Alignment::FOUR, + } + } +} /// Size and alignment information for a type. #[derive(Clone, Copy, Debug, Hash, PartialEq)] @@ -15,7 +94,7 @@ pub struct TypeLayout { impl TypeLayout { /// Produce the stride as if this type is a base of an array. pub const fn to_stride(&self) -> u32 { - Layouter::round_up(self.alignment, self.size) + self.alignment.round_up(self.size) } } @@ -49,8 +128,8 @@ pub enum LayoutErrorInner { InvalidArrayElementType(Handle), #[error("Struct member[{0}] type {1:?} doesn't exist")] InvalidStructMemberType(u32, Handle), - #[error("Zero width is not supported")] - ZeroWidth, + #[error("Type width must be a power of two")] + NonPowerOfTwoWidth, #[error("Array size is a bad handle")] BadHandle(#[from] BadHandle), } @@ -74,40 +153,6 @@ impl Layouter { self.layouts.clear(); } - /// Round `offset` up to the nearest `alignment` boundary. - pub const fn round_up(alignment: Alignment, offset: u32) -> u32 { - match offset & (alignment.get() - 1) { - 0 => offset, - other => offset + alignment.get() - other, - } - } - - /// Return the offset and span of a struct member. - /// - /// The member must fall at or after `offset`. The member's alignment and - /// size are `align` and `size` if given, defaulting to the values this - /// `Layouter` has previously determined for `ty`. - /// - /// The return value is the range of offsets within the containing struct to - /// reserve for this member, along with the alignment used. The containing - /// struct must have sufficient space and alignment to accommodate these. - pub fn member_placement( - &self, - offset: u32, - ty: Handle, - align: Option, - size: Option, - ) -> (ops::Range, Alignment) { - let layout = self.layouts[ty.index()]; - let alignment = align.unwrap_or(layout.alignment); - let start = Self::round_up(alignment, offset); - let span = match size { - Some(size) => size.get(), - None => layout.size, - }; - (start..start + span, alignment) - } - /// Extend this `Layouter` with layouts for any new entries in `types`. /// /// Ensure that every type in `types` has a corresponding [TypeLayout] in @@ -135,42 +180,38 @@ impl Layouter { .try_size(constants) .map_err(|error| LayoutErrorInner::BadHandle(error).with(ty_handle))?; let layout = match ty.inner { - Ti::Scalar { width, .. } | Ti::Atomic { width, .. } => TypeLayout { - size, - alignment: Alignment::new(width as u32) - .ok_or(LayoutErrorInner::ZeroWidth.with(ty_handle))?, - }, + Ti::Scalar { width, .. } | Ti::Atomic { width, .. } => { + let alignment = Alignment::new(width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { size, alignment } + } Ti::Vector { size: vec_size, width, .. - } => TypeLayout { - size, - alignment: { - let count = if vec_size >= crate::VectorSize::Tri { - 4 - } else { - 2 - }; - Alignment::new(count * width as u32) - .ok_or(LayoutErrorInner::ZeroWidth.with(ty_handle))? - }, - }, + } => { + let alignment = Alignment::new(width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { + size, + alignment: Alignment::from(vec_size) * alignment, + } + } Ti::Matrix { columns: _, rows, width, - } => TypeLayout { - size, - alignment: { - let count = if rows >= crate::VectorSize::Tri { 4 } else { 2 }; - Alignment::new(count * width as u32) - .ok_or(LayoutErrorInner::ZeroWidth.with(ty_handle))? - }, - }, + } => { + let alignment = Alignment::new(width as u32) + .ok_or(LayoutErrorInner::NonPowerOfTwoWidth.with(ty_handle))?; + TypeLayout { + size, + alignment: Alignment::from(rows) * alignment, + } + } Ti::Pointer { .. } | Ti::ValuePointer { .. } => TypeLayout { size, - alignment: Alignment::new(1).unwrap(), + alignment: Alignment::ONE, }, Ti::Array { base, @@ -185,7 +226,7 @@ impl Layouter { }, }, Ti::Struct { span, ref members } => { - let mut alignment = Alignment::new(1).unwrap(); + let mut alignment = Alignment::ONE; for (index, member) in members.iter().enumerate() { alignment = if member.ty < ty_handle { alignment.max(self[member.ty].alignment) @@ -204,7 +245,7 @@ impl Layouter { } Ti::Image { .. } | Ti::Sampler { .. } | Ti::BindingArray { .. } => TypeLayout { size, - alignment: Alignment::new(1).unwrap(), + alignment: Alignment::ONE, }, }; debug_assert!(size <= layout.size); diff --git a/third_party/rust/naga/src/proc/mod.rs b/third_party/rust/naga/src/proc/mod.rs index a4a63abbd0dc..823a190dec82 100644 --- a/third_party/rust/naga/src/proc/mod.rs +++ b/third_party/rust/naga/src/proc/mod.rs @@ -113,10 +113,7 @@ impl super::TypeInner { columns, rows, width, - } => { - let aligned_rows = if rows > crate::VectorSize::Bi { 4 } else { 2 }; - columns as u32 * aligned_rows * width as u32 - } + } => Alignment::from(rows) * width as u32 * columns as u32, Self::Pointer { .. } | Self::ValuePointer { .. } => POINTER_SPAN, Self::Array { base: _, diff --git a/third_party/rust/naga/src/span.rs b/third_party/rust/naga/src/span.rs index 0c60ee005c0b..6f2583a5eb8a 100644 --- a/third_party/rust/naga/src/span.rs +++ b/third_party/rust/naga/src/span.rs @@ -59,6 +59,21 @@ impl Span { pub fn is_defined(&self) -> bool { *self != Self::default() } + + /// Return a [`SourceLocation`] for this span in the provided source. + pub fn location(&self, source: &str) -> SourceLocation { + let prefix = &source[..self.start as usize]; + let line_number = prefix.matches('\n').count() as u32 + 1; + let line_start = prefix.rfind('\n').map(|pos| pos + 1).unwrap_or(0); + let line_position = source[line_start..self.start as usize].chars().count() as u32 + 1; + + SourceLocation { + line_number, + line_position, + offset: self.start, + length: self.end - self.start, + } + } } impl From> for Span { @@ -70,6 +85,25 @@ impl From> for Span { } } +/// A human-readable representation for a span, tailored for text source. +/// +/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from +/// the WebGPU specification, except that `offset` and `length` are in bytes +/// (UTF-8 code units), instead of UTF-16 code units. +/// +/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct SourceLocation { + /// 1-based line number. + pub line_number: u32, + /// 1-based column of the start of this span + pub line_position: u32, + /// 0-based Offset in code units (in bytes) of the start of the span. + pub offset: u32, + /// Length in code units (in bytes) of the span. + pub length: u32, +} + /// A source code span together with "context", a user-readable description of what part of the error it refers to. pub type SpanContext = (Span, String); @@ -186,6 +220,22 @@ impl WithSpan { res.spans.extend(self.spans); res } + + #[cfg(feature = "span")] + /// Return a [`SourceLocation`] for our first span, if we have one. + pub fn location(&self, source: &str) -> Option { + if self.spans.is_empty() { + return None; + } + + Some(self.spans[0].0.location(source)) + } + + #[cfg(not(feature = "span"))] + /// Return a [`SourceLocation`] for our first span, if we have one. + pub fn location(&self, _source: &str) -> Option { + None + } } /// Convenience trait for [`Error`] to be able to apply spans to anything. @@ -273,3 +323,107 @@ impl MapErrWithSpan for Result> { self.map_err(|e| e.and_then(func).into_other::()) } } + +#[test] +fn span_location() { + let source = "12\n45\n\n89\n"; + assert_eq!( + Span { start: 0, end: 1 }.location(source), + SourceLocation { + line_number: 1, + line_position: 1, + offset: 0, + length: 1 + } + ); + assert_eq!( + Span { start: 1, end: 2 }.location(source), + SourceLocation { + line_number: 1, + line_position: 2, + offset: 1, + length: 1 + } + ); + assert_eq!( + Span { start: 2, end: 3 }.location(source), + SourceLocation { + line_number: 1, + line_position: 3, + offset: 2, + length: 1 + } + ); + assert_eq!( + Span { start: 3, end: 5 }.location(source), + SourceLocation { + line_number: 2, + line_position: 1, + offset: 3, + length: 2 + } + ); + assert_eq!( + Span { start: 4, end: 6 }.location(source), + SourceLocation { + line_number: 2, + line_position: 2, + offset: 4, + length: 2 + } + ); + assert_eq!( + Span { start: 5, end: 6 }.location(source), + SourceLocation { + line_number: 2, + line_position: 3, + offset: 5, + length: 1 + } + ); + assert_eq!( + Span { start: 6, end: 7 }.location(source), + SourceLocation { + line_number: 3, + line_position: 1, + offset: 6, + length: 1 + } + ); + assert_eq!( + Span { start: 7, end: 8 }.location(source), + SourceLocation { + line_number: 4, + line_position: 1, + offset: 7, + length: 1 + } + ); + assert_eq!( + Span { start: 8, end: 9 }.location(source), + SourceLocation { + line_number: 4, + line_position: 2, + offset: 8, + length: 1 + } + ); + assert_eq!( + Span { start: 9, end: 10 }.location(source), + SourceLocation { + line_number: 4, + line_position: 3, + offset: 9, + length: 1 + } + ); + assert_eq!( + Span { start: 10, end: 11 }.location(source), + SourceLocation { + line_number: 5, + line_position: 1, + offset: 10, + length: 1 + } + ); +} diff --git a/third_party/rust/naga/src/valid/analyzer.rs b/third_party/rust/naga/src/valid/analyzer.rs index 932e8b0f8ee4..9a7130ff93a9 100644 --- a/third_party/rust/naga/src/valid/analyzer.rs +++ b/third_party/rust/naga/src/valid/analyzer.rs @@ -841,6 +841,7 @@ impl FunctionInfo { S::Loop { ref body, ref continuing, + break_if: _, } => { let body_uniformity = self.process_block(body, other_functions, disruptor, expression_arena)?; diff --git a/third_party/rust/naga/src/valid/function.rs b/third_party/rust/naga/src/valid/function.rs index 151952750e3c..67903601c64f 100644 --- a/third_party/rust/naga/src/valid/function.rs +++ b/third_party/rust/naga/src/valid/function.rs @@ -86,6 +86,8 @@ pub enum FunctionError { }, #[error("Argument '{name}' at index {index} has a type that can't be passed into functions.")] InvalidArgumentType { index: usize, name: String }, + #[error("The function's given return type cannot be returned from functions")] + NonConstructibleReturnType, #[error("Argument '{name}' at index {index} is a pointer of space {space:?}, which can't be passed into functions.")] InvalidArgumentPointerSpace { index: usize, @@ -497,6 +499,7 @@ impl super::Validator { S::Loop { ref body, ref continuing, + break_if, } => { // special handling for block scoping is needed here, // because the continuing{} block inherits the scope @@ -518,6 +521,20 @@ impl super::Validator { &context.with_abilities(ControlFlowAbility::empty()), )? .stages; + + if let Some(condition) = break_if { + match *context.resolve_type(condition, &self.valid_expression_set)? { + Ti::Scalar { + kind: crate::ScalarKind::Bool, + width: _, + } => {} + _ => { + return Err(FunctionError::InvalidIfType(condition) + .with_span_handle(condition, context.expressions)) + } + } + } + for handle in self.valid_expression_list.drain(base_expression_count..) { self.valid_expression_set.remove(handle.index()); } @@ -894,6 +911,17 @@ impl super::Validator { } } + #[cfg(feature = "validate")] + if let Some(ref result) = fun.result { + if !self.types[result.ty.index()] + .flags + .contains(super::TypeFlags::CONSTRUCTIBLE) + { + return Err(FunctionError::NonConstructibleReturnType + .with_span_handle(result.ty, &module.types)); + } + } + self.valid_expression_set.clear(); self.valid_expression_list.clear(); for (handle, expr) in fun.expressions.iter() { diff --git a/third_party/rust/naga/src/valid/interface.rs b/third_party/rust/naga/src/valid/interface.rs index 91c121dced90..bee57e877b8a 100644 --- a/third_party/rust/naga/src/valid/interface.rs +++ b/third_party/rust/naga/src/valid/interface.rs @@ -135,6 +135,16 @@ impl VaryingContext<'_> { } self.built_ins.insert(canonical); + let required = match built_in { + Bi::ClipDistance => Capabilities::CLIP_DISTANCE, + Bi::CullDistance => Capabilities::CULL_DISTANCE, + Bi::PrimitiveIndex => Capabilities::PRIMITIVE_INDEX, + _ => Capabilities::empty(), + }; + if !self.capabilities.contains(required) { + return Err(VaryingError::UnsupportedCapability(required)); + } + let width = 4; let (visible, type_good) = match built_in { Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => ( @@ -206,21 +216,14 @@ impl VaryingContext<'_> { width: crate::BOOL_WIDTH, }, ), - Bi::PrimitiveIndex => { - if !self.capabilities.contains(Capabilities::PRIMITIVE_INDEX) { - return Err(VaryingError::UnsupportedCapability( - Capabilities::PRIMITIVE_INDEX, - )); - } - ( - self.stage == St::Fragment && !self.output, - *ty_inner - == Ti::Scalar { - kind: Sk::Uint, - width, - }, - ) - } + Bi::PrimitiveIndex => ( + self.stage == St::Fragment && !self.output, + *ty_inner + == Ti::Scalar { + kind: Sk::Uint, + width, + }, + ), Bi::SampleIndex => ( self.stage == St::Fragment && !self.output, *ty_inner diff --git a/third_party/rust/naga/src/valid/mod.rs b/third_party/rust/naga/src/valid/mod.rs index 29ef658db2a5..60178bb47ed3 100644 --- a/third_party/rust/naga/src/valid/mod.rs +++ b/third_party/rust/naga/src/valid/mod.rs @@ -82,11 +82,11 @@ bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub struct Capabilities: u8 { - /// Support for `AddressSpace:PushConstant`. + /// Support for [`AddressSpace:PushConstant`]. const PUSH_CONSTANT = 0x1; /// Float values with width = 8. const FLOAT64 = 0x2; - /// Support for `Builtin:PrimitiveIndex`. + /// Support for [`Builtin:PrimitiveIndex`]. const PRIMITIVE_INDEX = 0x4; /// Support for non-uniform indexing of sampled textures and storage buffer arrays. const SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING = 0x8; @@ -94,6 +94,10 @@ bitflags::bitflags! { const UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 0x10; /// Support for non-uniform indexing of samplers. const SAMPLER_NON_UNIFORM_INDEXING = 0x20; + /// Support for [`Builtin::ClipDistance`]. + const CLIP_DISTANCE = 0x40; + /// Support for [`Builtin::CullDistance`]. + const CULL_DISTANCE = 0x80; } } diff --git a/third_party/rust/naga/src/valid/type.rs b/third_party/rust/naga/src/valid/type.rs index 71af43e99c30..f103017dd9fd 100644 --- a/third_party/rust/naga/src/valid/type.rs +++ b/third_party/rust/naga/src/valid/type.rs @@ -4,8 +4,6 @@ use crate::{ proc::Alignment, }; -const UNIFORM_MIN_ALIGNMENT: Alignment = unsafe { Alignment::new_unchecked(16) }; - bitflags::bitflags! { /// Flags associated with [`Type`]s by [`Validator`]. /// @@ -52,20 +50,29 @@ bitflags::bitflags! { /// This type can be passed as a function argument. const ARGUMENT = 0x40; + + /// A WGSL [constructible] type. + /// + /// The constructible types are scalars, vectors, matrices, fixed-size + /// arrays of constructible types, and structs whose members are all + /// constructible. + /// + /// [constructible]: https://gpuweb.github.io/gpuweb/wgsl/#constructible + const CONSTRUCTIBLE = 0x80; } } #[derive(Clone, Copy, Debug, thiserror::Error)] pub enum Disalignment { #[error("The array stride {stride} is not a multiple of the required alignment {alignment}")] - ArrayStride { stride: u32, alignment: u32 }, + ArrayStride { stride: u32, alignment: Alignment }, #[error("The struct span {span}, is not a multiple of the required alignment {alignment}")] - StructSpan { span: u32, alignment: u32 }, + StructSpan { span: u32, alignment: Alignment }, #[error("The struct member[{index}] offset {offset} is not a multiple of the required alignment {alignment}")] MemberOffset { index: u32, offset: u32, - alignment: u32, + alignment: Alignment, }, #[error("The struct member[{index}] offset {offset} must be at least {expected}")] MemberOffsetAfterStruct { @@ -125,8 +132,8 @@ pub enum TypeError { EmptyStruct, } -// Only makes sense if `flags.contains(HOST_SHARED)` -type LayoutCompatibility = Result, (Handle, Disalignment)>; +// Only makes sense if `flags.contains(HOST_SHAREABLE)` +type LayoutCompatibility = Result, Disalignment)>; fn check_member_layout( accum: &mut LayoutCompatibility, @@ -136,20 +143,18 @@ fn check_member_layout( parent_handle: Handle, ) { *accum = match (*accum, member_layout) { - (Ok(cur_alignment), Ok(align)) => { - let align = align.unwrap().get(); - if member.offset % align != 0 { + (Ok(cur_alignment), Ok(alignment)) => { + if alignment.is_aligned(member.offset) { + Ok(cur_alignment.max(alignment)) + } else { Err(( parent_handle, Disalignment::MemberOffset { index: member_index, offset: member.offset, - alignment: align, + alignment, }, )) - } else { - let combined_alignment = ((cur_alignment.unwrap().get() - 1) | (align - 1)) + 1; - Ok(Alignment::new(combined_alignment)) } } (Err(e), _) | (_, Err(e)) => Err(e), @@ -183,13 +188,12 @@ impl TypeInfo { const fn dummy() -> Self { TypeInfo { flags: TypeFlags::empty(), - uniform_layout: Ok(None), - storage_layout: Ok(None), + uniform_layout: Ok(Alignment::ONE), + storage_layout: Ok(Alignment::ONE), } } - const fn new(flags: TypeFlags, align: u32) -> Self { - let alignment = Alignment::new(align); + const fn new(flags: TypeFlags, alignment: Alignment) -> Self { TypeInfo { flags, uniform_layout: Ok(alignment), @@ -237,8 +241,9 @@ impl super::Validator { | TypeFlags::SIZED | TypeFlags::COPY | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE | shareable, - width as u32, + Alignment::from_width(width), ) } Ti::Vector { size, kind, width } => { @@ -250,15 +255,15 @@ impl super::Validator { } else { TypeFlags::empty() }; - let count = if size >= crate::VectorSize::Tri { 4 } else { 2 }; TypeInfo::new( TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE | shareable, - count * (width as u32), + Alignment::from(size) * Alignment::from_width(width), ) } Ti::Matrix { @@ -269,14 +274,14 @@ impl super::Validator { if !self.check_width(crate::ScalarKind::Float, width) { return Err(TypeError::InvalidWidth(crate::ScalarKind::Float, width)); } - let count = if rows >= crate::VectorSize::Tri { 4 } else { 2 }; TypeInfo::new( TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE - | TypeFlags::ARGUMENT, - count * (width as u32), + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE, + Alignment::from(rows) * Alignment::from_width(width), ) } Ti::Atomic { kind, width } => { @@ -289,7 +294,7 @@ impl super::Validator { } TypeInfo::new( TypeFlags::DATA | TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE, - width as u32, + Alignment::from_width(width), ) } Ti::Pointer { base, space } => { @@ -332,7 +337,10 @@ impl super::Validator { // Pointers cannot be stored in variables, structure members, or // array elements, so we do not mark them as `DATA`. - TypeInfo::new(argument_flag | TypeFlags::SIZED | TypeFlags::COPY, 0) + TypeInfo::new( + argument_flag | TypeFlags::SIZED | TypeFlags::COPY, + Alignment::ONE, + ) } Ti::ValuePointer { size: _, @@ -359,7 +367,10 @@ impl super::Validator { // Pointers cannot be stored in variables, structure members, or // array elements, so we do not mark them as `DATA`. - TypeInfo::new(argument_flag | TypeFlags::SIZED | TypeFlags::COPY, 0) + TypeInfo::new( + argument_flag | TypeFlags::SIZED | TypeFlags::COPY, + Alignment::ONE, + ) } Ti::Array { base, size, stride } => { if base >= handle { @@ -379,42 +390,27 @@ impl super::Validator { }); } - let general_alignment = base_layout.alignment.get(); + let general_alignment = base_layout.alignment; let uniform_layout = match base_info.uniform_layout { Ok(base_alignment) => { - // combine the alignment requirements - let align = base_alignment - .unwrap() - .get() + let alignment = base_alignment .max(general_alignment) - .max(UNIFORM_MIN_ALIGNMENT.get()); - if stride % align != 0 { - Err(( - handle, - Disalignment::ArrayStride { - stride, - alignment: align, - }, - )) + .max(Alignment::MIN_UNIFORM); + if alignment.is_aligned(stride) { + Ok(alignment) } else { - Ok(Alignment::new(align)) + Err((handle, Disalignment::ArrayStride { stride, alignment })) } } Err(e) => Err(e), }; let storage_layout = match base_info.storage_layout { Ok(base_alignment) => { - let align = base_alignment.unwrap().get().max(general_alignment); - if stride % align != 0 { - Err(( - handle, - Disalignment::ArrayStride { - stride, - alignment: align, - }, - )) + let alignment = base_alignment.max(general_alignment); + if alignment.is_aligned(stride) { + Ok(alignment) } else { - Ok(Alignment::new(align)) + Err((handle, Disalignment::ArrayStride { stride, alignment })) } } Err(e) => Err(e), @@ -467,7 +463,7 @@ impl super::Validator { return Err(TypeError::NonPositiveArrayLength(const_handle)); } - TypeFlags::SIZED | TypeFlags::ARGUMENT + TypeFlags::SIZED | TypeFlags::ARGUMENT | TypeFlags::CONSTRUCTIBLE } crate::ArraySize::Dynamic => { // Non-SIZED types may only appear as the last element of a structure. @@ -495,10 +491,11 @@ impl super::Validator { | TypeFlags::COPY | TypeFlags::HOST_SHAREABLE | TypeFlags::IO_SHAREABLE - | TypeFlags::ARGUMENT, - 1, + | TypeFlags::ARGUMENT + | TypeFlags::CONSTRUCTIBLE, + Alignment::ONE, ); - ti.uniform_layout = Ok(Some(UNIFORM_MIN_ALIGNMENT)); + ti.uniform_layout = Ok(Alignment::MIN_UNIFORM); let mut min_offset = 0; @@ -523,7 +520,7 @@ impl super::Validator { ti.flags &= base_info.flags; if member.offset < min_offset { - //HACK: this could be nicer. We want to allow some structures + // HACK: this could be nicer. We want to allow some structures // to not bother with offsets/alignments if they are never // used for host sharing. if member.offset == 0 { @@ -536,7 +533,6 @@ impl super::Validator { } } - //Note: `unwrap()` is fine because `Layouter` goes first and checks this let base_size = types[member.ty].inner.size(constants); min_offset = member.offset + base_size; if min_offset > span { @@ -568,7 +564,7 @@ impl super::Validator { // the start of any following member must be at least roundUp(16, SizeOf(S)). if let Some((span, offset)) = prev_struct_data { let diff = member.offset - offset; - let min = crate::valid::Layouter::round_up(UNIFORM_MIN_ALIGNMENT, span); + let min = Alignment::MIN_UNIFORM.round_up(span); if diff < min { ti.uniform_layout = Err(( handle, @@ -603,16 +599,18 @@ impl super::Validator { } } - let alignment = self.layouter[handle].alignment.get(); - if span % alignment != 0 { + let alignment = self.layouter[handle].alignment; + if !alignment.is_aligned(span) { ti.uniform_layout = Err((handle, Disalignment::StructSpan { span, alignment })); ti.storage_layout = Err((handle, Disalignment::StructSpan { span, alignment })); } ti } - Ti::Image { .. } | Ti::Sampler { .. } => TypeInfo::new(TypeFlags::ARGUMENT, 0), - Ti::BindingArray { .. } => TypeInfo::new(TypeFlags::empty(), 0), + Ti::Image { .. } | Ti::Sampler { .. } => { + TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE) + } + Ti::BindingArray { .. } => TypeInfo::new(TypeFlags::empty(), Alignment::ONE), }) } } diff --git a/third_party/rust/wgpu-core/.cargo-checksum.json b/third_party/rust/wgpu-core/.cargo-checksum.json index 0667deda91a9..fc7507033152 100644 --- a/third_party/rust/wgpu-core/.cargo-checksum.json +++ b/third_party/rust/wgpu-core/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"73d22ddbc04b486026d12675ef898363c6eea04ae23a9251acdd1b000c73b126","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","build.rs":"fedccfe06a4d75ba68233f0756de4161213c5d25851963f5b0521d8b7292b395","src/binding_model.rs":"79f024cdb136f44066d67ec7dc56bde7fdf3bf9e89874dc4db40e504099b2822","src/command/bind.rs":"309f3f1b1719d25115d385368cff0a2c85e94da825b2930141db78235901c673","src/command/bundle.rs":"6f940e6de1e84b858790e2801ba82c83f2bc0c6afbff962576b3dd64ac315de3","src/command/clear.rs":"568aaf9d0843bada18b68980b52dd8021830c28fff36551459fad5f6baea72e1","src/command/compute.rs":"b58ae86ffbd8280af27f063d514b17c5dafee3f3ddfd5637ca050135681eb764","src/command/draw.rs":"1b9b6531b7536bc0f864ab9fdeff376993de04e33554e84c7b2db7dc65e31327","src/command/memory_init.rs":"03c3267b311f389af859615ceea8a648b402a323062cc8f0fe2690a0fb390b97","src/command/mod.rs":"c0f00529bce224972d844d2fdc9f659ffa065512086315b7bcd767501961ee1a","src/command/query.rs":"34d22d33e4713ff7ca0b345b14cdbb6177236e782b5dfb38d907215c4deb6907","src/command/render.rs":"b21201c5b9574e98c066f988bcf91b1cde0d1847fc1db683291cb059a10f3dd8","src/command/transfer.rs":"7e5e13f04fef63e036291b2838c0f0097717ec497f98f420b71296b2cc691907","src/conv.rs":"87097903e86048c9110f526f7df1749186f84cb663d75d9d40a0c467befc89ea","src/device/life.rs":"857a71da94f5f6f043f304ada7dc9ab95c6a26ed0ff63f3d64a77942e28bcafe","src/device/mod.rs":"8b886c68cd2aaec9aabdbaea0f2f256fe546ae0242fe7c9b0b8a55686f215071","src/device/queue.rs":"5fe332a0d27dafff720b19e436d991a35affd2a8031f78c2a81439a49105edd6","src/device/trace.rs":"de575a8213c8ae9df711e4b6afe5736d71ac65bf141375fe044d3b6c4375e039","src/error.rs":"34a4adbb6ec669d8de22b932363506eeef1b1115c422bcc8daa3b26f62378518","src/hub.rs":"4cc404cc79578d7a6757f74ab1fbeeb357a13a4de5f0fe87affaea8895395c8d","src/id.rs":"3ec97d09f900f34f9ad38a555ddcadb77bd9977d3d39bfad030b9b34649cf502","src/init_tracker/buffer.rs":"ccdddaace101f921463bf6c62ed5aca01a6520717a850b5d4442c3551e9f1875","src/init_tracker/mod.rs":"273c6ee67a927784a617899c6fe5560e47108248ab67cabdc2eebcba53133364","src/init_tracker/texture.rs":"d02babc4f194f91853b5e9a71bd5b20d9434842cf242f29ed9d39661bfa44980","src/instance.rs":"4a19ac634a4dd22938586e3bc554ab69f079abb2d836ef932f06cee1655d9336","src/lib.rs":"f44250478f095aa7d61fb4773692037f465d1e8df9c5626000723d4e1961166e","src/pipeline.rs":"ffabdc74656717276241b1ca2ed043fabf18795662a523828193aea99d7a9ef5","src/present.rs":"5b760e252242be41d70f09cc46b95f2bfcb8258c3482755a7bec3b5a7e4bbcb6","src/resource.rs":"50021911ff214165a32129eabc2275945c2fd22bb736fad2977634ea8ef8362d","src/track/buffer.rs":"1a7400ec55f3c16bc074c46d11b9515762b558a333d36eb236d2e7d99701bbe5","src/track/mod.rs":"3a4b07c8f1ff168609ca521b441e1e2acc00c62d7e9e4dc39cb8ab83d9813d58","src/track/range.rs":"5bbfed6e103b3234d9de8e42057022da6d628c2cc1db6bb51b88f87f2d8adf8b","src/track/stateless.rs":"593ec39e01e18048100ab0e1869f430851b83b96bd0497b8e524efda38782a46","src/track/texture.rs":"de154923e4825fa120360aae61aec27370b44196464edea6468bf933976ea20c","src/validation.rs":"27c76c48eaf3ca6be111855d7b1ab8ef94c2f73ed5d5e4f758d82799099f014b"},"package":null} \ No newline at end of file +{"files":{"Cargo.toml":"d5e071d03da58ff1154b4667fd8863b802928abcf9e732fa00b6414481a8201e","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","build.rs":"fedccfe06a4d75ba68233f0756de4161213c5d25851963f5b0521d8b7292b395","src/binding_model.rs":"630414d5f50f18ede6b52387d49d0a3528d40b4804c2339d22e28a119b961666","src/command/bind.rs":"309f3f1b1719d25115d385368cff0a2c85e94da825b2930141db78235901c673","src/command/bundle.rs":"1d8e00a4b654e3d84bb18f311e686c9387c93d7307313b0f2797ff8e66e77439","src/command/clear.rs":"681837be0d0cda73b4e8ffb22b2e9dd8a663bd7cce191a14ff83ebb3489968cd","src/command/compute.rs":"fe6d43d09583a7c71da4de7e5a7ef0318b753d09c1081b5f3f52ef80add3a442","src/command/draw.rs":"bc9fa98a0897e82e88404433716b70ab66922e5128d524a4d5a2c63c627f78e8","src/command/memory_init.rs":"03c3267b311f389af859615ceea8a648b402a323062cc8f0fe2690a0fb390b97","src/command/mod.rs":"f096b26f9bc200a2e1c279225bf1197df82746b1b5f53174647f2f9ccb846d0b","src/command/query.rs":"34d22d33e4713ff7ca0b345b14cdbb6177236e782b5dfb38d907215c4deb6907","src/command/render.rs":"bb5670d09998f40f4e73315f8a5d59be603f8be3bba75ea5f8629ad45556a891","src/command/transfer.rs":"7e5e13f04fef63e036291b2838c0f0097717ec497f98f420b71296b2cc691907","src/conv.rs":"d2a18b98fd9361aab8d32044093e57c532140a1914b33b290eb2eb1306a801b7","src/device/life.rs":"6390be9658a59b0470288c9fd81f6a94f76a0afd34eafd80c14640064b779b6c","src/device/mod.rs":"3d7d289edaa50d06992b83c4d1494084e448fcb6895c55e394598ff93d5b3d1d","src/device/queue.rs":"d77574ceb5fa573b03026b6d2c37cc7c89e1e4e8934868bf2001a3fce737a0d5","src/device/trace.rs":"9c03f5ec06cae37f294179a2285e2af7a5497db8a5572bf1dbe50136943d69be","src/error.rs":"34a4adbb6ec669d8de22b932363506eeef1b1115c422bcc8daa3b26f62378518","src/hub.rs":"d8a904e188a9168c1552765b44944a15565f2f18dc6692c03e8eb533cdee02e6","src/id.rs":"910d5ef3403b0c476ef17bbaf27270653cf50382a0e1092206004ab775a91246","src/init_tracker/buffer.rs":"ccdddaace101f921463bf6c62ed5aca01a6520717a850b5d4442c3551e9f1875","src/init_tracker/mod.rs":"273c6ee67a927784a617899c6fe5560e47108248ab67cabdc2eebcba53133364","src/init_tracker/texture.rs":"d02babc4f194f91853b5e9a71bd5b20d9434842cf242f29ed9d39661bfa44980","src/instance.rs":"8494a66e40267370fd7a61d7c73c12549f3ef88c2dd5b2e268fdf177576024f0","src/lib.rs":"5f2803283022856ac9c2534d3096cb10e0e39f3cc7ddd7c973e8beffe9b3cc1f","src/pipeline.rs":"9b3d1ce4452a585c6a5b88201799f4bebb85ad236c62098e0ebb54c43522bdcf","src/present.rs":"89d0c226a6dec63c3e4255578634fb635a46c32412fd16065717fadd71435201","src/resource.rs":"51236b4893f1471d21e3e74cecf084f201b3fb0ac89fe1398619f6caf75a64bd","src/track/buffer.rs":"30b072df6d128d9beb3d885d01e6578d3559bfa4f15c36fd0236996975bbe596","src/track/mod.rs":"73a6bb425a28b4bf639a197a2eea9134548aaca869745bd8153e276561034984","src/track/range.rs":"5bbfed6e103b3234d9de8e42057022da6d628c2cc1db6bb51b88f87f2d8adf8b","src/track/stateless.rs":"593ec39e01e18048100ab0e1869f430851b83b96bd0497b8e524efda38782a46","src/track/texture.rs":"de154923e4825fa120360aae61aec27370b44196464edea6468bf933976ea20c","src/validation.rs":"27c76c48eaf3ca6be111855d7b1ab8ef94c2f73ed5d5e4f758d82799099f014b"},"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 index aa1309128217..6ad988218d69 100644 --- a/third_party/rust/wgpu-core/Cargo.toml +++ b/third_party/rust/wgpu-core/Cargo.toml @@ -42,7 +42,7 @@ thiserror = "1" [dependencies.naga] git = "https://github.com/gfx-rs/naga" -rev = "571302e" +rev = "27d38aae" #version = "0.8" features = ["span", "validate", "wgsl-in"] @@ -58,6 +58,7 @@ version = "0.12" [target.'cfg(target_arch = "wasm32")'.dependencies] hal = { path = "../wgpu-hal", package = "wgpu-hal", version = "0.12", features = ["gles"] } +web-sys = { version = "0.3", features = ["HtmlCanvasElement"] } [target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))'.dependencies] hal = { path = "../wgpu-hal", package = "wgpu-hal", version = "0.12", features = ["metal"] } diff --git a/third_party/rust/wgpu-core/src/binding_model.rs b/third_party/rust/wgpu-core/src/binding_model.rs index ef97af6d91c6..458723a3bfb1 100644 --- a/third_party/rust/wgpu-core/src/binding_model.rs +++ b/third_party/rust/wgpu-core/src/binding_model.rs @@ -24,6 +24,8 @@ use thiserror::Error; 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(transparent)] diff --git a/third_party/rust/wgpu-core/src/command/bundle.rs b/third_party/rust/wgpu-core/src/command/bundle.rs index 6121ab8f9fe0..c756137f604a 100644 --- a/third_party/rust/wgpu-core/src/command/bundle.rs +++ b/third_party/rust/wgpu-core/src/command/bundle.rs @@ -6,6 +6,18 @@ 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 @@ -17,14 +29,11 @@ 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. -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". However, `wgpu`'s implementation of render bundles does not -take advantage of those underlying platform features. At the hal level, `wgpu` -render bundles just replay the commands. +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 @@ -105,7 +114,7 @@ pub struct RenderBundleEncoderDescriptor<'a> { pub label: Label<'a>, /// The formats of the color attachments that this render bundle is capable to rendering to. This /// must match the formats of the color attachments in the renderpass this render bundle is executed in. - pub color_formats: Cow<'a, [wgt::TextureFormat]>, + pub color_formats: Cow<'a, [Option]>, /// 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, @@ -122,7 +131,8 @@ pub struct RenderBundleEncoder { base: BasePass, parent_id: id::DeviceId, pub(crate) context: RenderPassContext, - pub(crate) is_ds_read_only: bool, + 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))] @@ -137,6 +147,20 @@ impl RenderBundleEncoder { parent_id: id::DeviceId, base: Option>, ) -> Result { + 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 { @@ -144,7 +168,7 @@ impl RenderBundleEncoder { parent_id, context: RenderPassContext { attachments: AttachmentData { - colors: if desc.color_formats.len() > hal::MAX_COLOR_TARGETS { + colors: if desc.color_formats.len() > hal::MAX_COLOR_ATTACHMENTS { return Err(CreateRenderBundleError::TooManyColorAttachments); } else { desc.color_formats.iter().cloned().collect() @@ -161,15 +185,9 @@ impl RenderBundleEncoder { }, multiview: desc.multiview, }, - is_ds_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) - } - None => false, - }, + is_depth_read_only, + is_stencil_read_only, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), }) @@ -188,7 +206,8 @@ impl RenderBundleEncoder { sample_count: 0, multiview: None, }, - is_ds_read_only: false, + is_depth_read_only: false, + is_stencil_read_only: false, current_bind_groups: BindGroupStateChange::new(), current_pipeline: StateChange::new(), @@ -236,18 +255,13 @@ impl RenderBundleEncoder { &*pipeline_guard, &*query_set_guard, ), - index: IndexState::new(), - vertex: (0..hal::MAX_VERTEX_BUFFERS) - .map(|_| VertexState::new()) - .collect(), - bind: (0..hal::MAX_BIND_GROUPS).map(|_| None).collect(), - push_constant_ranges: PushConstantState::new(), - flat_dynamic_offsets: Vec::new(), - used_bind_groups: 0, 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 pipeline_layout_id = None::>; let mut buffer_memory_init_actions = Vec::new(); let mut texture_memory_init_actions = Vec::new(); @@ -328,8 +342,6 @@ impl RenderBundleEncoder { RenderCommand::SetPipeline(pipeline_id) => { let scope = PassErrorScope::SetPipelineRender(pipeline_id); - state.pipeline = Some(pipeline_id); - let pipeline: &pipeline::RenderPipeline = state .trackers .render_pipelines @@ -344,26 +356,27 @@ impl RenderBundleEncoder { .map_err(RenderCommandError::IncompatiblePipelineTargets) .map_pass_err(scope)?; - if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH_STENCIL) - && self.is_ds_read_only + 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]; - pipeline_layout_id = Some(pipeline.layout_id.value); + let pipeline_state = PipelineState::new(pipeline_id, pipeline, layout); - state.set_pipeline( - pipeline.strip_index_format, - &pipeline.vertex_strides, - &layout.bind_group_layout_ids, - &layout.push_constant_ranges, - ); commands.push(command); - if let Some(iter) = state.flush_push_constants() { + + // 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, @@ -391,8 +404,7 @@ impl RenderBundleEncoder { offset..end, MemoryInitKind::NeedsInitializedMemory, )); - state.index.set_format(index_format); - state.index.set_buffer(buffer_id, offset..end); + state.set_index_buffer(buffer_id, index_format, offset..end); } RenderCommand::SetVertexBuffer { slot, @@ -420,7 +432,7 @@ impl RenderBundleEncoder { offset..end, MemoryInitKind::NeedsInitializedMemory, )); - state.vertex[slot as usize].set_buffer(buffer_id, offset..end); + state.vertex[slot as usize] = Some(VertexState::new(buffer_id, offset..end)); } RenderCommand::SetPushConstant { stages, @@ -431,10 +443,8 @@ impl RenderBundleEncoder { let scope = PassErrorScope::SetPushConstant; let end_offset = offset + size_bytes; - let pipeline_layout_id = pipeline_layout_id - .ok_or(DrawError::MissingPipeline) - .map_pass_err(scope)?; - let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id]; + let pipeline = state.pipeline(scope)?; + let pipeline_layout = &pipeline_layout_guard[pipeline.layout_id]; pipeline_layout .validate_push_constant_ranges(stages, offset, end_offset) @@ -451,9 +461,11 @@ impl RenderBundleEncoder { let scope = PassErrorScope::Draw { indexed: false, indirect: false, - pipeline: state.pipeline, + pipeline: state.pipeline_id(), }; - let vertex_limits = state.vertex_limits(); + 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 { @@ -473,7 +485,7 @@ impl RenderBundleEncoder { .map_pass_err(scope); } commands.extend(state.flush_vertices()); - commands.extend(state.flush_binds(base.dynamic_offsets)); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); commands.push(command); } RenderCommand::DrawIndexed { @@ -486,11 +498,17 @@ impl RenderBundleEncoder { let scope = PassErrorScope::Draw { indexed: true, indirect: false, - pipeline: state.pipeline, + 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(); - let index_limit = state.index.limit(); + 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 { @@ -508,9 +526,9 @@ impl RenderBundleEncoder { }) .map_pass_err(scope); } - commands.extend(state.index.flush()); + commands.extend(state.flush_index()); commands.extend(state.flush_vertices()); - commands.extend(state.flush_binds(base.dynamic_offsets)); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); commands.push(command); } RenderCommand::MultiDrawIndirect { @@ -522,12 +540,15 @@ impl RenderBundleEncoder { let scope = PassErrorScope::Draw { indexed: false, indirect: true, - pipeline: state.pipeline, + 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 = state .trackers .buffers @@ -545,7 +566,7 @@ impl RenderBundleEncoder { )); commands.extend(state.flush_vertices()); - commands.extend(state.flush_binds(base.dynamic_offsets)); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); commands.push(command); } RenderCommand::MultiDrawIndirect { @@ -557,12 +578,15 @@ impl RenderBundleEncoder { let scope = PassErrorScope::Draw { indexed: true, indirect: true, - pipeline: state.pipeline, + 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 = state .trackers .buffers @@ -579,9 +603,14 @@ impl RenderBundleEncoder { MemoryInitKind::NeedsInitializedMemory, )); - commands.extend(state.index.flush()); + 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(base.dynamic_offsets)); + commands.extend(state.flush_binds(used_bind_groups, base.dynamic_offsets)); commands.push(command); } RenderCommand::MultiDrawIndirect { .. } @@ -589,7 +618,7 @@ impl RenderBundleEncoder { RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(), RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(), RenderCommand::PopDebugGroup => unimplemented!(), - RenderCommand::WriteTimestamp { .. } + RenderCommand::WriteTimestamp { .. } // Must check the WRITE_TIMESTAMP_INSIDE_PASSES feature | RenderCommand::BeginPipelineStatisticsQuery { .. } | RenderCommand::EndPipelineStatisticsQuery => unimplemented!(), RenderCommand::ExecuteBundle(_) @@ -608,7 +637,8 @@ impl RenderBundleEncoder { string_data: Vec::new(), push_constant_data: Vec::new(), }, - is_ds_read_only: self.is_ds_read_only, + 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(), @@ -686,7 +716,8 @@ pub struct RenderBundle { // Normalized command stream. It can be executed verbatim, // without re-binding anything on the pipeline change. base: BasePass, - pub(super) is_ds_read_only: bool, + pub(super) is_depth_read_only: bool, + pub(super) is_stencil_read_only: bool, pub(crate) device_id: Stored, pub(crate) used: RenderBundleScope, pub(super) buffer_memory_init_actions: Vec, @@ -907,39 +938,22 @@ impl Resource for RenderBundle { /// A render bundle's current index buffer state. /// -/// [`RenderBundleEncoder::finish`] uses this to drop redundant -/// `SetIndexBuffer` commands from the final [`RenderBundle`]. It -/// records index buffer state changes here, and then calls this -/// type's [`flush`] method before any indexed draw command to produce +/// [`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. -/// -/// [`flush`]: IndexState::flush #[derive(Debug)] struct IndexState { - buffer: Option, + buffer: id::BufferId, format: wgt::IndexFormat, - pipeline_format: Option, range: Range, is_dirty: bool, } impl IndexState { - /// Return a fresh state: no index buffer has been set yet. - fn new() -> Self { - Self { - buffer: None, - format: wgt::IndexFormat::default(), - pipeline_format: None, - range: 0..0, - is_dirty: false, - } - } - /// Return the number of entries in the current index buffer. /// /// Panic if no index buffer has been set. fn limit(&self) -> u32 { - assert!(self.buffer.is_some()); let bytes_per_index = match self.format { wgt::IndexFormat::Uint16 => 2, wgt::IndexFormat::Uint32 => 4, @@ -947,13 +961,13 @@ impl IndexState { ((self.range.end - self.range.start) / bytes_per_index) as u32 } - /// Prepare for an indexed draw, producing a `SetIndexBuffer` - /// command if necessary. + /// Generate a `SetIndexBuffer` command to prepare for an indexed draw + /// command, if needed. fn flush(&mut self) -> Option { if self.is_dirty { self.is_dirty = false; Some(RenderCommand::SetIndexBuffer { - buffer_id: self.buffer.unwrap(), + buffer_id: self.buffer, index_format: self.format, offset: self.range.start, size: wgt::BufferSize::new(self.range.end - self.range.start), @@ -962,21 +976,6 @@ impl IndexState { None } } - - /// Set the current index buffer's format. - fn set_format(&mut self, format: wgt::IndexFormat) { - if self.format != format { - self.format = format; - self.is_dirty = true; - } - } - - /// Set the current index buffer. - fn set_buffer(&mut self, id: id::BufferId, range: Range) { - self.buffer = Some(id); - self.range = range; - self.is_dirty = true; - } } /// The state of a single vertex buffer slot during render bundle encoding. @@ -990,33 +989,20 @@ impl IndexState { /// [`flush`]: IndexState::flush #[derive(Debug)] struct VertexState { - buffer: Option, + buffer: id::BufferId, range: Range, - stride: wgt::BufferAddress, - rate: wgt::VertexStepMode, is_dirty: bool, } impl VertexState { - /// Construct a fresh `VertexState`: no buffer has been set for - /// this slot. - fn new() -> Self { + fn new(buffer: id::BufferId, range: Range) -> Self { Self { - buffer: None, - range: 0..0, - stride: 0, - rate: wgt::VertexStepMode::Vertex, - is_dirty: false, + buffer, + range, + is_dirty: true, } } - /// Set this slot's vertex buffer. - fn set_buffer(&mut self, buffer_id: id::BufferId, range: Range) { - self.buffer = Some(buffer_id); - self.range = range; - self.is_dirty = true; - } - /// Generate a `SetVertexBuffer` command for this slot, if necessary. /// /// `slot` is the index of the vertex buffer slot that `self` tracks. @@ -1025,7 +1011,7 @@ impl VertexState { self.is_dirty = false; Some(RenderCommand::SetVertexBuffer { slot, - buffer_id: self.buffer.unwrap(), + buffer_id: self.buffer, offset: self.range.start, size: wgt::BufferSize::new(self.range.end - self.range.start), }) @@ -1053,30 +1039,6 @@ struct BindState { is_dirty: bool, } -#[derive(Debug)] -struct PushConstantState { - ranges: ArrayVec, - is_dirty: bool, -} -impl PushConstantState { - fn new() -> Self { - Self { - ranges: ArrayVec::new(), - is_dirty: false, - } - } - - fn set_push_constants(&mut self, new_ranges: &[wgt::PushConstantRange]) -> bool { - if &*self.ranges != new_ranges { - self.ranges = new_ranges.iter().cloned().collect(); - self.is_dirty = true; - true - } else { - false - } - } -} - #[derive(Debug)] struct VertexLimitState { /// Length of the shortest vertex rate vertex buffer @@ -1089,6 +1051,64 @@ struct VertexLimitState { 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, + + /// How this pipeline's vertex shader traverses each vertex buffer, indexed + /// by vertex buffer slot number. + steps: Vec, + + /// Ranges of push constants this pipeline uses, copied from the pipeline + /// layout. + push_constant_ranges: ArrayVec, + + /// The number of bind groups this pipeline uses. + used_bind_groups: usize, +} + +impl PipelineState { + fn new( + pipeline_id: id::RenderPipelineId, + pipeline: &pipeline::RenderPipeline, + layout: &binding_model::PipelineLayout, + ) -> 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> { + 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`] @@ -1096,21 +1116,25 @@ struct VertexLimitState { /// [`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 { /// Resources used by this bundle. This will become [`RenderBundle::used`]. trackers: RenderBundleScope, - /// The current index buffer. We flush this state before indexed - /// draw commands. - index: IndexState, - - /// The state of each vertex buffer slot. - vertex: ArrayVec, + /// The currently set pipeline, if any. + pipeline: Option, /// The bind group set at each index, if any. bind: ArrayVec, { hal::MAX_BIND_GROUPS }>, - push_constant_ranges: PushConstantState, + /// The state of each vertex buffer slot. + vertex: ArrayVec, { hal::MAX_VERTEX_BUFFERS }>, + + /// The current index buffer, if one has been set. We flush this state + /// before indexed draw commands. + index: Option, /// Dynamic offset values used by the cleaned-up command sequence. /// @@ -1119,35 +1143,31 @@ struct State { /// /// [`dynamic_offsets`]: BasePass::dynamic_offsets flat_dynamic_offsets: Vec, - - used_bind_groups: usize, - pipeline: Option, } impl State { - fn vertex_limits(&self) -> VertexLimitState { + 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) in self.vertex.iter().enumerate() { - if vbs.stride == 0 { - continue; - } - let limit = ((vbs.range.end - vbs.range.start) / vbs.stride) as u32; - match vbs.rate { - wgt::VertexStepMode::Vertex => { - if limit < vert_state.vertex_limit { - vert_state.vertex_limit = limit; - vert_state.vertex_limit_slot = idx as _; + 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 _; + wgt::VertexStepMode::Instance => { + if limit < vert_state.instance_limit { + vert_state.instance_limit = limit; + vert_state.instance_limit_slot = idx as _; + } } } } @@ -1155,8 +1175,21 @@ impl State { vert_state } + /// Return the id of the current pipeline, if any. + fn pipeline_id(&self) -> Option { + 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_group_from(&mut self, index: usize) { + fn invalidate_bind_group_from(&mut self, index: usize) { for contents in self.bind[index..].iter_mut().flatten() { contents.is_dirty = true; } @@ -1190,83 +1223,106 @@ impl State { // Once we've changed the bind group at a particular index, all // subsequent indices need to be rewritten. - self.invalidate_group_from(slot as usize + 1); + self.invalidate_bind_group_from(slot as usize + 1); } - fn set_pipeline( + /// 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, - index_format: Option, - vertex_strides: &[(wgt::BufferAddress, wgt::VertexStepMode)], - layout_ids: &[id::Valid], - push_constant_layouts: &[wgt::PushConstantRange], + new: &PipelineState, + layout: &binding_model::PipelineLayout, ) { - self.index.pipeline_format = index_format; + 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; + } - for (vs, &(stride, step_mode)) in self.vertex.iter_mut().zip(vertex_strides) { - if vs.stride != stride || vs.rate != step_mode { - vs.stride = stride; - vs.rate = step_mode; - vs.is_dirty = true; + // 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); + } + } } } - - let push_constants_changed = self - .push_constant_ranges - .set_push_constants(push_constant_layouts); - - self.used_bind_groups = layout_ids.len(); - let invalid_from = if push_constants_changed { - Some(0) - } else { - self.bind - .iter() - .zip(layout_ids) - .position(|(entry, &layout_id)| match *entry { - Some(ref contents) => contents.layout_id != layout_id, - None => false, - }) - }; - if let Some(slot) = invalid_from { - self.invalidate_group_from(slot); - } } - fn flush_push_constants(&mut self) -> Option> { - let is_dirty = self.push_constant_ranges.is_dirty; - - if is_dirty { - let nonoverlapping_ranges = - super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges.ranges); - - Some( - nonoverlapping_ranges - .into_iter() - .map(|range| RenderCommand::SetPushConstant { - stages: range.stages, - offset: range.range.start, - size_bytes: range.range.end - range.range.start, - values_offset: None, - }), - ) - } else { - None + /// 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, + ) { + 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 { + self.index.as_mut().and_then(|index| index.flush()) } fn flush_vertices(&mut self) -> impl Iterator + '_ { self.vertex .iter_mut() .enumerate() - .flat_map(|(i, vs)| vs.flush(i as u32)) + .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 + '_ { // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`. - for contents in self.bind[..self.used_bind_groups].iter().flatten() { + 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()]); @@ -1275,7 +1331,7 @@ impl State { // Then, generate `SetBindGroup` commands to update the dirty bind // groups. After this, all bind groups are clean. - self.bind[..self.used_bind_groups] + self.bind[..used_bind_groups] .iter_mut() .enumerate() .flat_map(|(i, entry)| { diff --git a/third_party/rust/wgpu-core/src/command/clear.rs b/third_party/rust/wgpu-core/src/command/clear.rs index 3e94843798f0..bf0c90ff491c 100644 --- a/third_party/rust/wgpu-core/src/command/clear.rs +++ b/third_party/rust/wgpu-core/src/command/clear.rs @@ -408,7 +408,7 @@ fn clear_texture_via_render_passes( for depth_or_layer in layer_or_depth_range { let color_attachments_tmp; let (color_attachments, depth_stencil_attachment) = if is_color { - color_attachments_tmp = [hal::ColorAttachment { + 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, @@ -416,7 +416,7 @@ fn clear_texture_via_render_passes( resolve_target: None, ops: hal::AttachmentOps::STORE, clear_value: wgt::Color::TRANSPARENT, - }]; + })]; (&color_attachments_tmp[..], None) } else { ( diff --git a/third_party/rust/wgpu-core/src/command/compute.rs b/third_party/rust/wgpu-core/src/command/compute.rs index d81c726ad294..c58bd4d64f21 100644 --- a/third_party/rust/wgpu-core/src/command/compute.rs +++ b/third_party/rust/wgpu-core/src/command/compute.rs @@ -9,7 +9,7 @@ use crate::{ BasePass, BasePassRef, BindGroupStateChange, CommandBuffer, CommandEncoderError, CommandEncoderStatus, MapPassErr, PassErrorScope, QueryUseError, StateChange, }, - device::MissingDownlevelFlags, + device::{MissingDownlevelFlags, MissingFeatures}, error::{ErrorFormatter, PrettyError}, hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, id, @@ -43,11 +43,24 @@ pub enum ComputeCommand { 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, @@ -179,6 +192,8 @@ pub enum ComputePassErrorInner { #[error(transparent)] QueryUse(#[from] QueryUseError), #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), + #[error(transparent)] MissingDownlevelFlags(#[from] MissingDownlevelFlags), } @@ -253,6 +268,7 @@ impl State { 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, @@ -260,6 +276,7 @@ impl State { bind_group_guard: &Storage, id::BindGroupId>, buffer_guard: &Storage, id::BufferId>, texture_guard: &Storage, id::TextureId>, + indirect_buffer: Option>, ) -> Result<(), UsageConflict> { for id in self.binder.list_active() { unsafe { @@ -280,6 +297,13 @@ impl State { } } + // 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); @@ -569,6 +593,7 @@ impl Global { &*bind_group_guard, &*buffer_guard, &*texture_guard, + None, ) .map_pass_err(scope)?; @@ -644,6 +669,7 @@ impl Global { &*bind_group_guard, &*buffer_guard, &*texture_guard, + Some(id::Valid(buffer_id)), ) .map_pass_err(scope)?; unsafe { @@ -685,6 +711,10 @@ impl Global { } => { let scope = PassErrorScope::WriteTimestamp; + device + .require_features(wgt::Features::WRITE_TIMESTAMP_INSIDE_PASSES) + .map_pass_err(scope)?; + let query_set: &resource::QuerySet = cmd_buf .trackers .query_sets diff --git a/third_party/rust/wgpu-core/src/command/draw.rs b/third_party/rust/wgpu-core/src/command/draw.rs index 2206f3f204d7..33ee68554807 100644 --- a/third_party/rust/wgpu-core/src/command/draw.rs +++ b/third_party/rust/wgpu-core/src/command/draw.rs @@ -87,10 +87,12 @@ pub enum RenderCommandError { MissingTextureUsage(#[from] MissingTextureUsageError), #[error(transparent)] PushConstants(#[from] PushConstantUploadError), - #[error("Invalid Viewport parameters")] - InvalidViewport, - #[error("Invalid ScissorRect parameters")] - InvalidScissorRect, + #[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, wgt::Extent3d), #[error("Support for {0} is not implemented yet")] Unimplemented(&'static str), } @@ -170,13 +172,32 @@ pub enum RenderCommand { depth_max: f32, }, SetScissor(Rect), + + /// 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, - /// None means there is no data and the data should be an array of zeros. + + /// Index in [`BasePass::push_constant_data`] of the start of the data + /// to be written. /// - /// Facilitates clears in renderbundles which explicitly do their clears. + /// 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, }, Draw { diff --git a/third_party/rust/wgpu-core/src/command/mod.rs b/third_party/rust/wgpu-core/src/command/mod.rs index 47ee9c0eccbf..4c42251af858 100644 --- a/third_party/rust/wgpu-core/src/command/mod.rs +++ b/third_party/rust/wgpu-core/src/command/mod.rs @@ -287,6 +287,10 @@ pub struct BasePass { /// instruction consumes the next `len` bytes from this vector. pub string_data: Vec, + /// Data used by `SetPushConstant` instructions. + /// + /// See the documentation for [`RenderCommand::SetPushConstant`] + /// and [`ComputeCommand::SetPushConstant`] for details. pub push_constant_data: Vec, } diff --git a/third_party/rust/wgpu-core/src/command/render.rs b/third_party/rust/wgpu-core/src/command/render.rs index 7ef45aa0a647..0b2c0acd420c 100644 --- a/third_party/rust/wgpu-core/src/command/render.rs +++ b/third_party/rust/wgpu-core/src/command/render.rs @@ -17,7 +17,7 @@ use crate::{ hub::{Global, GlobalIdentityHandlerFactory, HalApi, Storage, Token}, id, init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction}, - pipeline::PipelineFlags, + pipeline::{self, PipelineFlags}, resource::{self, Buffer, Texture, TextureView}, track::{TextureSelector, UsageConflict, UsageScope}, validation::{ @@ -131,20 +131,40 @@ pub struct RenderPassDepthStencilAttachment { } impl RenderPassDepthStencilAttachment { - fn is_read_only(&self, aspects: hal::FormatAspects) -> Result { - if aspects.contains(hal::FormatAspects::DEPTH) && !self.depth.read_only { - return Ok(false); + /// 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 (self.depth.load_op, self.depth.store_op) != (LoadOp::Load, StoreOp::Store) { - return Err(RenderPassErrorInner::InvalidDepthOps); + + 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; } - if aspects.contains(hal::FormatAspects::STENCIL) && !self.stencil.read_only { - return Ok(false); - } - if (self.stencil.load_op, self.stencil.store_op) != (LoadOp::Load, StoreOp::Store) { - return Err(RenderPassErrorInner::InvalidStencilOps); - } - Ok(true) + + Ok((depth_read_only, stencil_read_only)) } } @@ -153,7 +173,7 @@ impl RenderPassDepthStencilAttachment { pub struct RenderPassDescriptor<'a> { pub label: Label<'a>, /// The color attachments of the render pass. - pub color_attachments: Cow<'a, [RenderPassColorAttachment]>, + pub color_attachments: Cow<'a, [Option]>, /// The depth and stencil attachment of the render pass, if any. pub depth_stencil_attachment: Option<&'a RenderPassDepthStencilAttachment>, } @@ -162,7 +182,7 @@ pub struct RenderPassDescriptor<'a> { pub struct RenderPass { base: BasePass, parent_id: id::CommandEncoderId, - color_targets: ArrayVec, + color_targets: ArrayVec, { hal::MAX_COLOR_ATTACHMENTS }>, depth_stencil_target: Option, // Resource binding dedupe state. @@ -278,16 +298,17 @@ impl IndexState { #[derive(Clone, Copy, Debug)] struct VertexBufferState { total_size: BufferAddress, - stride: BufferAddress, - rate: VertexStepMode, + step: pipeline::VertexStep, bound: bool, } impl VertexBufferState { const EMPTY: Self = Self { total_size: 0, - stride: 0, - rate: VertexStepMode::Vertex, + step: pipeline::VertexStep { + stride: 0, + mode: VertexStepMode::Vertex, + }, bound: false, }; } @@ -312,11 +333,11 @@ impl VertexState { self.vertex_limit = u32::MAX; self.instance_limit = u32::MAX; for (idx, vbs) in self.inputs.iter().enumerate() { - if vbs.stride == 0 || !vbs.bound { + if vbs.step.stride == 0 || !vbs.bound { continue; } - let limit = (vbs.total_size / vbs.stride) as u32; - match vbs.rate { + 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; @@ -420,7 +441,7 @@ pub enum RenderPassErrorInner { InvalidDepthStencilAttachmentFormat(wgt::TextureFormat), #[error("attachment format {0:?} can not be resolved")] UnsupportedResolveTargetFormat(wgt::TextureFormat), - #[error("necessary attachments are missing")] + #[error("missing color or depth_stencil attachments, at least one is required.")] MissingAttachments, #[error("attachments have differing sizes: {previous:?} is followed by {mismatch:?}")] AttachmentsDimensionMismatch { @@ -474,8 +495,18 @@ pub enum RenderPassErrorInner { ResourceUsageConflict(#[from] UsageConflict), #[error("render bundle has incompatible targets, {0}")] IncompatibleBundleTargets(#[from] RenderPassCompatibilityError), - #[error("render bundle has an incompatible read-only depth/stencil flag: bundle is {bundle}, while the pass is {pass}")] - IncompatibleBundleRods { pass: bool, bundle: bool }, + #[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." + )] + IncompatibleBundleRods { + pass_depth: bool, + pass_stencil: bool, + bundle_depth: bool, + bundle_stencil: bool, + }, #[error(transparent)] RenderCommand(#[from] RenderCommandError), #[error(transparent)] @@ -558,14 +589,15 @@ impl TextureView { } } -const MAX_TOTAL_ATTACHMENTS: usize = hal::MAX_COLOR_TARGETS + hal::MAX_COLOR_TARGETS + 1; +const MAX_TOTAL_ATTACHMENTS: usize = hal::MAX_COLOR_ATTACHMENTS + hal::MAX_COLOR_ATTACHMENTS + 1; type AttachmentDataVec = ArrayVec; struct RenderPassInfo<'a, A: HalApi> { context: RenderPassContext, usage_scope: UsageScope, render_attachments: AttachmentDataVec>, // All render attachments, including depth/stencil - is_ds_read_only: bool, + is_depth_read_only: bool, + is_stencil_read_only: bool, extent: wgt::Extent3d, _phantom: PhantomData, @@ -614,7 +646,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { fn start( device: &Device, label: Option<&str>, - color_attachments: &[RenderPassColorAttachment], + color_attachments: &[Option], depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, cmd_buf: &mut CommandBuffer, view_guard: &'a Storage, id::TextureViewId>, @@ -626,7 +658,8 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { // We default to false intentionally, even if depth-stencil isn't used at all. // This allows us to use the primary raw pipeline in `RenderPipeline`, // instead of the special read-only one, which would be `None`. - let mut is_ds_read_only = false; + let mut is_depth_read_only = false; + let mut is_stencil_read_only = false; let mut render_attachments = AttachmentDataVec::::new(); let mut discarded_surfaces = AttachmentDataVec::new(); @@ -689,15 +722,19 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { expected: sample_count, }); } + if sample_count != 1 && sample_count != 4 { + return Err(RenderPassErrorInner::InvalidSampleCount(sample_count)); + } attachment_type_name = type_name; Ok(()) }; - let mut colors = ArrayVec::, { hal::MAX_COLOR_TARGETS }>::new(); + let mut colors = + ArrayVec::>, { hal::MAX_COLOR_ATTACHMENTS }>::new(); let mut depth_stencil = None; if let Some(at) = depth_stencil_attachment { - let view = cmd_buf + let view: &TextureView = cmd_buf .trackers .views .add_single(&*view_guard, at.view) @@ -786,8 +823,9 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { } } - let usage = if at.is_read_only(ds_aspects)? { - is_ds_read_only = true; + (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 @@ -806,7 +844,13 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { } for at in color_attachments { - let color_view = cmd_buf + let at = if let Some(attachment) = at.as_ref() { + attachment + } else { + colors.push(None); + continue; + }; + let color_view: &TextureView = cmd_buf .trackers .views .add_single(&*view_guard, at.view) @@ -836,7 +880,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { let mut hal_resolve_target = None; if let Some(resolve_target) = at.resolve_target { - let resolve_view = cmd_buf + let resolve_view: &TextureView = cmd_buf .trackers .views .add_single(&*view_guard, resolve_target) @@ -885,7 +929,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { }); } - colors.push(hal::ColorAttachment { + colors.push(Some(hal::ColorAttachment { target: hal::Attachment { view: &color_view.raw, usage: hal::TextureUses::COLOR_TARGET, @@ -893,28 +937,30 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { resolve_target: hal_resolve_target, ops: at.channel.hal_ops(), clear_value: at.channel.clear_value, - }); + })); } - if sample_count != 1 && sample_count != 4 { - return Err(RenderPassErrorInner::InvalidSampleCount(sample_count)); - } + 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| view_guard.get(at.view).unwrap()) + .map(|at| at.as_ref().map(|at| view_guard.get(at.view).unwrap())) .collect(), resolves: color_attachments .iter() - .filter_map(|at| at.resolve_target) - .map(|attachment| view_guard.get(attachment).unwrap()) + .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 extent = extent.ok_or(RenderPassErrorInner::MissingAttachments)?; - let multiview = detected_multiview.expect("Multiview was not detected, no attachments"); let context = RenderPassContext { attachments: view_data.map(|view| view.desc.format), sample_count, @@ -937,7 +983,8 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> { context, usage_scope: UsageScope::new(buffer_guard, texture_guard), render_attachments, - is_ds_read_only, + is_depth_read_only, + is_stencil_read_only, extent, _phantom: PhantomData, pending_discard_init_fixups, @@ -1041,7 +1088,7 @@ impl Global { &self, encoder_id: id::CommandEncoderId, base: BasePassRef, - color_attachments: &[RenderPassColorAttachment], + color_attachments: &[Option], depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>, ) -> Result<(), RenderPassError> { profiling::scope!("run_render_pass", "CommandEncoder"); @@ -1156,7 +1203,7 @@ impl Global { ); dynamic_offset_count += num_dynamic_offsets as usize; - let bind_group = cmd_buf + let bind_group: &crate::binding_model::BindGroup = cmd_buf .trackers .bind_groups .add_single(&*bind_group_guard, bind_group_id) @@ -1220,7 +1267,7 @@ impl Global { let scope = PassErrorScope::SetPipelineRender(pipeline_id); state.pipeline = Some(pipeline_id); - let pipeline = cmd_buf + let pipeline: &pipeline::RenderPipeline = cmd_buf .trackers .render_pipelines .add_single(&*render_pipeline_guard, pipeline_id) @@ -1234,8 +1281,10 @@ impl Global { state.pipeline_flags = pipeline.flags; - if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH_STENCIL) - && info.is_ds_read_only + 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); @@ -1304,24 +1353,25 @@ impl Global { state.index.pipeline_format = pipeline.strip_index_format; - let vertex_strides_len = pipeline.vertex_strides.len(); - state.vertex.buffers_required = vertex_strides_len as u32; + let vertex_steps_len = pipeline.vertex_steps.len(); + state.vertex.buffers_required = vertex_steps_len as u32; - while state.vertex.inputs.len() < vertex_strides_len { + // 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); } - // Update vertex buffer limits - for (vbs, &(stride, rate)) in - state.vertex.inputs.iter_mut().zip(&pipeline.vertex_strides) - { - vbs.stride = stride; - vbs.rate = rate; - } - for vbs in state.vertex.inputs.iter_mut().skip(vertex_strides_len) { - vbs.stride = 0; - vbs.rate = VertexStepMode::Vertex; + // 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 { @@ -1451,14 +1501,17 @@ impl Global { depth_max, } => { let scope = PassErrorScope::SetViewport; - if rect.w <= 0.0 - || rect.h <= 0.0 - || depth_min < 0.0 - || depth_min > 1.0 - || depth_max < 0.0 - || depth_max > 1.0 - { - return Err(RenderCommandError::InvalidViewport).map_pass_err(scope); + 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, @@ -1510,7 +1563,8 @@ impl Global { || rect.x + rect.w > info.extent.width || rect.y + rect.h > info.extent.height { - return Err(RenderCommandError::InvalidScissorRect).map_pass_err(scope); + return Err(RenderCommandError::InvalidScissorRect(*rect, info.extent)) + .map_pass_err(scope); } let r = hal::Rect { x: rect.x, @@ -1827,6 +1881,10 @@ impl Global { } => { let scope = PassErrorScope::WriteTimestamp; + device + .require_features(wgt::Features::WRITE_TIMESTAMP_INSIDE_PASSES) + .map_pass_err(scope)?; + let query_set: &resource::QuerySet = cmd_buf .trackers .query_sets @@ -1886,10 +1944,14 @@ impl Global { .map_err(RenderPassErrorInner::IncompatibleBundleTargets) .map_pass_err(scope)?; - if info.is_ds_read_only != bundle.is_ds_read_only { + if (info.is_depth_read_only && !bundle.is_depth_read_only) + || (info.is_stencil_read_only && !bundle.is_stencil_read_only) + { return Err(RenderPassErrorInner::IncompatibleBundleRods { - pass: info.is_ds_read_only, - bundle: bundle.is_ds_read_only, + 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); } diff --git a/third_party/rust/wgpu-core/src/conv.rs b/third_party/rust/wgpu-core/src/conv.rs index 9ab3cbbecb23..fd59a25eb47e 100644 --- a/third_party/rust/wgpu-core/src/conv.rs +++ b/third_party/rust/wgpu-core/src/conv.rs @@ -116,14 +116,7 @@ pub fn check_texture_dimension_size( use wgt::TextureDimension::*; let (extent_limits, sample_limit) = match dimension { - D1 => ( - [ - limits.max_texture_dimension_1d, - 1, - limits.max_texture_array_layers, - ], - 1, - ), + D1 => ([limits.max_texture_dimension_1d, 1, 1], 1), D2 => ( [ limits.max_texture_dimension_2d, diff --git a/third_party/rust/wgpu-core/src/device/life.rs b/third_party/rust/wgpu-core/src/device/life.rs index 5062f9189fc6..87d84633bd7a 100644 --- a/third_party/rust/wgpu-core/src/device/life.rs +++ b/third_party/rust/wgpu-core/src/device/life.rs @@ -224,6 +224,8 @@ struct ActiveSubmission { 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, } @@ -459,7 +461,7 @@ impl LifetimeTracker { impl LifetimeTracker { /// Identify resources to free, according to `trackers` and `self.suspected_resources`. /// - /// Given `trackers`, the [`TrackerSet`] belonging to same [`Device`] as + /// 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 diff --git a/third_party/rust/wgpu-core/src/device/mod.rs b/third_party/rust/wgpu-core/src/device/mod.rs index 0832bedd6963..f79c95038fa5 100644 --- a/third_party/rust/wgpu-core/src/device/mod.rs +++ b/third_party/rust/wgpu-core/src/device/mod.rs @@ -52,15 +52,15 @@ pub enum HostMap { #[derive(Clone, Debug, Hash, PartialEq)] #[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct AttachmentData { - pub colors: ArrayVec, - pub resolves: ArrayVec, + pub colors: ArrayVec, { hal::MAX_COLOR_ATTACHMENTS }>, + pub resolves: ArrayVec, pub depth_stencil: Option, } impl Eq for AttachmentData {} impl AttachmentData { pub(crate) fn map U>(&self, fun: F) -> AttachmentData { AttachmentData { - colors: self.colors.iter().map(&fun).collect(), + 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), } @@ -78,8 +78,8 @@ pub(crate) struct RenderPassContext { pub enum RenderPassCompatibilityError { #[error("Incompatible color attachment: the renderpass expected {0:?} but was given {1:?}")] IncompatibleColorAttachment( - ArrayVec, - ArrayVec, + ArrayVec, { hal::MAX_COLOR_ATTACHMENTS }>, + ArrayVec, { hal::MAX_COLOR_ATTACHMENTS }>, ), #[error( "Incompatible depth-stencil attachment: the renderpass expected {0:?} but was given {1:?}" @@ -428,6 +428,9 @@ impl Device { /// 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 @@ -439,7 +442,7 @@ impl Device { fn maintain<'this, 'token: 'this, G: GlobalIdentityHandlerFactory>( &'this self, hub: &Hub, - force_wait: bool, + maintain: wgt::Maintain, token: &mut Token<'token, Self>, ) -> Result<(UserClosures, bool), WaitIdleError> { profiling::scope!("maintain", "Device"); @@ -463,14 +466,21 @@ impl Device { ); life_tracker.triage_mapped(hub, token); - let last_done_index = if force_wait { - let current_index = self.active_submission_index; + 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, current_index, CLEANUP_WAIT_MS) + .wait(&self.fence, index_to_wait_for, CLEANUP_WAIT_MS) .map_err(DeviceError::from)? }; - current_index + index_to_wait_for } else { unsafe { self.raw @@ -566,6 +576,14 @@ impl Device { transient: bool, ) -> Result, 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, + }); + } + let mut usage = conv::map_buffer_usage(desc.usage); if desc.usage.is_empty() { @@ -668,47 +686,10 @@ impl Device { adapter: &crate::instance::Adapter, desc: &resource::TextureDescriptor, ) -> Result, resource::CreateTextureError> { - let format_desc = desc.format.describe(); - - if desc.dimension != wgt::TextureDimension::D2 { - // Depth textures can only be 2D - if format_desc.sample_type == wgt::TextureSampleType::Depth { - return Err(resource::CreateTextureError::InvalidDepthDimension( - desc.dimension, - desc.format, - )); - } - // Renderable textures can only be 2D - if desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { - return Err(resource::CreateTextureError::InvalidDimensionUsages( - wgt::TextureUsages::RENDER_ATTACHMENT, - desc.dimension, - )); - } - - // Compressed textures can only be 2D - if format_desc.is_compressed() { - return Err(resource::CreateTextureError::InvalidCompressedDimension( - desc.dimension, - desc.format, - )); - } - } - - let format_features = self - .describe_format_features(adapter, desc.format) - .map_err(|error| resource::CreateTextureError::MissingFeatures(desc.format, error))?; + use resource::{CreateTextureError, TextureDimensionError}; if desc.usage.is_empty() { - return Err(resource::CreateTextureError::EmptyUsage); - } - - let missing_allowed_usages = desc.usage - format_features.allowed_usages; - if !missing_allowed_usages.is_empty() { - return Err(resource::CreateTextureError::InvalidFormatUsages( - missing_allowed_usages, - desc.format, - )); + return Err(CreateTextureError::EmptyUsage); } conv::check_texture_dimension_size( @@ -718,15 +699,114 @@ impl Device { &self.limits, )?; + let format_desc = desc.format.describe(); + + if desc.dimension != wgt::TextureDimension::D2 { + // Depth textures can only be 2D + if format_desc.sample_type == wgt::TextureSampleType::Depth { + 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 format_desc.is_compressed() { + return Err(CreateTextureError::InvalidCompressedDimension( + desc.dimension, + desc.format, + )); + } + } + + if format_desc.is_compressed() { + let block_width = format_desc.block_dimensions.0 as u32; + let block_height = format_desc.block_dimensions.1 as u32; + + 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, + }, + )); + } + } + + 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_desc + .guaranteed_format_features + .flags + .contains(wgt::TextureFormatFeatureFlags::MULTISAMPLE) + { + return Err(CreateTextureError::InvalidMultisampledFormat(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(resource::CreateTextureError::InvalidMipLevelCount { + return Err(CreateTextureError::InvalidMipLevelCount { requested: mips, maximum: max_levels_allowed, }); } + let format_features = self + .describe_format_features(adapter, desc.format) + .map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?; + + let missing_allowed_usages = desc.usage - format_features.allowed_usages; + if !missing_allowed_usages.is_empty() { + return Err(CreateTextureError::InvalidFormatUsages( + missing_allowed_usages, + desc.format, + )); + } + + // TODO: validate missing TextureDescriptor::view_formats. + // Enforce having COPY_DST/DEPTH_STENCIL_WRIT/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 format_desc.sample_type == wgt::TextureSampleType::Depth { @@ -1117,6 +1197,18 @@ impl Device { } pipeline::ShaderModuleSource::Naga(module) => (module, String::new()), }; + 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"); @@ -1370,6 +1462,20 @@ impl Device { } _ => (), } + 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 @@ -2359,9 +2465,11 @@ impl Device { .map_or(&[][..], |fragment| &fragment.targets); let depth_stencil_state = desc.depth_stencil.as_ref(); - if !color_targets.is_empty() && { - let first = &color_targets[0]; - color_targets[1..] + 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) } { @@ -2372,13 +2480,14 @@ impl Device { let mut io = validation::StageIo::default(); let mut validated_stages = wgt::ShaderStages::empty(); - let mut vertex_strides = Vec::with_capacity(desc.vertex.buffers.len()); + 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_strides - .alloc() - .init((vb_state.array_stride, vb_state.step_mode)); + vertex_steps.alloc().init(pipeline::VertexStep { + stride: vb_state.array_stride, + mode: vb_state.step_mode, + }); if vb_state.attributes.is_empty() { continue; } @@ -2473,29 +2582,32 @@ impl Device { } for (i, cs) in color_targets.iter().enumerate() { - let error = loop { - 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)); - } - if cs.blend.is_some() && !format_features.flags.contains(Tfff::FILTERABLE) { - 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.contains(Tfff::MULTISAMPLE) - { - break Some(pipeline::ColorStateError::FormatNotMultisampled(cs.format)); - } + if let Some(cs) = cs.as_ref() { + let error = loop { + 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)); + } + if cs.blend.is_some() && !format_features.flags.contains(Tfff::FILTERABLE) { + 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.contains(Tfff::MULTISAMPLE) + { + break Some(pipeline::ColorStateError::FormatNotMultisampled(cs.format)); + } - break None; - }; - if let Some(e) = error { - return Err(pipeline::CreateRenderPipelineError::ColorState(i as u8, e)); + break None; + }; + if let Some(e) = error { + return Err(pipeline::CreateRenderPipelineError::ColorState(i as u8, e)); + } } } @@ -2647,13 +2759,13 @@ impl Device { }; if validated_stages.contains(wgt::ShaderStages::FRAGMENT) { - for (i, state) in color_targets.iter().enumerate() { - match io.get(&(i as wgt::ShaderLocation)) { - Some(output) => { + 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, + *i as u8, pipeline::ColorStateError::IncompatibleFormat { pipeline, shader: output.ty, @@ -2662,11 +2774,14 @@ impl Device { }, )?; } - None if state.write_mask.is_empty() => {} - None => { - log::warn!("Missing fragment output[{}], expected {:?}", i, state,); + Some(&None) => { + return Err( + pipeline::CreateRenderPipelineError::InvalidFragmentOutputLocation(*i), + ); + } + _ => { return Err(pipeline::CreateRenderPipelineError::ColorState( - i as u8, + *i as u8, pipeline::ColorStateError::Missing, )); } @@ -2700,6 +2815,14 @@ impl Device { self.require_features(wgt::Features::MULTIVIEW)?; } + for size in shader_binding_sizes.values() { + if size.get() % 16 != 0 { + self.require_downlevel_flags( + wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED, + )?; + } + } + let late_sized_buffer_groups = Device::make_late_sized_buffer_groups(&shader_binding_sizes, layout, &*bgl_guard); @@ -2735,7 +2858,10 @@ impl Device { let pass_context = RenderPassContext { attachments: AttachmentData { - colors: color_targets.iter().map(|state| state.format).collect(), + 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), }, @@ -2744,7 +2870,7 @@ impl Device { }; let mut flags = pipeline::PipelineFlags::empty(); - for state in color_targets.iter() { + 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; @@ -2755,8 +2881,11 @@ impl Device { if ds.stencil.is_enabled() && ds.stencil.needs_ref_value() { flags |= pipeline::PipelineFlags::STENCIL_REFERENCE; } - if !ds.is_read_only() { - flags |= pipeline::PipelineFlags::WRITES_DEPTH_STENCIL; + if !ds.is_depth_read_only() { + flags |= pipeline::PipelineFlags::WRITES_DEPTH; + } + if !ds.is_stencil_read_only() { + flags |= pipeline::PipelineFlags::WRITES_STENCIL; } } @@ -2773,7 +2902,7 @@ impl Device { pass_context, flags, strip_index_format: desc.primitive.strip_index_format, - vertex_strides, + vertex_steps, late_sized_buffer_groups, life_guard: LifeGuard::new(desc.label.borrow_or_default()), }; @@ -3003,12 +3132,12 @@ impl Global { .map_err(|_| instance::IsSurfaceSupportedError::InvalidSurface)?; Ok(adapter.is_surface_supported(surface)) } - pub fn surface_get_preferred_format( + pub fn surface_get_supported_formats( &self, surface_id: id::SurfaceId, adapter_id: id::AdapterId, - ) -> Result { - profiling::scope!("surface_get_preferred_format"); + ) -> Result, instance::GetSurfacePreferredFormatError> { + profiling::scope!("Surface::get_supported_formats"); let hub = A::hub(self); let mut token = Token::root(); @@ -3021,7 +3150,7 @@ impl Global { .get(surface_id) .map_err(|_| instance::GetSurfacePreferredFormatError::InvalidSurface)?; - surface.get_preferred_format(adapter) + surface.get_supported_formats(adapter) } pub fn device_features( @@ -3221,6 +3350,18 @@ impl Global { 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(&self, id_in: Input, label: Label) { + let hub = A::hub(self); + let mut token = Token::root(); + let fid = hub.textures.prepare(id_in); + + let (_, mut token) = hub.devices.read(&mut token); + fid.assign_error(label.borrow_or_default(), &mut token); + } + #[cfg(feature = "replay")] pub fn device_wait_for_buffer( &self, @@ -4368,7 +4509,8 @@ impl Global { desc: trace::new_render_bundle_encoder_descriptor( desc.label.clone(), &bundle_encoder.context, - bundle_encoder.is_ds_read_only, + bundle_encoder.is_depth_read_only, + bundle_encoder.is_stencil_read_only, ), base: bundle_encoder.to_base_pass(), }); @@ -4957,16 +5099,25 @@ impl Global { pub fn device_poll( &self, device_id: id::DeviceId, - force_wait: bool, + maintain: wgt::Maintain, ) -> Result { 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, force_wait, &mut token)? + .maintain(hub, maintain, &mut token)? }; closures.fire(); @@ -4994,7 +5145,12 @@ impl Global { let (device_guard, mut token) = hub.devices.read(&mut token); for (id, device) in device_guard.iter(A::VARIANT) { - let (cbs, queue_empty) = device.maintain(hub, force_wait, &mut token)?; + 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 diff --git a/third_party/rust/wgpu-core/src/device/queue.rs b/third_party/rust/wgpu-core/src/device/queue.rs index 83de498ef656..f7c394c0bec3 100644 --- a/third_party/rust/wgpu-core/src/device/queue.rs +++ b/third_party/rust/wgpu-core/src/device/queue.rs @@ -12,7 +12,7 @@ use crate::{ id, init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange}, resource::{BufferAccessError, BufferMapState, TextureInner}, - track, FastHashSet, + track, FastHashSet, SubmissionIndex, }; use hal::{CommandEncoder as _, Device as _, Queue as _}; @@ -30,8 +30,8 @@ const WRITE_COMMAND_BUFFERS_PER_POOL: usize = 64; #[repr(C)] pub struct SubmittedWorkDoneClosureC { - callback: unsafe extern "C" fn(user_data: *mut u8), - user_data: *mut u8, + pub callback: unsafe extern "C" fn(user_data: *mut u8), + pub user_data: *mut u8, } unsafe impl Send for SubmittedWorkDoneClosureC {} @@ -79,6 +79,13 @@ impl SubmittedWorkDoneClosure { } } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WrappedSubmissionIndex { + pub queue_id: id::QueueId, + pub index: SubmissionIndex, +} + struct StagingData { buffer: A::Buffer, } @@ -620,10 +627,10 @@ impl Global { &self, queue_id: id::QueueId, command_buffer_ids: &[id::CommandBufferId], - ) -> Result<(), QueueSubmitError> { + ) -> Result { profiling::scope!("submit", "Queue"); - let callbacks = { + let (submit_index, callbacks) = { let hub = A::hub(self); let mut token = Token::root(); @@ -958,23 +965,27 @@ impl Global { // 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, false, &mut token) { + let (closures, _) = match device.maintain(hub, wgt::Maintain::Wait, &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!(), }; device.pending_writes.temp_resources = pending_write_resources; device.temp_suspected.clear(); device.lock_life(&mut token).post_submit(); - closures + (submit_index, closures) }; // the closures should execute with nothing locked! callbacks.fire(); - Ok(()) + Ok(WrappedSubmissionIndex { + queue_id, + index: submit_index, + }) } pub fn queue_get_timestamp_period( diff --git a/third_party/rust/wgpu-core/src/device/trace.rs b/third_party/rust/wgpu-core/src/device/trace.rs index 3e0c79182958..8892a8e0163b 100644 --- a/third_party/rust/wgpu-core/src/device/trace.rs +++ b/third_party/rust/wgpu-core/src/device/trace.rs @@ -13,17 +13,17 @@ pub const FILE_NAME: &str = "trace.ron"; pub(crate) fn new_render_bundle_encoder_descriptor<'a>( label: crate::Label<'a>, context: &'a super::RenderPassContext, - is_ds_read_only: bool, + 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| { - let aspects = hal::FormatAspects::from(format); wgt::RenderBundleDepthStencil { format, - depth_read_only: is_ds_read_only && aspects.contains(hal::FormatAspects::DEPTH), - stencil_read_only: is_ds_read_only && aspects.contains(hal::FormatAspects::STENCIL), + depth_read_only, + stencil_read_only, } }), sample_count: context.sample_count, @@ -176,7 +176,7 @@ pub enum Command { }, RunRenderPass { base: crate::command::BasePass, - target_colors: Vec, + target_colors: Vec>, target_depth_stencil: Option, }, } diff --git a/third_party/rust/wgpu-core/src/hub.rs b/third_party/rust/wgpu-core/src/hub.rs index 4e8ddf78f6f9..1a817f1e7ceb 100644 --- a/third_party/rust/wgpu-core/src/hub.rs +++ b/third_party/rust/wgpu-core/src/hub.rs @@ -930,6 +930,18 @@ impl Global { hal_instance_callback(hal_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!("new", "Global"); + Self { + instance, + surfaces: Registry::without_backend(&factory, "Surface"), + hubs: Hubs::new(&factory), + } + } + pub fn clear_backend(&self, _dummy: ()) { let mut surface_guard = self.surfaces.data.write(); let hub = A::hub(self); diff --git a/third_party/rust/wgpu-core/src/id.rs b/third_party/rust/wgpu-core/src/id.rs index 095585723559..ea28fec642b3 100644 --- a/third_party/rust/wgpu-core/src/id.rs +++ b/third_party/rust/wgpu-core/src/id.rs @@ -22,6 +22,35 @@ 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` value identifies a value stored in a [`Global`]'s [`Hub`]'s [`Storage`]. +/// `Storage` implements [`Index`] and [`IndexMut`], accepting `Id` values as indices. +/// +/// ## Note on `Id` typing +/// +/// You might assume that an `Id` can only be used to retrieve a resource of +/// type `T`, but that is not quite the case. The id types in `wgpu-core`'s +/// public API ([`TextureId`], for example) can refer to resources belonging to +/// any backend, but the corresponding resource types ([`Texture`], for +/// example) are always parameterized by a specific backend `A`. +/// +/// So the `T` in `Id` is usually a resource type like `Texture`, +/// where [`Empty`] is the `wgpu_hal` dummy back end. These empty types are +/// never actually used, beyond just making sure you access each `Storage` with +/// the right kind of identifier. The members of [`Hub`] pair up each +/// `X` type with the resource type `X`, for some specific backend +/// `A`. +/// +/// [`Global`]: crate::hub::Global +/// [`Hub`]: crate::hub::Hub +/// [`Hub`]: crate::hub::Hub +/// [`Storage`]: crate::hub::Storage +/// [`Texture`]: crate::resource::Texture +/// [`Index`]: std::ops::Index +/// [`IndexMut`]: std::ops::IndexMut +/// [`Registry`]: crate::hub::Registry +/// [`Empty`]: hal::api::Empty #[repr(transparent)] #[cfg_attr(feature = "trace", derive(serde::Serialize), serde(into = "SerialId"))] #[cfg_attr( diff --git a/third_party/rust/wgpu-core/src/instance.rs b/third_party/rust/wgpu-core/src/instance.rs index 907c65d08cef..c0efedcf9574 100644 --- a/third_party/rust/wgpu-core/src/instance.rs +++ b/third_party/rust/wgpu-core/src/instance.rs @@ -23,8 +23,8 @@ pub struct HalSurface { #[error("Limit '{name}' value {requested} is better than allowed {allowed}")] pub struct FailedLimit { name: &'static str, - requested: u32, - allowed: u32, + requested: u64, + allowed: u64, } fn check_limits(requested: &wgt::Limits, allowed: &wgt::Limits) -> Vec { @@ -152,23 +152,12 @@ impl crate::hub::Resource for Surface { } impl Surface { - pub fn get_preferred_format( + pub fn get_supported_formats( &self, adapter: &Adapter, - ) -> Result { - // Check the four formats mentioned in the WebGPU spec. - // Also, prefer sRGB over linear as it is better in - // representing perceived colors. - let preferred_formats = [ - wgt::TextureFormat::Bgra8UnormSrgb, - wgt::TextureFormat::Rgba8UnormSrgb, - wgt::TextureFormat::Bgra8Unorm, - wgt::TextureFormat::Rgba8Unorm, - wgt::TextureFormat::Rgba16Float, - ]; - + ) -> Result, GetSurfacePreferredFormatError> { let suf = A::get_surface(self); - let caps = unsafe { + let mut caps = unsafe { profiling::scope!("surface_capabilities"); adapter .raw @@ -177,11 +166,10 @@ impl Surface { .ok_or(GetSurfacePreferredFormatError::UnsupportedQueueFamily)? }; - preferred_formats - .iter() - .cloned() - .find(|preferred| caps.formats.contains(preferred)) - .ok_or(GetSurfacePreferredFormatError::NotFound) + // TODO: maybe remove once we support texture view changing srgb-ness + caps.formats.sort_by_key(|f| !f.describe().srgb); + + Ok(caps.formats) } } @@ -354,8 +342,6 @@ pub enum IsSurfaceSupportedError { #[derive(Clone, Debug, Error)] pub enum GetSurfacePreferredFormatError { - #[error("no suitable format found")] - NotFound, #[error("invalid adapter")] InvalidAdapter, #[error("invalid surface")] @@ -493,6 +479,52 @@ impl Global { 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, + ) -> SurfaceId { + profiling::scope!("create_surface_webgl_canvas", "Instance"); + + let surface = Surface { + presentation: None, + gl: self.instance.gl.as_ref().map(|inst| HalSurface { + raw: { + inst.create_surface_from_canvas(canvas) + .expect("Create surface from canvas") + }, + }), + }; + + 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_offscreen_canvas( + &self, + canvas: &web_sys::OffscreenCanvas, + id_in: Input, + ) -> SurfaceId { + profiling::scope!("create_surface_webgl_offscreen_canvas", "Instance"); + + let surface = Surface { + presentation: None, + gl: self.instance.gl.as_ref().map(|inst| HalSurface { + raw: { + inst.create_surface_from_offscreen_canvas(canvas) + .expect("Create surface from offscreen canvas") + }, + }), + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + #[cfg(dx12)] /// # Safety /// diff --git a/third_party/rust/wgpu-core/src/lib.rs b/third_party/rust/wgpu-core/src/lib.rs index 8d28a3df2692..be3c4c33680a 100644 --- a/third_party/rust/wgpu-core/src/lib.rs +++ b/third_party/rust/wgpu-core/src/lib.rs @@ -48,7 +48,7 @@ pub mod resource; mod track; mod validation; -pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_TARGETS, MAX_VERTEX_BUFFERS}; +pub use hal::{api, MAX_BIND_GROUPS, MAX_COLOR_ATTACHMENTS, MAX_VERTEX_BUFFERS}; use atomic::{AtomicUsize, Ordering}; @@ -238,36 +238,50 @@ If you are running this program on native and not in a browser and wish to work Adapter::downlevel_properties or Device::downlevel_properties to get a listing of the features the current \ platform supports."; -/// Call a `Global` method, dispatching dynamically to the appropriate back end. +/// Dispatch on an [`Id`]'s backend to a backend-generic method. /// /// Uses of this macro have the form: /// /// ```ignore /// -/// gfx_select!(id => global.method(args...)) +/// gfx_select!(id => value.method(args...)) /// /// ``` /// -/// where `id` is some [`id::Id`] resource id, `global` is a [`hub::Global`], -/// and `method` is any method on [`Global`] that takes a single generic -/// parameter that implements [`hal::Api`] (for example, -/// [`Global::device_create_buffer`]). +/// This expands to an expression that calls `value.method::(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. /// -/// The `wgpu-core` crate can support multiple back ends simultaneously (Vulkan, -/// Metal, etc.), depending on features and availability. Each [`Id`]'s value -/// indicates which back end its resource belongs to. This macro does a switch -/// on `id`'s back end, and calls the `Global` method specialized for that 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: /// -/// Internally to `wgpu-core`, most types take the back end (some type that -/// implements `hal::Api`) as a generic parameter, so their methods are compiled -/// with full knowledge of which back end they're working with. This macro -/// serves as the boundary between dynamic `Id` values provided by `wgpu-core`'s -/// users and the crate's mostly-monomorphized implementation, selecting the -/// `hal::Api` implementation appropriate to the `Id` value's back end. +/// ```ignore +/// gfx_select!(device_id => global.device_create_buffer(device_id, ...)) +/// ``` /// -/// [`Global`]: hub::Global -/// [`Global::device_create_buffer`]: hub::Global::device_create_buffer +/// where the `device_create_buffer` method is defined like this: +/// +/// ```ignore +/// impl<...> Global<...> { +/// pub fn device_create_buffer(&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`. +/// +/// 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 { diff --git a/third_party/rust/wgpu-core/src/pipeline.rs b/third_party/rust/wgpu-core/src/pipeline.rs index 34a57efcd030..8c31d23e896b 100644 --- a/third_party/rust/wgpu-core/src/pipeline.rs +++ b/third_party/rust/wgpu-core/src/pipeline.rs @@ -124,6 +124,24 @@ pub enum CreateShaderModuleError { Validation(#[from] ShaderError>), #[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 { + match *self { + CreateShaderModuleError::Parsing(ref err) => err.inner.location(source), + CreateShaderModuleError::Validation(ref err) => err.inner.location(source), + _ => None, + } + } } /// Describes a programmable pipeline stage. @@ -231,7 +249,7 @@ 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, [wgt::ColorTargetState]>, + pub targets: Cow<'a, [Option]>, } /// Describes a render (graphics) pipeline. @@ -299,6 +317,8 @@ pub enum CreateRenderPipelineError { Device(#[from] DeviceError), #[error("pipeline layout is invalid")] InvalidLayout, + #[error("fragment output @location({0}) is invalid")] + InvalidFragmentOutputLocation(u32), #[error("unable to derive an implicit layout")] Implicit(#[from] ImplicitLayoutError), #[error("color state [{0}] is invalid")] @@ -352,7 +372,27 @@ bitflags::bitflags! { pub struct PipelineFlags: u32 { const BLEND_CONSTANT = 1 << 0; const STENCIL_REFERENCE = 1 << 1; - const WRITES_DEPTH_STENCIL = 1 << 2; + 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, + } } } @@ -364,7 +404,7 @@ pub struct RenderPipeline { pub(crate) pass_context: RenderPassContext, pub(crate) flags: PipelineFlags, pub(crate) strip_index_format: Option, - pub(crate) vertex_strides: Vec<(wgt::BufferAddress, wgt::VertexStepMode)>, + pub(crate) vertex_steps: Vec, pub(crate) late_sized_buffer_groups: ArrayVec, pub(crate) life_guard: LifeGuard, } diff --git a/third_party/rust/wgpu-core/src/present.rs b/third_party/rust/wgpu-core/src/present.rs index fa7a34bf9d3b..8f7182a9be2d 100644 --- a/third_party/rust/wgpu-core/src/present.rs +++ b/third_party/rust/wgpu-core/src/present.rs @@ -123,7 +123,12 @@ impl Global { let _ = device; let suf = A::get_surface_mut(surface); - let (texture_id, status) = match unsafe { suf.raw.acquire_texture(FRAME_TIMEOUT_MS) } { + let (texture_id, status) = match unsafe { + suf.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"), diff --git a/third_party/rust/wgpu-core/src/resource.rs b/third_party/rust/wgpu-core/src/resource.rs index 6f45e26399a4..10812271d4d1 100644 --- a/third_party/rust/wgpu-core/src/resource.rs +++ b/third_party/rust/wgpu-core/src/resource.rs @@ -47,8 +47,8 @@ unsafe impl Sync for BufferMapState {} #[repr(C)] pub struct BufferMapCallbackC { - callback: unsafe extern "C" fn(status: BufferMapAsyncStatus, user_data: *mut u8), - user_data: *mut u8, + pub callback: unsafe extern "C" fn(status: BufferMapAsyncStatus, user_data: *mut u8), + pub user_data: *mut u8, } unsafe impl Send for BufferMapCallbackC {} @@ -174,6 +174,8 @@ pub enum CreateBufferError { EmptyUsage, #[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 }, } impl Resource for Buffer { @@ -330,14 +332,28 @@ pub enum TextureErrorDimension { pub enum TextureDimensionError { #[error("Dimension {0:?} is zero")] Zero(TextureErrorDimension), - #[error("Dimension {0:?} value {given} exceeds the limit of {limit}")] + #[error("Dimension {dim:?} value {given} exceeds the limit of {limit}")] LimitExceeded { dim: TextureErrorDimension, given: u32, limit: u32, }, - #[error("sample count {0} is invalid")] + #[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)] @@ -360,6 +376,12 @@ pub enum CreateTextureError { InvalidFormatUsages(wgt::TextureUsages, 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("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), } diff --git a/third_party/rust/wgpu-core/src/track/buffer.rs b/third_party/rust/wgpu-core/src/track/buffer.rs index 7770b423087a..2ece124a8135 100644 --- a/third_party/rust/wgpu-core/src/track/buffer.rs +++ b/third_party/rust/wgpu-core/src/track/buffer.rs @@ -467,8 +467,8 @@ impl BufferTracker { /// 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. + /// 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 /// @@ -477,15 +477,15 @@ impl BufferTracker { pub unsafe fn set_and_remove_from_usage_scope_sparse( &mut self, scope: &mut BufferUsageScope, - bind_group_state: &BufferBindGroupState, + id_source: impl IntoIterator>, ) { let incoming_size = scope.state.len(); if incoming_size > self.start.len() { self.set_size(incoming_size); } - for &(id, ref ref_count, _) in bind_group_state.buffers.iter() { - let (index32, epoch, _) = id.0.unzip(); + for id in id_source { + let (index32, _, _) = id.0.unzip(); let index = index32 as usize; scope.debug_assert_in_bounds(index); @@ -504,9 +504,8 @@ impl BufferTracker { state: &scope.state, }, None, - ResourceMetadataProvider::Direct { - epoch, - ref_count: Cow::Borrowed(ref_count), + ResourceMetadataProvider::Indirect { + metadata: &scope.metadata, }, &mut self.temp, ); diff --git a/third_party/rust/wgpu-core/src/track/mod.rs b/third_party/rust/wgpu-core/src/track/mod.rs index 8d38ff8e725a..f1a43b3297ce 100644 --- a/third_party/rust/wgpu-core/src/track/mod.rs +++ b/third_party/rust/wgpu-core/src/track/mod.rs @@ -690,9 +690,10 @@ impl Tracker { /// 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 [`Self::drain`] is needed to get those transitions. + /// 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 @@ -714,7 +715,7 @@ impl Tracker { bind_group: &BindGroupStates, ) { self.buffers - .set_and_remove_from_usage_scope_sparse(&mut scope.buffers, &bind_group.buffers); + .set_and_remove_from_usage_scope_sparse(&mut scope.buffers, bind_group.buffers.used()); self.textures.set_and_remove_from_usage_scope_sparse( textures, &mut scope.textures, diff --git a/third_party/rust/wgpu-hal/.cargo-checksum.json b/third_party/rust/wgpu-hal/.cargo-checksum.json index d06e73b39999..9d30d62bcca3 100644 --- a/third_party/rust/wgpu-hal/.cargo-checksum.json +++ b/third_party/rust/wgpu-hal/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"61752b031b63d9ce967085f9a43eb3dbbad3b472bd264612a88faf3e7d6fcd57","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","README.md":"78377f5876fafd77963eff7e3c2ba3a7e3ad5cf9201b09ed5612e49c2288eb18","examples/halmark/main.rs":"fefa4f8d16f1a40156e0c0ce7aee06569b222a7a6284b69a000adeebb34a915d","examples/halmark/shader.wgsl":"59e3628abe34c66708bf0106658e791ef24357df3cae72194d34ff07b40e8007","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"d6db84b269b934409ef85fa31914b3f4bc7e7012e40269aad3eff5454eae2a09","src/auxil/dxgi/conv.rs":"310a23866e652257e3dca55c85c78420118e6dea4e69ff907db4a52bda9ac1c5","src/auxil/dxgi/exception.rs":"f8d69d3d475e03c4d96d22778e5a6f322afd98fcfafb1414cd4a76239fa97a37","src/auxil/dxgi/factory.rs":"82451fcfcc1f73a570ae9e708c94efa9c125d269dfb7396de97da5b32f8a4090","src/auxil/dxgi/mod.rs":"63db737b48378d4843e2f7904f104790688029ff614bc80387cd9efe444f1e52","src/auxil/dxgi/result.rs":"20c8eb03d738062dff198feca6327addb9882ed0462be842c789eadf7dca0573","src/auxil/mod.rs":"f899555124ad6d44f5326ef935f4911702539fd933ec2ab07c6009badb3ea22c","src/auxil/renderdoc.rs":"3a4da908ebd6230177ca55c541c8278639e83d78badb4595a941aea30dd7f80f","src/dx11/adapter.rs":"bf123464ef748d021f2e0c40d27b3f6bdd50222c6f91cce6d25686a912eef093","src/dx11/command.rs":"cdad8dcdb800acba56c931f1726ddada652af18db0f066465af643f82a034492","src/dx11/device.rs":"76ac52095c639482adc2058509cd3acafd49cebc0694fcd64f8d9f53abc823de","src/dx11/instance.rs":"3bbf2730956472cb8023bd8fbd2d53e49f93c5e4ce3d14664112a293a165d191","src/dx11/library.rs":"0da08a780eefa7ff50f2e0998117202f26e5dd3d3a433c58b585801cff9863d2","src/dx11/mod.rs":"e4f7c6100e1bec479b41f3e3af96e01d53e6597c1c3a8fcde6f14cc9eb8537f8","src/dx12/adapter.rs":"3d830a70684c568a0b3f226beecc8e0dd311c3efd2b1be2caa629f688e98511e","src/dx12/command.rs":"e48636f686f4ff9efc1758f4e54522aeda284d27439c87c6a669a55352294d58","src/dx12/conv.rs":"e1bc82d9f0c019bb67aa7ee8d59e4677c047e56fee4ce3154ebc50e5388850cd","src/dx12/descriptor.rs":"7145d3dc6be13fae4cf6bb8bf34a1ea1749ad87e5f429b84f3cbbea7bf63c148","src/dx12/device.rs":"1dd830070de6e0a755164f96408d50e5c8a1bbfee539a1183a57c8f93c79e669","src/dx12/instance.rs":"ccc36443cb1df8ab8ed8366cf8599ec3d75fb5fefa5f9bb0f0f0b5e6fc1c5102","src/dx12/mod.rs":"e88f7396dca4aba859a6e28d3f9de64a57a0df85acd53cecd6ada3d96386062c","src/dx12/view.rs":"b7a5cb8933f30517a97b4bd767e10b4c09f686dbf493d53b9b265d2d0b16f1a6","src/empty.rs":"6bf65e405f63eff49b25d079af644b352b95a9c7edcf3a57be2e96a50307b66b","src/gles/adapter.rs":"47403c6cf736659b6c035873346e0aa1760b8b4b5763e64b9783e1358e599ba0","src/gles/command.rs":"31c85f3841131dc34553f7a66339396650ceb19763fa6c194c10fb4a5a3fc07e","src/gles/conv.rs":"1462ce906a4fe83139cc8375e385f8ce5a15d70588b81083ae8d5d9104f4457e","src/gles/device.rs":"66c30c4010f410bf3b8a03ee9d8e14753832fa2b6e17b518481281f06e3d7cd9","src/gles/egl.rs":"16516ef1ad62a976996a1b2123fd89ce6835a8468a2915841efd558516bb8b4f","src/gles/mod.rs":"75612e8ddd91735ba7b1bb7ecb58210b7b8469bde9671e437206c010600d16a2","src/gles/queue.rs":"b6dd8404ff53f1f9a8c9de87d4b78bd42468c146560d26fb585801d813919dab","src/gles/shaders/clear.frag":"aac702eed9ece5482db5ba6783a678b119a5e7802b1ecf93f4975dee8acab0b3","src/gles/shaders/clear.vert":"8f636168e1da2cac48091c466a543c3b09fb4a0dd8c60c1c9bf34cc890766740","src/gles/shaders/present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"083500c0b36d079a82754895d06b993ea8ed4393690b226c85f07cbec373a730","src/lib.rs":"dbd24a5fa263412c16cf821e6ff51ebda07608776accbd8b0bfb792435740619","src/metal/adapter.rs":"78f4a9eff186ab919e7c8900c08cd0b3325e15bf1656d5dcdae30a96a9d76f87","src/metal/command.rs":"b06983d7e11cdde526b7c9f5f0b86f1ea8faef02b7666367cb231211a8301570","src/metal/conv.rs":"2349ec6331a7a471c06615be249dc22b808742aca222e6d8861662d848b0c094","src/metal/device.rs":"dd823c8e12ba3ed69ef7cdcb543e8d995d0056d1f838516b0901068c83d8ffe2","src/metal/mod.rs":"c4f3959732f5f506fa881aa5812205a6452d6a946d661d7f81d1c7785359a10c","src/metal/surface.rs":"82836cadc751d94fb016bd590cdfec5649cbfae2f44d14599ed074dfb0a004dc","src/vulkan/adapter.rs":"90c4f57483589a09d9840c3f93efb8da66bc9eb5be975899877aa0192f86e4bd","src/vulkan/command.rs":"60d1867acd0e46c34dabecea708cd776a1f435721b6673a506b5bb8aee87ff80","src/vulkan/conv.rs":"b480f9d1cde0df92d6f9a07e8a42b86aaeb251f9b0692038286f4994caf45fec","src/vulkan/device.rs":"9b264c74f581345be889f1ed61ad6f7ab22e12e04183eb954dbfed1681c32d0c","src/vulkan/instance.rs":"c078d529f6955a662a3adc7739ffdb8a01b83dbef8dd1e2c3810d232b82cbb18","src/vulkan/mod.rs":"1ba41f2ea7650dc0757c1444ef62c95a8aa0f6671d98c73b4ea80eb4ea60f289"},"package":null} \ No newline at end of file +{"files":{"Cargo.toml":"187fffaaf3f370c2fac8a1060093b5c11c680711ed067b5d5a607178f93e4b30","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","README.md":"78377f5876fafd77963eff7e3c2ba3a7e3ad5cf9201b09ed5612e49c2288eb18","examples/halmark/main.rs":"dc6e304e13882ba49f7cf81ed62982cb2dc1d239410e703d074a7a40db0ba362","examples/halmark/shader.wgsl":"59e3628abe34c66708bf0106658e791ef24357df3cae72194d34ff07b40e8007","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"4fcf856aac125c7de14665fb3032ff457782ba03446e96f77845e5d5623ec23e","src/auxil/dxgi/conv.rs":"310a23866e652257e3dca55c85c78420118e6dea4e69ff907db4a52bda9ac1c5","src/auxil/dxgi/exception.rs":"f8d69d3d475e03c4d96d22778e5a6f322afd98fcfafb1414cd4a76239fa97a37","src/auxil/dxgi/factory.rs":"82451fcfcc1f73a570ae9e708c94efa9c125d269dfb7396de97da5b32f8a4090","src/auxil/dxgi/mod.rs":"63db737b48378d4843e2f7904f104790688029ff614bc80387cd9efe444f1e52","src/auxil/dxgi/result.rs":"20c8eb03d738062dff198feca6327addb9882ed0462be842c789eadf7dca0573","src/auxil/mod.rs":"f899555124ad6d44f5326ef935f4911702539fd933ec2ab07c6009badb3ea22c","src/auxil/renderdoc.rs":"3a4da908ebd6230177ca55c541c8278639e83d78badb4595a941aea30dd7f80f","src/dx11/adapter.rs":"fe0c5f92e70520a9097e109ada0eeb135eb01eaa20918069bbfec56fa241bb8b","src/dx11/command.rs":"cdad8dcdb800acba56c931f1726ddada652af18db0f066465af643f82a034492","src/dx11/device.rs":"76ac52095c639482adc2058509cd3acafd49cebc0694fcd64f8d9f53abc823de","src/dx11/instance.rs":"3bbf2730956472cb8023bd8fbd2d53e49f93c5e4ce3d14664112a293a165d191","src/dx11/library.rs":"0da08a780eefa7ff50f2e0998117202f26e5dd3d3a433c58b585801cff9863d2","src/dx11/mod.rs":"41aead56ca3e00fd5e11d8f67ea3284f305247ac298089a0216dc7396b49bf57","src/dx12/adapter.rs":"cfbb6eb8b98d861457f43795e20e8c6badd12148b31722ff126ae0c065b1855e","src/dx12/command.rs":"7612cd93a97edce571c2215b3bfbbfe545daa13eaf8d83a0cedd7ca785f2e069","src/dx12/conv.rs":"996ebf03b9826ba568a4e00c088eba972c1fce706671b1d8007a8945154c7ca7","src/dx12/descriptor.rs":"7145d3dc6be13fae4cf6bb8bf34a1ea1749ad87e5f429b84f3cbbea7bf63c148","src/dx12/device.rs":"ea715d3b31c697678e610d1696ceb36fc7a46df6647cce2dd1b7dd00e4639298","src/dx12/instance.rs":"ccc36443cb1df8ab8ed8366cf8599ec3d75fb5fefa5f9bb0f0f0b5e6fc1c5102","src/dx12/mod.rs":"3c2c1d6c2c0774a841b81b56748f7b9ecf97e24a80ec6554703903350bb027d7","src/dx12/view.rs":"b7a5cb8933f30517a97b4bd767e10b4c09f686dbf493d53b9b265d2d0b16f1a6","src/empty.rs":"389ea75882d0974c26649bb028aacdade28f4e0ea418a55970118e2de949e6fc","src/gles/adapter.rs":"0dab3dfa4c9277ba40ed8224f31aac8f3255c56f2dad39680dec9bf2d901f796","src/gles/command.rs":"306301fcf2ca75fb6a638b2c69dc0597aa50b2ebb16eaca6ec7005b4cfd84a30","src/gles/conv.rs":"1462ce906a4fe83139cc8375e385f8ce5a15d70588b81083ae8d5d9104f4457e","src/gles/device.rs":"54555c751a99c5327b21561a5ef1d2c3b4aee34db24b926d4393c2ec5cb8d8ad","src/gles/egl.rs":"e4263e63112a53001cc8cb8527ac2cc2e8004f8502272aaf5212a6e2c12de31b","src/gles/mod.rs":"6a90b7e84c699fed4b0ef2ab1a97e35337095eba36275116191678c3232601f2","src/gles/queue.rs":"3aee4a426a9ef6b62b1085780b42ea920c9a2a889894d5bccb968c98ed990879","src/gles/shaders/clear.frag":"aac702eed9ece5482db5ba6783a678b119a5e7802b1ecf93f4975dee8acab0b3","src/gles/shaders/clear.vert":"8f636168e1da2cac48091c466a543c3b09fb4a0dd8c60c1c9bf34cc890766740","src/gles/shaders/present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"24722575d4bb818dcc22b75f58af27f163a8b4d8284242e9f8ae5a27280086c7","src/lib.rs":"491c9aa88310a6dd7bb3fe6524edd7fbd88ad7e7733d42ae728ef3d74c23e62d","src/metal/adapter.rs":"bac7ba96fbea9aca823777442e72dc3fd407db7d890e2982b68926d179478770","src/metal/command.rs":"bae3efabc3bff841ebe1e584e25a8e9172ddaffdf3f3d363ba08f2aad43a68a8","src/metal/conv.rs":"2349ec6331a7a471c06615be249dc22b808742aca222e6d8861662d848b0c094","src/metal/device.rs":"a3bfbd9fd1db6eb465bc5d4732e441383c19deb19d617c74a451c8cca93ba509","src/metal/mod.rs":"c4f3959732f5f506fa881aa5812205a6452d6a946d661d7f81d1c7785359a10c","src/metal/surface.rs":"9ec61f4162b85b8a653e426bae037a36f66d62a300c62eea75129dc34f370a59","src/vulkan/adapter.rs":"617a9a5cba8139e1e4784ec2fe7833f3d04c593828aee3c960b57317a6722b90","src/vulkan/command.rs":"ddc38c2732ba34eb065d0785f923fb226cf7f95c39f5a9c8d7fcfa1c90cab38b","src/vulkan/conv.rs":"b480f9d1cde0df92d6f9a07e8a42b86aaeb251f9b0692038286f4994caf45fec","src/vulkan/device.rs":"2323b201cc594a3dd0ee1f552a655389bfd8bd3fcb6b763cfba179d255669bfe","src/vulkan/instance.rs":"27a7bed9d0a7304e4f1da57ff008457ef7904a03a952a33a70d32d8edf61de8b","src/vulkan/mod.rs":"93eb2f416669f810cd748e003a1d90cd9942f2aee365c89d01efd0a003d4e5c6"},"package":null} \ No newline at end of file diff --git a/third_party/rust/wgpu-hal/Cargo.toml b/third_party/rust/wgpu-hal/Cargo.toml index caad696d40c3..38c36293d4c0 100644 --- a/third_party/rust/wgpu-hal/Cargo.toml +++ b/third_party/rust/wgpu-hal/Cargo.toml @@ -90,16 +90,19 @@ wasm-bindgen = { version = "0.2" } web-sys = { version = "0.3", features = ["Window", "HtmlCanvasElement", "WebGl2RenderingContext"] } js-sys = { version = "0.3" } +[target.'cfg(target_os = "android")'.dependencies] +android_system_properties = "0.1.1" + [dependencies.naga] git = "https://github.com/gfx-rs/naga" -rev = "571302e" +rev = "27d38aae" #version = "0.8" # DEV dependencies [dev-dependencies.naga] git = "https://github.com/gfx-rs/naga" -rev = "571302e" +rev = "27d38aae" #version = "0.8" features = ["wgsl-in"] diff --git a/third_party/rust/wgpu-hal/examples/halmark/main.rs b/third_party/rust/wgpu-hal/examples/halmark/main.rs index ff021a889d23..383efcdc5371 100644 --- a/third_party/rust/wgpu-hal/examples/halmark/main.rs +++ b/third_party/rust/wgpu-hal/examples/halmark/main.rs @@ -238,11 +238,11 @@ impl Example { }, depth_stencil: None, multisample: wgt::MultisampleState::default(), - color_targets: &[wgt::ColorTargetState { + color_targets: &[Some(wgt::ColorTargetState { format: surface_config.format, blend: Some(wgt::BlendState::ALPHA_BLENDING), write_mask: wgt::ColorWrites::default(), - }], + })], multiview: None, }; let pipeline = unsafe { device.create_render_pipeline(&pipeline_desc).unwrap() }; @@ -614,7 +614,7 @@ impl Example { let ctx = &mut self.contexts[self.context_index]; - let surface_tex = unsafe { self.surface.acquire_texture(!0).unwrap().unwrap().texture }; + let surface_tex = unsafe { self.surface.acquire_texture(None).unwrap().unwrap().texture }; let target_barrier0 = hal::TextureBarrier { texture: surface_tex.borrow(), @@ -646,7 +646,7 @@ impl Example { depth_or_array_layers: 1, }, sample_count: 1, - color_attachments: &[hal::ColorAttachment { + color_attachments: &[Some(hal::ColorAttachment { target: hal::Attachment { view: &surface_tex_view, usage: hal::TextureUses::COLOR_TARGET, @@ -659,7 +659,7 @@ impl Example { b: 0.3, a: 1.0, }, - }], + })], depth_stencil_attachment: None, multiview: None, }; diff --git a/third_party/rust/wgpu-hal/examples/raw-gles.rs b/third_party/rust/wgpu-hal/examples/raw-gles.rs index 34ad0214782e..1620794fb019 100644 --- a/third_party/rust/wgpu-hal/examples/raw-gles.rs +++ b/third_party/rust/wgpu-hal/examples/raw-gles.rs @@ -163,7 +163,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height depth_or_array_layers: 1, }, sample_count: 1, - color_attachments: &[hal::ColorAttachment { + color_attachments: &[Some(hal::ColorAttachment { target: hal::Attachment { view: &view, usage: hal::TextureUses::COLOR_TARGET, @@ -171,7 +171,7 @@ fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height resolve_target: None, ops: hal::AttachmentOps::STORE, clear_value: wgt::Color::BLUE, - }], + })], depth_stencil_attachment: None, multiview: None, }; diff --git a/third_party/rust/wgpu-hal/src/dx11/adapter.rs b/third_party/rust/wgpu-hal/src/dx11/adapter.rs index 576eb810f655..bfae7b2030e6 100644 --- a/third_party/rust/wgpu-hal/src/dx11/adapter.rs +++ b/third_party/rust/wgpu-hal/src/dx11/adapter.rs @@ -130,6 +130,7 @@ impl super::Adapter { if feature_level >= FL11_0 { downlevel |= wgt::DownlevelFlags::INDIRECT_EXECUTION; + downlevel |= wgt::DownlevelFlags::WEBGPU_TEXTURE_FORMAT_SUPPORT; features |= wgt::Features::TEXTURE_COMPRESSION_BC; } @@ -219,6 +220,8 @@ impl super::Adapter { max_compute_workgroup_size_y: max_workgroup_size_xy, max_compute_workgroup_size_z: max_workgroup_size_z, max_compute_workgroups_per_dimension, + // D3D11_BUFFER_DESC represents the buffer size as a 32 bit int. + max_buffer_size: u32::MAX as u64, }; // diff --git a/third_party/rust/wgpu-hal/src/dx11/mod.rs b/third_party/rust/wgpu-hal/src/dx11/mod.rs index ff9c49e0b3e4..a77bb95919a1 100644 --- a/third_party/rust/wgpu-hal/src/dx11/mod.rs +++ b/third_party/rust/wgpu-hal/src/dx11/mod.rs @@ -124,7 +124,7 @@ impl crate::Surface for Surface { unsafe fn acquire_texture( &mut self, - timeout_ms: u32, + _timeout: Option, ) -> Result>, crate::SurfaceError> { todo!() } diff --git a/third_party/rust/wgpu-hal/src/dx12/adapter.rs b/third_party/rust/wgpu-hal/src/dx12/adapter.rs index 039eb9ab11ee..7d4b993e6863 100644 --- a/third_party/rust/wgpu-hal/src/dx12/adapter.rs +++ b/third_party/rust/wgpu-hal/src/dx12/adapter.rs @@ -41,6 +41,10 @@ impl super::Adapter { } } + pub fn raw_adapter(&self) -> &native::DxgiAdapter { + &self.raw + } + #[allow(trivial_casts)] pub(super) fn expose( adapter: native::DxgiAdapter, @@ -198,6 +202,7 @@ impl super::Adapter { | wgt::Features::VERTEX_WRITABLE_STORAGE | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgt::Features::TIMESTAMP_QUERY + | wgt::Features::WRITE_TIMESTAMP_INSIDE_PASSES | wgt::Features::TEXTURE_COMPRESSION_BC | wgt::Features::CLEAR_TEXTURE | wgt::Features::TEXTURE_FORMAT_16BIT_NORM; @@ -278,6 +283,7 @@ impl super::Adapter { max_compute_workgroup_size_z: d3d12::D3D12_CS_THREAD_GROUP_MAX_Z, max_compute_workgroups_per_dimension: d3d12::D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION, + max_buffer_size: u64::MAX, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new( diff --git a/third_party/rust/wgpu-hal/src/dx12/command.rs b/third_party/rust/wgpu-hal/src/dx12/command.rs index dc653911c5f9..ca2f036430bc 100644 --- a/third_party/rust/wgpu-hal/src/dx12/command.rs +++ b/third_party/rust/wgpu-hal/src/dx12/command.rs @@ -579,11 +579,15 @@ impl crate::CommandEncoder for super::CommandEncoder { unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { self.begin_pass(super::PassKind::Render, desc.label); - - let mut color_views = [native::CpuDescriptor { ptr: 0 }; crate::MAX_COLOR_TARGETS]; + let mut color_views = [native::CpuDescriptor { ptr: 0 }; crate::MAX_COLOR_ATTACHMENTS]; for (rtv, cat) in color_views.iter_mut().zip(desc.color_attachments.iter()) { - *rtv = cat.target.view.handle_rtv.unwrap().raw; + if let Some(cat) = cat.as_ref() { + *rtv = cat.target.view.handle_rtv.unwrap().raw; + } else { + *rtv = self.null_rtv_handle.raw; + } } + let ds_view = match desc.depth_stencil_attachment { None => ptr::null(), Some(ref ds) => { @@ -605,23 +609,26 @@ impl crate::CommandEncoder for super::CommandEncoder { self.pass.resolves.clear(); for (rtv, cat) in color_views.iter().zip(desc.color_attachments.iter()) { - if !cat.ops.contains(crate::AttachmentOps::LOAD) { - let value = [ - cat.clear_value.r as f32, - cat.clear_value.g as f32, - cat.clear_value.b as f32, - cat.clear_value.a as f32, - ]; - list.clear_render_target_view(*rtv, value, &[]); - } - if let Some(ref target) = cat.resolve_target { - self.pass.resolves.push(super::PassResolve { - src: cat.target.view.target_base, - dst: target.view.target_base, - format: target.view.raw_format, - }); + if let Some(cat) = cat.as_ref() { + if !cat.ops.contains(crate::AttachmentOps::LOAD) { + let value = [ + cat.clear_value.r as f32, + cat.clear_value.g as f32, + cat.clear_value.b as f32, + cat.clear_value.a as f32, + ]; + list.clear_render_target_view(*rtv, value, &[]); + } + if let Some(ref target) = cat.resolve_target { + self.pass.resolves.push(super::PassResolve { + src: cat.target.view.target_base, + dst: target.view.target_base, + format: target.view.raw_format, + }); + } } } + if let Some(ref ds) = desc.depth_stencil_attachment { let mut flags = native::ClearFlags::empty(); let aspects = ds.target.view.format_aspects; diff --git a/third_party/rust/wgpu-hal/src/dx12/conv.rs b/third_party/rust/wgpu-hal/src/dx12/conv.rs index 8dfd40c92b94..4114fba002be 100644 --- a/third_party/rust/wgpu-hal/src/dx12/conv.rs +++ b/third_party/rust/wgpu-hal/src/dx12/conv.rs @@ -267,7 +267,7 @@ fn map_blend_component( } pub fn map_render_targets( - color_targets: &[wgt::ColorTargetState], + color_targets: &[Option], ) -> [d3d12::D3D12_RENDER_TARGET_BLEND_DESC; d3d12::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as usize] { let dummy_target = d3d12::D3D12_RENDER_TARGET_BLEND_DESC { @@ -285,17 +285,19 @@ pub fn map_render_targets( let mut raw_targets = [dummy_target; d3d12::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as usize]; for (raw, ct) in raw_targets.iter_mut().zip(color_targets.iter()) { - raw.RenderTargetWriteMask = ct.write_mask.bits() as u8; - if let Some(ref blend) = ct.blend { - let (color_op, color_src, color_dst) = map_blend_component(&blend.color, false); - let (alpha_op, alpha_src, alpha_dst) = map_blend_component(&blend.alpha, true); - raw.BlendEnable = 1; - raw.BlendOp = color_op; - raw.SrcBlend = color_src; - raw.DestBlend = color_dst; - raw.BlendOpAlpha = alpha_op; - raw.SrcBlendAlpha = alpha_src; - raw.DestBlendAlpha = alpha_dst; + if let Some(ct) = ct.as_ref() { + raw.RenderTargetWriteMask = ct.write_mask.bits() as u8; + if let Some(ref blend) = ct.blend { + let (color_op, color_src, color_dst) = map_blend_component(&blend.color, false); + let (alpha_op, alpha_src, alpha_dst) = map_blend_component(&blend.alpha, true); + raw.BlendEnable = 1; + raw.BlendOp = color_op; + raw.SrcBlend = color_src; + raw.DestBlend = color_dst; + raw.BlendOpAlpha = alpha_op; + raw.SrcBlendAlpha = alpha_src; + raw.DestBlendAlpha = alpha_dst; + } } } diff --git a/third_party/rust/wgpu-hal/src/dx12/device.rs b/third_party/rust/wgpu-hal/src/dx12/device.rs index c25f58c76794..65fde1f42225 100644 --- a/third_party/rust/wgpu-hal/src/dx12/device.rs +++ b/third_party/rust/wgpu-hal/src/dx12/device.rs @@ -125,6 +125,20 @@ impl super::Device { )?, }; + let mut rtv_pool = descriptor::CpuPool::new(raw, native::DescriptorHeapType::Rtv); + let null_rtv_handle = rtv_pool.alloc_handle(); + // A null pResource is used to initialize a null descriptor, + // which guarantees D3D11-like null binding behavior (reading 0s, writes are discarded) + raw.create_render_target_view( + native::WeakPtr::null(), + &native::RenderTargetViewDesc::texture_2d( + winapi::shared::dxgiformat::DXGI_FORMAT_R8G8B8A8_UNORM, + 0, + 0, + ), + null_rtv_handle.raw, + ); + Ok(super::Device { raw, present_queue, @@ -134,10 +148,7 @@ impl super::Device { }, private_caps, shared: Arc::new(shared), - rtv_pool: Mutex::new(descriptor::CpuPool::new( - raw, - native::DescriptorHeapType::Rtv, - )), + rtv_pool: Mutex::new(rtv_pool), dsv_pool: Mutex::new(descriptor::CpuPool::new( raw, native::DescriptorHeapType::Dsv, @@ -153,6 +164,7 @@ impl super::Device { library: Arc::clone(library), #[cfg(feature = "renderdoc")] render_doc: Default::default(), + null_rtv_handle, }) } @@ -306,6 +318,7 @@ impl super::Device { impl crate::Device for super::Device { unsafe fn exit(self, queue: super::Queue) { + self.rtv_pool.lock().free_handle(self.null_rtv_handle); self.rtv_pool.into_inner().destroy(); self.dsv_pool.into_inner().destroy(); self.srv_uav_pool.into_inner().destroy(); @@ -658,6 +671,7 @@ impl crate::Device for super::Device { allocator, device: self.raw, shared: Arc::clone(&self.shared), + null_rtv_handle: self.null_rtv_handle.clone(), list: None, free_lists: Vec::new(), pass: super::PassState::new(), @@ -1283,7 +1297,9 @@ impl crate::Device for super::Device { let mut rtv_formats = [dxgiformat::DXGI_FORMAT_UNKNOWN; d3d12::D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT as usize]; for (rtv_format, ct) in rtv_formats.iter_mut().zip(desc.color_targets) { - *rtv_format = auxil::dxgi::conv::map_texture_format(ct.format); + if let Some(ct) = ct.as_ref() { + *rtv_format = auxil::dxgi::conv::map_texture_format(ct.format); + } } let bias = desc diff --git a/third_party/rust/wgpu-hal/src/dx12/mod.rs b/third_party/rust/wgpu-hal/src/dx12/mod.rs index 4b048ff0092d..1d67d07a4950 100644 --- a/third_party/rust/wgpu-hal/src/dx12/mod.rs +++ b/third_party/rust/wgpu-hal/src/dx12/mod.rs @@ -229,6 +229,7 @@ pub struct Device { library: Arc, #[cfg(feature = "renderdoc")] render_doc: crate::auxil::renderdoc::RenderDoc, + null_rtv_handle: descriptor::Handle, } unsafe impl Send for Device {} @@ -287,7 +288,7 @@ enum PassKind { struct PassState { has_label: bool, - resolves: ArrayVec, + resolves: ArrayVec, layout: PipelineLayoutShared, root_elements: [RootElement; MAX_ROOT_ELEMENTS], dirty_root_elements: u64, @@ -329,6 +330,7 @@ pub struct CommandEncoder { allocator: native::CommandAllocator, device: native::Device, shared: Arc, + null_rtv_handle: descriptor::Handle, list: Option, free_lists: Vec, pass: PassState, @@ -527,7 +529,14 @@ impl SwapChain { self.raw } - unsafe fn wait(&mut self, timeout_ms: u32) -> Result { + unsafe fn wait( + &mut self, + timeout: Option, + ) -> Result { + let timeout_ms = match timeout { + Some(duration) => duration.as_millis() as u32, + None => winbase::INFINITE, + }; match synchapi::WaitForSingleObject(self.waitable, timeout_ms) { winbase::WAIT_ABANDONED | winbase::WAIT_FAILED => Err(crate::SurfaceError::Lost), winbase::WAIT_OBJECT_0 => Ok(true), @@ -690,7 +699,7 @@ impl crate::Surface for Surface { unsafe fn unconfigure(&mut self, device: &Device) { if let Some(mut sc) = self.swap_chain.take() { - let _ = sc.wait(winbase::INFINITE); + let _ = sc.wait(None); //TODO: this shouldn't be needed, // but it complains that the queue is still used otherwise let _ = device.wait_idle(); @@ -701,11 +710,11 @@ impl crate::Surface for Surface { unsafe fn acquire_texture( &mut self, - timeout_ms: u32, + timeout: Option, ) -> Result>, crate::SurfaceError> { let sc = self.swap_chain.as_mut().unwrap(); - sc.wait(timeout_ms)?; + sc.wait(timeout)?; let base_index = sc.raw.GetCurrentBackBufferIndex() as usize; let index = (base_index + sc.acquired_count) % sc.resources.len(); diff --git a/third_party/rust/wgpu-hal/src/empty.rs b/third_party/rust/wgpu-hal/src/empty.rs index be5708da3849..0c546469b222 100644 --- a/third_party/rust/wgpu-hal/src/empty.rs +++ b/third_party/rust/wgpu-hal/src/empty.rs @@ -66,7 +66,7 @@ impl crate::Surface for Context { unsafe fn acquire_texture( &mut self, - timeout_ms: u32, + timeout: Option, ) -> Result>, crate::SurfaceError> { Ok(None) } diff --git a/third_party/rust/wgpu-hal/src/gles/adapter.rs b/third_party/rust/wgpu-hal/src/gles/adapter.rs index 90f845813b7e..a0fc40fed103 100644 --- a/third_party/rust/wgpu-hal/src/gles/adapter.rs +++ b/third_party/rust/wgpu-hal/src/gles/adapter.rs @@ -212,6 +212,9 @@ impl super::Adapter { naga::back::glsl::Version::Embedded(value) }; + // ANGLE provides renderer strings like: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)" + let is_angle = renderer.contains("ANGLE"); + let vertex_shader_storage_blocks = if supports_storage { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) as u32 } else { @@ -289,6 +292,12 @@ impl super::Adapter { wgt::DownlevelFlags::ANISOTROPIC_FILTERING, extensions.contains("EXT_texture_filter_anisotropic"), ); + downlevel_flags.set( + wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED, + !(cfg!(target_arch = "wasm32") || is_angle), + ); + + let is_ext_color_buffer_float_supported = extensions.contains("EXT_color_buffer_float"); let mut features = wgt::Features::empty() | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES @@ -459,6 +468,7 @@ impl super::Adapter { 0 }, max_compute_workgroups_per_dimension, + max_buffer_size: i32::MAX as u64, }; let mut workarounds = super::Workarounds::empty(); @@ -500,6 +510,7 @@ impl super::Adapter { workarounds, shading_language_version, max_texture_size, + is_ext_color_buffer_float_supported, }), }, info: Self::make_info(vendor, renderer), @@ -619,6 +630,13 @@ impl crate::Adapter for super::Adapter { unfilterable | Tfc::COLOR_ATTACHMENT | Tfc::MULTISAMPLE | Tfc::MULTISAMPLE_RESOLVE; let filterable_renderable = filterable | renderable | Tfc::COLOR_ATTACHMENT_BLEND; let storage = Tfc::STORAGE | Tfc::STORAGE_READ_WRITE; + + let float_renderable = if self.shared.is_ext_color_buffer_float_supported { + Tfc::COLOR_ATTACHMENT | Tfc::COLOR_ATTACHMENT_BLEND + } else { + Tfc::empty() + }; + match format { Tf::R8Unorm => filterable_renderable, Tf::R8Snorm => filterable, @@ -628,37 +646,37 @@ impl crate::Adapter for super::Adapter { Tf::R16Sint => renderable, Tf::R16Unorm => empty, Tf::R16Snorm => empty, - Tf::R16Float => filterable, + Tf::R16Float => filterable | float_renderable, Tf::Rg8Unorm => filterable_renderable, Tf::Rg8Snorm => filterable, Tf::Rg8Uint => renderable, Tf::Rg8Sint => renderable, Tf::R32Uint => renderable | storage, Tf::R32Sint => renderable | storage, - Tf::R32Float => unfilterable | storage, + Tf::R32Float => unfilterable | storage | float_renderable, Tf::Rg16Uint => renderable, Tf::Rg16Sint => renderable, Tf::Rg16Unorm => empty, Tf::Rg16Snorm => empty, - Tf::Rg16Float => filterable, + Tf::Rg16Float => filterable | float_renderable, Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb => filterable_renderable | storage, Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => filterable_renderable, Tf::Rgba8Snorm => filterable, Tf::Rgba8Uint => renderable | storage, Tf::Rgba8Sint => renderable | storage, Tf::Rgb10a2Unorm => filterable_renderable, - Tf::Rg11b10Float => filterable, + Tf::Rg11b10Float => filterable | float_renderable, Tf::Rg32Uint => renderable, Tf::Rg32Sint => renderable, - Tf::Rg32Float => unfilterable, + Tf::Rg32Float => unfilterable | float_renderable, Tf::Rgba16Uint => renderable | storage, Tf::Rgba16Sint => renderable | storage, Tf::Rgba16Unorm => empty, Tf::Rgba16Snorm => empty, - Tf::Rgba16Float => filterable | storage, + Tf::Rgba16Float => filterable | storage | float_renderable, Tf::Rgba32Uint => renderable | storage, Tf::Rgba32Sint => renderable | storage, - Tf::Rgba32Float => unfilterable | storage, + Tf::Rgba32Float => unfilterable | storage | float_renderable, Tf::Depth32Float | Tf::Depth32FloatStencil8 | Tf::Depth24Plus diff --git a/third_party/rust/wgpu-hal/src/gles/command.rs b/third_party/rust/wgpu-hal/src/gles/command.rs index 307a2865c3dc..beaf600e6e43 100644 --- a/third_party/rust/wgpu-hal/src/gles/command.rs +++ b/third_party/rust/wgpu-hal/src/gles/command.rs @@ -17,14 +17,14 @@ pub(super) struct State { vertex_buffers: [(super::VertexBufferDesc, Option); crate::MAX_VERTEX_BUFFERS], vertex_attributes: ArrayVec, - color_targets: ArrayVec, + color_targets: ArrayVec, stencil: super::StencilState, depth_bias: wgt::DepthBiasState, samplers: [Option; super::MAX_SAMPLERS], texture_slots: [TextureSlotDesc; super::MAX_TEXTURE_SLOTS], render_size: wgt::Extent3d, - resolve_attachments: ArrayVec<(u32, super::TextureView), { crate::MAX_COLOR_TARGETS }>, - invalidate_attachments: ArrayVec, + resolve_attachments: ArrayVec<(u32, super::TextureView), { crate::MAX_COLOR_ATTACHMENTS }>, + invalidate_attachments: ArrayVec, has_pass_label: bool, instance_vbuf_mask: usize, dirty_vbuf_mask: usize, @@ -327,6 +327,7 @@ impl crate::CommandEncoder for super::CommandEncoder { dst: dst_raw, dst_target, copy, + dst_is_cubemap: dst.is_cubemap, }) } } @@ -428,7 +429,8 @@ impl crate::CommandEncoder for super::CommandEncoder { match desc .color_attachments .first() - .map(|at| &at.target.view.inner) + .filter(|at| at.is_some()) + .and_then(|at| at.as_ref().map(|at| &at.target.view.inner)) { // default framebuffer (provided externally) Some(&super::TextureInner::DefaultRenderbuffer) => { @@ -443,18 +445,20 @@ impl crate::CommandEncoder for super::CommandEncoder { .push(C::ResetFramebuffer { is_default: false }); for (i, cat) in desc.color_attachments.iter().enumerate() { - let attachment = glow::COLOR_ATTACHMENT0 + i as u32; - self.cmd_buffer.commands.push(C::BindAttachment { - attachment, - view: cat.target.view.clone(), - }); - if let Some(ref rat) = cat.resolve_target { - self.state - .resolve_attachments - .push((attachment, rat.view.clone())); - } - if !cat.ops.contains(crate::AttachmentOps::STORE) { - self.state.invalidate_attachments.push(attachment); + if let Some(cat) = cat.as_ref() { + let attachment = glow::COLOR_ATTACHMENT0 + i as u32; + self.cmd_buffer.commands.push(C::BindAttachment { + attachment, + view: cat.target.view.clone(), + }); + if let Some(ref rat) = cat.resolve_target { + self.state + .resolve_attachments + .push((attachment, rat.view.clone())); + } + if !cat.ops.contains(crate::AttachmentOps::STORE) { + self.state.invalidate_attachments.push(attachment); + } } } if let Some(ref dsat) = desc.depth_stencil_attachment { @@ -504,7 +508,12 @@ impl crate::CommandEncoder for super::CommandEncoder { }); // issue the clears - for (i, cat) in desc.color_attachments.iter().enumerate() { + for (i, cat) in desc + .color_attachments + .iter() + .filter_map(|at| at.as_ref()) + .enumerate() + { if !cat.ops.contains(crate::AttachmentOps::LOAD) { let c = &cat.clear_value; self.cmd_buffer diff --git a/third_party/rust/wgpu-hal/src/gles/device.rs b/third_party/rust/wgpu-hal/src/gles/device.rs index 3defc44c0bca..c554d38a1f2f 100644 --- a/third_party/rust/wgpu-hal/src/gles/device.rs +++ b/third_party/rust/wgpu-hal/src/gles/device.rs @@ -144,6 +144,23 @@ impl super::Device { .position(|ep| ep.name.as_str() == stage.entry_point) .ok_or(crate::PipelineError::EntryPoint(naga_stage))?; + use naga::proc::BoundsCheckPolicy; + // The image bounds checks require the TEXTURE_LEVELS feature available in GL core 1.3+. + let version = gl.version(); + let image_check = if !version.is_embedded && (version.major, version.minor) >= (1, 3) { + BoundsCheckPolicy::ReadZeroSkipWrite + } else { + BoundsCheckPolicy::Unchecked + }; + + // Other bounds check are either provided by glsl or not implemented yet. + let policies = naga::proc::BoundsCheckPolicies { + index: BoundsCheckPolicy::Unchecked, + buffer: BoundsCheckPolicy::Unchecked, + image: image_check, + binding_array: BoundsCheckPolicy::Unchecked, + }; + let mut output = String::new(); let mut writer = glsl::Writer::new( &mut output, @@ -151,6 +168,7 @@ impl super::Device { &shader.info, &context.layout.naga_options, &pipeline_options, + policies, ) .map_err(|e| { let msg = format!("{}", e); @@ -528,7 +546,7 @@ impl crate::Device for super::Device { depth: 1, }; - let inner = if render_usage.contains(desc.usage) + let (inner, is_cubemap) = if render_usage.contains(desc.usage) && desc.dimension == wgt::TextureDimension::D2 && desc.size.depth_or_array_layers == 1 { @@ -559,10 +577,10 @@ impl crate::Device for super::Device { } gl.bind_renderbuffer(glow::RENDERBUFFER, None); - super::TextureInner::Renderbuffer { raw } + (super::TextureInner::Renderbuffer { raw }, false) } else { let raw = gl.create_texture().unwrap(); - let (target, is_3d) = match desc.dimension { + let (target, is_3d, is_cubemap) = match desc.dimension { wgt::TextureDimension::D1 | wgt::TextureDimension::D2 => { if desc.size.depth_or_array_layers > 1 { //HACK: detect a cube map @@ -575,17 +593,17 @@ impl crate::Device for super::Device { None }; match cube_count { - None => (glow::TEXTURE_2D_ARRAY, true), - Some(1) => (glow::TEXTURE_CUBE_MAP, false), - Some(_) => (glow::TEXTURE_CUBE_MAP_ARRAY, true), + None => (glow::TEXTURE_2D_ARRAY, true, false), + Some(1) => (glow::TEXTURE_CUBE_MAP, false, true), + Some(_) => (glow::TEXTURE_CUBE_MAP_ARRAY, true, true), } } else { - (glow::TEXTURE_2D, false) + (glow::TEXTURE_2D, false, false) } } wgt::TextureDimension::D3 => { copy_size.depth = desc.size.depth_or_array_layers; - (glow::TEXTURE_3D, true) + (glow::TEXTURE_3D, true, false) } }; @@ -639,7 +657,7 @@ impl crate::Device for super::Device { } gl.bind_texture(target, None); - super::TextureInner::Texture { raw, target } + (super::TextureInner::Texture { raw, target }, is_cubemap) }; Ok(super::Texture { @@ -653,6 +671,7 @@ impl crate::Device for super::Device { format: desc.format, format_desc, copy_size, + is_cubemap, }) } unsafe fn destroy_texture(&self, texture: super::Texture) { @@ -983,7 +1002,7 @@ impl crate::Device for super::Device { let color_targets = { let mut targets = Vec::new(); - for ct in desc.color_targets.iter() { + for ct in desc.color_targets.iter().filter_map(|at| at.as_ref()) { targets.push(super::ColorTargetDesc { mask: ct.write_mask, blend: ct.blend.as_ref().map(conv::map_blend), diff --git a/third_party/rust/wgpu-hal/src/gles/egl.rs b/third_party/rust/wgpu-hal/src/gles/egl.rs index 06167ec841ae..a10c9444491f 100644 --- a/third_party/rust/wgpu-hal/src/gles/egl.rs +++ b/third_party/rust/wgpu-hal/src/gles/egl.rs @@ -1194,7 +1194,7 @@ impl crate::Surface for Surface { unsafe fn acquire_texture( &mut self, - _timeout_ms: u32, //TODO + _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { let sc = self.swapchain.as_ref().unwrap(); let texture = super::Texture { @@ -1210,6 +1210,7 @@ impl crate::Surface for Surface { height: sc.extent.height, depth: 1, }, + is_cubemap: false, }; Ok(Some(crate::AcquiredSurfaceTexture { texture, diff --git a/third_party/rust/wgpu-hal/src/gles/mod.rs b/third_party/rust/wgpu-hal/src/gles/mod.rs index 338cbca774cc..ec0d39fc196e 100644 --- a/third_party/rust/wgpu-hal/src/gles/mod.rs +++ b/third_party/rust/wgpu-hal/src/gles/mod.rs @@ -184,6 +184,7 @@ struct AdapterShared { workarounds: Workarounds, shading_language_version: naga::back::glsl::Version, max_texture_size: u32, + is_ext_color_buffer_float_supported: bool, } pub struct Adapter { @@ -262,6 +263,7 @@ pub struct Texture { #[allow(unused)] format_desc: TextureFormatDesc, copy_size: crate::CopyExtent, + is_cubemap: bool, } impl Texture { @@ -281,6 +283,7 @@ impl Texture { height: 0, depth: 0, }, + is_cubemap: false, } } } @@ -564,7 +567,7 @@ struct PrimitiveState { unclipped_depth: bool, } -type InvalidatedAttachments = ArrayVec; +type InvalidatedAttachments = ArrayVec; #[derive(Debug)] enum Command { @@ -616,6 +619,7 @@ enum Command { dst: glow::Texture, dst_target: BindTarget, copy: crate::TextureCopy, + dst_is_cubemap: bool, }, CopyBufferToTexture { src: Buffer, diff --git a/third_party/rust/wgpu-hal/src/gles/queue.rs b/third_party/rust/wgpu-hal/src/gles/queue.rs index 2af9e72f2184..6cdccd9141da 100644 --- a/third_party/rust/wgpu-hal/src/gles/queue.rs +++ b/third_party/rust/wgpu-hal/src/gles/queue.rs @@ -50,7 +50,7 @@ impl super::Queue { // Reset the draw buffers to what they were before the clear let indices = (0..self.draw_buffer_count as u32) .map(|i| glow::COLOR_ATTACHMENT0 + i) - .collect::>(); + .collect::>(); gl.draw_buffers(&indices); } #[cfg(not(target_arch = "wasm32"))] @@ -213,19 +213,39 @@ impl super::Queue { ref range, } => match dst.raw { Some(buffer) => { - gl.bind_buffer(glow::COPY_READ_BUFFER, Some(self.zero_buffer)); - gl.bind_buffer(dst_target, Some(buffer)); - let mut dst_offset = range.start; - while dst_offset < range.end { - let size = (range.end - dst_offset).min(super::ZERO_BUFFER_SIZE as u64); - gl.copy_buffer_sub_data( - glow::COPY_READ_BUFFER, - dst_target, - 0, - dst_offset as i32, - size as i32, - ); - dst_offset += size; + // When `INDEX_BUFFER_ROLE_CHANGE` isn't available, we can't copy into the + // index buffer from the zero buffer. This would fail in Chrome with the + // following message: + // + // > Cannot copy into an element buffer destination from a non-element buffer + // > source + // + // Instead, we'll upload zeroes into the buffer. + let can_use_zero_buffer = self + .shared + .private_caps + .contains(super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE) + || dst_target != glow::ELEMENT_ARRAY_BUFFER; + + if can_use_zero_buffer { + gl.bind_buffer(glow::COPY_READ_BUFFER, Some(self.zero_buffer)); + gl.bind_buffer(dst_target, Some(buffer)); + let mut dst_offset = range.start; + while dst_offset < range.end { + let size = (range.end - dst_offset).min(super::ZERO_BUFFER_SIZE as u64); + gl.copy_buffer_sub_data( + glow::COPY_READ_BUFFER, + dst_target, + 0, + dst_offset as i32, + size as i32, + ); + dst_offset += size; + } + } else { + gl.bind_buffer(dst_target, Some(buffer)); + let zeroes = vec![0u8; (range.end - range.start) as usize]; + gl.buffer_sub_data_u8_slice(dst_target, range.start as i32, &zeroes); } } None => { @@ -308,10 +328,10 @@ impl super::Queue { src_target, dst, dst_target, + dst_is_cubemap, ref copy, } => { //TODO: handle 3D copies - //TODO: handle cubemap copies gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(self.copy_fbo)); if is_layered_target(src_target) { //TODO: handle GLES without framebuffer_texture_3d @@ -333,7 +353,18 @@ impl super::Queue { } gl.bind_texture(dst_target, Some(dst)); - if is_layered_target(dst_target) { + if dst_is_cubemap { + gl.copy_tex_sub_image_2d( + CUBEMAP_FACES[copy.dst_base.array_layer as usize], + copy.dst_base.mip_level as i32, + copy.dst_base.origin.x as i32, + copy.dst_base.origin.y as i32, + copy.src_base.origin.x as i32, + copy.src_base.origin.y as i32, + copy.size.width as i32, + copy.size.height as i32, + ); + } else if is_layered_target(dst_target) { gl.copy_tex_sub_image_3d( dst_target, copy.dst_base.mip_level as i32, @@ -397,7 +428,7 @@ impl super::Queue { } }; match dst_target { - glow::TEXTURE_3D | glow::TEXTURE_2D_ARRAY => { + glow::TEXTURE_3D => { gl.tex_sub_image_3d( dst_target, copy.texture_base.mip_level as i32, @@ -412,6 +443,21 @@ impl super::Queue { unpack_data, ); } + glow::TEXTURE_2D_ARRAY => { + gl.tex_sub_image_3d( + dst_target, + copy.texture_base.mip_level as i32, + copy.texture_base.origin.x as i32, + copy.texture_base.origin.y as i32, + copy.texture_base.array_layer as i32, + copy.size.width as i32, + copy.size.height as i32, + copy.size.depth as i32, + format_desc.external, + format_desc.data_type, + unpack_data, + ); + } glow::TEXTURE_2D => { gl.tex_sub_image_2d( dst_target, @@ -662,7 +708,7 @@ impl super::Queue { None, 0, ); - for i in 0..crate::MAX_COLOR_TARGETS { + for i in 0..crate::MAX_COLOR_ATTACHMENTS { let target = glow::COLOR_ATTACHMENT0 + i as u32; gl.framebuffer_texture_2d( glow::DRAW_FRAMEBUFFER, @@ -717,7 +763,7 @@ impl super::Queue { self.draw_buffer_count = count; let indices = (0..count as u32) .map(|i| glow::COLOR_ATTACHMENT0 + i) - .collect::>(); + .collect::>(); gl.draw_buffers(&indices); if self diff --git a/third_party/rust/wgpu-hal/src/gles/web.rs b/third_party/rust/wgpu-hal/src/gles/web.rs index 1c0ee6a9dbe6..bb561f5bae97 100644 --- a/third_party/rust/wgpu-hal/src/gles/web.rs +++ b/third_party/rust/wgpu-hal/src/gles/web.rs @@ -5,7 +5,7 @@ use wasm_bindgen::JsCast; use super::TextureFormatDesc; /// A wrapper around a [`glow::Context`] to provide a fake `lock()` api that makes it compatible -/// with the `AdapterContext` API fromt the EGL implementation. +/// with the `AdapterContext` API from the EGL implementation. pub struct AdapterContext { pub glow_context: glow::Context, } @@ -25,7 +25,62 @@ impl AdapterContext { #[derive(Debug)] pub struct Instance { - canvas: Mutex>, + webgl2_context: Mutex>, +} + +impl Instance { + pub fn create_surface_from_canvas( + &self, + canvas: &web_sys::HtmlCanvasElement, + ) -> Result { + let webgl2_context = canvas + .get_context_with_context_options("webgl2", &Self::create_context_options()) + .expect("Cannot create WebGL2 context") + .and_then(|context| context.dyn_into::().ok()) + .expect("Cannot convert into WebGL2 context"); + + *self.webgl2_context.lock() = Some(webgl2_context.clone()); + + Ok(Surface { + webgl2_context, + present_program: None, + swapchain: None, + texture: None, + presentable: true, + }) + } + + pub fn create_surface_from_offscreen_canvas( + &self, + canvas: &web_sys::OffscreenCanvas, + ) -> Result { + let webgl2_context = canvas + .get_context_with_context_options("webgl2", &Self::create_context_options()) + .expect("Cannot create WebGL2 context") + .and_then(|context| context.dyn_into::().ok()) + .expect("Cannot convert into WebGL2 context"); + + *self.webgl2_context.lock() = Some(webgl2_context.clone()); + + Ok(Surface { + webgl2_context, + present_program: None, + swapchain: None, + texture: None, + presentable: true, + }) + } + + fn create_context_options() -> js_sys::Object { + let context_options = js_sys::Object::new(); + js_sys::Reflect::set( + &context_options, + &"antialias".into(), + &wasm_bindgen::JsValue::FALSE, + ) + .expect("Cannot create context options"); + context_options + } } // SAFE: WASM doesn't have threads @@ -35,28 +90,14 @@ unsafe impl Send for Instance {} impl crate::Instance for Instance { unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result { Ok(Instance { - canvas: Mutex::new(None), + webgl2_context: Mutex::new(None), }) } unsafe fn enumerate_adapters(&self) -> Vec> { - let canvas_guard = self.canvas.lock(); - let gl = match *canvas_guard { - Some(ref canvas) => { - let context_options = js_sys::Object::new(); - js_sys::Reflect::set( - &context_options, - &"antialias".into(), - &wasm_bindgen::JsValue::FALSE, - ) - .expect("Cannot create context options"); - let webgl2_context = canvas - .get_context_with_context_options("webgl2", &context_options) - .expect("Cannot create WebGL2 context") - .and_then(|context| context.dyn_into::().ok()) - .expect("Cannot convert into WebGL2 context"); - glow::Context::from_webgl2_context(webgl2_context) - } + let context_guard = self.webgl2_context.lock(); + let gl = match *context_guard { + Some(ref webgl2_context) => glow::Context::from_webgl2_context(webgl2_context.clone()), None => return Vec::new(), }; @@ -79,26 +120,18 @@ impl crate::Instance for Instance { .dyn_into() .expect("Failed to downcast to canvas type"); - *self.canvas.lock() = Some(canvas.clone()); - - Ok(Surface { - canvas, - present_program: None, - swapchain: None, - texture: None, - presentable: true, - }) + self.create_surface_from_canvas(&canvas) } else { unreachable!() } } unsafe fn destroy_surface(&self, surface: Surface) { - let mut canvas_option_ref = self.canvas.lock(); + let mut context_option_ref = self.webgl2_context.lock(); - if let Some(canvas) = canvas_option_ref.as_ref() { - if canvas == &surface.canvas { - *canvas_option_ref = None; + if let Some(context) = context_option_ref.as_ref() { + if context == &surface.webgl2_context { + *context_option_ref = None; } } } @@ -106,7 +139,7 @@ impl crate::Instance for Instance { #[derive(Clone, Debug)] pub struct Surface { - canvas: web_sys::HtmlCanvasElement, + webgl2_context: web_sys::WebGl2RenderingContext, pub(super) swapchain: Option, texture: Option, pub(super) presentable: bool, @@ -253,7 +286,7 @@ impl crate::Surface for Surface { unsafe fn acquire_texture( &mut self, - _timeout_ms: u32, + _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { let sc = self.swapchain.as_ref().unwrap(); let texture = super::Texture { @@ -270,6 +303,7 @@ impl crate::Surface for Surface { height: sc.extent.height, depth: 1, }, + is_cubemap: false, }; Ok(Some(crate::AcquiredSurfaceTexture { texture, diff --git a/third_party/rust/wgpu-hal/src/lib.rs b/third_party/rust/wgpu-hal/src/lib.rs index 1ce7c657bae3..a343f111bb15 100644 --- a/third_party/rust/wgpu-hal/src/lib.rs +++ b/third_party/rust/wgpu-hal/src/lib.rs @@ -97,7 +97,7 @@ use thiserror::Error; pub const MAX_ANISOTROPY: u8 = 16; pub const MAX_BIND_GROUPS: usize = 8; pub const MAX_VERTEX_BUFFERS: usize = 16; -pub const MAX_COLOR_TARGETS: usize = 8; +pub const MAX_COLOR_ATTACHMENTS: usize = 8; pub const MAX_MIP_LEVELS: u32 = 16; /// Size of a single occlusion/timestamp query, when copied into a buffer, in bytes. pub const QUERY_SIZE: wgt::BufferAddress = 8; @@ -193,10 +193,19 @@ pub trait Surface: Send + Sync { unsafe fn unconfigure(&mut self, device: &A::Device); + /// Returns the next texture to be presented by the swapchain for drawing + /// + /// A `timeout` of `None` means to wait indefinitely, with no timeout. + /// + /// # Portability + /// + /// Some backends can't support a timeout when acquiring a texture and + /// the timeout will be ignored. + /// /// Returns `None` on timing out. unsafe fn acquire_texture( &mut self, - timeout_ms: u32, + timeout: Option, ) -> Result>, SurfaceError>; unsafe fn discard_texture(&mut self, texture: A::SurfaceTexture); } @@ -304,6 +313,7 @@ pub trait Device: Send + Sync { unsafe fn create_fence(&self) -> Result; unsafe fn destroy_fence(&self, fence: A::Fence); unsafe fn get_fence_value(&self, fence: &A::Fence) -> Result; + /// Calling wait with a lower value than the current fence value will immediately return. unsafe fn wait( &self, fence: &A::Fence, @@ -1014,7 +1024,7 @@ pub struct RenderPipelineDescriptor<'a, A: Api> { /// The fragment stage for this pipeline. pub fragment_stage: Option>, /// The effect of draw calls on the color aspect of the output target. - pub color_targets: &'a [wgt::ColorTargetState], + pub color_targets: &'a [Option], /// If the pipeline will be used with a multiview render pass, this indicates how many array /// layers the attachments will have. pub multiview: Option, @@ -1169,7 +1179,7 @@ pub struct RenderPassDescriptor<'a, A: Api> { pub label: Label<'a>, pub extent: wgt::Extent3d, pub sample_count: u32, - pub color_attachments: &'a [ColorAttachment<'a, A>], + pub color_attachments: &'a [Option>], pub depth_stencil_attachment: Option>, pub multiview: Option, } diff --git a/third_party/rust/wgpu-hal/src/metal/adapter.rs b/third_party/rust/wgpu-hal/src/metal/adapter.rs index f59237100f99..1325b901f677 100644 --- a/third_party/rust/wgpu-hal/src/metal/adapter.rs +++ b/third_party/rust/wgpu-hal/src/metal/adapter.rs @@ -760,7 +760,8 @@ impl super::PrivateCapabilities { | F::CLEAR_TEXTURE | F::TEXTURE_FORMAT_16BIT_NORM | F::SHADER_FLOAT16 - | F::DEPTH32FLOAT_STENCIL8; + | F::DEPTH32FLOAT_STENCIL8 + | F::MULTI_DRAW_INDIRECT; features.set(F::TEXTURE_COMPRESSION_ASTC_LDR, self.format_astc); features.set(F::TEXTURE_COMPRESSION_ASTC_HDR, self.format_astc_hdr); @@ -846,6 +847,7 @@ impl super::PrivateCapabilities { max_compute_workgroup_size_y: self.max_threads_per_group, max_compute_workgroup_size_z: self.max_threads_per_group, max_compute_workgroups_per_dimension: 0xFFFF, + max_buffer_size: self.max_buffer_size, }, alignments: crate::Alignments { buffer_copy_offset: wgt::BufferSize::new(self.buffer_alignment).unwrap(), diff --git a/third_party/rust/wgpu-hal/src/metal/command.rs b/third_party/rust/wgpu-hal/src/metal/command.rs index 7eaf97b6597d..9231df93039b 100644 --- a/third_party/rust/wgpu-hal/src/metal/command.rs +++ b/third_party/rust/wgpu-hal/src/metal/command.rs @@ -353,24 +353,26 @@ impl crate::CommandEncoder for super::CommandEncoder { //TODO: set visibility results buffer for (i, at) in desc.color_attachments.iter().enumerate() { - let at_descriptor = descriptor.color_attachments().object_at(i as u64).unwrap(); - at_descriptor.set_texture(Some(&at.target.view.raw)); - if let Some(ref resolve) = at.resolve_target { - //Note: the selection of levels and slices is already handled by `TextureView` - at_descriptor.set_resolve_texture(Some(&resolve.view.raw)); + if let Some(at) = at.as_ref() { + let at_descriptor = descriptor.color_attachments().object_at(i as u64).unwrap(); + at_descriptor.set_texture(Some(&at.target.view.raw)); + if let Some(ref resolve) = at.resolve_target { + //Note: the selection of levels and slices is already handled by `TextureView` + at_descriptor.set_resolve_texture(Some(&resolve.view.raw)); + } + let load_action = if at.ops.contains(crate::AttachmentOps::LOAD) { + mtl::MTLLoadAction::Load + } else { + at_descriptor.set_clear_color(conv::map_clear_color(&at.clear_value)); + mtl::MTLLoadAction::Clear + }; + let store_action = conv::map_store_action( + at.ops.contains(crate::AttachmentOps::STORE), + at.resolve_target.is_some(), + ); + at_descriptor.set_load_action(load_action); + at_descriptor.set_store_action(store_action); } - let load_action = if at.ops.contains(crate::AttachmentOps::LOAD) { - mtl::MTLLoadAction::Load - } else { - at_descriptor.set_clear_color(conv::map_clear_color(&at.clear_value)); - mtl::MTLLoadAction::Clear - }; - let store_action = conv::map_store_action( - at.ops.contains(crate::AttachmentOps::STORE), - at.resolve_target.is_some(), - ); - at_descriptor.set_load_action(load_action); - at_descriptor.set_store_action(store_action); } if let Some(ref at) = desc.depth_stencil_attachment { diff --git a/third_party/rust/wgpu-hal/src/metal/device.rs b/third_party/rust/wgpu-hal/src/metal/device.rs index 4b36e3d757b2..8d3301554e0a 100644 --- a/third_party/rust/wgpu-hal/src/metal/device.rs +++ b/third_party/rust/wgpu-hal/src/metal/device.rs @@ -841,6 +841,12 @@ impl crate::Device for super::Device { for (i, ct) in desc.color_targets.iter().enumerate() { let at_descriptor = descriptor.color_attachments().object_at(i as u64).unwrap(); + let ct = if let Some(color_target) = ct.as_ref() { + color_target + } else { + at_descriptor.set_pixel_format(mtl::MTLPixelFormat::Invalid); + continue; + }; let raw_format = self.shared.private_caps.map_format(ct.format); at_descriptor.set_pixel_format(raw_format); diff --git a/third_party/rust/wgpu-hal/src/metal/surface.rs b/third_party/rust/wgpu-hal/src/metal/surface.rs index 124cf910f33e..3031e4c67cc3 100644 --- a/third_party/rust/wgpu-hal/src/metal/surface.rs +++ b/third_party/rust/wgpu-hal/src/metal/surface.rs @@ -9,7 +9,7 @@ use objc::{ declare::ClassDecl, msg_send, rc::autoreleasepool, - runtime::{Class, Object, Sel, BOOL, YES}, + runtime::{Class, Object, Sel, BOOL, NO, YES}, sel, sel_impl, }; use parking_lot::Mutex; @@ -74,6 +74,7 @@ impl super::Surface { } } + /// If not called on the main thread, this will panic. #[allow(clippy::transmute_ptr_to_ref)] pub unsafe fn from_view( view: *mut c_void, @@ -84,6 +85,11 @@ impl super::Surface { panic!("window does not have a valid contentView"); } + let is_main_thread: BOOL = msg_send![class!(NSThread), isMainThread]; + if is_main_thread == NO { + panic!("create_surface cannot be called in non-ui thread."); + } + let main_layer: *mut Object = msg_send![view, layer]; let class = class!(CAMetalLayer); let is_valid_layer: BOOL = msg_send![main_layer, isKindOfClass: class]; @@ -216,7 +222,7 @@ impl crate::Surface for super::Surface { unsafe fn acquire_texture( &mut self, - _timeout_ms: u32, //TODO + _timeout_ms: Option, //TODO ) -> Result>, crate::SurfaceError> { let render_layer = self.render_layer.lock(); let (drawable, texture) = match autoreleasepool(|| { diff --git a/third_party/rust/wgpu-hal/src/vulkan/adapter.rs b/third_party/rust/wgpu-hal/src/vulkan/adapter.rs index e2b914cb6a00..878a5ed73cd6 100644 --- a/third_party/rust/wgpu-hal/src/vulkan/adapter.rs +++ b/third_party/rust/wgpu-hal/src/vulkan/adapter.rs @@ -360,7 +360,7 @@ impl PhysicalDeviceFeatures { | F::ADDRESS_MODE_CLAMP_TO_BORDER | F::ADDRESS_MODE_CLAMP_TO_ZERO | F::TIMESTAMP_QUERY - | F::PIPELINE_STATISTICS_QUERY + | F::WRITE_TIMESTAMP_INSIDE_PASSES | F::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | F::CLEAR_TEXTURE; let mut dl_flags = Df::all(); @@ -397,8 +397,10 @@ impl PhysicalDeviceFeatures { F::TEXTURE_COMPRESSION_BC, self.core.texture_compression_bc != 0, ); - //if self.core.occlusion_query_precise != 0 { - //if self.core.pipeline_statistics_query != 0 { //TODO + features.set( + F::PIPELINE_STATISTICS_QUERY, + self.core.pipeline_statistics_query != 0, + ); features.set( F::VERTEX_WRITABLE_STORAGE, self.core.vertex_pipeline_stores_and_atomics != 0, @@ -607,7 +609,11 @@ unsafe impl Send for PhysicalDeviceCapabilities {} unsafe impl Sync for PhysicalDeviceCapabilities {} impl PhysicalDeviceCapabilities { - fn supports_extension(&self, extension: &CStr) -> bool { + pub fn properties(&self) -> vk::PhysicalDeviceProperties { + self.properties + } + + pub fn supports_extension(&self, extension: &CStr) -> bool { self.supported_extensions .iter() .any(|ep| unsafe { CStr::from_ptr(ep.extension_name.as_ptr()) } == extension) @@ -763,6 +769,15 @@ impl PhysicalDeviceCapabilities { .min(limits.max_compute_work_group_count[1]) .min(limits.max_compute_work_group_count[2]); + // Prevent very large buffers on mesa and most android devices. + let is_nvidia = self.properties.vendor_id == crate::auxil::db::nvidia::VENDOR; + let max_buffer_size = + if (cfg!(target_os = "linux") || cfg!(target_os = "android")) && !is_nvidia { + i32::MAX as u64 + } else { + u64::MAX + }; + wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, max_texture_dimension_2d: limits.max_image_dimension2_d, @@ -803,6 +818,7 @@ impl PhysicalDeviceCapabilities { max_compute_workgroup_size_y: max_compute_workgroup_sizes[1], max_compute_workgroup_size_z: max_compute_workgroup_sizes[2], max_compute_workgroups_per_dimension, + max_buffer_size, } } @@ -1043,19 +1059,17 @@ impl super::Instance { || phd_capabilities.supports_extension(vk::KhrMaintenance1Fn::name()), imageless_framebuffers: match phd_features.vulkan_1_2 { Some(features) => features.imageless_framebuffer == vk::TRUE, - None => match phd_features.imageless_framebuffer { - Some(ref ext) => ext.imageless_framebuffer != 0, - None => false, - }, + None => phd_features + .imageless_framebuffer + .map_or(false, |ext| ext.imageless_framebuffer != 0), }, image_view_usage: phd_capabilities.properties.api_version >= vk::API_VERSION_1_1 || phd_capabilities.supports_extension(vk::KhrMaintenance2Fn::name()), timeline_semaphores: match phd_features.vulkan_1_2 { Some(features) => features.timeline_semaphore == vk::TRUE, - None => match phd_features.timeline_semaphore { - Some(ref ext) => ext.timeline_semaphore != 0, - None => false, - }, + None => phd_features + .timeline_semaphore + .map_or(false, |ext| ext.timeline_semaphore != 0), }, texture_d24: unsafe { self.shared @@ -1077,13 +1091,11 @@ impl super::Instance { robust_buffer_access: phd_features.core.robust_buffer_access != 0, robust_image_access: match phd_features.robustness2 { Some(ref f) => f.robust_image_access2 != 0, - None => match phd_features.image_robustness { - Some(ref f) => f.robust_image_access != 0, - None => false, - }, + None => phd_features + .image_robustness + .map_or(false, |ext| ext.robust_image_access != 0), }, }; - let capabilities = crate::Capabilities { limits: phd_capabilities.to_wgpu_limits(&phd_features), alignments: phd_capabilities.to_hal_alignments(), @@ -1124,6 +1136,14 @@ impl super::Adapter { self.raw } + pub fn physical_device_capabilities(&self) -> &PhysicalDeviceCapabilities { + &self.phd_capabilities + } + + pub fn shared_instance(&self) -> &super::InstanceShared { + &self.instance + } + pub fn required_device_extensions(&self, features: wgt::Features) -> Vec<&'static CStr> { let (supported_extensions, unsupported_extensions) = self .phd_capabilities @@ -1237,6 +1257,10 @@ impl super::Adapter { capabilities.push(spv::Capability::MultiView); } + if features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX) { + capabilities.push(spv::Capability::Geometry); + } + if features.intersects( wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING | wgt::Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, @@ -1294,6 +1318,8 @@ impl super::Adapter { raw: raw_device, handle_is_owned, instance: Arc::clone(&self.instance), + physical_device: self.raw, + enabled_extensions: enabled_extensions.into(), extension_fns: super::DeviceExtensionFunctions { draw_indirect_count: indirect_count_fn, timeline_semaphore: timeline_semaphore_fn, diff --git a/third_party/rust/wgpu-hal/src/vulkan/command.rs b/third_party/rust/wgpu-hal/src/vulkan/command.rs index dbcb1123ee98..abfc3d6216f4 100644 --- a/third_party/rust/wgpu-hal/src/vulkan/command.rs +++ b/third_party/rust/wgpu-hal/src/vulkan/command.rs @@ -362,31 +362,36 @@ impl crate::CommandEncoder for super::CommandEncoder { let caps = &self.device.private_caps; for cat in desc.color_attachments { - vk_clear_values.push(vk::ClearValue { - color: cat.make_vk_clear_color(), - }); - vk_image_views.push(cat.target.view.raw); - rp_key.colors.push(super::ColorAttachmentKey { - base: cat.target.make_attachment_key(cat.ops, caps), - resolve: cat - .resolve_target - .as_ref() - .map(|target| target.make_attachment_key(crate::AttachmentOps::STORE, caps)), - }); - fb_key.attachments.push(cat.target.view.attachment.clone()); - if let Some(ref at) = cat.resolve_target { - vk_clear_values.push(mem::zeroed()); - vk_image_views.push(at.view.raw); - fb_key.attachments.push(at.view.attachment.clone()); - } + if let Some(cat) = cat.as_ref() { + vk_clear_values.push(vk::ClearValue { + color: cat.make_vk_clear_color(), + }); + vk_image_views.push(cat.target.view.raw); + let color = super::ColorAttachmentKey { + base: cat.target.make_attachment_key(cat.ops, caps), + resolve: cat.resolve_target.as_ref().map(|target| { + target.make_attachment_key(crate::AttachmentOps::STORE, caps) + }), + }; - // Assert this attachment is valid for the detected multiview, as a sanity check - // The driver crash for this is really bad on AMD, so the check is worth it - if let Some(multiview) = desc.multiview { - assert_eq!(cat.target.view.layers, multiview); - if let Some(ref resolve_target) = cat.resolve_target { - assert_eq!(resolve_target.view.layers, multiview); + rp_key.colors.push(Some(color)); + fb_key.attachments.push(cat.target.view.attachment.clone()); + if let Some(ref at) = cat.resolve_target { + vk_clear_values.push(mem::zeroed()); + vk_image_views.push(at.view.raw); + fb_key.attachments.push(at.view.attachment.clone()); } + + // Assert this attachment is valid for the detected multiview, as a sanity check + // The driver crash for this is really bad on AMD, so the check is worth it + if let Some(multiview) = desc.multiview { + assert_eq!(cat.target.view.layers, multiview); + if let Some(ref resolve_target) = cat.resolve_target { + assert_eq!(resolve_target.view.layers, multiview); + } + } + } else { + rp_key.colors.push(None); } } if let Some(ref ds) = desc.depth_stencil_attachment { @@ -431,25 +436,29 @@ impl crate::CommandEncoder for super::CommandEncoder { min_depth: 0.0, max_depth: 1.0, }]; - let vk_scissors = [render_area]; let raw_pass = self.device.make_render_pass(rp_key).unwrap(); - let raw_framebuffer = self .device .make_framebuffer(fb_key, raw_pass, desc.label) .unwrap(); - let mut vk_attachment_info = vk::RenderPassAttachmentBeginInfo::builder() - .attachments(&vk_image_views) - .build(); let mut vk_info = vk::RenderPassBeginInfo::builder() .render_pass(raw_pass) .render_area(render_area) .clear_values(&vk_clear_values) .framebuffer(raw_framebuffer); - if caps.imageless_framebuffers { - vk_info = vk_info.push_next(&mut vk_attachment_info); + let mut vk_attachment_info = if caps.imageless_framebuffers { + Some( + vk::RenderPassAttachmentBeginInfo::builder() + .attachments(&vk_image_views) + .build(), + ) + } else { + None + }; + if let Some(attachment_info) = vk_attachment_info.as_mut() { + vk_info = vk_info.push_next(attachment_info); } if let Some(label) = desc.label { @@ -462,7 +471,7 @@ impl crate::CommandEncoder for super::CommandEncoder { .cmd_set_viewport(self.active, 0, &vk_viewports); self.device .raw - .cmd_set_scissor(self.active, 0, &vk_scissors); + .cmd_set_scissor(self.active, 0, &[render_area]); self.device .raw .cmd_begin_render_pass(self.active, &vk_info, vk::SubpassContents::INLINE); diff --git a/third_party/rust/wgpu-hal/src/vulkan/device.rs b/third_party/rust/wgpu-hal/src/vulkan/device.rs index 3dd6b8b31b00..6696dcca7bff 100644 --- a/third_party/rust/wgpu-hal/src/vulkan/device.rs +++ b/third_party/rust/wgpu-hal/src/vulkan/device.rs @@ -8,7 +8,7 @@ use parking_lot::Mutex; use std::{ borrow::Cow, collections::{hash_map::Entry, BTreeMap}, - ffi::CString, + ffi::{CStr, CString}, num::NonZeroU32, ptr, sync::Arc, @@ -21,8 +21,6 @@ impl super::DeviceShared { object: impl vk::Handle, name: &str, ) { - use std::ffi::CStr; - let extension = match self.instance.debug_utils { Some(ref debug_utils) => &debug_utils.extension, None => return, @@ -73,46 +71,54 @@ impl super::DeviceShared { let mut resolve_refs = Vec::with_capacity(color_refs.capacity()); let mut ds_ref = None; let samples = vk::SampleCountFlags::from_raw(e.key().sample_count); - + let unused = vk::AttachmentReference { + attachment: vk::ATTACHMENT_UNUSED, + layout: vk::ImageLayout::UNDEFINED, + }; for cat in e.key().colors.iter() { - color_refs.push(vk::AttachmentReference { - attachment: vk_attachments.len() as u32, - layout: cat.base.layout, - }); - vk_attachments.push({ - let (load_op, store_op) = conv::map_attachment_ops(cat.base.ops); - vk::AttachmentDescription::builder() - .format(cat.base.format) - .samples(samples) - .load_op(load_op) - .store_op(store_op) - .initial_layout(cat.base.layout) - .final_layout(cat.base.layout) - .build() - }); - let at_ref = if let Some(ref rat) = cat.resolve { - let at_ref = vk::AttachmentReference { + let (color_ref, resolve_ref) = if let Some(cat) = cat.as_ref() { + let color_ref = vk::AttachmentReference { attachment: vk_attachments.len() as u32, - layout: rat.layout, + layout: cat.base.layout, }; - let (load_op, store_op) = conv::map_attachment_ops(rat.ops); - let vk_attachment = vk::AttachmentDescription::builder() - .format(rat.format) - .samples(vk::SampleCountFlags::TYPE_1) - .load_op(load_op) - .store_op(store_op) - .initial_layout(rat.layout) - .final_layout(rat.layout) - .build(); - vk_attachments.push(vk_attachment); - at_ref + vk_attachments.push({ + let (load_op, store_op) = conv::map_attachment_ops(cat.base.ops); + vk::AttachmentDescription::builder() + .format(cat.base.format) + .samples(samples) + .load_op(load_op) + .store_op(store_op) + .initial_layout(cat.base.layout) + .final_layout(cat.base.layout) + .build() + }); + let resolve_ref = if let Some(ref rat) = cat.resolve { + let (load_op, store_op) = conv::map_attachment_ops(rat.ops); + let vk_attachment = vk::AttachmentDescription::builder() + .format(rat.format) + .samples(vk::SampleCountFlags::TYPE_1) + .load_op(load_op) + .store_op(store_op) + .initial_layout(rat.layout) + .final_layout(rat.layout) + .build(); + vk_attachments.push(vk_attachment); + + vk::AttachmentReference { + attachment: vk_attachments.len() as u32 - 1, + layout: rat.layout, + } + } else { + unused + }; + + (color_ref, resolve_ref) } else { - vk::AttachmentReference { - attachment: vk::ATTACHMENT_UNUSED, - layout: vk::ImageLayout::UNDEFINED, - } + (unused, unused) }; - resolve_refs.push(at_ref); + + color_refs.push(color_ref); + resolve_refs.push(resolve_ref); } if let Some(ref ds) = e.key().depth_stencil { @@ -699,6 +705,18 @@ impl super::Device { pub fn raw_device(&self) -> &ash::Device { &self.shared.raw } + + pub fn raw_physical_device(&self) -> ash::vk::PhysicalDevice { + self.shared.physical_device + } + + pub fn enabled_device_extensions(&self) -> &[&'static CStr] { + &self.shared.enabled_extensions + } + + pub fn shared_instance(&self) -> &super::InstanceShared { + &self.shared.instance + } } impl crate::Device for super::Device { @@ -1564,30 +1582,39 @@ impl crate::Device for super::Device { let mut vk_attachments = Vec::with_capacity(desc.color_targets.len()); for cat in desc.color_targets { - let vk_format = self.shared.private_caps.map_texture_format(cat.format); - compatible_rp_key.colors.push(super::ColorAttachmentKey { - base: super::AttachmentKey::compatible( - vk_format, - vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, - ), - resolve: None, - }); + let (key, attarchment) = if let Some(cat) = cat.as_ref() { + let mut vk_attachment = vk::PipelineColorBlendAttachmentState::builder() + .color_write_mask(vk::ColorComponentFlags::from_raw(cat.write_mask.bits())); + if let Some(ref blend) = cat.blend { + let (color_op, color_src, color_dst) = conv::map_blend_component(&blend.color); + let (alpha_op, alpha_src, alpha_dst) = conv::map_blend_component(&blend.alpha); + vk_attachment = vk_attachment + .blend_enable(true) + .color_blend_op(color_op) + .src_color_blend_factor(color_src) + .dst_color_blend_factor(color_dst) + .alpha_blend_op(alpha_op) + .src_alpha_blend_factor(alpha_src) + .dst_alpha_blend_factor(alpha_dst); + } - let mut vk_attachment = vk::PipelineColorBlendAttachmentState::builder() - .color_write_mask(vk::ColorComponentFlags::from_raw(cat.write_mask.bits())); - if let Some(ref blend) = cat.blend { - let (color_op, color_src, color_dst) = conv::map_blend_component(&blend.color); - let (alpha_op, alpha_src, alpha_dst) = conv::map_blend_component(&blend.alpha); - vk_attachment = vk_attachment - .blend_enable(true) - .color_blend_op(color_op) - .src_color_blend_factor(color_src) - .dst_color_blend_factor(color_dst) - .alpha_blend_op(alpha_op) - .src_alpha_blend_factor(alpha_src) - .dst_alpha_blend_factor(alpha_dst); - } - vk_attachments.push(vk_attachment.build()); + let vk_format = self.shared.private_caps.map_texture_format(cat.format); + ( + Some(super::ColorAttachmentKey { + base: super::AttachmentKey::compatible( + vk_format, + vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + ), + resolve: None, + }), + vk_attachment.build(), + ) + } else { + (None, vk::PipelineColorBlendAttachmentState::default()) + }; + + compatible_rp_key.colors.push(key); + vk_attachments.push(attarchment); } let vk_color_blend = vk::PipelineColorBlendStateCreateInfo::builder() diff --git a/third_party/rust/wgpu-hal/src/vulkan/instance.rs b/third_party/rust/wgpu-hal/src/vulkan/instance.rs index 4a814fc16c1d..59b2130dfe46 100644 --- a/third_party/rust/wgpu-hal/src/vulkan/instance.rs +++ b/third_party/rust/wgpu-hal/src/vulkan/instance.rs @@ -132,22 +132,28 @@ impl super::Swapchain { } } -impl super::Instance { +impl super::InstanceShared { pub fn entry(&self) -> &ash::Entry { - &self.shared.entry + &self.entry } pub fn raw_instance(&self) -> &ash::Instance { - &self.shared.raw + &self.raw } pub fn driver_api_version(&self) -> u32 { - self.shared.driver_api_version + self.driver_api_version } pub fn extensions(&self) -> &[&'static CStr] { &self.extensions[..] } +} + +impl super::Instance { + pub fn shared_instance(&self) -> &super::InstanceShared { + &self.shared + } pub fn required_extensions( entry: &ash::Entry, @@ -214,10 +220,13 @@ impl super::Instance { /// - `raw_instance` must be created respecting `driver_api_version`, `extensions` and `flags` /// - `extensions` must be a superset of `required_extensions()` and must be created from the /// same entry, driver_api_version and flags. + /// - `android_sdk_version` is ignored and can be `0` for all platforms besides Android + #[allow(clippy::too_many_arguments)] pub unsafe fn from_raw( entry: ash::Entry, raw_instance: ash::Instance, driver_api_version: u32, + android_sdk_version: u32, extensions: Vec<&'static CStr>, flags: crate::InstanceFlags, has_nv_optimus: bool, @@ -276,6 +285,7 @@ impl super::Instance { Ok(Self { shared: Arc::new(super::InstanceShared { raw: raw_instance, + extensions, drop_guard, flags, debug_utils, @@ -283,8 +293,8 @@ impl super::Instance { entry, has_nv_optimus, driver_api_version, + android_sdk_version, }), - extensions, }) } @@ -294,7 +304,7 @@ impl super::Instance { dpy: *mut vk::Display, window: vk::Window, ) -> super::Surface { - if !self.extensions.contains(&khr::XlibSurface::name()) { + if !self.shared.extensions.contains(&khr::XlibSurface::name()) { panic!("Vulkan driver does not support VK_KHR_XLIB_SURFACE"); } @@ -318,7 +328,7 @@ impl super::Instance { connection: *mut vk::xcb_connection_t, window: vk::xcb_window_t, ) -> super::Surface { - if !self.extensions.contains(&khr::XcbSurface::name()) { + if !self.shared.extensions.contains(&khr::XcbSurface::name()) { panic!("Vulkan driver does not support VK_KHR_XCB_SURFACE"); } @@ -342,7 +352,11 @@ impl super::Instance { display: *mut c_void, surface: *mut c_void, ) -> super::Surface { - if !self.extensions.contains(&khr::WaylandSurface::name()) { + if !self + .shared + .extensions + .contains(&khr::WaylandSurface::name()) + { panic!("Vulkan driver does not support VK_KHR_WAYLAND_SURFACE"); } @@ -379,7 +393,7 @@ impl super::Instance { hinstance: *mut c_void, hwnd: *mut c_void, ) -> super::Surface { - if !self.extensions.contains(&khr::Win32Surface::name()) { + if !self.shared.extensions.contains(&khr::Win32Surface::name()) { panic!("Vulkan driver does not support VK_KHR_WIN32_SURFACE"); } @@ -557,6 +571,28 @@ impl crate::Instance for super::Instance { layers }; + #[cfg(target_os = "android")] + let android_sdk_version = { + let properties = android_system_properties::AndroidSystemProperties::new(); + // See: https://developer.android.com/reference/android/os/Build.VERSION_CODES + if let Some(val) = properties.get("ro.build.version.sdk") { + match val.parse::() { + Ok(sdk_ver) => sdk_ver, + Err(err) => { + log::error!( + "Couldn't parse Android's ro.build.version.sdk system property ({val}): {err}" + ); + 0 + } + } + } else { + log::error!("Couldn't read Android's ro.build.version.sdk system property"); + 0 + } + }; + #[cfg(not(target_os = "android"))] + let android_sdk_version = 0; + let vk_instance = { let str_pointers = layers .iter() @@ -583,6 +619,7 @@ impl crate::Instance for super::Instance { entry, vk_instance, driver_api_version, + android_sdk_version, extensions, desc.flags, has_nv_optimus, @@ -598,16 +635,21 @@ impl crate::Instance for super::Instance { match has_handle.raw_window_handle() { RawWindowHandle::Wayland(handle) - if self.extensions.contains(&khr::WaylandSurface::name()) => + if self + .shared + .extensions + .contains(&khr::WaylandSurface::name()) => { Ok(self.create_surface_from_wayland(handle.display, handle.surface)) } RawWindowHandle::Xlib(handle) - if self.extensions.contains(&khr::XlibSurface::name()) => + if self.shared.extensions.contains(&khr::XlibSurface::name()) => { Ok(self.create_surface_from_xlib(handle.display as *mut _, handle.window)) } - RawWindowHandle::Xcb(handle) if self.extensions.contains(&khr::XcbSurface::name()) => { + RawWindowHandle::Xcb(handle) + if self.shared.extensions.contains(&khr::XcbSurface::name()) => + { Ok(self.create_surface_from_xcb(handle.connection, handle.window)) } RawWindowHandle::AndroidNdk(handle) => { @@ -622,13 +664,13 @@ impl crate::Instance for super::Instance { } #[cfg(target_os = "macos")] RawWindowHandle::AppKit(handle) - if self.extensions.contains(&ext::MetalSurface::name()) => + if self.shared.extensions.contains(&ext::MetalSurface::name()) => { Ok(self.create_surface_from_view(handle.ns_view)) } #[cfg(target_os = "ios")] RawWindowHandle::UiKit(handle) - if self.extensions.contains(&ext::MetalSurface::name()) => + if self.shared.extensions.contains(&ext::MetalSurface::name()) => { Ok(self.create_surface_from_view(handle.ui_view)) } @@ -707,10 +749,27 @@ impl crate::Surface for super::Surface { unsafe fn acquire_texture( &mut self, - timeout_ms: u32, + timeout: Option, ) -> Result>, crate::SurfaceError> { let sc = self.swapchain.as_mut().unwrap(); - let timeout_ns = timeout_ms as u64 * super::MILLIS_TO_NANOS; + + let mut timeout_ns = match timeout { + Some(duration) => duration.as_nanos() as u64, + None => u64::MAX, + }; + + // AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts + // and will also log verbose warnings if tying to use a timeout. + // + // Android 10 implementation for reference: + // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426 + // Android 11 implementation for reference: + // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438 + // + // Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30 + if cfg!(target_os = "android") && self.instance.android_sdk_version < 30 { + timeout_ns = u64::MAX; + } // will block if no image is available let (index, suboptimal) = diff --git a/third_party/rust/wgpu-hal/src/vulkan/mod.rs b/third_party/rust/wgpu-hal/src/vulkan/mod.rs index b3e1a0aa7f72..94d759899575 100644 --- a/third_party/rust/wgpu-hal/src/vulkan/mod.rs +++ b/third_party/rust/wgpu-hal/src/vulkan/mod.rs @@ -41,7 +41,7 @@ use ash::{ use parking_lot::Mutex; const MILLIS_TO_NANOS: u64 = 1_000_000; -const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_TARGETS * 2 + 1; +const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1; pub type DropGuard = Box; @@ -79,20 +79,21 @@ struct DebugUtils { messenger: vk::DebugUtilsMessengerEXT, } -struct InstanceShared { +pub struct InstanceShared { raw: ash::Instance, + extensions: Vec<&'static CStr>, drop_guard: Option, flags: crate::InstanceFlags, debug_utils: Option, get_physical_device_properties: Option, entry: ash::Entry, has_nv_optimus: bool, + android_sdk_version: u32, driver_api_version: u32, } pub struct Instance { shared: Arc, - extensions: Vec<&'static CStr>, } struct Swapchain { @@ -211,7 +212,7 @@ struct DepthStencilAttachmentKey { #[derive(Clone, Eq, Default, Hash, PartialEq)] struct RenderPassKey { - colors: ArrayVec, + colors: ArrayVec, { crate::MAX_COLOR_ATTACHMENTS }>, depth_stencil: Option, sample_count: u32, multiview: Option, @@ -313,6 +314,8 @@ struct DeviceShared { raw: ash::Device, handle_is_owned: bool, instance: Arc, + physical_device: ash::vk::PhysicalDevice, + enabled_extensions: Vec<&'static CStr>, extension_fns: DeviceExtensionFunctions, vendor_id: u32, timestamp_period: f32, diff --git a/third_party/rust/wgpu-types/.cargo-checksum.json b/third_party/rust/wgpu-types/.cargo-checksum.json index 6c6757b36c8f..d1bbac6a0205 100644 --- a/third_party/rust/wgpu-types/.cargo-checksum.json +++ b/third_party/rust/wgpu-types/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.toml":"742abe387ad0a6b054a3e525040ff1e64f16072ae7cd305db0616beaa844e389","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/lib.rs":"bfac3d39cff5da6d59889dc72206ddae4d553a8aaae20c890a204d3bdc7cdf6a"},"package":null} \ No newline at end of file +{"files":{"Cargo.toml":"742abe387ad0a6b054a3e525040ff1e64f16072ae7cd305db0616beaa844e389","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/lib.rs":"0cca795714dd31ddaf1bf6cf960d677b6a4a21e3fde9fd3c4d407a8b71afd917"},"package":null} \ No newline at end of file diff --git a/third_party/rust/wgpu-types/src/lib.rs b/third_party/rust/wgpu-types/src/lib.rs index 5e3a010b5a14..9203d494a3b9 100644 --- a/third_party/rust/wgpu-types/src/lib.rs +++ b/third_party/rust/wgpu-types/src/lib.rs @@ -173,6 +173,12 @@ bitflags::bitflags! { #[repr(transparent)] #[derive(Default)] pub struct Features: u64 { + // + // ---- Start numbering at 1 << 0 ---- + // + // WebGPU features: + // + /// By default, polygon depth is clipped to 0-1 range before/during rasterization. /// Anything outside of that range is rejected, and respective fragments are not touched. /// @@ -299,6 +305,13 @@ bitflags::bitflags! { /// /// This is a web and native feature. const SHADER_FLOAT16 = 1 << 9; + + // + // ---- Restart Numbering for Native Features --- + // + // Native Features: + // + /// Webgpu only allows the MAP_READ and MAP_WRITE buffer usage to be matched with /// COPY_DST and COPY_SRC respectively. This removes this requirement. /// @@ -422,6 +435,7 @@ bitflags::bitflags! { /// Supported platforms: /// - DX12 /// - Vulkan + /// - Metal (Emulated on top of `draw_indirect` and `draw_indexed_indirect`) /// /// This is a native only feature. const MULTI_DRAW_INDIRECT = 1 << 23; @@ -592,11 +606,29 @@ bitflags::bitflags! { /// /// This is a native only feature. const ADDRESS_MODE_CLAMP_TO_ZERO = 1 << 39; + /// Enables ASTC HDR family of compressed textures. + /// + /// Compressed textures sacrifice some quality in exchange for significantly reduced + /// bandwidth usage. + /// + /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for BCn formats. + /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] may enable additional usages. + /// /// Supported Platforms: /// - Metal /// /// This is a native-only feature. const TEXTURE_COMPRESSION_ASTC_HDR = 1 << 40; + /// Allows for timestamp queries inside renderpasses. Metal does not allow this + /// on Apple GPUs. + /// + /// Implies [`Features::TIMESTAMP_QUERIES`] is supported. + /// + /// Supported platforms: + /// - Vulkan + /// - DX12 + /// - Metal (Intel and AMD GPUs) + const WRITE_TIMESTAMP_INSIDE_PASSES = 1 << 41; } } @@ -736,6 +768,11 @@ pub struct Limits { /// The maximum value for each dimension of a `ComputePass::dispatch(x, y, z)` operation. /// Defaults to 65535. pub max_compute_workgroups_per_dimension: u32, + /// A limit above which buffer allocations are guaranteed to fail. + /// + /// Buffer allocations below the maximum buffer size may not succed depending on available memory, + /// fragmentation and other factors. + pub max_buffer_size: u64, } impl Default for Limits { @@ -768,6 +805,7 @@ impl Default for Limits { max_compute_workgroup_size_y: 256, max_compute_workgroup_size_z: 64, max_compute_workgroups_per_dimension: 65535, + max_buffer_size: 1 << 30, } } } @@ -803,6 +841,7 @@ impl Limits { max_compute_workgroup_size_y: 256, max_compute_workgroup_size_z: 64, max_compute_workgroups_per_dimension: 65535, + max_buffer_size: 1 << 28, } } @@ -875,7 +914,7 @@ impl Limits { &self, allowed: &Self, fatal: bool, - mut fail_fn: impl FnMut(&'static str, u32, u32), + mut fail_fn: impl FnMut(&'static str, u64, u64), ) { use std::cmp::Ordering; @@ -884,7 +923,7 @@ impl Limits { match self.$name.cmp(&allowed.$name) { Ordering::$ordering | Ordering::Equal => (), _ => { - fail_fn(stringify!($name), self.$name, allowed.$name); + fail_fn(stringify!($name), self.$name as u64, allowed.$name as u64); if fatal { return; } @@ -920,6 +959,7 @@ impl Limits { compare!(max_compute_workgroup_size_y, Less); compare!(max_compute_workgroup_size_z, Less); compare!(max_compute_workgroups_per_dimension, Less); + compare!(max_buffer_size, Less); } } @@ -1026,6 +1066,15 @@ bitflags::bitflags! { /// /// GLES/WebGL don't support this. const DEPTH_TEXTURE_AND_BUFFER_COPIES = 1 << 13; + + /// Supports all the texture usages described in WebGPU. If this isn't supported, you + /// should call `get_texture_format_features` to get how you can use textures of a given format + const WEBGPU_TEXTURE_FORMAT_SUPPORT = 1 << 14; + + /// Supports buffer bindings with sizes that aren't a multiple of 16. + /// + /// WebGL doesn't support this. + const BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED = 1 << 15; } } @@ -2287,6 +2336,43 @@ impl Default for ColorWrites { } } +/// Passed to `Device::poll` to control how and if it should block. +#[derive(Clone)] +pub enum Maintain { + /// On native backends, block until the given submission has + /// completed execution, and any callbacks have been invoked. + /// + /// On the web, this has no effect. Callbacks are invoked from the + /// window event loop. + WaitForSubmissionIndex(T), + /// Same as WaitForSubmissionIndex but waits for the most recent submission. + Wait, + /// Check the device for a single time without blocking. + Poll, +} + +impl Maintain { + /// This maintain represents a wait of some kind. + pub fn is_wait(&self) -> bool { + match *self { + Self::WaitForSubmissionIndex(..) | Self::Wait => true, + Self::Poll => false, + } + } + + /// Map on the wait index type. + pub fn map_index(self, func: F) -> Maintain + where + F: FnOnce(T) -> U, + { + match self { + Self::WaitForSubmissionIndex(i) => Maintain::WaitForSubmissionIndex(func(i)), + Self::Wait => Maintain::Wait, + Self::Poll => Maintain::Poll, + } + } +} + /// State of the stencil operation (fixed-pipeline stage). /// /// For use in [`DepthStencilState`]. @@ -2381,9 +2467,20 @@ impl DepthStencilState { pub fn is_depth_enabled(&self) -> bool { self.depth_compare != CompareFunction::Always || self.depth_write_enabled } + + /// Returns true if the state doesn't mutate the depth buffer. + pub fn is_depth_read_only(&self) -> bool { + !self.depth_write_enabled + } + + /// Returns true if the state doesn't mutate the stencil. + pub fn is_stencil_read_only(&self) -> bool { + self.stencil.is_read_only() + } + /// Returns true if the state doesn't mutate either depth or stencil of the target. pub fn is_read_only(&self) -> bool { - !self.depth_write_enabled && self.stencil.is_read_only() + self.is_depth_read_only() && self.is_stencil_read_only() } } @@ -3257,6 +3354,7 @@ pub struct TextureDescriptor { pub format: TextureFormat, /// Allowed usages of the texture. If used in other ways, the operation will panic. pub usage: TextureUsages, + // TODO: missing view_formats https://www.w3.org/TR/webgpu/#dom-gputexturedescriptor-viewformats } impl TextureDescriptor {