diff --git a/.deny.toml b/.deny.toml index efcee7579e..1976f709c9 100644 --- a/.deny.toml +++ b/.deny.toml @@ -6,12 +6,17 @@ skip-tree = [ { name = "windows-sys", version = "0.45" }, { name = "winit", version = "0.29" }, { name = "rustc_version", version = "0.2.3" }, - { name = "sourcemap", version = "7.1.1" }, { name = "miniz_oxide", version = "0.7.4" }, + + # introduced by Deno, to be investigated + { name = "deno_core", version = "0.321.0" }, + { name = "deno_permissions", version = "0.39.0" }, ] skip = [ # Strum uses an old version { name = "heck", version = "0.4.0" }, + # Deno uses an old version + { name = "strum", version = "0.25.0" }, ] wildcards = "deny" allow-wildcard-paths = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 261a7636af..a10a2d0d9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,9 +34,9 @@ env: # We sometimes need nightly to use special things in CI. # # In order to prevent CI regressions, we pin the nightly version. - NIGHTLY_VERSION: "nightly-2023-12-17" + NIGHTLY_VERSION: "nightly-2024-10-10" # This is the MSRV used by `wgpu` itself and all surrounding infrastructure. - REPO_MSRV: "1.76" + REPO_MSRV: "1.83" # This is the MSRV used by the `wgpu-core`, `wgpu-hal`, and `wgpu-types` crates, # to ensure that they can be used with firefox. CORE_MSRV: "1.76" @@ -48,7 +48,7 @@ env: CARGO_INCREMENTAL: false CARGO_TERM_COLOR: always WGPU_DX12_COMPILER: dxc - RUST_LOG: info + RUST_LOG: debug RUST_BACKTRACE: full PKG_CONFIG_ALLOW_CROSS: 1 # allow android to work RUSTFLAGS: -D warnings @@ -88,6 +88,12 @@ jobs: target: x86_64-pc-windows-msvc kind: native + # Windows + - name: Windows aarch64 + os: windows-2022 + target: aarch64-pc-windows-msvc + kind: native + # MacOS - name: MacOS x86_64 os: macos-14 @@ -629,7 +635,7 @@ jobs: run: taplo format --check --diff - name: Check for typos - uses: crate-ci/typos@v1.28.2 + uses: crate-ci/typos@v1.29.4 check-cts-runner: # runtime is normally 2 minutes diff --git a/CHANGELOG.md b/CHANGELOG.md index 501ee4b713..0208d57504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,20 +40,42 @@ Bottom level categories: ## Unreleased -## Major changes +### Major changes -### Refactored Dispatch Between `wgpu-core` and `webgpu` +#### Refactored Dispatch Between `wgpu-core` and `webgpu` The crate `wgpu` has two different "backends", one which targets webgpu in the browser, one which targets `wgpu_core` on native platforms and webgl. This was previously very difficult to traverse and add new features to. The entire system was refactored to make it simpler. Additionally the new system has zero overhead if there is only one "backend" in use. You can see the new system in action by using go-to-definition on any wgpu functions in your IDE. By @cwfitzgerald in [#6619](https://github.com/gfx-rs/wgpu/pull/6619). -### Render and Compute Passes Now Properly Enforce Their Lifetime +#### Render and Compute Passes Now Properly Enforce Their Lifetime A regression introduced in 23.0.0 caused lifetimes of render and compute passes to be incorrectly enforced. While this is not a soundness issue, the intent is to move an error from runtime to compile time. This issue has been fixed and restored to the 22.0.0 behavior. -### The `diagnostic(…);` directive is now supported in WGSL +#### Bindless (`binding_array`) Grew More Capabilities + +- DX12 now supports `PARTIALLY_BOUND_BINDING_ARRAY` on Resource Binding Tier 3 Hardware. This is most D3D12 hardware [D3D12 Feature Table] for more information on what hardware supports this feature. By @cwfitzgerald in [#6734](https://github.com/gfx-rs/wgpu/pull/6734). + +[D3D12 Feature Table]: https://d3d12infodb.boolka.dev/FeatureTable.html + +#### `Device::create_shader_module_unchecked` Renamed and Now Has Configuration Options + +`create_shader_module_unchecked` became `create_shader_module_trusted`. + +This allows you to customize which exact checks are omitted so that you can get the correct balance of performance and safety for your use case. Calling the function is still unsafe, but now can be used to skip certain checks only on certain builds. + +This also allows users to disable the workarounds in the `msl-out` backend to prevent the compiler from optimizing infinite loops. This can have a big impact on performance, but is not recommended for untrusted shaders. + +```diff +let desc: ShaderModuleDescriptor = include_wgsl!(...) +- let module = unsafe { device.create_shader_module_unchecked(desc) }; ++ let module = unsafe { device.create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked()) }; +``` + +By @cwfitzgerald and @rudderbucky in [#6662](https://github.com/gfx-rs/wgpu/pull/6662). + +#### The `diagnostic(…);` directive is now supported in WGSL Naga now parses `diagnostic(…);` directives according to the WGSL spec. This allows users to control certain lints, similar to Rust's `allow`, `warn`, and `deny` attributes. For example, in standard WGSL (but, notably, not Naga yet—see ) this snippet would emit a uniformity error: @@ -103,12 +125,32 @@ There are some limitations to keep in mind with this new functionality: By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456), [#6148](https://github.com/gfx-rs/wgpu/pull/6148), [#6533](https://github.com/gfx-rs/wgpu/pull/6533), [#6353](https://github.com/gfx-rs/wgpu/pull/6353), [#6537](https://github.com/gfx-rs/wgpu/pull/6537). -### New Features +#### `wgpu::Instance::new` now takes `InstanceDescriptor` by reference -Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs/wgpu/pull/6706) +Previously `wgpu::Instance::new` took `InstanceDescriptor` by value (which is overall fairly uncommon in wgpu). +Furthermore, `InstanceDescriptor` is now cloneable. -#### Naga +```diff +- let instance = wgpu::Instance::new(instance_desc); ++ let instance = wgpu::Instance::new(&instance_desc); +``` + +By @wumpf in [#6849](https://github.com/gfx-rs/wgpu/pull/6849). + +#### New Features + +##### General + +- Add unified documentation for ray-tracing. By @Vecvec in [#6747](https://github.com/gfx-rs/wgpu/pull/6747) +- Return submission index in `map_async` and `on_submitted_work_done` to track down completion of async callbacks. By @eliemichel in [#6360](https://github.com/gfx-rs/wgpu/pull/6360). +- Move raytracing alignments into HAL instead of in core. By @Vecvec in [#6563](https://github.com/gfx-rs/wgpu/pull/6563). +- Allow for statically linking DXC rather than including separate `.dll` files. By @DouglasDwyer in [#6574](https://github.com/gfx-rs/wgpu/pull/6574). +- `DeviceType` and `AdapterInfo` now impl `Hash` by @cwfitzgerald in [#6868](https://github.com/gfx-rs/wgpu/pull/6868) +- Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs/wgpu/pull/6706) + +##### Naga +- Support atomic operations on fields of global structs in the SPIR-V frontend. By @schell in [#6693](https://github.com/gfx-rs/wgpu/pull/6693). - Clean up tests for atomic operations support in SPIR-V frontend. By @schell in [#6692](https://github.com/gfx-rs/wgpu/pull/6692) - Fix an issue where `naga` CLI would incorrectly skip the first positional argument when `--stdin-file-path` was specified. By @ErichDonGubler in [#6480](https://github.com/gfx-rs/wgpu/pull/6480). - Fix textureNumLevels in the GLSL backend. By @magcius in [#6483](https://github.com/gfx-rs/wgpu/pull/6483). @@ -116,25 +158,22 @@ Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs - Implement `quantizeToF16()` for WGSL frontend, and WGSL, SPIR-V, HLSL, MSL, and GLSL backends. By @jamienicol in [#6519](https://github.com/gfx-rs/wgpu/pull/6519). - Add support for GLSL `usampler*` and `isampler*`. By @DavidPeicho in [#6513](https://github.com/gfx-rs/wgpu/pull/6513). - Expose Ray Query flags as constants in WGSL. Implement candidate intersections. By @kvark in [#5429](https://github.com/gfx-rs/wgpu/pull/5429) +- Add new vertex formats (`{U,S}{int,norm}{8,16}`, `Float16` and `Unorm8x4Bgra`). By @nolanderc in [#6632](https://github.com/gfx-rs/wgpu/pull/6632) - Allow for override-expressions in `workgroup_size`. By @KentSlaney in [#6635](https://github.com/gfx-rs/wgpu/pull/6635). - Add support for OpAtomicCompareExchange in SPIR-V frontend. By @schell in [#6590](https://github.com/gfx-rs/wgpu/pull/6590). - Implement type inference for abstract arguments to user-defined functions. By @jamienicol in [#6577](https://github.com/gfx-rs/wgpu/pull/6577). - Allow for override-expressions in array sizes. By @KentSlaney in [#6654](https://github.com/gfx-rs/wgpu/pull/6654). -#### General - -- Return submission index in `map_async` and `on_submitted_work_done` to track down completion of async callbacks. By @eliemichel in [#6360](https://github.com/gfx-rs/wgpu/pull/6360). -- Move raytracing alignments into HAL instead of in core. By @Vecvec in [#6563](https://github.com/gfx-rs/wgpu/pull/6563). -- Allow for statically linking DXC rather than including separate `.dll` files. By @DouglasDwyer in [#6574](https://github.com/gfx-rs/wgpu/pull/6574). - -### Changes +#### Changes -#### Naga +##### Naga - Show types of LHS and RHS in binary operation type mismatch errors. By @ErichDonGubler in [#6450](https://github.com/gfx-rs/wgpu/pull/6450). - The GLSL parser now uses less expressions for function calls. By @magcius in [#6604](https://github.com/gfx-rs/wgpu/pull/6604). +- Add a note to help with a common syntax error case for global diagnostic filter directives. By @e-hat in [#6718](https://github.com/gfx-rs/wgpu/pull/6718) +- Change arithmetic operations between two i32 variables to wrap on overflow to match WGSL spec. By @matthew-wong1 in [#6835](https://github.com/gfx-rs/wgpu/pull/6835). -#### General +##### General - Align Storage Access enums to the webgpu spec. By @atlv24 in [#6642](https://github.com/gfx-rs/wgpu/pull/6642) - Make `Surface::as_hal` take an immutable reference to the surface. By @jerzywilczek in [#9999](https://github.com/gfx-rs/wgpu/pull/9999) @@ -142,11 +181,15 @@ Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs - Improve binding error to give a clearer message when there is a mismatch between resource binding as it is in the shader and as it is in the binding layout. By @eliemichel in [#6553](https://github.com/gfx-rs/wgpu/pull/6553). - `Surface::configure` and `Surface::get_current_texture` are no longer fatal. By @alokedesai in [#6253](https://github.com/gfx-rs/wgpu/pull/6253) -#### D3D12 +##### D3D12 - Avoid using FXC as fallback when the DXC container was passed at instance creation. Paths to `dxcompiler.dll` & `dxil.dll` are also now required. By @teoxoy in [#6643](https://github.com/gfx-rs/wgpu/pull/6643). -#### HAL +##### Vulkan + +- Add a cache for samplers, deduplicating any samplers, allowing more programs to stay within the global sampler limit. By @cwfitzgerald in [#6847](https://github.com/gfx-rs/wgpu/pull/6847) + +##### HAL - Replace `usage: Range`, for `BufferUses`, `TextureUses`, and `AccelerationStructureBarrier` with a new `StateTransition`. By @atlv24 in [#6703](https://github.com/gfx-rs/wgpu/pull/6703) - Change the `DropCallback` API to use `FnOnce` instead of `FnMut`. By @jerzywilczek in [#6482](https://github.com/gfx-rs/wgpu/pull/6482) @@ -168,12 +211,31 @@ Image atomic support in shaders. By @atlv24 in [#6706](https://github.com/gfx-rs - Check that at least one index is specified. - Reject destroyed buffers in query set resolution. By @ErichDonGubler in [#6579](https://github.com/gfx-rs/wgpu/pull/6579). - Fix panic when dropping `Device` on some environments. By @Dinnerbone in [#6681](https://github.com/gfx-rs/wgpu/pull/6681). +- Reduced the overhead of command buffer validation. By @nical in [#6721](https://github.com/gfx-rs/wgpu/pull/6721). +- Set index type to NONE in `get_acceleration_structure_build_sizes`. By @Vecvec in [#6802](https://github.com/gfx-rs/wgpu/pull/6802). +- Fix `wgpu-info` not showing dx12 adapters. By @wumpf in [#6844](https://github.com/gfx-rs/wgpu/pull/6844). +- Use `transform_buffer_offset` when initialising `transform_buffer`. By @Vecvec in [#6864](https://github.com/gfx-rs/wgpu/pull/6864). #### Naga - Fix crash when a texture argument is missing. By @aedm in [#6486](https://github.com/gfx-rs/wgpu/pull/6486) - Emit an error in constant evaluation, rather than crash, in certain cases where `vecN` constructors have less than N arguments. By @ErichDonGubler in [#6508](https://github.com/gfx-rs/wgpu/pull/6508). +#### Vulkan + +- Allocate descriptors for acceleration structures. By @Vecvec in [#6861](https://github.com/gfx-rs/wgpu/pull/6861). +- `max_color_attachment_bytes_per_sample` is now correctly set to 128. By @cwfitzgerald in [#6866](https://github.com/gfx-rs/wgpu/pull/6866) + +#### D3D12 + +- Fix no longer showing software rasterizer adapters. By @wumpf in [#6843](https://github.com/gfx-rs/wgpu/pull/6843). +- `max_color_attachment_bytes_per_sample` is now correctly set to 128. By @cwfitzgerald in [#6866](https://github.com/gfx-rs/wgpu/pull/6866) + +### Examples + +- Add multiple render targets example. By @kaphula in [#5297](https://github.com/gfx-rs/wgpu/pull/5313) + + ### Testing - Tests the early returns in the acceleration structure build calls with empty calls. By @Vecvec in [#6651](https://github.com/gfx-rs/wgpu/pull/6651). diff --git a/Cargo.lock b/Cargo.lock index 9170790dc2..226cdae506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,12 +61,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android-activity" version = "0.5.2" @@ -85,7 +79,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -160,9 +154,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -175,19 +169,20 @@ dependencies = [ [[package]] name = "argh" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" dependencies = [ "argh_derive", "argh_shared", + "rust-fuzzy-search", ] [[package]] name = "argh_derive" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" dependencies = [ "argh_shared", "proc-macro2", @@ -197,9 +192,9 @@ dependencies = [ [[package]] name = "argh_shared" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" dependencies = [ "serde", ] @@ -236,9 +231,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", @@ -266,7 +261,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.0", + "miniz_oxide 0.8.2", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -306,15 +301,50 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -337,6 +367,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -370,18 +412,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", @@ -411,7 +453,7 @@ dependencies = [ "polling", "rustix", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -434,9 +476,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ "jobserver", "libc", @@ -449,6 +491,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -461,6 +512,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cgl" version = "0.3.2" @@ -497,6 +554,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.23" @@ -600,15 +668,9 @@ dependencies = [ [[package]] name = "const_panic" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" - -[[package]] -name = "convert_case" -version = "0.4.0" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" [[package]] name = "cooked-waker" @@ -677,7 +739,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -698,14 +760,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -722,9 +784,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -780,33 +842,34 @@ dependencies = [ [[package]] name = "deno_console" -version = "0.143.0" +version = "0.179.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f770d8deb0eb0bfd596d242d9eaef5312ef57f0130964cb53c7f6a8107d13be" +checksum = "2e09f2bbb2d842329b602da25dbab5cd4a342f9a8adcb7c02509fc322f796e79" dependencies = [ "deno_core", ] [[package]] name = "deno_core" -version = "0.272.0" +version = "0.321.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07093891f2af763023614cfe2d1ce5f9ce5a7920c4fcf2f00911bd0d93083523" +checksum = "cd2a54cda74cdc187d5fc2d23370a45cf09f912caf566dd1cd24a50157d809c7" dependencies = [ "anyhow", "bincode", - "bit-set", - "bit-vec", + "bit-set 0.5.3", + "bit-vec 0.6.3", "bytes", "cooked-waker", "deno_core_icudata", "deno_ops", "deno_unsync", "futures", + "indexmap", "libc", - "log", "memoffset", "parking_lot", + "percent-encoding", "pin-project", "serde", "serde_json", @@ -817,76 +880,122 @@ dependencies = [ "tokio", "url", "v8", + "wasm_dep_analyzer", ] [[package]] name = "deno_core_icudata" -version = "0.0.73" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13951ea98c0a4c372f162d669193b4c9d991512de9f2381dd161027f34b26b1" +checksum = "fe4dccb6147bb3f3ba0c7a48e993bfeb999d2c2e47a81badee80e2b370c8d695" [[package]] name = "deno_ops" -version = "0.148.0" +version = "0.197.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc73fc07ad26e71715d5a726d1dd228587c0d121a591b1931a0fcf958a2ec3b" +checksum = "37a8825d92301cf445727c43f17fee2a20fcdf4370004339965156ae7c56c97e" dependencies = [ "proc-macro-rules", "proc-macro2", "quote", - "strum", - "strum_macros", + "stringcase", + "strum 0.25.0", + "strum_macros 0.25.3", "syn", - "thiserror", + "thiserror 1.0.69", +] + +[[package]] +name = "deno_path_util" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff25f6e08e7a0214bbacdd6f7195c7f1ebcd850c87a624e4ff06326b68b42d99" +dependencies = [ + "percent-encoding", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "deno_permissions" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e822f98185ab3ddf06104b2407681e0008af52361af32f1cd171b7eda5aa59" +dependencies = [ + "deno_core", + "deno_path_util", + "deno_terminal", + "fqdn", + "libc", + "log", + "once_cell", + "percent-encoding", + "serde", + "thiserror 1.0.69", + "which 4.4.2", + "winapi", +] + +[[package]] +name = "deno_terminal" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daef12499e89ee99e51ad6000a91f600d3937fb028ad4918af76810c5bc9e0d5" +dependencies = [ + "once_cell", + "termcolor", ] [[package]] name = "deno_unsync" -version = "0.3.10" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c8b95582c2023dbb66fccc37421b374026f5915fa507d437cb566904db9a3a" +checksum = "d774fd83f26b24f0805a6ab8b26834a0d06ceac0db517b769b1e4633c96a2057" dependencies = [ + "futures", "parking_lot", "tokio", ] [[package]] name = "deno_url" -version = "0.143.0" +version = "0.179.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39d9e6ffd6a7157bfd3cf1385c59232e49587c9bbb898e64010f7f082242a203" +checksum = "ad9a108794e505f2b07665e19ff336c1bcba6adcf7182c90c1d3a6c741d7fcd0" dependencies = [ "deno_core", - "serde", + "thiserror 1.0.69", "urlpattern", ] [[package]] name = "deno_web" -version = "0.174.0" +version = "0.210.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708666b5b346e6880c1372006615814db7fc5ef36bd1785f0b0e4f8617082999" +checksum = "7679087bcc41f7ae3385f8c12d43bc81cfc54cb9b1ef73983d20f5e39fa4e0da" dependencies = [ "async-trait", "base64-simd 0.8.0", "bytes", "deno_core", + "deno_permissions", "encoding_rs", "flate2", "futures", "serde", + "thiserror 1.0.69", "tokio", "uuid", - "windows-sys 0.48.0", ] [[package]] name = "deno_webgpu" -version = "0.118.0" +version = "0.146.0" dependencies = [ "deno_core", "raw-window-handle 0.6.2", "serde", + "thiserror 2.0.9", "tokio", "wgpu-core", "wgpu-types", @@ -894,9 +1003,9 @@ dependencies = [ [[package]] name = "deno_webidl" -version = "0.143.0" +version = "0.179.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddad93aa68e3c3c2d36976cd401af27a6ce750c23060e02401daf240f2acbe2" +checksum = "5b55d845e3d64f8de7eff67aaa4b6fe1b23bbc2efe967c984f8c64c8dd85fad4" dependencies = [ "deno_core", ] @@ -912,19 +1021,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_more" -version = "0.99.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.1", - "syn", -] - [[package]] name = "diff" version = "0.1.13" @@ -980,30 +1076,30 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encase" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0265fa0e7bcdb058128cdf7597cdacea42e33911713663a04d971a39cad16afa" +checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020" dependencies = [ "const_panic", "encase_derive", "glam", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "encase_derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b6f7502bafc52a60b5582560a2aaee16921eef79a742ae48dd411fe7a9263b" +checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85" dependencies = [ "encase_derive_impl", ] [[package]] name = "encase_derive_impl" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36f2ddfca91251bed7f931f24b192e4eaf0a0e0fa70cf81cfb1416a1973620e" +checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" dependencies = [ "proc-macro2", "quote", @@ -1021,9 +1117,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -1031,9 +1127,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -1058,6 +1154,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "fastrand" version = "2.3.0" @@ -1075,9 +1177,9 @@ dependencies = [ [[package]] name = "fern" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" dependencies = [ "log", ] @@ -1095,7 +1197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide 0.8.2", ] [[package]] @@ -1110,6 +1212,12 @@ dependencies = [ "spin", ] +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.5.0" @@ -1146,6 +1254,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fqdn" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb540cf7bc4fe6df9d8f7f0c974cfd0dce8ed4e9e8884e73433b503ee78b4e7d" + [[package]] name = "fslock" version = "0.2.1" @@ -1156,6 +1270,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1313,18 +1433,18 @@ dependencies = [ [[package]] name = "glam" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" +checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" dependencies = [ "bytemuck", ] [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "glow" @@ -1345,7 +1465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "cgl", "core-foundation", "dispatch", @@ -1367,7 +1487,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "glutin", "raw-window-handle 0.5.2", "winit", @@ -1428,19 +1548,19 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "windows", ] [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -1471,21 +1591,14 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -1499,12 +1612,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1527,11 +1634,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1717,7 +1824,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.2", + "hashbrown", "serde", ] @@ -1727,7 +1834,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", "windows-sys 0.52.0", ] @@ -1747,6 +1854,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -1764,7 +1880,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1786,9 +1902,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -1828,15 +1944,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" dependencies = [ "arbitrary", "cc", @@ -1850,7 +1966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1861,18 +1977,19 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] name = "libtest-mimic" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" +checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" dependencies = [ + "anstream", + "anstyle", "clap", - "termcolor", - "threadpool", + "escape8259", ] [[package]] @@ -1933,9 +2050,9 @@ dependencies = [ [[package]] name = "mach-dxcompiler-rs" -version = "0.1.3+2024.11.22-df583a3.1" +version = "0.1.4+2024.11.22-df583a3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42adcec8ba129fc3972105e3736a6e6caa61128ead89e90b13cb5b83d70f8881" +checksum = "0e3cd67e8ea2ba061339150970542cf1c60ba44c6d17e31279cbc133a4b018f8" [[package]] name = "malloc_buf" @@ -1982,8 +2099,7 @@ dependencies = [ [[package]] name = "metal" version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9" +source = "git+https://github.com/gfx-rs/metal-rs.git?rev=ef768ff9d7#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" dependencies = [ "bitflags 2.6.0", "block", @@ -2004,6 +2120,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2015,9 +2137,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", @@ -2040,16 +2162,16 @@ version = "23.0.0" dependencies = [ "arbitrary", "arrayvec", - "bit-set", + "bit-set 0.8.0", "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.2.1", "codespan-reporting", "diff", "env_logger", "hexf-parse", "hlsl-snapshots", "indexmap", - "itertools", + "itertools 0.13.0", "log", "petgraph", "pp-rs", @@ -2058,9 +2180,9 @@ dependencies = [ "rustc-hash", "serde", "spirv 0.3.0+sdk-1.3.268.0", - "strum", + "strum 0.26.3", "termcolor", - "thiserror", + "thiserror 2.0.9", "unicode-xid", ] @@ -2082,6 +2204,7 @@ name = "naga-fuzz" version = "0.0.0" dependencies = [ "arbitrary", + "cfg_aliases 0.2.1", "libfuzzer-sys", "naga", ] @@ -2123,7 +2246,7 @@ dependencies = [ "num_enum", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2151,6 +2274,16 @@ dependencies = [ "rand_xorshift", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2190,16 +2323,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.3" @@ -2278,9 +2401,9 @@ checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2306,6 +2429,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "outref" version = "0.1.0" @@ -2359,7 +2491,7 @@ dependencies = [ "cfg-if", "libc", "petgraph", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "thread-id", "windows-targets 0.52.6", @@ -2395,18 +2527,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", @@ -2415,9 +2547,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2475,15 +2607,15 @@ dependencies = [ [[package]] name = "png" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.0", + "miniz_oxide 0.8.2", ] [[package]] @@ -2494,7 +2626,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -2503,9 +2635,9 @@ dependencies = [ [[package]] name = "pollster" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "pp-rs" @@ -2522,6 +2654,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "prettyplease" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -2580,13 +2722,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2660,9 +2808,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -2738,6 +2886,12 @@ dependencies = [ "spirv 0.2.0+sdk-1.2.198", ] +[[package]] +name = "rust-fuzzy-search" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2756,16 +2910,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver 1.0.23", + "semver", ] [[package]] @@ -2783,9 +2928,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -2836,12 +2981,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "semver-parser" version = "0.7.0" @@ -2850,18 +2989,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -2870,9 +3009,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "indexmap", "itoa", @@ -2892,15 +3031,14 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.181.0" +version = "0.230.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd25bb66a20a1a405fb3733aaaf8a8a77a14fd55c8f5fd9db2a2e95bbd7eeab9" +checksum = "b5a783242d2af51d6955cc04bf2b64adb643ab588b61e9573c908a69dabf8c2f" dependencies = [ - "bytes", "num-bigint", "serde", "smallvec", - "thiserror", + "thiserror 1.0.69", "v8", ] @@ -2981,7 +3119,7 @@ dependencies = [ "log", "memmap2", "rustix", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -3013,15 +3151,17 @@ dependencies = [ [[package]] name = "sourcemap" -version = "7.1.1" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7768edd06c02535e0d50653968f46e1e0d3aa54742190d35dd9466f59de9c71" +checksum = "208d40b9e8cad9f93613778ea295ed8f3c2b1824217c6cfc7219d3f6f45b96d4" dependencies = [ "base64-simd 0.7.0", + "bitvec", "data-encoding", "debugid", "if_chain", - "rustc_version 0.2.3", + "rustc-hash", + "rustc_version", "serde", "serde_json", "unicode-id-start", @@ -3073,6 +3213,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "stringcase" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04028eeb851ed08af6aba5caa29f2d59a13ed168cee4d6bd753aeefcf1d636b0" + [[package]] name = "strsim" version = "0.11.1" @@ -3085,7 +3231,16 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros", + "strum_macros 0.25.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", ] [[package]] @@ -3101,11 +3256,24 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" -version = "2.0.90" +version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", @@ -3123,6 +3291,12 @@ dependencies = [ "syn", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-triple" version = "0.1.3" @@ -3144,7 +3318,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -3158,6 +3341,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread-id" version = "4.2.2" @@ -3178,15 +3372,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -3346,9 +3531,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e295eae54124872df35720dc3a5b1e827c7deee352b342ec7f7e626d0d0ef3" +checksum = "73202d787346a5418f8222eddb5a00f29ea47caf3c7d38a8f2f69f8455fa7c7e" dependencies = [ "loom", "once_cell", @@ -3357,12 +3542,12 @@ dependencies = [ [[package]] name = "tracy-client-sys" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3637e734239e12ab152cd269302500bd063f37624ee210cd04b4936ed671f3b1" +checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -3471,11 +3656,10 @@ dependencies = [ [[package]] name = "urlpattern" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" dependencies = [ - "derive_more", "regex", "serde", "unic-ucd-ident", @@ -3512,17 +3696,19 @@ dependencies = [ [[package]] name = "v8" -version = "0.89.0" +version = "130.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe2197fbef82c98f7953d13568a961d4e1c663793b5caf3c74455a13918cdf33" +checksum = "eefb620efa1e8f2d0f4dd1b2a72b0924a0a0e8b710e27e7ce7da7fac95c7aae5" dependencies = [ + "bindgen", "bitflags 2.6.0", "fslock", "gzip-header", "home", "miniz_oxide 0.7.4", "once_cell", - "which", + "paste", + "which 6.0.3", ] [[package]] @@ -3561,9 +3747,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -3572,13 +3758,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -3587,9 +3772,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -3600,9 +3785,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3610,9 +3795,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -3623,19 +3808,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ "js-sys", "minicov", - "once_cell", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -3644,15 +3828,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "wasm_dep_analyzer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f270206a91783fd90625c8bb0d8fbd459d0b1d1bf209b656f713f01ae7c04b8" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "wayland-backend" version = "0.3.7" @@ -3764,9 +3957,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -3787,7 +3980,7 @@ name = "wgpu" version = "23.0.1" dependencies = [ "arrayvec", - "cfg_aliases", + "cfg_aliases 0.2.1", "document-features", "js-sys", "log", @@ -3828,10 +4021,10 @@ name = "wgpu-core" version = "23.0.1" dependencies = [ "arrayvec", - "bit-vec", + "bit-vec 0.8.0", "bitflags 2.6.0", "bytemuck", - "cfg_aliases", + "cfg_aliases 0.2.1", "document-features", "indexmap", "log", @@ -3844,7 +4037,7 @@ dependencies = [ "rustc-hash", "serde", "smallvec", - "thiserror", + "thiserror 2.0.9", "wgpu-hal", "wgpu-types", ] @@ -3887,12 +4080,12 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.8.0", "bitflags 2.6.0", "block", "bytemuck", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "core-graphics-types", "env_logger", "glam", @@ -3914,6 +4107,7 @@ dependencies = [ "ndk-sys", "objc", "once_cell", + "ordered-float", "parking_lot", "profiling", "range-alloc", @@ -3922,7 +4116,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror", + "thiserror 2.0.9", "wasm-bindgen", "web-sys", "wgpu-types", @@ -3968,7 +4162,7 @@ dependencies = [ "futures-lite", "glam", "image", - "itertools", + "itertools 0.13.0", "js-sys", "libtest-mimic", "log", @@ -3979,7 +4173,7 @@ dependencies = [ "profiling", "serde", "serde_json", - "strum", + "strum 0.26.3", "trybuild", "wasm-bindgen", "wasm-bindgen-futures", @@ -4003,15 +4197,26 @@ dependencies = [ [[package]] name = "which" -version = "5.0.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix", - "windows-sys 0.48.0", +] + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", ] [[package]] @@ -4036,7 +4241,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4335,7 +4540,7 @@ dependencies = [ "bitflags 2.6.0", "bytemuck", "calloop", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-foundation", "core-graphics", "cursor-icon", @@ -4374,13 +4579,19 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "write16" version = "1.0.0" @@ -4393,6 +4604,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11-dl" version = "2.21.0" diff --git a/Cargo.toml b/Cargo.toml index 6092f2c818..d693596e99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,13 @@ default-members = [ "wgpu", ] +[workspace.lints.clippy] +manual_c_str_literals = "allow" +ref_as_ptr = "warn" + [workspace.package] edition = "2021" -rust-version = "1.76" +rust-version = "1.83" keywords = ["graphics"] license = "MIT OR Apache-2.0" homepage = "https://wgpu.rs/" @@ -70,34 +74,34 @@ path = "./naga" version = "23.0.0" [workspace.dependencies] -anyhow = "1.0.93" -argh = "0.1.5" +anyhow = "1.0.95" +argh = "0.1.13" arrayvec = "0.7" bincode = "1" bit-vec = "0.8" bitflags = "2.6" -bytemuck = { version = "1.20" } -cfg_aliases = "0.1" +bytemuck = { version = "1.21" } +cfg_aliases = "0.2.1" cfg-if = "1" criterion = "0.5" codespan-reporting = "0.11" ctor = "0.2" document-features = "0.2.10" -encase = "0.9" +encase = "0.10.0" env_logger = "0.11" -fern = "0.6" +fern = "0.7" flume = "0.11" futures-lite = "2" getrandom = "0.2" -glam = "0.28" +glam = "0.29" heck = "0.5.0" image = { version = "0.24", default-features = false, features = ["png"] } indexmap = "2" -itertools = { version = "0.10.5" } +itertools = { version = "0.13.0" } ktx2 = "0.3" libc = "0.2" libloading = "0.8" -libtest-mimic = "0.6" +libtest-mimic = "0.8.1" log = "0.4" nanorand = { version = "0.7", default-features = false, features = ["wyrand"] } # https://github.com/Razaekel/noise-rs/issues/335 (Updated dependencies) @@ -105,28 +109,31 @@ noise = { version = "0.8", git = "https://github.com/Razaekel/noise-rs.git", rev nv-flip = "0.1" obj = "0.10" once_cell = "1.20.2" +# Firefox has 3.4.0 vendored, so we allow that version in our dependencies +ordered-float = ">=3,<=4.6" parking_lot = "0.12.1" pico-args = { version = "0.5.0", features = [ "eq-separator", "short-space-opt", "combined-flags", ] } -png = "0.17.15" -pollster = "0.3" +png = "0.17.16" +pollster = "0.4" profiling = { version = "1", default-features = false } raw-window-handle = "0.6" rayon = "1" renderdoc-sys = "1.1.0" ron = "0.8" -rustc-hash = "1.1.0" +# rustc-hash 2.0 is a completely different hasher with different performance characteristics +serde_json = "1.0.134" +rustc-hash = "1" serde = "1" -serde_json = "1.0.133" smallvec = "1" static_assertions = "1.1.0" -strum = { version = "0.25.0", features = ["derive"] } +strum = { version = "0.26.0", features = ["derive"] } trybuild = "1" tracy-client = "0.17" -thiserror = "1.0.69" +thiserror = "2" wgpu = { version = "23.0.1", path = "./wgpu", default-features = false } wgpu-core = { version = "23.0.1", path = "./wgpu-core" } wgpu-macros = { version = "23.0.0", path = "./wgpu-macros" } @@ -135,9 +142,9 @@ wgpu-types = { version = "23.0.0", path = "./wgpu-types" } winit = { version = "0.29", features = ["android-native-activity"] } # Metal dependencies +metal = { version = "0.30.0", git = "https://github.com/gfx-rs/metal-rs.git", rev = "ef768ff9d7" } block = "0.1" core-graphics-types = "0.1" -metal = { version = "0.30.0" } objc = "0.2.5" # Vulkan dependencies @@ -150,7 +157,7 @@ gpu-descriptor = "0.3" bit-set = "0.8" gpu-allocator = { version = "0.27", default-features = false } range-alloc = "0.1" -mach-dxcompiler-rs = { version = "0.1.3", default-features = false } +mach-dxcompiler-rs = { version = "0.1.4", default-features = false } windows-core = { version = "0.58", default-features = false } # Gles dependencies @@ -174,12 +181,12 @@ web-sys = "0.3.74" web-time = "0.2.4" # deno dependencies -deno_console = "0.143.0" -deno_core = "0.272.0" -deno_url = "0.143.0" -deno_web = "0.174.0" -deno_webidl = "0.143.0" -deno_webgpu = { version = "0.118.0", path = "./deno_webgpu" } +deno_console = "0.179.0" +deno_core = "0.321.0" +deno_url = "0.179.0" +deno_web = "0.210.0" +deno_webidl = "0.179.0" +deno_webgpu = { version = "0.146.0", path = "./deno_webgpu" } tokio = "1.41.1" termcolor = "1.4.1" diff --git a/README.md b/README.md index ef586cea0a..55b1a415b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ `wgpu` is a cross-platform, safe, pure-rust graphics API. It runs natively on Vulkan, Metal, D3D12, and OpenGL; and on top of WebGL2 and WebGPU on wasm. -The API is based on the [WebGPU standard](https://gpuweb.github.io/gpuweb/). It serves as the core of the WebGPU integration in Firefox and Deno. +The API is based on the [WebGPU standard](https://gpuweb.github.io/gpuweb/). It serves as the core of the WebGPU integration in Firefox, Servo, and Deno. ## Repo Overview @@ -34,6 +34,10 @@ For an overview of all the components in the gfx-rs ecosystem, see [the big pict ## Getting Started +### Play with our Examples + +Go to [https://wgpu.rs/examples/] to play with our examples in your browser. Requires a browser supporting WebGPU for the WebGPU examples. + ### Rust Rust examples can be found at [wgpu/examples](examples). You can run the examples on native with `cargo run --bin wgpu-examples `. See the [list of examples](examples). @@ -71,6 +75,15 @@ We have the Matrix space [![Matrix Space](https://img.shields.io/static/v1?label We have a [wiki](https://github.com/gfx-rs/wgpu/wiki) that serves as a knowledge base. +## Extension Specifications + +While the core of wgpu is based on the WebGPU standard, we also support extensions that allow for features that the standard does not have yet. +For high-level documentation on how to use these extensions, see the individual specifications: + +🧪EXPERIMENTAL🧪 APIs are subject to change and may allow undefined behavior if used incorrectly. + +- 🧪EXPERIMENTAL🧪 [Ray Tracing](./etc/specs/ray_tracing.md). + ## Supported Platforms | API | Windows | Linux/Android | macOS/iOS | Web (wasm) | @@ -120,7 +133,7 @@ On Linux, you can point to them using `LD_LIBRARY_PATH` environment. Due to complex dependants, we have two MSRV policies: - `naga`, `wgpu-core`, `wgpu-hal`, and `wgpu-types`'s MSRV is **1.76**, but may be lower than the rest of the workspace in the future. -- The rest of the workspace has an MSRV of **1.76** as well right now, but may be higher than above listed crates. +- The rest of the workspace has an MSRV of **1.83** as well right now, but may be higher than above listed crates. It is enforced on CI (in "/.github/workflows/ci.yml") with the `CORE_MSRV` and `REPO_MSRV` variables. This version can only be upgraded in breaking releases, though we release a breaking version every three months. diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 82207d5105..ba47bc1b6a 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -21,6 +21,11 @@ path = "benches/root.rs" # tracy = ["dep:tracy-client", "profiling/profile-with-tracy"] # superluminal = ["profiling/profile-with-superluminal"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(feature, values("tracy"))', +] } + [dependencies] bincode.workspace = true bytemuck.workspace = true diff --git a/benches/benches/bind_groups.rs b/benches/benches/bind_groups.rs new file mode 100644 index 0000000000..f14fa9a3b1 --- /dev/null +++ b/benches/benches/bind_groups.rs @@ -0,0 +1,149 @@ +use std::{ + num::NonZeroU32, + time::{Duration, Instant}, +}; + +use criterion::{criterion_group, Criterion, Throughput}; +use nanorand::{Rng, WyRand}; +use once_cell::sync::Lazy; + +use crate::DeviceState; + +struct BindGroupState { + device_state: DeviceState, + texture_views: Vec, +} + +impl BindGroupState { + /// Create and prepare all the resources needed for the renderpass benchmark. + fn new() -> Self { + let device_state = DeviceState::new(); + + const TEXTURE_COUNT: u32 = 50_000; + + // Performance gets considerably worse if the resources are shuffled. + // + // This more closely matches the real-world use case where resources have no + // well defined usage order. + let mut random = WyRand::new_seed(0x8BADF00D); + + let mut texture_views = Vec::with_capacity(TEXTURE_COUNT as usize); + for i in 0..TEXTURE_COUNT { + let texture = device_state + .device + .create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("Texture {i}")), + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + texture_views.push(texture.create_view(&wgpu::TextureViewDescriptor { + label: Some(&format!("Texture View {i}")), + ..Default::default() + })); + } + random.shuffle(&mut texture_views); + + Self { + device_state, + texture_views, + } + } +} + +fn run_bench(ctx: &mut Criterion) { + let state = Lazy::new(BindGroupState::new); + + if !state + .device_state + .device + .features() + .contains(wgpu::Features::TEXTURE_BINDING_ARRAY) + { + return; + } + + let mut group = ctx.benchmark_group("Bind Group Creation"); + + for count in [5, 50, 500, 5_000, 50_000] { + if count + > state + .device_state + .device + .limits() + .max_sampled_textures_per_shader_stage + { + continue; + } + + let bind_group_layout = + state + .device_state + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: Some(NonZeroU32::new(count).unwrap()), + }], + }); + + group.throughput(Throughput::Elements(count as u64)); + group.bench_with_input( + format!("{} Element Bind Group", count), + &count, + |b, &count| { + b.iter_custom(|iters| { + let texture_view_refs: Vec<_> = + state.texture_views.iter().take(count as usize).collect(); + + let mut duration = Duration::ZERO; + for _ in 0..iters { + profiling::scope!("benchmark iteration"); + + let start = Instant::now(); + let bind_group = state.device_state.device.create_bind_group( + &wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureViewArray( + &texture_view_refs, + ), + }], + label: None, + }, + ); + + duration += start.elapsed(); + + drop(bind_group); + state.device_state.device.poll(wgpu::Maintain::Wait); + } + + duration + }); + }, + ); + } +} + +criterion_group! { + name = bind_groups; + config = Criterion::default().measurement_time(Duration::from_secs(10)); + targets = run_bench, +} diff --git a/benches/benches/computepass.rs b/benches/benches/computepass.rs index 2a5a9d3a04..a8e693974f 100644 --- a/benches/benches/computepass.rs +++ b/benches/benches/computepass.rs @@ -11,9 +11,9 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use crate::DeviceState; fn dispatch_count() -> usize { - // On CI we only want to run a very lightweight version of the benchmark + // When testing we only want to run a very lightweight version of the benchmark // to ensure that it does not break. - if std::env::var("WGPU_TESTING").is_ok() { + if std::env::var("NEXTEST").is_ok() { 8 } else { 10_000 @@ -28,7 +28,7 @@ fn dispatch_count() -> usize { fn dispatch_count_bindless() -> usize { // On CI we only want to run a very lightweight version of the benchmark // to ensure that it does not break. - if std::env::var("WGPU_TESTING").is_ok() { + if std::env::var("NEXTEST").is_ok() { 8 } else { 1_000 diff --git a/benches/benches/renderpass.rs b/benches/benches/renderpass.rs index f8153df82b..7f4ab06bbc 100644 --- a/benches/benches/renderpass.rs +++ b/benches/benches/renderpass.rs @@ -11,9 +11,9 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use crate::DeviceState; fn draw_count() -> usize { - // On CI we only want to run a very lightweight version of the benchmark + // When testing we only want to run a very lightweight version of the benchmark // to ensure that it does not break. - if std::env::var("WGPU_TESTING").is_ok() { + if std::env::var("NEXTEST").is_ok() { 8 } else { 10_000 diff --git a/benches/benches/root.rs b/benches/benches/root.rs index 630ea60270..7d9f84e6b8 100644 --- a/benches/benches/root.rs +++ b/benches/benches/root.rs @@ -1,6 +1,7 @@ use criterion::criterion_main; use pollster::block_on; +mod bind_groups; mod computepass; mod renderpass; mod resource_creation; @@ -24,7 +25,7 @@ impl DeviceState { wgpu::Backends::all() }; - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::util::backend_bits_from_env().unwrap_or(base_backend), flags: wgpu::InstanceFlags::empty(), dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env() @@ -61,6 +62,7 @@ impl DeviceState { } criterion_main!( + bind_groups::bind_groups, renderpass::renderpass, computepass::computepass, resource_creation::resource_creation, diff --git a/cts_runner/src/main.rs b/cts_runner/src/main.rs index fe8c1cf818..1899c122c1 100644 --- a/cts_runner/src/main.rs +++ b/cts_runner/src/main.rs @@ -21,6 +21,78 @@ mod native { use termcolor::ColorSpec; use termcolor::WriteColor; + // temporary + fn get_webgpu_error_class(e: &deno_webgpu::InitError) -> &'static str { + match e { + deno_webgpu::InitError::Resource(e) => { + deno_core::error::get_custom_error_class(e).unwrap_or("Error") + } + deno_webgpu::InitError::RequestDevice(_) => "DOMExceptionOperationError", + } + } + + fn get_webgpu_buffer_error_class(e: &deno_webgpu::buffer::BufferError) -> &'static str { + match e { + deno_webgpu::buffer::BufferError::Resource(e) => { + deno_core::error::get_custom_error_class(e).unwrap_or("Error") + } + deno_webgpu::buffer::BufferError::InvalidUsage => "TypeError", + deno_webgpu::buffer::BufferError::Access(_) => "DOMExceptionOperationError", + deno_webgpu::buffer::BufferError::Canceled(_) => "Error", + } + } + + fn get_webgpu_bundle_error_class(e: &deno_webgpu::bundle::BundleError) -> &'static str { + match e { + deno_webgpu::bundle::BundleError::Resource(e) => { + deno_core::error::get_custom_error_class(e).unwrap_or("Error") + } + deno_webgpu::bundle::BundleError::InvalidSize => "TypeError", + } + } + + fn get_webgpu_byow_error_class(e: &deno_webgpu::byow::ByowError) -> &'static str { + match e { + deno_webgpu::byow::ByowError::WebGPUNotInitiated => "TypeError", + deno_webgpu::byow::ByowError::InvalidParameters => "TypeError", + deno_webgpu::byow::ByowError::CreateSurface(_) => "Error", + deno_webgpu::byow::ByowError::InvalidSystem => "TypeError", + #[cfg(any( + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + deno_webgpu::byow::ByowError::NullWindow => "TypeError", + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + deno_webgpu::byow::ByowError::NullDisplay => "TypeError", + #[cfg(target_os = "macos")] + deno_webgpu::byow::ByowError::NSViewDisplay => "TypeError", + } + } + + fn get_webgpu_render_pass_error_class( + e: &deno_webgpu::render_pass::RenderPassError, + ) -> &'static str { + match e { + deno_webgpu::render_pass::RenderPassError::Resource(e) => { + deno_core::error::get_custom_error_class(e).unwrap_or("Error") + } + deno_webgpu::render_pass::RenderPassError::InvalidSize => "TypeError", + deno_webgpu::render_pass::RenderPassError::RenderPass(_) => "Error", + } + } + + fn get_webgpu_surface_error_class(e: &deno_webgpu::surface::SurfaceError) -> &'static str { + match e { + deno_webgpu::surface::SurfaceError::Resource(e) => { + deno_core::error::get_custom_error_class(e).unwrap_or("Error") + } + deno_webgpu::surface::SurfaceError::Surface(_) => "Error", + deno_webgpu::surface::SurfaceError::InvalidStatus => "Error", + } + } + pub async fn run() -> Result<(), AnyError> { let mut args_iter = env::args(); let _ = args_iter.next(); @@ -112,7 +184,30 @@ mod native { fn get_error_class_name(e: &AnyError) -> &'static str { deno_core::error::get_custom_error_class(e) - .or_else(|| deno_webgpu::error::get_error_class_name(e)) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_buffer_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_bundle_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_byow_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_render_pass_error_class) + }) + .or_else(|| { + e.downcast_ref::() + .map(get_webgpu_surface_error_class) + }) .unwrap_or_else(|| { panic!("Error '{e}' contains boxed error of unsupported type: {e:#}"); }) diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index f79d0ecd4f..0222391713 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -98,6 +98,11 @@ const { ArrayPrototypePush, DataViewPrototypeGetBuffer, Error, + Number, + NumberPOSITIVE_INFINITY, + NumberMAX_SAFE_INTEGER, + NumberNEGATIVE_INFINITY, + NumberMIN_SAFE_INTEGER, MathMax, ObjectDefineProperty, ObjectHasOwn, @@ -137,6 +142,8 @@ const _mappingRange = Symbol("[[mapping_range]]"); const _mappedRanges = Symbol("[[mapped_ranges]]"); const _mapMode = Symbol("[[map_mode]]"); const _adapter = Symbol("[[adapter]]"); +const _adapterInfo = Symbol("[[adapterInfo]]"); +const _invalid = Symbol("[[invalid]]"); const _cleanup = Symbol("[[cleanup]]"); const _vendor = Symbol("[[vendor]]"); const _architecture = Symbol("[[architecture]]"); @@ -173,7 +180,7 @@ function assertDevice(self, prefix, context) { const deviceRid = device?.rid; if (deviceRid === undefined) { throw new DOMException( - `${prefix}: ${context} references an invalid or destroyed device.`, + `${prefix}: ${context} references an invalid or destroyed device`, "OperationError", ); } @@ -190,7 +197,7 @@ function assertResource(self, prefix, context) { const rid = self[_rid]; if (rid === undefined) { throw new DOMException( - `${prefix}: ${context} an invalid or destroyed resource.`, + `${prefix}: ${context} an invalid or destroyed resource`, "OperationError", ); } @@ -337,7 +344,7 @@ class GPU { * @param {GPURequestAdapterOptions} options */ // deno-lint-ignore require-await - async requestAdapter(options = {}) { + async requestAdapter(options = { __proto__: null }) { webidl.assertBranded(this, GPUPrototype); options = webidl.converters.GPURequestAdapterOptions( options, @@ -396,11 +403,11 @@ function createGPUAdapter(inner) { return adapter; } -const _invalid = Symbol("[[invalid]]"); class GPUAdapter { /** @type {InnerGPUAdapter} */ [_adapter]; - /** @type {bool} */ + [_adapterInfo]; + /** @type {boolean} */ [_invalid]; /** @returns {GPUSupportedFeatures} */ @@ -416,7 +423,7 @@ class GPUAdapter { /** @returns {boolean} */ get isFallbackAdapter() { webidl.assertBranded(this, GPUAdapterPrototype); - return this[_adapter].isFallbackAdapter; + return this[_adapter].isFallback; } constructor() { @@ -428,7 +435,7 @@ class GPUAdapter { * @returns {Promise} */ // deno-lint-ignore require-await - async requestDevice(descriptor = {}) { + async requestDevice(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUAdapterPrototype); const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; descriptor = webidl.converters.GPUDeviceDescriptor( @@ -443,7 +450,7 @@ class GPUAdapter { !SetPrototypeHas(this[_adapter].features[webidl.setlikeInner], feature) ) { throw new TypeError( - `${prefix}: requiredFeatures must be a subset of the adapter features.`, + `${prefix}: requiredFeatures must be a subset of the adapter features`, ); } } @@ -481,11 +488,15 @@ class GPUAdapter { } /** - * @returns {Promise} + * @returns {GPUAdapterInfo} */ - requestAdapterInfo() { + get info() { webidl.assertBranded(this, GPUAdapterPrototype); + if (this[_adapterInfo] !== undefined) { + return this[_adapterInfo]; + } + if (this[_invalid]) { throw new TypeError( "The adapter cannot be reused, as it has been invalidated by a device creation", @@ -504,7 +515,8 @@ class GPUAdapter { adapterInfo[_architecture] = architecture; adapterInfo[_device] = device; adapterInfo[_description] = description; - return PromiseResolve(adapterInfo); + this[_adapterInfo] = adapterInfo; + return adapterInfo; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { @@ -515,6 +527,7 @@ class GPUAdapter { keys: [ "features", "limits", + "info", "isFallbackAdapter", ], }), @@ -582,6 +595,18 @@ function createGPUSupportedLimits(limits) { return adapterFeatures; } +function normalizeLimit(limit) { + if (typeof limit === "bigint") { + limit = Number(limit); + if (limit === NumberPOSITIVE_INFINITY) { + limit = NumberMAX_SAFE_INTEGER; + } else if (limit === NumberNEGATIVE_INFINITY) { + limit = NumberMIN_SAFE_INTEGER; + } + } + return limit; +} + /** * @typedef InnerAdapterLimits * @property {number} maxTextureDimension1D @@ -621,123 +646,127 @@ class GPUSupportedLimits { get maxTextureDimension1D() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension1D; + return normalizeLimit(this[_limits].maxTextureDimension1D); } get maxTextureDimension2D() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension2D; + return normalizeLimit(this[_limits].maxTextureDimension2D); } get maxTextureDimension3D() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension3D; + return normalizeLimit(this[_limits].maxTextureDimension3D); } get maxTextureArrayLayers() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureArrayLayers; + return normalizeLimit(this[_limits].maxTextureArrayLayers); } get maxBindGroups() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindGroups; + return normalizeLimit(this[_limits].maxBindGroups); } get maxBindingsPerBindGroup() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindingsPerBindGroup; + return normalizeLimit(this[_limits].maxBindingsPerBindGroup); } get maxBufferSize() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBufferSize; + return normalizeLimit(this[_limits].maxBufferSize); } get maxDynamicUniformBuffersPerPipelineLayout() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + return normalizeLimit( + this[_limits].maxDynamicUniformBuffersPerPipelineLayout, + ); } get maxDynamicStorageBuffersPerPipelineLayout() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + return normalizeLimit( + this[_limits].maxDynamicStorageBuffersPerPipelineLayout, + ); } get maxSampledTexturesPerShaderStage() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSampledTexturesPerShaderStage; + return normalizeLimit(this[_limits].maxSampledTexturesPerShaderStage); } get maxSamplersPerShaderStage() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSamplersPerShaderStage; + return normalizeLimit(this[_limits].maxSamplersPerShaderStage); } get maxStorageBuffersPerShaderStage() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBuffersPerShaderStage; + return normalizeLimit(this[_limits].maxStorageBuffersPerShaderStage); } get maxStorageTexturesPerShaderStage() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageTexturesPerShaderStage; + return normalizeLimit(this[_limits].maxStorageTexturesPerShaderStage); } get maxUniformBuffersPerShaderStage() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBuffersPerShaderStage; + return normalizeLimit(this[_limits].maxUniformBuffersPerShaderStage); } get maxUniformBufferBindingSize() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBufferBindingSize; + return normalizeLimit(this[_limits].maxUniformBufferBindingSize); } get maxStorageBufferBindingSize() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBufferBindingSize; + return normalizeLimit(this[_limits].maxStorageBufferBindingSize); } get minUniformBufferOffsetAlignment() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minUniformBufferOffsetAlignment; + return normalizeLimit(this[_limits].minUniformBufferOffsetAlignment); } get minStorageBufferOffsetAlignment() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minStorageBufferOffsetAlignment; + return normalizeLimit(this[_limits].minStorageBufferOffsetAlignment); } get maxVertexBuffers() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBuffers; + return normalizeLimit(this[_limits].maxVertexBuffers); } get maxVertexAttributes() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexAttributes; + return normalizeLimit(this[_limits].maxVertexAttributes); } get maxVertexBufferArrayStride() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBufferArrayStride; + return normalizeLimit(this[_limits].maxVertexBufferArrayStride); } get maxInterStageShaderComponents() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxInterStageShaderComponents; + return normalizeLimit(this[_limits].maxInterStageShaderComponents); } get maxColorAttachments() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxColorAttachments; + return normalizeLimit(this[_limits].maxColorAttachments); } get maxColorAttachmentBytesPerSample() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxColorAttachmentBytesPerSample; + return normalizeLimit(this[_limits].maxColorAttachmentBytesPerSample); } get maxComputeWorkgroupStorageSize() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupStorageSize; + return normalizeLimit(this[_limits].maxComputeWorkgroupStorageSize); } get maxComputeInvocationsPerWorkgroup() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeInvocationsPerWorkgroup; + return normalizeLimit(this[_limits].maxComputeInvocationsPerWorkgroup); } get maxComputeWorkgroupSizeX() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeX; + return normalizeLimit(this[_limits].maxComputeWorkgroupSizeX); } get maxComputeWorkgroupSizeY() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeY; + return normalizeLimit(this[_limits].maxComputeWorkgroupSizeY); } get maxComputeWorkgroupSizeZ() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeZ; + return normalizeLimit(this[_limits].maxComputeWorkgroupSizeZ); } get maxComputeWorkgroupsPerDimension() { webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupsPerDimension; + return normalizeLimit(this[_limits].maxComputeWorkgroupsPerDimension); } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { @@ -875,6 +904,7 @@ const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; function GPUObjectBaseMixin(name, type) { type.prototype[_label] = null; ObjectDefineProperty(type.prototype, "label", { + __proto__: null, /** * @return {string | null} */ @@ -977,7 +1007,7 @@ class InnerGPUDevice { ); break; case "out-of-memory": - constructedError = new GPUOutOfMemoryError(); + constructedError = new GPUOutOfMemoryError("not enough memory left"); break; case "internal": constructedError = new GPUInternalError(); @@ -1153,7 +1183,7 @@ class GPUDevice extends EventTarget { * @param {GPUSamplerDescriptor} descriptor * @returns {GPUSampler} */ - createSampler(descriptor = {}) { + createSampler(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; descriptor = webidl.converters.GPUSamplerDescriptor( @@ -1434,6 +1464,7 @@ class GPUDevice extends EventTarget { fragment = { module, entryPoint: descriptor.fragment.entryPoint, + constants: descriptor.fragment.constants, targets: descriptor.fragment.targets, }; } @@ -1445,6 +1476,7 @@ class GPUDevice extends EventTarget { vertex: { module, entryPoint: descriptor.vertex.entryPoint, + constants: descriptor.vertex.constants, buffers: descriptor.vertex.buffers, }, primitive: descriptor.primitive, @@ -1557,6 +1589,7 @@ class GPUDevice extends EventTarget { fragment = { module, entryPoint: descriptor.fragment.entryPoint, + constants: descriptor.fragment.constants, targets: descriptor.fragment.targets, }; } @@ -1568,6 +1601,7 @@ class GPUDevice extends EventTarget { vertex: { module, entryPoint: descriptor.vertex.entryPoint, + constants: descriptor.vertex.constants, buffers: descriptor.vertex.buffers, }, primitive: descriptor.primitive, @@ -1599,14 +1633,14 @@ class GPUDevice extends EventTarget { rid, ); device.trackResource(renderPipeline); - return renderPipeline; + return PromiseResolve(renderPipeline); } /** * @param {GPUCommandEncoderDescriptor} descriptor * @returns {GPUCommandEncoder} */ - createCommandEncoder(descriptor = {}) { + createCommandEncoder(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUDevicePrototype); const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; descriptor = webidl.converters.GPUCommandEncoderDescriptor( @@ -1723,12 +1757,12 @@ class GPUDevice extends EventTarget { const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; const device = assertDevice(this, prefix, "this"); if (device.isLost) { - throw new DOMException("Device has been lost.", "OperationError"); + throw new DOMException("Device has been lost", "OperationError"); } const scope = ArrayPrototypePop(device.errorScopeStack); if (!scope) { throw new DOMException( - "There are no error scopes on the error scope stack.", + "There are no error scopes on the error scope stack", "OperationError", ); } @@ -1761,7 +1795,7 @@ defineEventHandler(GPUDevice.prototype, "uncapturederror"); class GPUPipelineError extends DOMException { #reason; - constructor(message = "", options = {}) { + constructor(message = "", options = { __proto__: null }) { const prefix = "Failed to construct 'GPUPipelineError'"; message = webidl.converters.DOMString(message, prefix, "Argument 1"); options = webidl.converters.GPUPipelineErrorInit( @@ -2084,25 +2118,25 @@ class GPUBuffer { } if ((offset % 8) !== 0) { throw new DOMException( - `${prefix}: offset must be a multiple of 8.`, + `${prefix}: offset must be a multiple of 8, received ${offset}`, "OperationError", ); } if ((rangeSize % 4) !== 0) { throw new DOMException( - `${prefix}: rangeSize must be a multiple of 4.`, + `${prefix}: rangeSize must be a multiple of 4, received ${rangeSize}`, "OperationError", ); } if ((offset + rangeSize) > this[_size]) { throw new DOMException( - `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + `${prefix}: offset + rangeSize must be less than or equal to buffer size`, "OperationError", ); } if (this[_state] !== "unmapped") { throw new DOMException( - `${prefix}: GPUBuffer is not currently unmapped.`, + `${prefix}: GPUBuffer is not currently unmapped`, "OperationError", ); } @@ -2110,19 +2144,19 @@ class GPUBuffer { const writeMode = (mode & 0x0002) === 0x0002; if ((readMode && writeMode) || (!readMode && !writeMode)) { throw new DOMException( - `${prefix}: exactly one of READ or WRITE map mode must be set.`, + `${prefix}: exactly one of READ or WRITE map mode must be set`, "OperationError", ); } if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { throw new DOMException( - `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, + `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage`, "OperationError", ); } if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { throw new DOMException( - `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage`, "OperationError", ); } @@ -2169,7 +2203,7 @@ class GPUBuffer { const mappedRanges = this[_mappedRanges]; if (!mappedRanges) { - throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + throw new DOMException(`${prefix}: invalid state`, "OperationError"); } for (let i = 0; i < mappedRanges.length; ++i) { const { 0: buffer, 1: _rid, 2: start } = mappedRanges[i]; @@ -2180,7 +2214,7 @@ class GPUBuffer { (end >= offset && end < (offset + rangeSize)) ) { throw new DOMException( - `${prefix}: requested buffer overlaps with another mapped range.`, + `${prefix}: requested buffer overlaps with another mapped range`, "OperationError", ); } @@ -2206,14 +2240,14 @@ class GPUBuffer { const bufferRid = assertResource(this, prefix, "this"); if (this[_state] === "unmapped" || this[_state] === "destroyed") { throw new DOMException( - `${prefix}: buffer is not ready to be unmapped.`, + `${prefix}: buffer is not ready to be unmapped`, "OperationError", ); } if (this[_state] === "pending") { // TODO(lucacasonato): this is not spec compliant. throw new DOMException( - `${prefix}: can not unmap while mapping. This is a Deno limitation.`, + `${prefix}: can not unmap while mapping, this is a Deno limitation`, "OperationError", ); } else if ( @@ -2227,7 +2261,7 @@ class GPUBuffer { const mapMode = this[_mapMode]; if (mapMode === undefined) { throw new DOMException( - `${prefix}: invalid state.`, + `${prefix}: invalid state`, "OperationError", ); } @@ -2238,7 +2272,7 @@ class GPUBuffer { const mappedRanges = this[_mappedRanges]; if (!mappedRanges) { - throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + throw new DOMException(`${prefix}: invalid state`, "OperationError"); } for (let i = 0; i < mappedRanges.length; ++i) { const { 0: buffer, 1: mappedRid } = mappedRanges[i]; @@ -2403,7 +2437,7 @@ class GPUTexture { /** * @param {GPUTextureViewDescriptor} descriptor */ - createView(descriptor = {}) { + createView(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUTexturePrototype); const prefix = "Failed to execute 'createView' on 'GPUTexture'"; webidl.requiredArguments(arguments.length, 0, prefix); @@ -3183,7 +3217,7 @@ class GPUCommandEncoder { /** * @param {GPUComputePassDescriptor} descriptor */ - beginComputePass(descriptor = {}) { + beginComputePass(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; @@ -3577,7 +3611,7 @@ class GPUCommandEncoder { * @param {GPUCommandBufferDescriptor} descriptor * @returns {GPUCommandBuffer} */ - finish(descriptor = {}) { + finish(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPUCommandEncoderPrototype); const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; descriptor = webidl.converters.GPUCommandBufferDescriptor( @@ -4544,7 +4578,7 @@ class GPURenderBundleEncoder { /** * @param {GPURenderBundleDescriptor} descriptor */ - finish(descriptor = {}) { + finish(descriptor = { __proto__: null }) { webidl.assertBranded(this, GPURenderBundleEncoderPrototype); const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; descriptor = webidl.converters.GPURenderBundleDescriptor( @@ -5271,7 +5305,7 @@ webidl.converters["GPUExtent3D"] = (V, opts) => { if (V.length < min || V.length > max) { throw webidl.makeException( TypeError, - `A sequence of number used as a GPUExtent3D must have between ${min} and ${max} elements.`, + `A sequence of number used as a GPUExtent3D must have between ${min} and ${max} elements, received ${V.length} elements`, opts, ); } @@ -5281,7 +5315,7 @@ webidl.converters["GPUExtent3D"] = (V, opts) => { } throw webidl.makeException( TypeError, - "can not be converted to sequence or GPUExtent3DDict.", + "can not be converted to sequence or GPUExtent3DDict", opts, ); }; @@ -6379,6 +6413,10 @@ webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( "src-alpha-saturated", "constant", "one-minus-constant", + "src1", + "one-minus-src1", + "src1-alpha", + "one-minus-src1-alpha", ], ); @@ -6615,7 +6653,7 @@ webidl.converters["GPUOrigin3D"] = (V, opts) => { if (V.length > length) { throw webidl.makeException( TypeError, - `A sequence of number used as a GPUOrigin3D must have at most ${length} elements.`, + `A sequence of number used as a GPUOrigin3D must have at most ${length} elements, received ${V.length} elements`, opts, ); } @@ -6625,7 +6663,7 @@ webidl.converters["GPUOrigin3D"] = (V, opts) => { } throw webidl.makeException( TypeError, - "can not be converted to sequence or GPUOrigin3DDict.", + "can not be converted to sequence or GPUOrigin3DDict", opts, ); }; @@ -6692,7 +6730,7 @@ webidl.converters["GPUOrigin2D"] = (V, opts) => { if (V.length > length) { throw webidl.makeException( TypeError, - `A sequence of number used as a GPUOrigin2D must have at most ${length} elements.`, + `A sequence of number used as a GPUOrigin2D must have at most ${length} elements`, opts, ); } @@ -6713,6 +6751,12 @@ webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( GPUComputePassEncoder.prototype, ); +// INTERFACE: GPUQuerySet +webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( + "GPUQuerySet", + GPUQuerySet.prototype, +); + // DICTIONARY: GPUComputePassTimestampWrites webidl.converters["GPUComputePassTimestampWrites"] = webidl .createDictionaryConverter( @@ -6786,7 +6830,7 @@ webidl.converters["GPUColor"] = (V, opts) => { if (V.length !== length) { throw webidl.makeException( TypeError, - `A sequence of number used as a GPUColor must have exactly ${length} elements.`, + `A sequence of number used as a GPUColor must have exactly ${length} elements, received ${V.length} elements`, opts, ); } @@ -6796,7 +6840,7 @@ webidl.converters["GPUColor"] = (V, opts) => { } throw webidl.makeException( TypeError, - "can not be converted to sequence or GPUColorDict.", + "can not be converted to sequence or GPUColorDict", opts, ); }; @@ -6885,12 +6929,6 @@ webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl dictMembersGPURenderPassDepthStencilAttachment, ); -// INTERFACE: GPUQuerySet -webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( - "GPUQuerySet", - GPUQuerySet.prototype, -); - // DICTIONARY: GPURenderPassTimestampWrites webidl.converters["GPURenderPassTimestampWrites"] = webidl .createDictionaryConverter( @@ -7165,16 +7203,6 @@ const dictMembersGPUCanvasConfiguration = [ key: "presentMode", converter: webidl.converters["GPUPresentMode"], }, - { - key: "width", - converter: webidl.converters["long"], - required: true, - }, - { - key: "height", - converter: webidl.converters["long"], - required: true, - }, { key: "viewFormats", converter: webidl.createSequenceConverter( diff --git a/deno_webgpu/02_surface.js b/deno_webgpu/02_surface.js index f35f745af4..1861b59b77 100644 --- a/deno_webgpu/02_surface.js +++ b/deno_webgpu/02_surface.js @@ -29,6 +29,8 @@ const _configuration = Symbol("[[configuration]]"); const _canvas = Symbol("[[canvas]]"); const _currentTexture = Symbol("[[currentTexture]]"); const _present = Symbol("[[present]]"); +const _dim = Symbol("[[dimensions]]"); + class GPUCanvasContext { /** @type {number} */ [_surfaceRid]; @@ -36,6 +38,7 @@ class GPUCanvasContext { [_canvas]; /** @type {GPUTexture | undefined} */ [_currentTexture]; + [_dim]; get canvas() { webidl.assertBranded(this, GPUCanvasContextPrototype); @@ -69,8 +72,8 @@ class GPUCanvasContext { format: configuration.format, viewFormats: configuration.viewFormats, usage: configuration.usage, - width: configuration.width, - height: configuration.height, + width: this[_dim].width, + height: this[_dim].height, alphaMode: configuration.alphaMode, }); @@ -92,7 +95,7 @@ class GPUCanvasContext { "Failed to execute 'getCurrentTexture' on 'GPUCanvasContext'"; if (this[_configuration] === null) { - throw new DOMException("context is not configured.", "InvalidStateError"); + throw new DOMException("Context is not configured", "InvalidStateError"); } const { createGPUTexture, assertDevice } = loadWebGPU(); @@ -163,6 +166,7 @@ function createCanvasContext(options) { const canvasContext = webidl.createBranded(GPUCanvasContext); canvasContext[_surfaceRid] = options.surfaceRid; canvasContext[_canvas] = options.canvas; + canvasContext[_dim] = { width: options.width, height: options.height }; return canvasContext; } @@ -172,16 +176,34 @@ function createCanvasContext(options) { class UnsafeWindowSurface { #ctx; #surfaceRid; + #options; + + constructor(options) { + if (typeof options !== "object") { + throw new TypeError("options must be provided."); + } + if ( + typeof options.width !== "number" || typeof options.height !== "number" + ) { + throw new TypeError("width and height must be provided."); + } - constructor(system, win, display) { - this.#surfaceRid = op_webgpu_surface_create(system, win, display); + this.#surfaceRid = op_webgpu_surface_create( + options.system, + options.windowHandle, + options.displayHandle, + ); + this.#options = options; } getContext(context) { if (context !== "webgpu") { - throw new TypeError("Only 'webgpu' context is supported."); + throw new TypeError("Only 'webgpu' context is supported"); } - this.#ctx = createCanvasContext({ surfaceRid: this.#surfaceRid }); + this.#ctx = createCanvasContext({ + surfaceRid: this.#surfaceRid, + ...this.#options, + }); return this.#ctx; } diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml index ba72507dd7..af1a3ed686 100644 --- a/deno_webgpu/Cargo.toml +++ b/deno_webgpu/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_webgpu" -version = "0.118.0" +version = "0.146.0" authors = ["the Deno authors"] edition.workspace = true license = "MIT" @@ -19,8 +19,9 @@ path = "lib.rs" deno_core.workspace = true serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["full"] } -wgpu-types = { workspace = true, features = ["serde"] } +wgt = { workspace = true, package = "wgpu-types", features = ["serde"] } raw-window-handle = { workspace = true } +thiserror.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core] workspace = true diff --git a/deno_webgpu/binding.rs b/deno_webgpu/binding.rs index f1f3a80d35..f82e0656ca 100644 --- a/deno_webgpu/binding.rs +++ b/deno_webgpu/binding.rs @@ -1,5 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::error::WebGpuResult; +use super::wgpu_types; use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; @@ -9,8 +11,6 @@ use serde::Deserialize; use std::borrow::Cow; use std::rc::Rc; -use super::error::WebGpuResult; - pub(crate) struct WebGpuBindGroupLayout( pub(crate) crate::Instance, pub(crate) wgpu_core::id::BindGroupLayoutId, diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 460a8cf233..0a422f8f6e 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; +use super::error::WebGpuResult; +use super::wgpu_types; use deno_core::futures::channel::oneshot; use deno_core::op2; use deno_core::OpState; @@ -14,8 +14,17 @@ use std::rc::Rc; use std::time::Duration; use wgpu_core::resource::BufferAccessResult; -use super::error::DomExceptionOperationError; -use super::error::WebGpuResult; +#[derive(Debug, thiserror::Error)] +pub enum BufferError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("usage is not valid")] + InvalidUsage, + #[error(transparent)] + Access(#[from] wgpu_core::resource::BufferAccessError), + #[error(transparent)] + Canceled(#[from] oneshot::Canceled), +} pub(crate) struct WebGpuBuffer( pub(crate) super::Instance, @@ -47,18 +56,18 @@ pub fn op_webgpu_create_buffer( #[number] size: u64, usage: u32, mapped_at_creation: bool, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(BufferError::Resource)?; let device = device_resource.1; let descriptor = wgpu_core::resource::BufferDescriptor { label: Some(label), size, - usage: wgpu_types::BufferUsages::from_bits(usage) - .ok_or_else(|| type_error("usage is not valid"))?, + usage: wgpu_types::BufferUsages::from_bits(usage).ok_or(BufferError::InvalidUsage)?, mapped_at_creation, }; @@ -78,18 +87,22 @@ pub async fn op_webgpu_buffer_get_map_async( mode: u32, #[number] offset: u64, #[number] size: u64, -) -> Result { +) -> Result { let (sender, receiver) = oneshot::channel::(); let device; { let state_ = state.borrow(); let instance = state_.borrow::(); - let buffer_resource = state_.resource_table.get::(buffer_rid)?; + let buffer_resource = state_ + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; let device_resource = state_ .resource_table - .get::(device_rid)?; + .get::(device_rid) + .map_err(BufferError::Resource)?; device = device_resource.1; let callback = Box::new(move |status| { @@ -131,14 +144,14 @@ pub async fn op_webgpu_buffer_get_map_async( } tokio::time::sleep(Duration::from_millis(10)).await; } - Ok::<(), AnyError>(()) + Ok::<(), BufferError>(()) }; let receiver_fut = async move { receiver.await??; let mut done = done_.borrow_mut(); *done = true; - Ok::<(), AnyError>(()) + Ok::<(), BufferError>(()) }; tokio::try_join!(device_poll_fut, receiver_fut)?; @@ -154,14 +167,17 @@ pub fn op_webgpu_buffer_get_mapped_range( #[number] offset: u64, #[number] size: Option, #[buffer] buf: &mut [u8], -) -> Result { +) -> Result { let instance = state.borrow::(); - let buffer_resource = state.resource_table.get::(buffer_rid)?; + let buffer_resource = state + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; let (slice_pointer, range_size) = instance .buffer_get_mapped_range(buffer, offset, size) - .map_err(|e| DomExceptionOperationError::new(&e.to_string()))?; + .map_err(BufferError::Access)?; // SAFETY: guarantee to be safe from wgpu let slice = @@ -182,12 +198,16 @@ pub fn op_webgpu_buffer_unmap( #[smi] buffer_rid: ResourceId, #[smi] mapped_rid: ResourceId, #[buffer] buf: Option<&[u8]>, -) -> Result { +) -> Result { let mapped_resource = state .resource_table - .take::(mapped_rid)?; + .take::(mapped_rid) + .map_err(BufferError::Resource)?; let instance = state.borrow::(); - let buffer_resource = state.resource_table.get::(buffer_rid)?; + let buffer_resource = state + .resource_table + .get::(buffer_rid) + .map_err(BufferError::Resource)?; let buffer = buffer_resource.1; if let Some(buf) = buf { diff --git a/deno_webgpu/bundle.rs b/deno_webgpu/bundle.rs index 58d179051b..d612a6d6ce 100644 --- a/deno_webgpu/bundle.rs +++ b/deno_webgpu/bundle.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; +use super::wgpu_types; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -11,6 +10,14 @@ use std::borrow::Cow; use std::cell::RefCell; use std::rc::Rc; +#[derive(Debug, thiserror::Error)] +pub enum BundleError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("size must be larger than 0")] + InvalidSize, +} + use super::error::WebGpuResult; struct WebGpuRenderBundleEncoder(RefCell); @@ -51,7 +58,7 @@ pub struct CreateRenderBundleEncoderArgs { pub fn op_webgpu_create_render_bundle_encoder( state: &mut OpState, #[serde] args: CreateRenderBundleEncoderArgs, -) -> Result { +) -> Result { let device_resource = state .resource_table .get::(args.device_rid)?; @@ -97,7 +104,7 @@ pub fn op_webgpu_render_bundle_encoder_finish( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] label: Cow, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .take::(render_bundle_encoder_rid)?; @@ -127,7 +134,7 @@ pub fn op_webgpu_render_bundle_encoder_set_bind_group( #[buffer] dynamic_offsets_data: &[u32], #[number] dynamic_offsets_data_start: usize, #[number] dynamic_offsets_data_length: usize, -) -> Result { +) -> Result { let bind_group_resource = state .resource_table .get::(bind_group)?; @@ -165,7 +172,7 @@ pub fn op_webgpu_render_bundle_encoder_push_debug_group( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] group_label: &str, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .get::(render_bundle_encoder_rid)?; @@ -188,7 +195,7 @@ pub fn op_webgpu_render_bundle_encoder_push_debug_group( pub fn op_webgpu_render_bundle_encoder_pop_debug_group( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .get::(render_bundle_encoder_rid)?; @@ -206,7 +213,7 @@ pub fn op_webgpu_render_bundle_encoder_insert_debug_marker( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[string] marker_label: &str, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .get::(render_bundle_encoder_rid)?; @@ -230,7 +237,7 @@ pub fn op_webgpu_render_bundle_encoder_set_pipeline( state: &mut OpState, #[smi] render_bundle_encoder_rid: ResourceId, #[smi] pipeline: ResourceId, -) -> Result { +) -> Result { let render_pipeline_resource = state .resource_table .get::(pipeline)?; @@ -255,16 +262,16 @@ pub fn op_webgpu_render_bundle_encoder_set_index_buffer( #[serde] index_format: wgpu_types::IndexFormat, #[number] offset: u64, #[number] size: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(BundleError::Resource)?; let render_bundle_encoder_resource = state .resource_table - .get::(render_bundle_encoder_rid)?; - let size = Some( - std::num::NonZeroU64::new(size).ok_or_else(|| type_error("size must be larger than 0"))?, - ); + .get::(render_bundle_encoder_rid) + .map_err(BundleError::Resource)?; + let size = Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?); render_bundle_encoder_resource .0 @@ -283,18 +290,17 @@ pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( #[smi] buffer: ResourceId, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(BundleError::Resource)?; let render_bundle_encoder_resource = state .resource_table - .get::(render_bundle_encoder_rid)?; + .get::(render_bundle_encoder_rid) + .map_err(BundleError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(BundleError::InvalidSize)?) } else { None }; @@ -319,7 +325,7 @@ pub fn op_webgpu_render_bundle_encoder_draw( instance_count: u32, first_vertex: u32, first_instance: u32, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .get::(render_bundle_encoder_rid)?; @@ -345,7 +351,7 @@ pub fn op_webgpu_render_bundle_encoder_draw_indexed( first_index: u32, base_vertex: i32, first_instance: u32, -) -> Result { +) -> Result { let render_bundle_encoder_resource = state .resource_table .get::(render_bundle_encoder_rid)?; @@ -369,7 +375,7 @@ pub fn op_webgpu_render_bundle_encoder_draw_indirect( #[smi] render_bundle_encoder_rid: ResourceId, #[smi] indirect_buffer: ResourceId, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; diff --git a/deno_webgpu/byow.rs b/deno_webgpu/byow.rs index 49944ea1e7..17182f0128 100644 --- a/deno_webgpu/byow.rs +++ b/deno_webgpu/byow.rs @@ -1,16 +1,52 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; use std::ffi::c_void; -#[cfg(any(target_os = "linux", target_os = "macos"))] +#[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "freebsd", + target_os = "openbsd" +))] use std::ptr::NonNull; use crate::surface::WebGpuSurface; +#[derive(Debug, thiserror::Error)] +pub enum ByowError { + #[error("Cannot create surface outside of WebGPU context. Did you forget to call `navigator.gpu.requestAdapter()`?")] + WebGPUNotInitiated, + #[error("Invalid parameters")] + InvalidParameters, + #[error(transparent)] + CreateSurface(wgpu_core::instance::CreateSurfaceError), + #[cfg(target_os = "windows")] + #[error("Invalid system on Windows")] + InvalidSystem, + #[cfg(target_os = "macos")] + #[error("Invalid system on macOS")] + InvalidSystem, + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + #[error("Invalid system on Linux/BSD")] + InvalidSystem, + #[cfg(any( + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + #[error("window is null")] + NullWindow, + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + #[error("display is null")] + NullDisplay, + #[cfg(target_os = "macos")] + #[error("ns_view is null")] + NSViewDisplay, +} + #[op2(fast)] #[smi] pub fn op_webgpu_surface_create( @@ -18,8 +54,10 @@ pub fn op_webgpu_surface_create( #[string] system: &str, p1: *const c_void, p2: *const c_void, -) -> Result { - let instance = state.borrow::(); +) -> Result { + let instance = state + .try_borrow::() + .ok_or(ByowError::WebGPUNotInitiated)?; // Security note: // // The `p1` and `p2` parameters are pointers to platform-specific window @@ -34,12 +72,16 @@ pub fn op_webgpu_surface_create( // // - Only FFI can export v8::External to user code. if p1.is_null() { - return Err(type_error("Invalid parameters")); + return Err(ByowError::InvalidParameters); } let (win_handle, display_handle) = raw_window(system, p1, p2)?; // SAFETY: see above comment - let surface = unsafe { instance.instance_create_surface(display_handle, win_handle, None)? }; + let surface = unsafe { + instance + .instance_create_surface(display_handle, win_handle, None) + .map_err(ByowError::CreateSurface)? + }; let rid = state .resource_table @@ -57,14 +99,14 @@ fn raw_window( system: &str, _ns_window: *const c_void, ns_view: *const c_void, -) -> Result { +) -> Result { if system != "cocoa" { - return Err(type_error("Invalid system on macOS")); + return Err(ByowError::InvalidSystem); } let win_handle = raw_window_handle::RawWindowHandle::AppKit(raw_window_handle::AppKitWindowHandle::new( - NonNull::new(ns_view as *mut c_void).ok_or(type_error("ns_view is null"))?, + NonNull::new(ns_view as *mut c_void).ok_or(ByowError::NSViewDisplay)?, )); let display_handle = @@ -77,15 +119,15 @@ fn raw_window( system: &str, window: *const c_void, hinstance: *const c_void, -) -> Result { +) -> Result { use raw_window_handle::WindowsDisplayHandle; if system != "win32" { - return Err(type_error("Invalid system on Windows")); + return Err(ByowError::InvalidSystem); } let win_handle = { let mut handle = raw_window_handle::Win32WindowHandle::new( - std::num::NonZeroIsize::new(window as isize).ok_or(type_error("window is null"))?, + std::num::NonZeroIsize::new(window as isize).ok_or(ByowError::NullWindow)?, ); handle.hinstance = std::num::NonZeroIsize::new(hinstance as isize); @@ -96,12 +138,12 @@ fn raw_window( Ok((win_handle, display_handle)) } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] fn raw_window( system: &str, window: *const c_void, display: *const c_void, -) -> Result { +) -> Result { let (win_handle, display_handle); if system == "x11" { win_handle = raw_window_handle::RawWindowHandle::Xlib( @@ -114,18 +156,33 @@ fn raw_window( } else if system == "wayland" { win_handle = raw_window_handle::RawWindowHandle::Wayland( raw_window_handle::WaylandWindowHandle::new( - NonNull::new(window as *mut c_void).ok_or(type_error("window is null"))?, + NonNull::new(window as *mut c_void).ok_or(ByowError::NullWindow)?, ), ); display_handle = raw_window_handle::RawDisplayHandle::Wayland( raw_window_handle::WaylandDisplayHandle::new( - NonNull::new(display as *mut c_void).ok_or(type_error("display is null"))?, + NonNull::new(display as *mut c_void).ok_or(ByowError::NullDisplay)?, ), ); } else { - return Err(type_error("Invalid system on Linux")); + return Err(ByowError::InvalidSystem); } Ok((win_handle, display_handle)) } + +#[cfg(not(any( + target_os = "macos", + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd", +)))] +fn raw_window( + _system: &str, + _window: *const c_void, + _display: *const c_void, +) -> Result { + Err(deno_core::error::type_error("Unsupported platform")) +} diff --git a/deno_webgpu/command_encoder.rs b/deno_webgpu/command_encoder.rs index d54a261762..3eceec8ce0 100644 --- a/deno_webgpu/command_encoder.rs +++ b/deno_webgpu/command_encoder.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::wgpu_types; use crate::WebGpuQuerySet; use deno_core::error::AnyError; use deno_core::op2; @@ -71,20 +72,38 @@ pub struct GpuRenderPassColorAttachment { view: ResourceId, resolve_target: Option, clear_value: Option, - load_op: wgpu_core::command::LoadOp, + load_op: LoadOp, store_op: wgpu_core::command::StoreOp, } +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum LoadOp { + /// Clear the output attachment with the clear color. Clearing is faster than loading. + Clear = 0, + /// Do not clear output attachment. + Load = 1, +} + +impl LoadOp { + fn into_wgt(self, clear: V) -> wgpu_types::LoadOp { + match self { + LoadOp::Clear => wgpu_types::LoadOp::Clear(clear), + LoadOp::Load => wgpu_types::LoadOp::Load, + } + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct GpuRenderPassDepthStencilAttachment { view: ResourceId, - depth_clear_value: f32, - depth_load_op: Option, + depth_clear_value: Option, + depth_load_op: Option, depth_store_op: Option, depth_read_only: bool, stencil_clear_value: u32, - stencil_load_op: Option, + stencil_load_op: Option, stencil_store_op: Option, stencil_read_only: bool, } @@ -133,12 +152,8 @@ pub fn op_webgpu_command_encoder_begin_render_pass( Some(wgpu_core::command::RenderPassColorAttachment { view: texture_view_resource.1, resolve_target, - channel: wgpu_core::command::PassChannel { - load_op: at.load_op, - store_op: at.store_op, - clear_value: at.clear_value.unwrap_or_default(), - read_only: false, - }, + load_op: at.load_op.into_wgt(at.clear_value.unwrap_or_default()), + store_op: at.store_op, }) } else { None @@ -160,21 +175,15 @@ pub fn op_webgpu_command_encoder_begin_render_pass( depth: wgpu_core::command::PassChannel { load_op: attachment .depth_load_op - .unwrap_or(wgpu_core::command::LoadOp::Load), - store_op: attachment - .depth_store_op - .unwrap_or(wgpu_core::command::StoreOp::Store), - clear_value: attachment.depth_clear_value, + .map(|load_op| load_op.into_wgt(attachment.depth_clear_value)), + store_op: attachment.depth_store_op, read_only: attachment.depth_read_only, }, stencil: wgpu_core::command::PassChannel { load_op: attachment .stencil_load_op - .unwrap_or(wgpu_core::command::LoadOp::Load), - store_op: attachment - .stencil_store_op - .unwrap_or(wgpu_core::command::StoreOp::Store), - clear_value: attachment.stencil_clear_value, + .map(|load_op| load_op.into_wgt(Some(attachment.stencil_clear_value))), + store_op: attachment.stencil_store_op, read_only: attachment.stencil_read_only, }, }); diff --git a/deno_webgpu/error.rs b/deno_webgpu/error.rs index caea7d9d81..8af30f62de 100644 --- a/deno_webgpu/error.rs +++ b/deno_webgpu/error.rs @@ -1,11 +1,9 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::ResourceId; use serde::Serialize; use std::convert::From; use std::error::Error; -use std::fmt; use std::fmt::Write; use wgpu_core::binding_model::CreateBindGroupError; use wgpu_core::binding_model::CreateBindGroupLayoutError; @@ -68,7 +66,6 @@ pub struct WebGpuResult { } impl WebGpuResult { - #[must_use] pub fn rid(rid: ResourceId) -> Self { Self { rid: Some(rid), @@ -76,7 +73,6 @@ impl WebGpuResult { } } - #[must_use] pub fn rid_err>(rid: ResourceId, err: Option) -> Self { Self { rid: Some(rid), @@ -84,7 +80,6 @@ impl WebGpuResult { } } - #[must_use] pub fn maybe_err>(err: Option) -> Self { Self { rid: None, @@ -92,7 +87,6 @@ impl WebGpuResult { } } - #[must_use] pub fn empty() -> Self { Self { rid: None, @@ -304,31 +298,3 @@ impl From for WebGpuError { WebGpuError::Validation(fmt_err(&err)) } } - -#[derive(Debug)] -pub struct DomExceptionOperationError { - pub msg: String, -} - -impl DomExceptionOperationError { - #[must_use] - pub fn new(msg: &str) -> Self { - DomExceptionOperationError { - msg: msg.to_string(), - } - } -} - -impl fmt::Display for DomExceptionOperationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.pad(&self.msg) - } -} - -impl std::error::Error for DomExceptionOperationError {} - -#[must_use] -pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { - e.downcast_ref::() - .map(|_| "DOMExceptionOperationError") -} diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index e31812e25f..4bfb247012 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -2,7 +2,6 @@ #![cfg(not(target_arch = "wasm32"))] #![warn(unsafe_op_in_unsafe_fn)] -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -14,9 +13,8 @@ use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; pub use wgpu_core; -pub use wgpu_types; +pub use wgt as wgpu_types; -use error::DomExceptionOperationError; use error::WebGpuResult; pub const UNSTABLE_FEATURE_NAME: &str = "webgpu"; @@ -54,6 +52,14 @@ pub mod shader; pub mod surface; pub mod texture; +#[derive(Debug, thiserror::Error)] +pub enum InitError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + RequestDevice(wgpu_core::instance::RequestDeviceError), +} + pub type Instance = std::sync::Arc; struct WebGpuAdapter(Instance, wgpu_core::id::AdapterId); @@ -235,6 +241,9 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { if features.contains(wgpu_types::Features::FLOAT32_FILTERABLE) { return_features.push("float32-filterable"); } + if features.contains(wgpu_types::Features::DUAL_SOURCE_BLENDING) { + return_features.push("dual-source-blending"); + } // extended from spec @@ -349,7 +358,7 @@ pub struct GpuAdapterRes { rid: ResourceId, limits: wgpu_types::Limits, features: Vec<&'static str>, - is_software: bool, + is_fallback: bool, } #[derive(Serialize)] @@ -359,7 +368,6 @@ pub struct GpuDeviceRes { queue_rid: ResourceId, limits: wgpu_types::Limits, features: Vec<&'static str>, - is_software: bool, } #[op2] @@ -368,15 +376,9 @@ pub fn op_webgpu_request_adapter( state: Rc>, #[serde] power_preference: Option, force_fallback_adapter: bool, -) -> Result { +) -> GpuAdapterResOrErr { let mut state = state.borrow_mut(); - // TODO(bartlomieju): replace with `state.feature_checker.check_or_exit` - // once we phase out `check_or_exit_with_legacy_fallback` - state - .feature_checker - .check_or_exit_with_legacy_fallback(UNSTABLE_FEATURE_NAME, "navigator.gpu.requestAdapter"); - let backends = std::env::var("DENO_WEBGPU_BACKEND").map_or_else( |_| wgpu_types::Backends::all(), |s| wgpu_core::instance::parse_backends_from_comma_list(&s), @@ -386,7 +388,7 @@ pub fn op_webgpu_request_adapter( } else { state.put(std::sync::Arc::new(wgpu_core::global::Global::new( "webgpu", - wgpu_types::InstanceDescriptor { + &wgpu_types::InstanceDescriptor { backends, flags: wgpu_types::InstanceFlags::from_build_config(), dx12_shader_compiler: wgpu_types::Dx12Compiler::Fxc, @@ -406,9 +408,9 @@ pub fn op_webgpu_request_adapter( let adapter = match res { Ok(adapter) => adapter, Err(err) => { - return Ok(GpuAdapterResOrErr::Error { + return GpuAdapterResOrErr::Error { err: err.to_string(), - }) + } } }; let adapter_features = instance.adapter_features(adapter); @@ -419,12 +421,13 @@ pub fn op_webgpu_request_adapter( let rid = state.resource_table.add(WebGpuAdapter(instance, adapter)); - Ok(GpuAdapterResOrErr::Features(GpuAdapterRes { + GpuAdapterResOrErr::Features(GpuAdapterRes { rid, features, limits: adapter_limits, - is_software: false, - })) + // TODO(lucacasonato): report correctly from wgpu + is_fallback: false, + }) } #[derive(Deserialize)] @@ -486,6 +489,10 @@ impl From for wgpu_types::Features { wgpu_types::Features::FLOAT32_FILTERABLE, required_features.0.contains("float32-filterable"), ); + features.set( + wgpu_types::Features::DUAL_SOURCE_BLENDING, + required_features.0.contains("dual-source-blending"), + ); // extended from spec @@ -633,9 +640,12 @@ pub fn op_webgpu_request_device( #[string] label: String, #[serde] required_features: GpuRequiredFeatures, #[serde] required_limits: Option, -) -> Result { +) -> Result { let mut state = state.borrow_mut(); - let adapter_resource = state.resource_table.take::(adapter_rid)?; + let adapter_resource = state + .resource_table + .take::(adapter_rid) + .map_err(InitError::Resource)?; let adapter = adapter_resource.1; let instance = state.borrow::(); @@ -658,7 +668,7 @@ pub fn op_webgpu_request_device( ); adapter_resource.close(); - let (device, queue) = res.map_err(|err| DomExceptionOperationError::new(&err.to_string()))?; + let (device, queue) = res.map_err(InitError::RequestDevice)?; let device_features = instance.device_features(device); let features = deserialize_features(&device_features); @@ -676,8 +686,6 @@ pub fn op_webgpu_request_device( queue_rid, features, limits, - // TODO(lucacasonato): report correctly from wgpu - is_software: false, }) } @@ -695,9 +703,9 @@ pub struct GPUAdapterInfo { pub fn op_webgpu_request_adapter_info( state: Rc>, #[smi] adapter_rid: ResourceId, -) -> Result { - let mut state = state.borrow_mut(); - let adapter_resource = state.resource_table.take::(adapter_rid)?; +) -> Result { + let state = state.borrow_mut(); + let adapter_resource = state.resource_table.get::(adapter_rid)?; let adapter = adapter_resource.1; let instance = state.borrow::(); @@ -743,7 +751,7 @@ impl From for wgpu_types::QueryType { pub fn op_webgpu_create_query_set( state: &mut OpState, #[serde] args: CreateQuerySetArgs, -) -> Result { +) -> Result { let device_resource = state.resource_table.get::(args.device_rid)?; let device = device_resource.1; let instance = state.borrow::(); diff --git a/deno_webgpu/pipeline.rs b/deno_webgpu/pipeline.rs index 0ab3c40262..910211e709 100644 --- a/deno_webgpu/pipeline.rs +++ b/deno_webgpu/pipeline.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::wgpu_types; use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; diff --git a/deno_webgpu/queue.rs b/deno_webgpu/queue.rs index a2e7d6a500..808185f17e 100644 --- a/deno_webgpu/queue.rs +++ b/deno_webgpu/queue.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::wgpu_types; use crate::command_encoder::WebGpuCommandBuffer; use crate::Instance; use deno_core::error::AnyError; diff --git a/deno_webgpu/render_pass.rs b/deno_webgpu/render_pass.rs index 4929fbbe90..e5bd3cfd57 100644 --- a/deno_webgpu/render_pass.rs +++ b/deno_webgpu/render_pass.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; +use super::wgpu_types; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -12,6 +11,16 @@ use std::cell::RefCell; use super::error::WebGpuResult; +#[derive(Debug, thiserror::Error)] +pub enum RenderPassError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("size must be larger than 0")] + InvalidSize, + #[error(transparent)] + RenderPass(#[from] wgpu_core::command::RenderPassError), +} + pub(crate) struct WebGpuRenderPass(pub(crate) RefCell); impl Resource for WebGpuRenderPass { fn name(&self) -> Cow { @@ -36,7 +45,7 @@ pub struct RenderPassSetViewportArgs { pub fn op_webgpu_render_pass_set_viewport( state: &mut OpState, #[serde] args: RenderPassSetViewportArgs, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(args.render_pass_rid)?; @@ -65,7 +74,7 @@ pub fn op_webgpu_render_pass_set_scissor_rect( y: u32, width: u32, height: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -89,7 +98,7 @@ pub fn op_webgpu_render_pass_set_blend_constant( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[serde] color: wgpu_types::Color, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -107,7 +116,7 @@ pub fn op_webgpu_render_pass_set_stencil_reference( state: &mut OpState, #[smi] render_pass_rid: ResourceId, reference: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -125,7 +134,7 @@ pub fn op_webgpu_render_pass_begin_occlusion_query( state: &mut OpState, #[smi] render_pass_rid: ResourceId, query_index: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -142,7 +151,7 @@ pub fn op_webgpu_render_pass_begin_occlusion_query( pub fn op_webgpu_render_pass_end_occlusion_query( state: &mut OpState, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -160,7 +169,7 @@ pub fn op_webgpu_render_pass_execute_bundles( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[serde] bundles: Vec, -) -> Result { +) -> Result { let bundles = bundles .iter() .map(|rid| { @@ -169,7 +178,7 @@ pub fn op_webgpu_render_pass_execute_bundles( .get::(*rid)?; Ok(render_bundle_resource.1) }) - .collect::, AnyError>>()?; + .collect::, deno_core::error::AnyError>>()?; let render_pass_resource = state .resource_table @@ -187,7 +196,7 @@ pub fn op_webgpu_render_pass_execute_bundles( pub fn op_webgpu_render_pass_end( state: &mut OpState, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .take::(render_pass_rid)?; @@ -209,7 +218,7 @@ pub fn op_webgpu_render_pass_set_bind_group( #[buffer] dynamic_offsets_data: &[u32], #[number] dynamic_offsets_data_start: usize, #[number] dynamic_offsets_data_length: usize, -) -> Result { +) -> Result { let bind_group_resource = state .resource_table .get::(bind_group)?; @@ -244,7 +253,7 @@ pub fn op_webgpu_render_pass_push_debug_group( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[string] group_label: &str, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -265,7 +274,7 @@ pub fn op_webgpu_render_pass_push_debug_group( pub fn op_webgpu_render_pass_pop_debug_group( state: &mut OpState, #[smi] render_pass_rid: ResourceId, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -283,7 +292,7 @@ pub fn op_webgpu_render_pass_insert_debug_marker( state: &mut OpState, #[smi] render_pass_rid: ResourceId, #[string] marker_label: &str, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -305,7 +314,7 @@ pub fn op_webgpu_render_pass_set_pipeline( state: &mut OpState, #[smi] render_pass_rid: ResourceId, pipeline: u32, -) -> Result { +) -> Result { let render_pipeline_resource = state .resource_table .get::(pipeline)?; @@ -332,19 +341,18 @@ pub fn op_webgpu_render_pass_set_index_buffer( #[serde] index_format: wgpu_types::IndexFormat, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(RenderPassError::Resource)?; let render_pass_resource = state .resource_table - .get::(render_pass_rid)?; + .get::(render_pass_rid) + .map_err(RenderPassError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?) } else { None }; @@ -371,19 +379,18 @@ pub fn op_webgpu_render_pass_set_vertex_buffer( buffer: u32, #[number] offset: u64, #[number] size: Option, -) -> Result { +) -> Result { let buffer_resource = state .resource_table - .get::(buffer)?; + .get::(buffer) + .map_err(RenderPassError::Resource)?; let render_pass_resource = state .resource_table - .get::(render_pass_rid)?; + .get::(render_pass_rid) + .map_err(RenderPassError::Resource)?; let size = if let Some(size) = size { - Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ) + Some(std::num::NonZeroU64::new(size).ok_or(RenderPassError::InvalidSize)?) } else { None }; @@ -410,7 +417,7 @@ pub fn op_webgpu_render_pass_draw( instance_count: u32, first_vertex: u32, first_instance: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -438,7 +445,7 @@ pub fn op_webgpu_render_pass_draw_indexed( first_index: u32, base_vertex: i32, first_instance: u32, -) -> Result { +) -> Result { let render_pass_resource = state .resource_table .get::(render_pass_rid)?; @@ -464,7 +471,7 @@ pub fn op_webgpu_render_pass_draw_indirect( #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; @@ -490,7 +497,7 @@ pub fn op_webgpu_render_pass_draw_indexed_indirect( #[smi] render_pass_rid: ResourceId, indirect_buffer: u32, #[number] indirect_offset: u64, -) -> Result { +) -> Result { let buffer_resource = state .resource_table .get::(indirect_buffer)?; diff --git a/deno_webgpu/sampler.rs b/deno_webgpu/sampler.rs index 59b6f4e302..31b8ce2a24 100644 --- a/deno_webgpu/sampler.rs +++ b/deno_webgpu/sampler.rs @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; +use super::wgpu_types; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -47,7 +47,7 @@ pub struct CreateSamplerArgs { pub fn op_webgpu_create_sampler( state: &mut OpState, #[serde] args: CreateSamplerArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table diff --git a/deno_webgpu/shader.rs b/deno_webgpu/shader.rs index 4c7a30b2bd..f09d89c63d 100644 --- a/deno_webgpu/shader.rs +++ b/deno_webgpu/shader.rs @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; +use super::wgpu_types; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -31,7 +31,7 @@ pub fn op_webgpu_create_shader_module( #[smi] device_rid: ResourceId, #[string] label: Cow, #[string] code: Cow, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table @@ -42,7 +42,7 @@ pub fn op_webgpu_create_shader_module( let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor { label: Some(label), - shader_bound_checks: wgpu_types::ShaderBoundChecks::default(), + runtime_checks: wgpu_types::ShaderRuntimeChecks::default(), }; gfx_put!(instance.device_create_shader_module( diff --git a/deno_webgpu/surface.rs b/deno_webgpu/surface.rs index ca5e795aa0..c90d5e3aca 100644 --- a/deno_webgpu/surface.rs +++ b/deno_webgpu/surface.rs @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use super::wgpu_types; use super::WebGpuResult; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -9,7 +9,16 @@ use deno_core::ResourceId; use serde::Deserialize; use std::borrow::Cow; use std::rc::Rc; -use wgpu_types::SurfaceStatus; + +#[derive(Debug, thiserror::Error)] +pub enum SurfaceError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("Invalid Surface Status")] + InvalidStatus, + #[error(transparent)] + Surface(wgpu_core::present::SurfaceError), +} pub struct WebGpuSurface(pub crate::Instance, pub wgpu_core::id::SurfaceId); impl Resource for WebGpuSurface { @@ -41,7 +50,7 @@ pub struct SurfaceConfigureArgs { pub fn op_webgpu_surface_configure( state: &mut OpState, #[serde] args: SurfaceConfigureArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table @@ -74,15 +83,20 @@ pub fn op_webgpu_surface_get_current_texture( state: &mut OpState, #[smi] _device_rid: ResourceId, #[smi] surface_rid: ResourceId, -) -> Result { +) -> Result { let instance = state.borrow::(); - let surface_resource = state.resource_table.get::(surface_rid)?; + let surface_resource = state + .resource_table + .get::(surface_rid) + .map_err(SurfaceError::Resource)?; let surface = surface_resource.1; - let output = instance.surface_get_current_texture(surface, None)?; + let output = instance + .surface_get_current_texture(surface, None) + .map_err(SurfaceError::Surface)?; match output.status { - SurfaceStatus::Good | SurfaceStatus::Suboptimal => { + wgpu_types::SurfaceStatus::Good | wgpu_types::SurfaceStatus::Suboptimal => { let id = output.texture_id.unwrap(); let rid = state.resource_table.add(crate::texture::WebGpuTexture { instance: instance.clone(), @@ -90,7 +104,7 @@ pub fn op_webgpu_surface_get_current_texture( }); Ok(WebGpuResult::rid(rid)) } - _ => Err(AnyError::msg("Invalid Surface Status")), + _ => Err(SurfaceError::InvalidStatus), } } @@ -99,12 +113,17 @@ pub fn op_webgpu_surface_present( state: &mut OpState, #[smi] _device_rid: ResourceId, #[smi] surface_rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), SurfaceError> { let instance = state.borrow::(); - let surface_resource = state.resource_table.get::(surface_rid)?; + let surface_resource = state + .resource_table + .get::(surface_rid) + .map_err(SurfaceError::Resource)?; let surface = surface_resource.1; - instance.surface_present(surface)?; + instance + .surface_present(surface) + .map_err(SurfaceError::Surface)?; Ok(()) } diff --git a/deno_webgpu/texture.rs b/deno_webgpu/texture.rs index 0f78d07744..2ad2814996 100644 --- a/deno_webgpu/texture.rs +++ b/deno_webgpu/texture.rs @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; +use super::wgpu_types; use deno_core::op2; use deno_core::OpState; use deno_core::Resource; @@ -59,7 +59,7 @@ pub struct CreateTextureArgs { pub fn op_webgpu_create_texture( state: &mut OpState, #[serde] args: CreateTextureArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let device_resource = state .resource_table @@ -103,7 +103,7 @@ pub struct CreateTextureViewArgs { pub fn op_webgpu_create_texture_view( state: &mut OpState, #[serde] args: CreateTextureViewArgs, -) -> Result { +) -> Result { let instance = state.borrow::(); let texture_resource = state .resource_table @@ -115,6 +115,7 @@ pub fn op_webgpu_create_texture_view( format: args.format, dimension: args.dimension, range: args.range, + usage: None, // FIXME: Obtain actual value from desc }; gfx_put!(instance.texture_create_view( diff --git a/deno_webgpu/webgpu.idl b/deno_webgpu/webgpu.idl index 60b5a4259d..bdff4e4e27 100644 --- a/deno_webgpu/webgpu.idl +++ b/deno_webgpu/webgpu.idl @@ -78,11 +78,11 @@ enum GPUPowerPreference { [Exposed=(Window, Worker), SecureContext] interface GPUAdapter { [SameObject] readonly attribute GPUSupportedFeatures features; + [SameObject] readonly attribute GPUAdapterInfo info; [SameObject] readonly attribute GPUSupportedLimits limits; readonly attribute boolean isFallbackAdapter; Promise requestDevice(optional GPUDeviceDescriptor descriptor = {}); - Promise requestAdapterInfo(); }; dictionary GPUDeviceDescriptor @@ -92,22 +92,19 @@ dictionary GPUDeviceDescriptor }; enum GPUFeatureName { - // api "depth-clip-control", - // texture formats "depth32float-stencil8", "texture-compression-bc", "texture-compression-bc-sliced-3d", "texture-compression-etc2", "texture-compression-astc", - // api "timestamp-query", "indirect-first-instance", - // shader "shader-f16", "rg11b10ufloat-renderable", "bgra8unorm-storage", "float32-filterable", + "dual-source-blending", // extended from spec @@ -752,6 +749,10 @@ enum GPUBlendFactor { "src-alpha-saturated", "constant", "one-minus-constant", + "src1", + "one-minus-src1", + "src1-alpha", + "one-minus-src1-alpha", }; enum GPUBlendOperation { @@ -1109,13 +1110,13 @@ interface GPUQueue { undefined writeBuffer( GPUBuffer buffer, GPUSize64 bufferOffset, - [AllowShared] BufferSource data, + AllowSharedBufferSource data, optional GPUSize64 dataOffset = 0, optional GPUSize64 size); undefined writeTexture( GPUTexelCopyTextureInfo destination, - [AllowShared] BufferSource data, + AllowSharedBufferSource data, GPUTexelCopyBufferLayout dataLayout, GPUExtent3D size); }; diff --git a/etc/specs/ray_tracing.md b/etc/specs/ray_tracing.md new file mode 100644 index 0000000000..f23b5305a2 --- /dev/null +++ b/etc/specs/ray_tracing.md @@ -0,0 +1,188 @@ +# Ray Tracing Extensions + +🧪Experimental🧪 + +`wgpu` supports an experimental version of ray tracing which is subject to change. The extensions allow for acceleration structures to be created and built (with +`Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE` enabled) and interacted with in shaders. Currently `naga` only supports ray queries +(accessible with `Features::EXPERIMENTAL_RAY_QUERY` enabled in wgpu). + +**Note**: The features documented here may have major bugs in them and are expected to be subject +to breaking changes, suggestions for the API exposed by this should be posted on [the ray-tracing issue](https://github.com/gfx-rs/wgpu/issues/1040). +Large changes may mean that this documentation may be out of date. + +***This is not*** an introduction to raytracing, and assumes basic prior knowledge, to look at the fundamentals look at +an [introduction](https://developer.nvidia.com/blog/introduction-nvidia-rtx-directx-ray-tracing/). + +## `wgpu`'s raytracing API: + +The documentation and specific details of the functions and structures provided +can be found with their definitions. + +A [`Blas`] can be created with [`Device::create_blas`]. +A [`Tlas`] can be created with [`Device::create_tlas`]. + +Unless one is planning on using the unsafe building API (not recommended for beginners) a [`Tlas`] should be put inside +a [`TlasPackage`]. After that a reference to the [`Tlas`] can be retrieved by calling [`TlasPackage::tlas`]. +This reference can be placed in a bind group to be used in a shader. A reference to a [`Blas`] can +be used to create [`TlasInstance`] alongside a transformation matrix, a custom index +(this can be any data that should be given to the shader on a hit) which only the first 24 +bits may be set, and a mask to filter hits in the shader. + +A [`Blas`] must be built in either the same build as any [`Tlas`] it is used to build or an earlier build call. +Before a [`Tlas`] is used in a shader it must +- have been built +- have all [`Blas`]es that it was last built with to have last been built in either the same build as + this [`Tlas`] or an earlier build call. + +[`Device::create_blas`]: https://wgpu.rs/doc/wgpu/struct.Device.html#method.create_blas +[`Device::create_tlas`]: https://wgpu.rs/doc/wgpu/struct.Device.html#method.create_tlas +[`Tlas`]: https://wgpu.rs/doc/wgpu/struct.Tlas.html +[`Blas`]: https://wgpu.rs/doc/wgpu/struct.Blas.html +[`TlasInstance`]: https://wgpu.rs/doc/wgpu/struct.TlasInstance.html +[`TlasPackage`]: https://wgpu.rs/doc/wgpu/struct.TlasPackage.html +[`TlasPackage::tlas`]: https://wgpu.rs/doc/wgpu/struct.TlasPackage.html#method.tlas + +## `naga`'s raytracing API: + +`naga` supports ray queries (also known as inline raytracing) only. Ray tracing pipelines are currently unsupported. +Naming is mostly taken from vulkan. + +```wgsl +// - Initializes the `ray_query` to check where (if anywhere) the ray defined by `ray_desc` hits in `acceleration_structure +rayQueryInitialize(rq: ptr, acceleration_structure: acceleration_structure, ray_desc: RayDesc) + +// - Traces the ray in the initialized ray_query (partially) through the scene. +// - Returns true if a triangle that was hit by the ray was in a `Blas` that is not marked as opaque. +// - Returns false if all triangles that were hit by the ray were in `Blas`es that were marked as opaque. +// - The hit is considered `Candidate` if this function returns true, and the hit is considered `Committed` if +// this function returns false. +// - A `Candidate` intersection interrupts the ray traversal. +// - A `Candidate` intersection may happen anywhere along the ray, it should not be relied on to give the closest hit. A +// `Candidate` intersection is to allow the user themselves to decide if that intersection is valid*. If one wants to get +// the closest hit a `Committed` intersection should be used. +// - Calling this function multiple times will cause the ray traversal to continue if it was interrupted by a `Candidate` +// intersection. +rayQueryProceed(rq: ptr) -> bool` + +// - Returns intersection details about a hit considered `Committed`. +rayQueryGetCommittedIntersection(rq: ptr) -> RayIntersection + +// - Returns intersection details about a hit considered `Candidate`. +rayQueryGetCandidateIntersection(rq: ptr) -> RayIntersection +``` + +*The API to commit a candidate intersection is not yet implemented but would be possible to be user implemented. + +> [!CAUTION] +> +> ### ⚠️Undefined behavior ⚠️: +> - Calling `rayQueryGetCommittedIntersection` or `rayQueryGetCandidateIntersection` when `rayQueryProceed` has not been +> called on this ray query since it was initialized (or if the ray query has not been previously initialized). +> - Calling `rayQueryGetCommittedIntersection` when `rayQueryProceed`'s latest return on this ray query is considered +> `Candidate`. +> - Calling `rayQueryGetCandidateIntersection` when `rayQueryProceed`'s latest return on this ray query is considered +> `Committed`. +> - Calling `rayQueryProceed` when `rayQueryInitialize` has not previously been called on this ray query +> +> *this is only known undefined behaviour, and will be worked around in the future. + +```wgsl +struct RayDesc { + // Contains flags to use for this ray (e.g. consider all `Blas`es opaque) + flags: u32, + // If the bitwise and of this and any `TlasInstance`'s `mask` is not zero then the object inside + // the `Blas` contained within that `TlasInstance` may be hit. + cull_mask: u32, + // Only points on the ray whose t is greater than this may be hit. + t_min: f32, + // Only points on the ray whose t is less than this may be hit. + t_max: f32, + // The origin of the ray. + origin: vec3, + // The direction of the ray, t is calculated as the length down the ray divided by the length of `dir`. + dir: vec3, +} + +struct RayIntersection { + // the kind of the hit, no other member of this structure is useful if this is equal + // to constant `RAY_QUERY_INTERSECTION_NONE`. + kind: u32, + // Distance from starting point, measured in units of `RayDesc::dir`. + t: f32, + // Corresponds to `instance.custom_index` where `instance` is the `TlasInstance` + // that the intersected object was contained in. + instance_custom_index: u32, + // The index into the `TlasPackage` to get the `TlasInstance` that the hit object is in + instance_id: u32, + // The offset into the shader binding table. Currently, this value is always 0. + sbt_record_offset: u32, + // The index into the `Blas`'s build descriptor (e.g. if `BlasBuildEntry::geometry` is + // `BlasGeometries::TriangleGeometries` then it is the index into that contained vector). + geometry_index: u32, + // The object hit's index into the provided buffer (e.g. if the object is a triangle + // then this is the triangle index) + primitive_index: u32, + // Two of the barycentric coordinates, the third can be calculated (only useful if this is a triangle). + barycentrics: vec2, + // Whether the hit face is the front (only useful if this is a triangle). + front_face: bool, + // Matrix for converting from object-space to world-space. + // + // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work. + // Use it this way: `let transformed_vector = intersecion.object_to_world * vec4(x, y, z, transform_multiplier); + object_to_world: mat4x3, + // Matrix for converting from world-space to object-space + // + // This matrix needs to be on the left side of the multiplication. Using it the other way round will not work. + // Use it this way: `let transformed_vector = intersecion.world_to_object * vec4(x, y, z, transform_multiplier); + world_to_object: mat4x3, +} + +/// -- Flags for `RayDesc::flags` -- + +// All `Blas`es are marked as opaque. +const FORCE_OPAQUE = 0x1; + +// All `Blas`es are marked as non-opaque. +const FORCE_NO_OPAQUE = 0x2; + +// Instead of searching for the closest hit return the first hit. +const TERMINATE_ON_FIRST_HIT = 0x4; + +// Unused: implemented for raytracing pipelines. +const SKIP_CLOSEST_HIT_SHADER = 0x8; + +// If `RayIntersection::front_face` is false do not return a hit. +const CULL_BACK_FACING = 0x10; + +// If `RayIntersection::front_face` is true do not return a hit. +const CULL_FRONT_FACING = 0x20; + +// If the `Blas` a intersection is checking is marked as opaque do not return a hit. +const CULL_OPAQUE = 0x40; + +// If the `Blas` a intersection is checking is not marked as opaque do not return a hit. +const CULL_NO_OPAQUE = 0x80; + +// If the `Blas` a intersection is checking contains triangles do not return a hit. +const SKIP_TRIANGLES = 0x100; + +// If the `Blas` a intersection is checking contains AABBs do not return a hit. +const SKIP_AABBS = 0x200; + +/// -- Constants for `RayIntersection::kind` -- + +// The ray hit nothing. +const RAY_QUERY_INTERSECTION_NONE = 0; + +// The ray hit a triangle. +const RAY_QUERY_INTERSECTION_TRIANGLE = 1; + +// The ray hit a custom object, this will only happen in a committed intersection +// if a ray which intersected a bounding box for a custom object which was then committed. +const RAY_QUERY_INTERSECTION_GENERATED = 2; + +// The ray hit a AABB, this will only happen in a candidate intersection +// if the ray intersects the bounding box for a custom object. +const RAY_QUERY_INTERSECTION_AABB = 3; +``` diff --git a/examples/README.md b/examples/README.md index c67941e067..799c8a8d5b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,6 +24,7 @@ The rest of the examples are for demonstrating specific features that you can co - `bunnymark` - Demonstrates many things, but chief among them is performing numerous draw calls with different bind groups in one render pass. The example also uses textures for the icon and uniform buffers to transfer both global and per-particle states. - `skybox` - Shows off too many concepts to list here. The name comes from game development where a "skybox" acts as a background for rendering, usually to add a sky texture for immersion, although they can also be used for backdrops to give the idea of a world beyond the game scene. This example does so much more than this, though, as it uses a car model loaded from a file and uses the user's mouse to rotate the car model in 3d. `skybox` also makes use of depth textures and similar app patterns to `uniform_values`. - `shadow` - Likely by far the most complex example (certainly the largest in lines of code) of the official WGPU examples. `shadow` demonstrates basic scene rendering with the main attraction being lighting and shadows (as the name implies). It is recommended that any user looking into lighting be very familiar with the basic concepts of not only rendering with WGPU but also the primary mathematical ideas of computer graphics. +- `multiple-render-targets` - Demonstrates how to render to two texture targets simultaneously from fragment shader. - `render_to_texture` - Renders to an image texture offscreen, demonstrating both off-screen rendering as well as how to add a sort of resolution-agnostic screenshot feature to an engine. This example either outputs an image file of your naming (pass command line arguments after specifying a `--` like `cargo run --bin wgpu-examples -- render_to_texture "test.png"`) or adds an `img` element containing the image to the page in WASM. - `ray_cube_fragment` - Demonstrates using ray queries with a fragment shader. - `ray_scene` - Demonstrates using ray queries and model loading diff --git a/examples/src/framework.rs b/examples/src/framework.rs index 1495a8d6fc..8f8aa89f90 100644 --- a/examples/src/framework.rs +++ b/examples/src/framework.rs @@ -268,48 +268,16 @@ impl ExampleContext { async fn init_async(surface: &mut SurfaceWrapper, window: Arc) -> Self { log::info!("Initializing wgpu..."); - let backends = wgpu::util::backend_bits_from_env().unwrap_or_default(); - let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(); - let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends, - flags: wgpu::InstanceFlags::from_build_config().with_env(), - dx12_shader_compiler, - gles_minor_version, - }); + let instance = wgpu::Instance::new(&wgpu::util::instance_descriptor_from_env()); surface.pre_adapter(&instance, window); - let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, surface.get()) - .await - .expect("No suitable GPU adapters found on the system!"); - - let adapter_info = adapter.get_info(); - log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); - - let optional_features = E::optional_features(); - let required_features = E::required_features(); - let adapter_features = adapter.features(); - assert!( - adapter_features.contains(required_features), - "Adapter does not support required features for this example: {:?}", - required_features - adapter_features - ); - - let required_downlevel_capabilities = E::required_downlevel_capabilities(); - let downlevel_capabilities = adapter.get_downlevel_capabilities(); - assert!( - downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, - "Adapter does not support the minimum shader model required to run this example: {:?}", - required_downlevel_capabilities.shader_model - ); - assert!( - downlevel_capabilities - .flags - .contains(required_downlevel_capabilities.flags), - "Adapter does not support the downlevel capabilities required to run this example: {:?}", - required_downlevel_capabilities.flags - downlevel_capabilities.flags - ); + let adapter = get_adapter_with_capabilities_or_from_env( + &instance, + &E::required_features(), + &E::required_downlevel_capabilities(), + &surface.get(), + ) + .await; // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. let needed_limits = E::required_limits().using_resolution(adapter.limits()); @@ -318,7 +286,8 @@ impl ExampleContext { .request_device( &wgpu::DeviceDescriptor { label: None, - required_features: (optional_features & adapter_features) | required_features, + required_features: (E::optional_features() & adapter.features()) + | E::required_features(), required_limits: needed_limits, memory_hints: wgpu::MemoryHints::MemoryUsage, }, @@ -514,6 +483,8 @@ pub fn parse_url_query_string<'a>(query: &'a str, search_key: &str) -> Option<&' #[cfg(test)] pub use wgpu_test::image::ComparisonType; +use crate::utils::get_adapter_with_capabilities_or_from_env; + #[cfg(test)] #[derive(Clone)] pub struct ExampleTestParams { diff --git a/examples/src/hello_triangle/mod.rs b/examples/src/hello_triangle/mod.rs index 7c82d49cf0..3a0781518b 100644 --- a/examples/src/hello_triangle/mod.rs +++ b/examples/src/hello_triangle/mod.rs @@ -10,7 +10,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { size.width = size.width.max(1); size.height = size.height.max(1); - let instance = wgpu::Instance::default(); + let instance = wgpu::Instance::new(&wgpu::util::instance_descriptor_from_env()); let surface = instance.create_surface(&window).unwrap(); let adapter = instance diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 358993e1af..71b3799744 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -15,6 +15,7 @@ pub mod hello_windows; pub mod hello_workgroups; pub mod mipmap; pub mod msaa_line; +pub mod multiple_render_targets; pub mod ray_cube_compute; pub mod ray_cube_fragment; pub mod ray_scene; diff --git a/examples/src/main.rs b/examples/src/main.rs index 77e658a883..66f0fea4d8 100644 --- a/examples/src/main.rs +++ b/examples/src/main.rs @@ -80,6 +80,12 @@ const EXAMPLES: &[ExampleDesc] = &[ webgl: true, webgpu: true, }, + ExampleDesc { + name: "multiple_render_targets", + function: wgpu_examples::multiple_render_targets::main, + webgl: false, + webgpu: true, + }, ExampleDesc { name: "render_to_texture", function: wgpu_examples::render_to_texture::main, diff --git a/examples/src/mipmap/mod.rs b/examples/src/mipmap/mod.rs index fa5bedf8cb..9e96fec87a 100644 --- a/examples/src/mipmap/mod.rs +++ b/examples/src/mipmap/mod.rs @@ -127,6 +127,7 @@ impl Example { label: Some("mip"), format: None, dimension: None, + usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: mip, mip_level_count: Some(1), diff --git a/examples/src/multiple_render_targets/README.md b/examples/src/multiple_render_targets/README.md new file mode 100644 index 0000000000..677f32b266 --- /dev/null +++ b/examples/src/multiple_render_targets/README.md @@ -0,0 +1,21 @@ +# multiple_render_targets + +This example demonstrates how to use fragment shader to output to two color attachments of a renderpass simultaneously. + +The program generates a black and white ball-shaped texture from scratch and uses the fragment shader to draw it to two +separate texture targets. The first texture target receives a red version of the texture and the second target receives +a green version. + +Once the colored shapes have been drawn to two separate textures, they +will be displayed on the screen by rendering each one of them to their own viewports that split the screen in half. + + +## To Run + +``` +cargo run --bin wgpu-examples multiple_render_targets +``` + +## Screenshots + +![Multi render target](./screenshot.png) diff --git a/examples/src/multiple_render_targets/mod.rs b/examples/src/multiple_render_targets/mod.rs new file mode 100644 index 0000000000..c7301024b5 --- /dev/null +++ b/examples/src/multiple_render_targets/mod.rs @@ -0,0 +1,545 @@ +const EXAMPLE_NAME: &str = "multiple_render_targets"; + +/// Renderer that draws its outputs to two output texture targets at the same time. +struct MultiTargetRenderer { + pipeline: wgpu::RenderPipeline, + bindgroup: wgpu::BindGroup, +} + +fn create_ball_texture_data(width: usize, height: usize) -> Vec { + // Creates black and white pixel data for the texture to sample. + let mut img_data = Vec::with_capacity(width * height); + let center: glam::Vec2 = glam::Vec2::new(width as f32 * 0.5, height as f32 * 0.5); + let half_distance = width as f32 * 0.5; + for y in 0..width { + for x in 0..height { + let cur_pos = glam::Vec2::new(x as f32, y as f32); + let distance_to_center_normalized = 1.0 - (cur_pos - center).length() / half_distance; + let val: u8 = (u8::MAX as f32 * distance_to_center_normalized) as u8; + img_data.push(val) + } + } + img_data +} + +impl MultiTargetRenderer { + fn create_image_texture( + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> (wgpu::Texture, wgpu::TextureView) { + const WIDTH: usize = 256; + const HEIGHT: usize = 256; + + let size = wgpu::Extent3d { + width: WIDTH as u32, + height: HEIGHT as u32, + depth_or_array_layers: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("data texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::R8Unorm, // we need only the red channel for black/white image, + usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], + }); + + let ball_texture_data = &create_ball_texture_data(WIDTH, HEIGHT); + + queue.write_texture( + wgpu::TexelCopyTextureInfo { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + ball_texture_data, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(WIDTH as u32), + rows_per_image: Some(HEIGHT as u32), + }, + size, + ); + + let view = texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("view"), + format: None, + dimension: Some(wgpu::TextureViewDimension::D2), + usage: None, + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + + (texture, view) + } + + fn init( + device: &wgpu::Device, + queue: &wgpu::Queue, + shader: &wgpu::ShaderModule, + target_states: &[Option], + ) -> MultiTargetRenderer { + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: None, + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::Repeat, + address_mode_w: wgpu::AddressMode::Repeat, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let (_, texture_view) = Self::create_image_texture(device, queue); + + let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + label: None, + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: shader, + entry_point: Some("fs_multi_main"), + // IMPORTANT: specify the color states for the outputs: + compilation_options: Default::default(), + targets: target_states, + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + Self { + pipeline, + bindgroup, + } + } + + fn draw( + &self, + encoder: &mut wgpu::CommandEncoder, + targets: &[Option], + ) { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: targets, + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bindgroup, &[]); + rpass.draw(0..3, 0..1); + } +} + +/// Renderer that displays results on the screen. +struct TargetRenderer { + pipeline: wgpu::RenderPipeline, + bindgroup_layout: wgpu::BindGroupLayout, + bindgroup_left: wgpu::BindGroup, + bindgroup_right: wgpu::BindGroup, + sampler: wgpu::Sampler, +} + +impl TargetRenderer { + fn init( + device: &wgpu::Device, + shader: &wgpu::ShaderModule, + format: wgpu::TextureFormat, + targets: &TextureTargets, + ) -> TargetRenderer { + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: None, + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::Repeat, + address_mode_w: wgpu::AddressMode::Repeat, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: shader, + entry_point: Some("vs_main"), + compilation_options: Default::default(), + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: shader, + entry_point: Some("fs_display_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: None, + write_mask: Default::default(), + })], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let (bg_left, bg_right) = + Self::create_bindgroups(device, &texture_bind_group_layout, targets, &sampler); + Self { + pipeline: render_pipeline, + bindgroup_layout: texture_bind_group_layout, + bindgroup_left: bg_left, + bindgroup_right: bg_right, + sampler, + } + } + fn create_bindgroups( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + texture_targets: &TextureTargets, + sampler: &wgpu::Sampler, + ) -> (wgpu::BindGroup, wgpu::BindGroup) { + let left = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_targets.red_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + label: None, + }); + + let right = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_targets.green_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sampler), + }, + ], + label: None, + }); + (left, right) + } + + fn draw( + &self, + encoder: &mut wgpu::CommandEncoder, + surface_view: &wgpu::TextureView, + width: u32, + height: u32, + ) { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: surface_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bindgroup_left, &[]); + + let height = height as f32; + let half_w = width as f32 * 0.5; + + // draw results in two separate viewports that split the screen: + + rpass.set_viewport(0.0, 0.0, half_w, height, 0.0, 1.0); + rpass.draw(0..3, 0..1); + + rpass.set_viewport(half_w, 0.0, half_w, height, 0.0, 1.0); + rpass.set_bind_group(0, &self.bindgroup_right, &[]); + rpass.draw(0..3, 0..1); + } + + fn rebuild_resources(&mut self, device: &wgpu::Device, texture_targets: &TextureTargets) { + (self.bindgroup_left, self.bindgroup_right) = Self::create_bindgroups( + device, + &self.bindgroup_layout, + texture_targets, + &self.sampler, + ) + } +} + +struct TextureTargets { + red_view: wgpu::TextureView, + green_view: wgpu::TextureView, +} + +impl TextureTargets { + fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + width: u32, + height: u32, + ) -> TextureTargets { + let size = wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }; + + let red_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[format], + }); + let green_texture = device.create_texture(&wgpu::TextureDescriptor { + label: None, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[format], + }); + let red_view = red_texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + ..wgpu::TextureViewDescriptor::default() + }); + let green_view = green_texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + ..wgpu::TextureViewDescriptor::default() + }); + TextureTargets { + red_view, + green_view, + } + } +} + +struct Example { + drawer: TargetRenderer, + multi_target_renderer: MultiTargetRenderer, + texture_targets: TextureTargets, + screen_width: u32, + screen_height: u32, +} + +impl crate::framework::Example for Example { + fn init( + config: &wgpu::SurfaceConfiguration, + _adapter: &wgpu::Adapter, + device: &wgpu::Device, + queue: &wgpu::Queue, + ) -> Self { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( + "shader.wgsl" + ))), + }); + // Renderer that draws to 2 textures at the same time: + let multi_target_renderer = MultiTargetRenderer::init( + device, + queue, + &shader, + // ColorTargetStates specify how the data will be written to the + // output textures: + &[ + Some(wgpu::ColorTargetState { + format: config.format, + blend: None, + write_mask: Default::default(), + }), + Some(wgpu::ColorTargetState { + format: config.format, + blend: None, + write_mask: Default::default(), + }), + ], + ); + + // create our target textures that will receive the simultaneous rendering: + let texture_targets = + TextureTargets::new(device, config.format, config.width, config.height); + + // helper renderer that displays the results in 2 separate viewports: + let drawer = TargetRenderer::init(device, &shader, config.format, &texture_targets); + + Self { + texture_targets, + multi_target_renderer, + drawer, + screen_width: config.width, + screen_height: config.height, + } + } + + fn resize( + &mut self, + config: &wgpu::SurfaceConfiguration, + device: &wgpu::Device, + _queue: &wgpu::Queue, + ) { + self.screen_width = config.width; + self.screen_height = config.height; + self.texture_targets = + TextureTargets::new(device, config.format, config.width, config.height); + self.drawer.rebuild_resources(device, &self.texture_targets); + } + + fn update(&mut self, _event: winit::event::WindowEvent) {} + + fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + // draw to 2 textures at the same time: + self.multi_target_renderer.draw( + &mut encoder, + &[ + Some(wgpu::RenderPassColorAttachment { + view: &self.texture_targets.red_view, + resolve_target: None, + ops: Default::default(), + }), + Some(wgpu::RenderPassColorAttachment { + view: &self.texture_targets.green_view, + resolve_target: None, + ops: Default::default(), + }), + ], + ); + + // display results of the both drawn textures on screen: + self.drawer + .draw(&mut encoder, view, self.screen_width, self.screen_height); + + queue.submit(Some(encoder.finish())); + } +} + +pub fn main() { + crate::framework::run::(EXAMPLE_NAME); +} + +#[cfg(test)] +#[wgpu_test::gpu_test] +static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: EXAMPLE_NAME, + image_path: "/examples/src/multiple_render_targets/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default(), + // Bounded by lavapipe + comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], + _phantom: std::marker::PhantomData::, +}; diff --git a/examples/src/multiple_render_targets/screenshot.png b/examples/src/multiple_render_targets/screenshot.png new file mode 100644 index 0000000000..35ebf5a46a Binary files /dev/null and b/examples/src/multiple_render_targets/screenshot.png differ diff --git a/examples/src/multiple_render_targets/shader.wgsl b/examples/src/multiple_render_targets/shader.wgsl new file mode 100644 index 0000000000..8d3630b308 --- /dev/null +++ b/examples/src/multiple_render_targets/shader.wgsl @@ -0,0 +1,44 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) uv: vec2, +}; + +@vertex +fn vs_main( @builtin(vertex_index) vi: u32) -> VertexOutput { + var out: VertexOutput; + out.uv = vec2( + f32((vi << 1u) & 2u), + f32(vi & 2u), + ); + out.clip_position = vec4(out.uv * 2.0 - 1.0, 0.0, 1.0); + out.uv.y = 1.0 - out.uv.y; + return out; +} + +@group(0) +@binding(0) +var image_texture: texture_2d; +@group(0) +@binding(1) +var image_sampler: sampler; + +struct FragmentOutput { + @location(0) red_target : vec4, + @location(1) green_target : vec4, +} + +@fragment +fn fs_multi_main(vs: VertexOutput) -> FragmentOutput { + let smp = textureSample(image_texture, image_sampler, vs.uv).x; + + var output: FragmentOutput; + output.red_target = vec4(smp, 0.0, 0.0, 1.0); + output.green_target = vec4(0.0, smp, 0.0, 1.0); + return output; +} + +@fragment +fn fs_display_main(vs: VertexOutput) -> @location(0) vec4 { + let smp = textureSample(image_texture, image_sampler, vs.uv).xyz; + return vec4(smp, 1.0); +} diff --git a/examples/src/ray_cube_compute/mod.rs b/examples/src/ray_cube_compute/mod.rs index 3f8f31ee0a..62a3e36aab 100644 --- a/examples/src/ray_cube_compute/mod.rs +++ b/examples/src/ray_cube_compute/mod.rs @@ -177,6 +177,7 @@ impl crate::framework::Example for Example { label: None, format: Some(wgpu::TextureFormat::Rgba8Unorm), dimension: Some(wgpu::TextureViewDimension::D2), + usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, diff --git a/examples/src/shadow/mod.rs b/examples/src/shadow/mod.rs index 44ad84a73f..a911bd7108 100644 --- a/examples/src/shadow/mod.rs +++ b/examples/src/shadow/mod.rs @@ -393,6 +393,7 @@ impl crate::framework::Example for Example { label: Some("shadow"), format: None, dimension: Some(wgpu::TextureViewDimension::D2), + usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, mip_level_count: None, diff --git a/examples/src/timestamp_queries/mod.rs b/examples/src/timestamp_queries/mod.rs index 69f5c57dd6..1a2841f7dc 100644 --- a/examples/src/timestamp_queries/mod.rs +++ b/examples/src/timestamp_queries/mod.rs @@ -181,7 +181,7 @@ impl Queries { async fn run() { // Instantiates instance of wgpu let backends = wgpu::util::backend_bits_from_env().unwrap_or_default(); - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends, flags: wgpu::InstanceFlags::from_build_config().with_env(), dx12_shader_compiler: wgpu::Dx12Compiler::default(), diff --git a/examples/src/utils.rs b/examples/src/utils.rs index 7b663e2bc3..bbc727582b 100644 --- a/examples/src/utils.rs +++ b/examples/src/utils.rs @@ -156,3 +156,105 @@ fn create_output_image_element(document: &web_sys::Document) -> web_sys::HtmlIma log::info!("Created new output target image: {:?}", &new_image); new_image } + +#[cfg(not(target_arch = "wasm32"))] +/// If the environment variable `WGPU_ADAPTER_NAME` is set, this function will attempt to +/// initialize the adapter with that name. If it is not set, it will attempt to initialize +/// the adapter which supports the required features. +pub(crate) async fn get_adapter_with_capabilities_or_from_env( + instance: &wgpu::Instance, + required_features: &wgpu::Features, + required_downlevel_capabilities: &wgpu::DownlevelCapabilities, + surface: &Option<&wgpu::Surface<'_>>, +) -> wgpu::Adapter { + use wgpu::Backends; + if std::env::var("WGPU_ADAPTER_NAME").is_ok() { + let adapter = wgpu::util::initialize_adapter_from_env_or_default(instance, *surface) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_info = adapter.get_info(); + log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); + + let adapter_features = adapter.features(); + assert!( + adapter_features.contains(*required_features), + "Adapter does not support required features for this example: {:?}", + *required_features - adapter_features + ); + + let downlevel_capabilities = adapter.get_downlevel_capabilities(); + assert!( + downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, + "Adapter does not support the minimum shader model required to run this example: {:?}", + required_downlevel_capabilities.shader_model + ); + assert!( + downlevel_capabilities + .flags + .contains(required_downlevel_capabilities.flags), + "Adapter does not support the downlevel capabilities required to run this example: {:?}", + required_downlevel_capabilities.flags - downlevel_capabilities.flags + ); + adapter + } else { + let adapters = instance.enumerate_adapters(Backends::all()); + + let mut chosen_adapter = None; + for adapter in adapters { + if let Some(surface) = surface { + if !adapter.is_surface_supported(surface) { + continue; + } + } + + let required_features = *required_features; + let adapter_features = adapter.features(); + if !adapter_features.contains(required_features) { + continue; + } else { + chosen_adapter = Some(adapter); + break; + } + } + + chosen_adapter.expect("No suitable GPU adapters found on the system!") + } +} + +#[cfg(target_arch = "wasm32")] +pub(crate) async fn get_adapter_with_capabilities_or_from_env( + instance: &wgpu::Instance, + required_features: &wgpu::Features, + required_downlevel_capabilities: &wgpu::DownlevelCapabilities, + surface: &Option<&wgpu::Surface<'_>>, +) -> wgpu::Adapter { + let adapter = wgpu::util::initialize_adapter_from_env_or_default(instance, *surface) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_info = adapter.get_info(); + log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend); + + let adapter_features = adapter.features(); + assert!( + adapter_features.contains(*required_features), + "Adapter does not support required features for this example: {:?}", + *required_features - adapter_features + ); + + let downlevel_capabilities = adapter.get_downlevel_capabilities(); + assert!( + downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model, + "Adapter does not support the minimum shader model required to run this example: {:?}", + required_downlevel_capabilities.shader_model + ); + assert!( + downlevel_capabilities + .flags + .contains(required_downlevel_capabilities.flags), + "Adapter does not support the downlevel capabilities required to run this example: {:?}", + required_downlevel_capabilities.flags - downlevel_capabilities.flags + ); + adapter +} diff --git a/naga-cli/Cargo.toml b/naga-cli/Cargo.toml index 02f68cc02a..3e41bca58d 100644 --- a/naga-cli/Cargo.toml +++ b/naga-cli/Cargo.toml @@ -3,7 +3,7 @@ name = "naga-cli" version = "23.0.0" authors = ["gfx-rs developers"] edition = "2021" -description = "Shader translation command line tool" +description = "CLI for the naga shader translator and validator. Part of the wgpu project" repository = "https://github.com/gfx-rs/wgpu/tree/trunk/naga-cli" keywords = ["shader", "SPIR-V", "GLSL", "MSL"] license = "MIT OR Apache-2.0" diff --git a/naga/Cargo.toml b/naga/Cargo.toml index 3c26288973..0cbd0f4368 100644 --- a/naga/Cargo.toml +++ b/naga/Cargo.toml @@ -3,7 +3,7 @@ name = "naga" version = "23.0.0" authors = ["gfx-rs developers"] edition = "2021" -description = "Shader translation infrastructure" +description = "Shader translator and validator. Part of the wgpu project" repository = "https://github.com/gfx-rs/wgpu/tree/trunk/naga" keywords = ["shader", "SPIR-V", "GLSL", "MSL"] license = "MIT OR Apache-2.0" @@ -77,7 +77,8 @@ indexmap.workspace = true log = "0.4" spirv = { version = "0.3", optional = true } thiserror.workspace = true -serde = { version = "1.0.215", features = ["derive"], optional = true } +serde = { version = "1.0.217", features = ["derive"], optional = true } +# Hold on updating to 0.7 until https://github.com/petgraph/petgraph/pull/714 is on crates.io petgraph = { version = "0.6", optional = true } pp-rs = { version = "0.2.1", optional = true } hexf-parse = { version = "0.2.1", optional = true } diff --git a/naga/fuzz/Cargo.toml b/naga/fuzz/Cargo.toml index 5aadd1bb67..b37f373ddc 100644 --- a/naga/fuzz/Cargo.toml +++ b/naga/fuzz/Cargo.toml @@ -5,19 +5,24 @@ authors = ["Automatically generated"] publish = false edition = "2018" license = "MIT OR Apache-2.0" +build = "build.rs" [package.metadata] cargo-fuzz = true [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dependencies] arbitrary = { version = "1.4.1", features = ["derive"] } -libfuzzer-sys = "0.4" +# See https://github.com/rust-fuzz/libfuzzer/issues/126 +libfuzzer-sys = ">0.4.0,<=0.4.7" [target.'cfg(not(any(target_arch = "wasm32", target_os = "ios")))'.dependencies.naga] path = ".." version = "23.0.0" features = ["arbitrary", "spv-in", "wgsl-in", "glsl-in"] +[build-dependencies] +cfg_aliases.workspace = true + [[bin]] name = "spv_parser" path = "fuzz_targets/spv_parser.rs" diff --git a/naga/fuzz/build.rs b/naga/fuzz/build.rs new file mode 100644 index 0000000000..fa71d088aa --- /dev/null +++ b/naga/fuzz/build.rs @@ -0,0 +1,5 @@ +fn main() { + cfg_aliases::cfg_aliases! { + enable_fuzzing: { not(any(target_arch = "wasm32", target_os = "ios", all(windows, target_arch = "aarch64"))) }, + } +} diff --git a/naga/fuzz/fuzz_targets/glsl_parser.rs b/naga/fuzz/fuzz_targets/glsl_parser.rs index aed7ba981b..97a6ae3fbf 100644 --- a/naga/fuzz/fuzz_targets/glsl_parser.rs +++ b/naga/fuzz/fuzz_targets/glsl_parser.rs @@ -1,5 +1,6 @@ -#![no_main] -#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +#![cfg_attr(enable_fuzzing, no_main)] + +#[cfg(enable_fuzzing)] mod fuzz { use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; @@ -47,3 +48,6 @@ mod fuzz { let _result = parser.parse(&options.into(), &source); }); } + +#[cfg(not(enable_fuzzing))] +fn main() {} diff --git a/naga/fuzz/fuzz_targets/ir.rs b/naga/fuzz/fuzz_targets/ir.rs index 6768917c3b..53815c0649 100644 --- a/naga/fuzz/fuzz_targets/ir.rs +++ b/naga/fuzz/fuzz_targets/ir.rs @@ -1,5 +1,6 @@ -#![no_main] -#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +#![cfg_attr(enable_fuzzing, no_main)] + +#[cfg(enable_fuzzing)] mod fuzz { use libfuzzer_sys::fuzz_target; @@ -12,3 +13,6 @@ mod fuzz { let _result = validator.validate(&module); }); } + +#[cfg(not(enable_fuzzing))] +fn main() {} diff --git a/naga/fuzz/fuzz_targets/spv_parser.rs b/naga/fuzz/fuzz_targets/spv_parser.rs index 2b0fae2960..cf72644cfc 100644 --- a/naga/fuzz/fuzz_targets/spv_parser.rs +++ b/naga/fuzz/fuzz_targets/spv_parser.rs @@ -1,5 +1,6 @@ -#![no_main] -#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +#![cfg_attr(enable_fuzzing, no_main)] + +#[cfg(enable_fuzzing)] mod fuzz { use libfuzzer_sys::fuzz_target; use naga::front::spv::{Frontend, Options}; @@ -10,3 +11,6 @@ mod fuzz { let _result = Frontend::new(data.into_iter(), &options).parse(); }); } + +#[cfg(not(enable_fuzzing))] +fn main() {} diff --git a/naga/fuzz/fuzz_targets/wgsl_parser.rs b/naga/fuzz/fuzz_targets/wgsl_parser.rs index 7513d63d1d..1507ef0506 100644 --- a/naga/fuzz/fuzz_targets/wgsl_parser.rs +++ b/naga/fuzz/fuzz_targets/wgsl_parser.rs @@ -1,5 +1,6 @@ -#![no_main] -#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] +#![cfg_attr(enable_fuzzing, no_main)] + +#[cfg(enable_fuzzing)] mod fuzz { use libfuzzer_sys::fuzz_target; use naga::front::wgsl::Frontend; @@ -9,3 +10,6 @@ mod fuzz { let _result = Frontend::new().parse(&data); }); } + +#[cfg(not(enable_fuzzing))] +fn main() {} diff --git a/naga/src/arena/handle_set.rs b/naga/src/arena/handle_set.rs index ef2ded2ddb..f2ce058d12 100644 --- a/naga/src/arena/handle_set.rs +++ b/naga/src/arena/handle_set.rs @@ -25,6 +25,10 @@ impl HandleSet { } } + pub fn is_empty(&self) -> bool { + self.members.is_empty() + } + /// Return a new, empty `HandleSet`, sized to hold handles from `arena`. pub fn for_arena(arena: &impl ArenaType) -> Self { let len = arena.len(); diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 3698577d82..df20f074c8 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -747,14 +747,17 @@ impl<'a, W: Write> Writer<'a, W> { // Write functions to create special types. for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() { match type_key { - &crate::PredeclaredType::ModfResult { size, width } - | &crate::PredeclaredType::FrexpResult { size, width } => { + &crate::PredeclaredType::ModfResult { size, scalar } + | &crate::PredeclaredType::FrexpResult { size, scalar } => { let arg_type_name_owner; let arg_type_name = if let Some(size) = size { - arg_type_name_owner = - format!("{}vec{}", if width == 8 { "d" } else { "" }, size as u8); + arg_type_name_owner = format!( + "{}vec{}", + if scalar.width == 8 { "d" } else { "" }, + size as u8 + ); &arg_type_name_owner - } else if width == 8 { + } else if scalar.width == 8 { "double" } else { "float" @@ -4108,7 +4111,7 @@ impl<'a, W: Write> Writer<'a, W> { ) -> Result<(), Error> { use crate::ImageDimension as IDim; - // NOTE: openGL requires that `imageStore`s have no effets when the texel is invalid + // NOTE: openGL requires that `imageStore`s have no effects 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 diff --git a/naga/src/back/hlsl/help.rs b/naga/src/back/hlsl/help.rs index e060529dcf..347addd67e 100644 --- a/naga/src/back/hlsl/help.rs +++ b/naga/src/back/hlsl/help.rs @@ -796,17 +796,17 @@ impl super::Writer<'_, W> { pub(super) fn write_special_functions(&mut self, module: &crate::Module) -> BackendResult { for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { match type_key { - &crate::PredeclaredType::ModfResult { size, width } - | &crate::PredeclaredType::FrexpResult { size, width } => { + &crate::PredeclaredType::ModfResult { size, scalar } + | &crate::PredeclaredType::FrexpResult { size, scalar } => { let arg_type_name_owner; let arg_type_name = if let Some(size) = size { arg_type_name_owner = format!( "{}{}", - if width == 8 { "double" } else { "float" }, + if scalar.width == 8 { "double" } else { "float" }, size as u8 ); &arg_type_name_owner - } else if width == 8 { + } else if scalar.width == 8 { "double" } else { "float" diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index bc71a3d4fd..8dff67f1fc 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -3320,6 +3320,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { write!(self.out, " >> 24) / {scale}.0)")?; } fun @ (Function::Unpack4xI8 | Function::Unpack4xU8) => { + write!(self.out, "(")?; if matches!(fun, Function::Unpack4xU8) { write!(self.out, "u")?; } @@ -3331,7 +3332,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_expr(module, arg, func_ctx)?; write!(self.out, " >> 16, ")?; self.write_expr(module, arg, func_ctx)?; - write!(self.out, " >> 24) << 24 >> 24")?; + write!(self.out, " >> 24) << 24 >> 24)")?; } Function::QuantizeToF16 => { write!(self.out, "f16tof32(f32tof16(")?; diff --git a/naga/src/back/msl/keywords.rs b/naga/src/back/msl/keywords.rs index 73c457dd34..a4eabab234 100644 --- a/naga/src/back/msl/keywords.rs +++ b/naga/src/back/msl/keywords.rs @@ -341,4 +341,5 @@ pub const RESERVED: &[&str] = &[ "DefaultConstructible", super::writer::FREXP_FUNCTION, super::writer::MODF_FUNCTION, + super::writer::ARGUMENT_BUFFER_WRAPPER_STRUCT, ]; diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 453b7136b8..28e99acc5f 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -59,8 +59,6 @@ pub struct BindTarget { pub buffer: Option, pub texture: Option, pub sampler: Option, - /// If the binding is an unsized binding array, this overrides the size. - pub binding_array_size: Option, pub mutable: bool, } @@ -211,6 +209,9 @@ pub struct Options { pub bounds_check_policies: index::BoundsCheckPolicies, /// Should workgroup variables be zero initialized (by polyfilling)? pub zero_initialize_workgroup_memory: bool, + /// If set, loops will have code injected into them, forcing the compiler + /// to think the number of iterations is bounded. + pub force_loop_bounding: bool, } impl Default for Options { @@ -223,6 +224,7 @@ impl Default for Options { fake_missing_bindings: true, bounds_check_policies: index::BoundsCheckPolicies::default(), zero_initialize_workgroup_memory: true, + force_loop_bounding: true, } } } @@ -234,72 +236,96 @@ impl Default for Options { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] pub enum VertexFormat { + /// One unsigned byte (u8). `u32` in shaders. + Uint8 = 0, /// Two unsigned bytes (u8). `vec2` in shaders. - Uint8x2 = 0, + Uint8x2 = 1, /// Four unsigned bytes (u8). `vec4` in shaders. - Uint8x4 = 1, + Uint8x4 = 2, + /// One signed byte (i8). `i32` in shaders. + Sint8 = 3, /// Two signed bytes (i8). `vec2` in shaders. - Sint8x2 = 2, + Sint8x2 = 4, /// Four signed bytes (i8). `vec4` in shaders. - Sint8x4 = 3, + Sint8x4 = 5, + /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders. + Unorm8 = 6, /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2` in shaders. - Unorm8x2 = 4, + Unorm8x2 = 7, /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4` in shaders. - Unorm8x4 = 5, + Unorm8x4 = 8, + /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders. + Snorm8 = 9, /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2` in shaders. - Snorm8x2 = 6, + Snorm8x2 = 10, /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4` in shaders. - Snorm8x4 = 7, + Snorm8x4 = 11, + /// One unsigned short (u16). `u32` in shaders. + Uint16 = 12, /// Two unsigned shorts (u16). `vec2` in shaders. - Uint16x2 = 8, + Uint16x2 = 13, /// Four unsigned shorts (u16). `vec4` in shaders. - Uint16x4 = 9, + Uint16x4 = 14, + /// One signed short (u16). `i32` in shaders. + Sint16 = 15, /// Two signed shorts (i16). `vec2` in shaders. - Sint16x2 = 10, + Sint16x2 = 16, /// Four signed shorts (i16). `vec4` in shaders. - Sint16x4 = 11, + Sint16x4 = 17, + /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders. + Unorm16 = 18, /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2` in shaders. - Unorm16x2 = 12, + Unorm16x2 = 19, /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4` in shaders. - Unorm16x4 = 13, + Unorm16x4 = 20, + /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders. + Snorm16 = 21, /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2` in shaders. - Snorm16x2 = 14, + Snorm16x2 = 22, /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4` in shaders. - Snorm16x4 = 15, + Snorm16x4 = 23, + /// One half-precision float (no Rust equiv). `f32` in shaders. + Float16 = 24, /// Two half-precision floats (no Rust equiv). `vec2` in shaders. - Float16x2 = 16, + Float16x2 = 25, /// Four half-precision floats (no Rust equiv). `vec4` in shaders. - Float16x4 = 17, + Float16x4 = 26, /// One single-precision float (f32). `f32` in shaders. - Float32 = 18, + Float32 = 27, /// Two single-precision floats (f32). `vec2` in shaders. - Float32x2 = 19, + Float32x2 = 28, /// Three single-precision floats (f32). `vec3` in shaders. - Float32x3 = 20, + Float32x3 = 29, /// Four single-precision floats (f32). `vec4` in shaders. - Float32x4 = 21, + Float32x4 = 30, /// One unsigned int (u32). `u32` in shaders. - Uint32 = 22, + Uint32 = 31, /// Two unsigned ints (u32). `vec2` in shaders. - Uint32x2 = 23, + Uint32x2 = 32, /// Three unsigned ints (u32). `vec3` in shaders. - Uint32x3 = 24, + Uint32x3 = 33, /// Four unsigned ints (u32). `vec4` in shaders. - Uint32x4 = 25, + Uint32x4 = 34, /// One signed int (i32). `i32` in shaders. - Sint32 = 26, + Sint32 = 35, /// Two signed ints (i32). `vec2` in shaders. - Sint32x2 = 27, + Sint32x2 = 36, /// Three signed ints (i32). `vec3` in shaders. - Sint32x3 = 28, + Sint32x3 = 37, /// Four signed ints (i32). `vec4` in shaders. - Sint32x4 = 29, + Sint32x4 = 38, /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4` in shaders. #[cfg_attr( any(feature = "serialize", feature = "deserialize"), serde(rename = "unorm10-10-10-2") )] - Unorm10_10_10_2 = 34, + Unorm10_10_10_2 = 43, + /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4` in shaders. + #[cfg_attr( + any(feature = "serialize", feature = "deserialize"), + serde(rename = "unorm8x4-bgra") + )] + Unorm8x4Bgra = 44, } /// A mapping of vertex buffers and their attributes to shader diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs index 94abec7d1b..4fc589ea37 100644 --- a/naga/src/back/msl/writer.rs +++ b/naga/src/back/msl/writer.rs @@ -36,6 +36,14 @@ const RAY_QUERY_FUN_MAP_INTERSECTION: &str = "_map_intersection_type"; pub(crate) const ATOMIC_COMP_EXCH_FUNCTION: &str = "naga_atomic_compare_exchange_weak_explicit"; pub(crate) const MODF_FUNCTION: &str = "naga_modf"; pub(crate) const FREXP_FUNCTION: &str = "naga_frexp"; +/// For some reason, Metal does not let you have `metal::texture<..>*` as a buffer argument. +/// However, if you put that texture inside a struct, everything is totally fine. This +/// baffles me to no end. +/// +/// As such, we wrap all argument buffers in a struct that has a single generic `` field. +/// This allows `NagaArgumentBufferWrapper>*` to work. The astute among +/// you have noticed that this should be exactly the same to the compiler, and you're correct. +pub(crate) const ARGUMENT_BUFFER_WRAPPER_STRUCT: &str = "NagaArgumentBufferWrapper"; /// Write the Metal name for a Naga numeric type: scalar, vector, or matrix. /// @@ -275,24 +283,17 @@ impl Display for TypeContext<'_> { crate::TypeInner::RayQuery => { write!(out, "{RAY_QUERY_TYPE}") } - crate::TypeInner::BindingArray { base, size } => { + crate::TypeInner::BindingArray { base, .. } => { let base_tyname = Self { handle: base, first_time: false, ..*self }; - if let Some(&super::ResolvedBinding::Resource(super::BindTarget { - binding_array_size: Some(override_size), - .. - })) = self.binding - { - write!(out, "{NAMESPACE}::array<{base_tyname}, {override_size}>") - } else if let crate::ArraySize::Constant(size) = size { - write!(out, "{NAMESPACE}::array<{base_tyname}, {size}>") - } else { - unreachable!("metal requires all arrays be constant sized"); - } + write!( + out, + "constant {ARGUMENT_BUFFER_WRAPPER_STRUCT}<{base_tyname}>*" + ) } } } @@ -602,6 +603,8 @@ struct ExpressionContext<'a> { /// accesses. These may need to be cached in temporary variables. See /// `index::find_checked_indexes` for details. guarded_indices: HandleSet, + /// See [`Writer::emit_force_bounded_loop_macro`] for details. + force_loop_bounding: bool, } impl<'a> ExpressionContext<'a> { @@ -2156,6 +2159,7 @@ impl Writer { } } fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => { + write!(self.out, "(")?; if matches!(fun, Mf::Unpack4xU8) { write!(self.out, "u")?; } @@ -2167,7 +2171,7 @@ impl Writer { self.put_expression(arg, context, true)?; write!(self.out, " >> 16, ")?; self.put_expression(arg, context, true)?; - write!(self.out, " >> 24) << 24 >> 24")?; + write!(self.out, " >> 24) << 24 >> 24)")?; } Mf::QuantizeToF16 => { match *context.resolve_type(arg) { @@ -2573,6 +2577,8 @@ impl Writer { } => true, _ => false, }; + let accessing_wrapped_binding_array = + matches!(*base_ty, crate::TypeInner::BindingArray { .. }); self.put_access_chain(base, policy, context)?; if accessing_wrapped_array { @@ -2609,6 +2615,10 @@ impl Writer { write!(self.out, "]")?; + if accessing_wrapped_binding_array { + write!(self.out, ".{WRAPPED_ARRAY_FIELD}")?; + } + Ok(()) } @@ -3092,13 +3102,15 @@ impl Writer { writeln!(self.out, "{level}while(true) {{",)?; } self.put_block(level.next(), body, context)?; - self.emit_force_bounded_loop_macro()?; - writeln!( - self.out, - "{}{}", - level.next(), - self.force_bounded_loop_macro_name - )?; + if context.expression.force_loop_bounding { + self.emit_force_bounded_loop_macro()?; + writeln!( + self.out, + "{}{}", + level.next(), + self.force_bounded_loop_macro_name + )?; + } writeln!(self.out, "{level}}}")?; } crate::Statement::Break => { @@ -3735,7 +3747,18 @@ impl Writer { } fn write_type_defs(&mut self, module: &crate::Module) -> BackendResult { + let mut generated_argument_buffer_wrapper = false; for (handle, ty) in module.types.iter() { + if let crate::TypeInner::BindingArray { .. } = ty.inner { + if !generated_argument_buffer_wrapper { + writeln!(self.out, "template ")?; + writeln!(self.out, "struct {ARGUMENT_BUFFER_WRAPPER_STRUCT} {{")?; + writeln!(self.out, "{}T {WRAPPED_ARRAY_FIELD};", back::INDENT)?; + writeln!(self.out, "}};")?; + generated_argument_buffer_wrapper = true; + } + } + if !ty.needs_alias() { continue; } @@ -3864,17 +3887,17 @@ impl Writer { // Write functions to create special types. for (type_key, struct_ty) in module.special_types.predeclared_types.iter() { match type_key { - &crate::PredeclaredType::ModfResult { size, width } - | &crate::PredeclaredType::FrexpResult { size, width } => { + &crate::PredeclaredType::ModfResult { size, scalar } + | &crate::PredeclaredType::FrexpResult { size, scalar } => { let arg_type_name_owner; let arg_type_name = if let Some(size) = size { arg_type_name_owner = format!( "{NAMESPACE}::{}{}", - if width == 8 { "double" } else { "float" }, + if scalar.width == 8 { "double" } else { "float" }, size as u8 ); &arg_type_name_owner - } else if width == 8 { + } else if scalar.width == 8 { "double" } else { "float" @@ -4049,6 +4072,13 @@ template ) -> Result<(String, u32, u32), Error> { use back::msl::VertexFormat::*; match format { + Uint8 => { + let name = self.namer.call("unpackUint8"); + writeln!(self.out, "uint {name}(metal::uchar b0) {{")?; + writeln!(self.out, "{}return uint(b0);", back::INDENT)?; + writeln!(self.out, "}}")?; + Ok((name, 1, 1)) + } Uint8x2 => { let name = self.namer.call("unpackUint8x2"); writeln!( @@ -4077,6 +4107,13 @@ template writeln!(self.out, "}}")?; Ok((name, 4, 4)) } + Sint8 => { + let name = self.namer.call("unpackSint8"); + writeln!(self.out, "int {name}(metal::uchar b0) {{")?; + writeln!(self.out, "{}return int(as_type(b0));", back::INDENT)?; + writeln!(self.out, "}}")?; + Ok((name, 1, 1)) + } Sint8x2 => { let name = self.namer.call("unpackSint8x2"); writeln!( @@ -4113,6 +4150,17 @@ template writeln!(self.out, "}}")?; Ok((name, 4, 4)) } + Unorm8 => { + let name = self.namer.call("unpackUnorm8"); + writeln!(self.out, "float {name}(metal::uchar b0) {{")?; + writeln!( + self.out, + "{}return float(float(b0) / 255.0f);", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 1, 1)) + } Unorm8x2 => { let name = self.namer.call("unpackUnorm8x2"); writeln!( @@ -4149,6 +4197,17 @@ template writeln!(self.out, "}}")?; Ok((name, 4, 4)) } + Snorm8 => { + let name = self.namer.call("unpackSnorm8"); + writeln!(self.out, "float {name}(metal::uchar b0) {{")?; + writeln!( + self.out, + "{}return float(metal::max(-1.0f, as_type(b0) / 127.0f));", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 1, 1)) + } Snorm8x2 => { let name = self.namer.call("unpackSnorm8x2"); writeln!( @@ -4185,6 +4244,21 @@ template writeln!(self.out, "}}")?; Ok((name, 4, 4)) } + Uint16 => { + let name = self.namer.call("unpackUint16"); + writeln!( + self.out, + "metal::uint {name}(metal::uint b0, \ + metal::uint b1) {{" + )?; + writeln!( + self.out, + "{}return metal::uint(b1 << 8 | b0);", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 2, 1)) + } Uint16x2 => { let name = self.namer.call("unpackUint16x2"); writeln!( @@ -4227,6 +4301,21 @@ template writeln!(self.out, "}}")?; Ok((name, 8, 4)) } + Sint16 => { + let name = self.namer.call("unpackSint16"); + writeln!( + self.out, + "int {name}(metal::ushort b0, \ + metal::ushort b1) {{" + )?; + writeln!( + self.out, + "{}return int(as_type(metal::ushort(b1 << 8 | b0)));", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 2, 1)) + } Sint16x2 => { let name = self.namer.call("unpackSint16x2"); writeln!( @@ -4269,6 +4358,21 @@ template writeln!(self.out, "}}")?; Ok((name, 8, 4)) } + Unorm16 => { + let name = self.namer.call("unpackUnorm16"); + writeln!( + self.out, + "float {name}(metal::ushort b0, \ + metal::ushort b1) {{" + )?; + writeln!( + self.out, + "{}return float(float(b1 << 8 | b0) / 65535.0f);", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 2, 1)) + } Unorm16x2 => { let name = self.namer.call("unpackUnorm16x2"); writeln!( @@ -4311,6 +4415,21 @@ template writeln!(self.out, "}}")?; Ok((name, 8, 4)) } + Snorm16 => { + let name = self.namer.call("unpackSnorm16"); + writeln!( + self.out, + "float {name}(metal::ushort b0, \ + metal::ushort b1) {{" + )?; + writeln!( + self.out, + "{}return metal::unpack_snorm2x16_to_float(b1 << 8 | b0).x;", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 2, 1)) + } Snorm16x2 => { let name = self.namer.call("unpackSnorm16x2"); writeln!( @@ -4350,6 +4469,21 @@ template writeln!(self.out, "}}")?; Ok((name, 8, 4)) } + Float16 => { + let name = self.namer.call("unpackFloat16"); + writeln!( + self.out, + "float {name}(metal::ushort b0, \ + metal::ushort b1) {{" + )?; + writeln!( + self.out, + "{}return float(as_type(metal::ushort(b1 << 8 | b0)));", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 2, 1)) + } Float16x2 => { let name = self.namer.call("unpackFloat16x2"); writeln!( @@ -4714,6 +4848,26 @@ template writeln!(self.out, "}}")?; Ok((name, 4, 4)) } + Unorm8x4Bgra => { + let name = self.namer.call("unpackUnorm8x4Bgra"); + writeln!( + self.out, + "metal::float4 {name}(metal::uchar b0, \ + metal::uchar b1, \ + metal::uchar b2, \ + metal::uchar b3) {{" + )?; + writeln!( + self.out, + "{}return metal::float4(float(b2) / 255.0f, \ + float(b1) / 255.0f, \ + float(b0) / 255.0f, \ + float(b3) / 255.0f);", + back::INDENT + )?; + writeln!(self.out, "}}")?; + Ok((name, 4, 4)) + } } } @@ -4924,6 +5078,7 @@ template module, mod_info, pipeline_options, + force_loop_bounding: options.force_loop_bounding, }, result_struct: None, }; @@ -5034,13 +5189,10 @@ template let target = options.get_resource_binding_target(ep, br); let good = match target { Some(target) => { - let binding_ty = match module.types[var.ty].inner { - crate::TypeInner::BindingArray { base, .. } => { - &module.types[base].inner - } - ref ty => ty, - }; - match *binding_ty { + // We intentionally don't dereference binding_arrays here, + // so that binding arrays fall to the buffer location. + + match module.types[var.ty].inner { crate::TypeInner::Image { .. } => target.texture.is_some(), crate::TypeInner::Sampler { .. } => { target.sampler.is_some() @@ -5167,7 +5319,7 @@ template AttributeMappingResolved { ty_name: ty_name.to_string(), dimension: ty_name.vertex_input_dimension(), - ty_is_int: ty_name.scalar().map_or(false, scalar_is_int), + ty_is_int: ty_name.scalar().is_some_and(scalar_is_int), name: name.to_string(), }, ); @@ -5824,6 +5976,7 @@ template module, mod_info, pipeline_options, + force_loop_bounding: options.force_loop_bounding, }, result_struct: Some(&stage_out_name), }; diff --git a/naga/src/back/pipeline_constants.rs b/naga/src/back/pipeline_constants.rs index 7f5504352b..420b5acf4f 100644 --- a/naga/src/back/pipeline_constants.rs +++ b/naga/src/back/pipeline_constants.rs @@ -213,7 +213,7 @@ fn process_pending( adjusted_global_expressions: &HandleVec>, ) -> Result<(), PipelineConstantError> { for (handle, ty) in module.types.clone().iter() { - if let crate::TypeInner::Array { + if let TypeInner::Array { base, size: crate::ArraySize::Pending(size), stride, @@ -253,7 +253,7 @@ fn process_pending( handle, crate::Type { name: None, - inner: crate::TypeInner::Array { + inner: TypeInner::Array { base, size: crate::ArraySize::Constant(value), stride, diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs index 9dff4a6cc2..6b41a2c9e2 100644 --- a/naga/src/compact/mod.rs +++ b/naga/src/compact/mod.rs @@ -216,30 +216,6 @@ pub fn compact(module: &mut crate::Module) { } } - for (handle, ty) in module.types.clone().iter() { - if let crate::TypeInner::Array { - base, - size: crate::ArraySize::Pending(crate::PendingArraySize::Expression(mut size_expr)), - stride, - } = ty.inner - { - module_map.global_expressions.adjust(&mut size_expr); - module.types.replace( - handle, - crate::Type { - name: None, - inner: crate::TypeInner::Array { - base, - size: crate::ArraySize::Pending(crate::PendingArraySize::Expression( - size_expr, - )), - stride, - }, - }, - ); - } - } - // Temporary storage to help us reuse allocations of existing // named expression tables. let mut reused_named_expressions = crate::NamedExpressions::default(); diff --git a/naga/src/compact/types.rs b/naga/src/compact/types.rs index ec75e3ae2a..ae4ae35580 100644 --- a/naga/src/compact/types.rs +++ b/naga/src/compact/types.rs @@ -82,9 +82,17 @@ impl ModuleMap { } => adjust(base), Ti::Array { ref mut base, - size: _, + ref mut size, stride: _, - } => adjust(base), + } => { + adjust(base); + if let crate::ArraySize::Pending(crate::PendingArraySize::Expression( + ref mut size_expr, + )) = *size + { + self.global_expressions.adjust(size_expr); + } + } Ti::Struct { ref mut members, span: _, diff --git a/naga/src/front/atomic_upgrade.rs b/naga/src/front/atomic_upgrade.rs index 171df33169..529883ae41 100644 --- a/naga/src/front/atomic_upgrade.rs +++ b/naga/src/front/atomic_upgrade.rs @@ -20,8 +20,6 @@ //! //! Future work: //! -//! - Atomics in structs are not implemented yet. -//! //! - The GLSL front end could use this transformation as well. //! //! [`Atomic`]: TypeInner::Atomic @@ -40,8 +38,8 @@ use crate::{GlobalVariable, Handle, Module, Type, TypeInner}; pub enum Error { #[error("encountered an unsupported expression")] Unsupported, - #[error("upgrading structs of more than one member is not yet implemented")] - MultiMemberStruct, + #[error("unexpected end of struct field access indices")] + UnexpectedEndOfIndices, #[error("encountered unsupported global initializer in an atomic variable")] GlobalInitUnsupported, #[error("expected to find a global variable")] @@ -87,9 +85,50 @@ impl Padding { } } +#[derive(Debug, Default)] +pub struct Upgrades { + /// Global variables that we've accessed using atomic operations. + /// + /// This includes globals with composite types (arrays, structs) where we've + /// only accessed some components (elements, fields) atomically. + globals: crate::arena::HandleSet, + + /// Struct fields that we've accessed using atomic operations. + /// + /// Each key refers to some [`Struct`] type, and each value is a set of + /// the indices of the fields in that struct that have been accessed + /// atomically. + /// + /// This includes fields with composite types (arrays, structs) + /// of which we've only accessed some components (elements, fields) + /// atomically. + /// + /// [`Struct`]: crate::TypeInner::Struct + fields: crate::FastHashMap, bit_set::BitSet>, +} + +impl Upgrades { + pub fn insert_global(&mut self, global: Handle) { + self.globals.insert(global); + } + + pub fn insert_field(&mut self, struct_type: Handle, field: usize) { + self.fields.entry(struct_type).or_default().insert(field); + } + + pub fn is_empty(&self) -> bool { + self.globals.is_empty() + } +} + struct UpgradeState<'a> { padding: Padding, module: &'a mut Module, + + /// A map from old types to their upgraded versions. + /// + /// This ensures we never try to rebuild a type more than once. + upgraded_types: crate::FastHashMap, Handle>, } impl UpgradeState<'_> { @@ -97,11 +136,38 @@ impl UpgradeState<'_> { self.padding.inc_padding() } - /// Upgrade the type, recursing until we reach the leaves. - /// At the leaves, replace scalars with atomic scalars. - fn upgrade_type(&mut self, ty: Handle) -> Result, Error> { + /// Get a type equivalent to `ty`, but with [`Scalar`] leaves upgraded to [`Atomic`] scalars. + /// + /// If such a type already exists in `self.module.types`, return its handle. + /// Otherwise, construct a new one and return that handle. + /// + /// If `ty` is a [`Pointer`], [`Array`], [`BindingArray`], recurse into the + /// type and upgrade its leaf types. + /// + /// If `ty` is a [`Struct`], recurse into it and upgrade only those fields + /// whose indices appear in `field_indices`. + /// + /// The existing type is not affected. + /// + /// [`Scalar`]: crate::TypeInner::Scalar + /// [`Atomic`]: crate::TypeInner::Atomic + /// [`Pointer`]: crate::TypeInner::Pointer + /// [`Array`]: crate::TypeInner::Array + /// [`Struct`]: crate::TypeInner::Struct + /// [`BindingArray`]: crate::TypeInner::BindingArray + fn upgrade_type( + &mut self, + ty: Handle, + upgrades: &Upgrades, + ) -> Result, Error> { let padding = self.inc_padding(); - padding.trace("upgrading type: ", ty); + padding.trace("visiting type: ", ty); + + // If we've already upgraded this type, return the handle we produced at + // the time. + if let Some(&new) = self.upgraded_types.get(&ty) { + return Ok(new); + } let inner = match self.module.types[ty].inner { TypeInner::Scalar(scalar) => { @@ -109,52 +175,41 @@ impl UpgradeState<'_> { TypeInner::Atomic(scalar) } TypeInner::Pointer { base, space } => TypeInner::Pointer { - base: self.upgrade_type(base)?, + base: self.upgrade_type(base, upgrades)?, space, }, TypeInner::Array { base, size, stride } => TypeInner::Array { - base: self.upgrade_type(base)?, + base: self.upgrade_type(base, upgrades)?, size, stride, }, TypeInner::Struct { ref members, span } => { - // In the future we should have to figure out which member needs - // upgrading, but for now we'll only cover the single-member - // case. - let &[crate::StructMember { - ref name, - ty, - ref binding, - offset, - }] = &members[..] - else { - return Err(Error::MultiMemberStruct); + // If no field or subfield of this struct was ever accessed + // atomically, no change is needed. We should never have arrived here. + let Some(fields) = upgrades.fields.get(&ty) else { + unreachable!("global or field incorrectly flagged as atomically accessed"); }; - // Take our own clones of these values now, so that - // `upgrade_type` can mutate the module. - let name = name.clone(); - let binding = binding.clone(); - let upgraded_member_type = self.upgrade_type(ty)?; + let mut new_members = members.clone(); + for field in fields { + new_members[field].ty = self.upgrade_type(new_members[field].ty, upgrades)?; + } + TypeInner::Struct { - members: vec![crate::StructMember { - name, - ty: upgraded_member_type, - binding, - offset, - }], + members: new_members, span, } } TypeInner::BindingArray { base, size } => TypeInner::BindingArray { - base: self.upgrade_type(base)?, + base: self.upgrade_type(base, upgrades)?, size, }, _ => return Ok(ty), }; - // Now that we've upgraded any subtypes, re-borrow a reference to our - // type and update its `inner`. + // At this point, we have a `TypeInner` that is the upgraded version of + // `ty`. Find a suitable `Type` for this, creating a new one if + // necessary, and return its handle. let r#type = &self.module.types[ty]; let span = self.module.types.get_span(ty); let new_type = Type { @@ -165,27 +220,32 @@ impl UpgradeState<'_> { padding.debug("from: ", r#type); padding.debug("to: ", &new_type); let new_handle = self.module.types.insert(new_type, span); + self.upgraded_types.insert(ty, new_handle); Ok(new_handle) } - fn upgrade_global_variable(&mut self, handle: Handle) -> Result<(), Error> { - let padding = self.inc_padding(); - padding.trace("upgrading global variable: ", handle); + fn upgrade_all(&mut self, upgrades: &Upgrades) -> Result<(), Error> { + for handle in upgrades.globals.iter() { + let padding = self.inc_padding(); - let var = &self.module.global_variables[handle]; + let global = &self.module.global_variables[handle]; + padding.trace("visiting global variable: ", handle); + padding.trace("var: ", global); - if var.init.is_some() { - return Err(Error::GlobalInitUnsupported); - } + if global.init.is_some() { + return Err(Error::GlobalInitUnsupported); + } - let var_ty = var.ty; - let new_ty = self.upgrade_type(var.ty)?; - if new_ty != var_ty { - padding.debug("upgrading global variable: ", handle); - padding.debug("from ty: ", var_ty); - padding.debug("to ty: ", new_ty); - self.module.global_variables[handle].ty = new_ty; + let var_ty = global.ty; + let new_ty = self.upgrade_type(var_ty, upgrades)?; + if new_ty != var_ty { + padding.debug("upgrading global variable: ", handle); + padding.debug("from ty: ", var_ty); + padding.debug("to ty: ", new_ty); + self.module.global_variables[handle].ty = new_ty; + } } + Ok(()) } } @@ -194,18 +254,17 @@ impl Module { /// Upgrade `global_var_handles` to have [`Atomic`] leaf types. /// /// [`Atomic`]: TypeInner::Atomic - pub(crate) fn upgrade_atomics( - &mut self, - global_var_handles: impl IntoIterator>, - ) -> Result<(), Error> { + pub(crate) fn upgrade_atomics(&mut self, upgrades: &Upgrades) -> Result<(), Error> { let mut state = UpgradeState { padding: Default::default(), module: self, + upgraded_types: crate::FastHashMap::with_capacity_and_hasher( + upgrades.fields.len(), + Default::default(), + ), }; - for handle in global_var_handles { - state.upgrade_global_variable(handle)?; - } + state.upgrade_all(upgrades)?; Ok(()) } diff --git a/naga/src/front/glsl/error.rs b/naga/src/front/glsl/error.rs index e25b29966f..92962db00d 100644 --- a/naga/src/front/glsl/error.rs +++ b/naga/src/front/glsl/error.rs @@ -75,7 +75,7 @@ pub enum ErrorKind { /// Whilst parsing an unexpected token was encountered. /// /// A list of expected tokens is also returned. - #[error("Expected {}, found {0:?}", join_with_comma(.1))] + #[error("Expected {expected_tokens}, found {found_token:?}", found_token = .0, expected_tokens = join_with_comma(.1))] InvalidToken(TokenValue, Vec), /// A specific feature is not yet implemented. /// diff --git a/naga/src/front/glsl/functions.rs b/naga/src/front/glsl/functions.rs index 394be22eaa..0d05c5433c 100644 --- a/naga/src/front/glsl/functions.rs +++ b/naga/src/front/glsl/functions.rs @@ -140,7 +140,7 @@ impl Frontend { )? } TypeInner::Vector { size, scalar } => { - if vector_size.map_or(true, |s| s != size) { + if vector_size != Some(size) { value = ctx.vector_resize(size, value, expr_meta)?; } diff --git a/naga/src/front/glsl/parser/declarations.rs b/naga/src/front/glsl/parser/declarations.rs index 225695cf89..1c5c151b5b 100644 --- a/naga/src/front/glsl/parser/declarations.rs +++ b/naga/src/front/glsl/parser/declarations.rs @@ -186,7 +186,7 @@ impl ParsingContext<'_> { // Consume any leading comma, e.g. this is valid: `float, a=1;` if self .peek(frontend) - .map_or(false, |t| t.value == TokenValue::Comma) + .is_some_and(|t| t.value == TokenValue::Comma) { self.next(frontend); } diff --git a/naga/src/front/glsl/parser/functions.rs b/naga/src/front/glsl/parser/functions.rs index da0c8af3c4..e9028c419b 100644 --- a/naga/src/front/glsl/parser/functions.rs +++ b/naga/src/front/glsl/parser/functions.rs @@ -15,7 +15,7 @@ use crate::{ impl ParsingContext<'_> { pub fn peek_parameter_qualifier(&mut self, frontend: &mut Frontend) -> bool { - self.peek(frontend).map_or(false, |t| match t.value { + self.peek(frontend).is_some_and(|t| match t.value { TokenValue::In | TokenValue::Out | TokenValue::InOut | TokenValue::Const => true, _ => false, }) diff --git a/naga/src/front/glsl/parser/types.rs b/naga/src/front/glsl/parser/types.rs index c7d61222f8..6952b0260a 100644 --- a/naga/src/front/glsl/parser/types.rs +++ b/naga/src/front/glsl/parser/types.rs @@ -147,7 +147,7 @@ impl ParsingContext<'_> { } pub fn peek_type_qualifier(&mut self, frontend: &mut Frontend) -> bool { - self.peek(frontend).map_or(false, |t| match t.value { + self.peek(frontend).is_some_and(|t| match t.value { TokenValue::Invariant | TokenValue::Interpolation(_) | TokenValue::Sampling(_) @@ -371,7 +371,7 @@ impl ParsingContext<'_> { } pub fn peek_type_name(&mut self, frontend: &mut Frontend) -> bool { - self.peek(frontend).map_or(false, |t| match t.value { + self.peek(frontend).is_some_and(|t| match t.value { TokenValue::TypeName(_) | TokenValue::Void => true, TokenValue::Struct => true, TokenValue::Identifier(ref ident) => frontend.lookup_type.contains_key(ident), diff --git a/naga/src/front/spv/mod.rs b/naga/src/front/spv/mod.rs index d028c51a25..2f1e5c8ed7 100644 --- a/naga/src/front/spv/mod.rs +++ b/naga/src/front/spv/mod.rs @@ -36,7 +36,6 @@ mod null; use convert::*; pub use error::Error; use function::*; -use indexmap::IndexSet; use crate::{ arena::{Arena, Handle, UniqueArena}, @@ -47,6 +46,8 @@ use crate::{ use petgraph::graphmap::GraphMap; use std::{convert::TryInto, mem, num::NonZeroU32, path::PathBuf}; +use super::atomic_upgrade::Upgrades; + pub const SUPPORTED_CAPABILITIES: &[spirv::Capability] = &[ spirv::Capability::Shader, spirv::Capability::VulkanMemoryModel, @@ -557,45 +558,6 @@ struct BlockContext<'function> { parameter_sampling: &'function mut [image::SamplingFlags], } -impl BlockContext<'_> { - /// Descend into the expression with the given handle, locating a contained - /// global variable. - /// - /// If the expression doesn't actually refer to something in a global - /// variable, we can't upgrade its type in a way that Naga validation would - /// pass, so reject the input instead. - /// - /// This is used to track atomic upgrades. - fn get_contained_global_variable( - &self, - mut handle: Handle, - ) -> Result, Error> { - log::debug!("\t\tlocating global variable in {handle:?}"); - loop { - match self.expressions[handle] { - crate::Expression::Access { base, index: _ } => { - handle = base; - log::debug!("\t\t access {handle:?}"); - } - crate::Expression::AccessIndex { base, index: _ } => { - handle = base; - log::debug!("\t\t access index {handle:?}"); - } - crate::Expression::GlobalVariable(h) => { - log::debug!("\t\t found {h:?}"); - return Ok(h); - } - _ => { - break; - } - } - } - Err(Error::AtomicUpgradeError( - crate::front::atomic_upgrade::Error::GlobalVariableMissing, - )) - } -} - enum SignAnchor { Result, Operand, @@ -612,11 +574,12 @@ pub struct Frontend { future_member_decor: FastHashMap<(spirv::Word, MemberIndex), Decoration>, lookup_member: FastHashMap<(Handle, MemberIndex), LookupMember>, handle_sampling: FastHashMap, image::SamplingFlags>, - /// The set of all global variables accessed by [`Atomic`] statements we've + + /// A record of what is accessed by [`Atomic`] statements we've /// generated, so we can upgrade the types of their operands. /// /// [`Atomic`]: crate::Statement::Atomic - upgrade_atomics: IndexSet>, + upgrade_atomics: Upgrades, lookup_type: FastHashMap, lookup_void_type: Option, @@ -1481,8 +1444,7 @@ impl> Frontend { block.push(stmt, span); // Store any associated global variables so we can upgrade their types later - self.upgrade_atomics - .insert(ctx.get_contained_global_variable(p_lexp_handle)?); + self.record_atomic_access(ctx, p_lexp_handle)?; Ok(()) } @@ -4178,8 +4140,7 @@ impl> Frontend { ); // Store any associated global variables so we can upgrade their types later - self.upgrade_atomics - .insert(ctx.get_contained_global_variable(p_lexp_handle)?); + self.record_atomic_access(ctx, p_lexp_handle)?; } Op::AtomicStore => { inst.expect(5)?; @@ -4208,8 +4169,7 @@ impl> Frontend { emitter.start(ctx.expressions); // Store any associated global variables so we can upgrade their types later - self.upgrade_atomics - .insert(ctx.get_contained_global_variable(p_lexp_handle)?); + self.record_atomic_access(ctx, p_lexp_handle)?; } Op::AtomicIIncrement | Op::AtomicIDecrement => { inst.expect(6)?; @@ -4273,8 +4233,7 @@ impl> Frontend { block.push(stmt, span); // Store any associated global variables so we can upgrade their types later - self.upgrade_atomics - .insert(ctx.get_contained_global_variable(p_exp_h)?); + self.record_atomic_access(ctx, p_exp_h)?; } Op::AtomicCompareExchange => { inst.expect(9)?; @@ -4369,8 +4328,7 @@ impl> Frontend { block.push(stmt, span); // Store any associated global variables so we can upgrade their types later - self.upgrade_atomics - .insert(ctx.get_contained_global_variable(p_exp_h)?); + self.record_atomic_access(ctx, p_exp_h)?; } Op::AtomicExchange | Op::AtomicIAdd @@ -4686,7 +4644,7 @@ impl> Frontend { if !self.upgrade_atomics.is_empty() { log::info!("Upgrading atomic pointers..."); - module.upgrade_atomics(mem::take(&mut self.upgrade_atomics))?; + module.upgrade_atomics(&self.upgrade_atomics)?; } // Do entry point specific processing after all functions are parsed so that we can @@ -5372,7 +5330,7 @@ impl> Frontend { let parent_decor = self.future_decor.remove(&id); let is_storage_buffer = parent_decor .as_ref() - .map_or(false, |decor| decor.storage_buffer); + .is_some_and(|decor| decor.storage_buffer); self.layouter.update(module.to_ctx()).unwrap(); @@ -5981,6 +5939,59 @@ impl> Frontend { ); Ok(()) } + + /// Record an atomic access to some component of a global variable. + /// + /// Given `handle`, an expression referring to a scalar that has had an + /// atomic operation applied to it, descend into the expression, noting + /// which global variable it ultimately refers to, and which struct fields + /// of that global's value it accesses. + /// + /// Return the handle of the type of the expression. + /// + /// If the expression doesn't actually refer to something in a global + /// variable, we can't upgrade its type in a way that Naga validation would + /// pass, so reject the input instead. + fn record_atomic_access( + &mut self, + ctx: &BlockContext, + handle: Handle, + ) -> Result, Error> { + log::debug!("\t\tlocating global variable in {handle:?}"); + match ctx.expressions[handle] { + crate::Expression::Access { base, index } => { + log::debug!("\t\t access {handle:?} {index:?}"); + let ty = self.record_atomic_access(ctx, base)?; + let crate::TypeInner::Array { base, .. } = ctx.module.types[ty].inner else { + unreachable!("Atomic operations on Access expressions only work for arrays"); + }; + Ok(base) + } + crate::Expression::AccessIndex { base, index } => { + log::debug!("\t\t access index {handle:?} {index:?}"); + let ty = self.record_atomic_access(ctx, base)?; + match ctx.module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => { + let index = index as usize; + self.upgrade_atomics.insert_field(ty, index); + Ok(members[index].ty) + } + crate::TypeInner::Array { base, .. } => { + Ok(base) + } + _ => unreachable!("Atomic operations on AccessIndex expressions only work for structs and arrays"), + } + } + crate::Expression::GlobalVariable(h) => { + log::debug!("\t\t found {h:?}"); + self.upgrade_atomics.insert_global(h); + Ok(ctx.module.global_variables[h].ty) + } + _ => Err(Error::AtomicUpgradeError( + crate::front::atomic_upgrade::Error::GlobalVariableMissing, + )), + } + } } fn make_index_literal( diff --git a/naga/src/front/type_gen.rs b/naga/src/front/type_gen.rs index 1cd9f7f378..737c456bbd 100644 --- a/naga/src/front/type_gen.rs +++ b/naga/src/front/type_gen.rs @@ -298,11 +298,11 @@ impl crate::Module { }, } } - crate::PredeclaredType::ModfResult { size, width } => { + crate::PredeclaredType::ModfResult { size, scalar } => { let float_ty = self.types.insert( crate::Type { name: None, - inner: crate::TypeInner::Scalar(crate::Scalar::float(width)), + inner: crate::TypeInner::Scalar(scalar), }, Span::UNDEFINED, ); @@ -311,23 +311,20 @@ impl crate::Module { let vec_ty = self.types.insert( crate::Type { name: None, - inner: crate::TypeInner::Vector { - size, - scalar: crate::Scalar::float(width), - }, + inner: crate::TypeInner::Vector { size, scalar }, }, Span::UNDEFINED, ); - (vec_ty, size as u32 * width as u32) + (vec_ty, size as u32 * scalar.width as u32) } else { - (float_ty, width as u32) + (float_ty, scalar.width as u32) }; let mut type_name = "__modf_result_".to_string(); if let Some(size) = size { let _ = write!(type_name, "vec{}_", size as u8); } - let _ = write!(type_name, "f{}", width * 8); + let _ = write!(type_name, "f{}", scalar.width * 8); crate::Type { name: Some(type_name), @@ -350,11 +347,11 @@ impl crate::Module { }, } } - crate::PredeclaredType::FrexpResult { size, width } => { + crate::PredeclaredType::FrexpResult { size, scalar } => { let float_ty = self.types.insert( crate::Type { name: None, - inner: crate::TypeInner::Scalar(crate::Scalar::float(width)), + inner: crate::TypeInner::Scalar(scalar), }, Span::UNDEFINED, ); @@ -364,7 +361,7 @@ impl crate::Module { name: None, inner: crate::TypeInner::Scalar(crate::Scalar { kind: crate::ScalarKind::Sint, - width, + width: scalar.width, }), }, Span::UNDEFINED, @@ -374,10 +371,7 @@ impl crate::Module { let vec_float_ty = self.types.insert( crate::Type { name: None, - inner: crate::TypeInner::Vector { - size, - scalar: crate::Scalar::float(width), - }, + inner: crate::TypeInner::Vector { size, scalar }, }, Span::UNDEFINED, ); @@ -388,22 +382,22 @@ impl crate::Module { size, scalar: crate::Scalar { kind: crate::ScalarKind::Sint, - width, + width: scalar.width, }, }, }, Span::UNDEFINED, ); - (vec_float_ty, vec_int_ty, size as u32 * width as u32) + (vec_float_ty, vec_int_ty, size as u32 * scalar.width as u32) } else { - (float_ty, int_ty, width as u32) + (float_ty, int_ty, scalar.width as u32) }; let mut type_name = "__frexp_result_".to_string(); if let Some(size) = size { let _ = write!(type_name, "vec{}_", size as u8); } - let _ = write!(type_name, "f{}", width * 8); + let _ = write!(type_name, "f{}", scalar.width * 8); crate::Type { name: Some(type_name), diff --git a/naga/src/front/wgsl/diagnostic_filter.rs b/naga/src/front/wgsl/diagnostic_filter.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index edd13313e5..28b908261a 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -303,7 +303,7 @@ pub(crate) enum Error<'a> { spans: Vec, }, DiagnosticAttributeNotSupported { - on_what_plural: &'static str, + on_what: DiagnosticAttributeNotSupportedPosition, spans: Vec, }, } @@ -314,6 +314,19 @@ impl From for Error<'_> { } } +/// Used for diagnostic refinement in [`Error::DiagnosticAttributeNotSupported`]. +#[derive(Clone, Copy, Debug)] +pub(crate) enum DiagnosticAttributeNotSupportedPosition { + SemicolonInModulePosition, + Other { display_plural: &'static str }, +} + +impl From<&'static str> for DiagnosticAttributeNotSupportedPosition { + fn from(display_plural: &'static str) -> Self { + Self::Other { display_plural } + } +} + #[derive(Clone, Debug)] pub(crate) struct AutoConversionError { pub dest_span: Span, @@ -1080,27 +1093,55 @@ impl<'a> Error<'a> { "so they can prioritize it!" ))], }, - Error::DiagnosticAttributeNotSupported { - on_what_plural, - ref spans, - } => ParseError { - message: format!( - "`@diagnostic(…)` attribute(s) on {on_what_plural} are not supported", - ), - labels: spans - .iter() - .cloned() - .map(|span| (span, "".into())) - .collect(), - notes: vec![ - concat!( - "`@diagnostic(…)` attributes are only permitted on `fn`s, ", - "some statements, and `switch`/`loop` bodies." - ) - .into(), - "These attributes are well-formed, you likely just need to move them.".into(), - ], - }, + Error::DiagnosticAttributeNotSupported { on_what, ref spans } => { + // In this case the user may have intended to create a global diagnostic filter directive, + // so display a note to them suggesting the correct syntax. + let intended_diagnostic_directive = match on_what { + DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition => true, + DiagnosticAttributeNotSupportedPosition::Other { .. } => false, + }; + let on_what_plural = match on_what { + DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition => { + "semicolons" + } + DiagnosticAttributeNotSupportedPosition::Other { display_plural } => { + display_plural + } + }; + ParseError { + message: format!( + "`@diagnostic(…)` attribute(s) on {on_what_plural} are not supported", + ), + labels: spans + .iter() + .cloned() + .map(|span| (span, "".into())) + .collect(), + notes: vec![ + concat!( + "`@diagnostic(…)` attributes are only permitted on `fn`s, ", + "some statements, and `switch`/`loop` bodies." + ) + .into(), + { + if intended_diagnostic_directive { + concat!( + "If you meant to declare a diagnostic filter that ", + "applies to the entire module, move this line to ", + "the top of the file and remove the `@` symbol." + ) + .into() + } else { + concat!( + "These attributes are well-formed, ", + "you likely just need to move them." + ) + .into() + } + }, + ], + } + } } } } diff --git a/naga/src/front/wgsl/lower/conversion.rs b/naga/src/front/wgsl/lower/conversion.rs index 37bb696f03..b7a649fd19 100644 --- a/naga/src/front/wgsl/lower/conversion.rs +++ b/naga/src/front/wgsl/lower/conversion.rs @@ -93,7 +93,7 @@ impl<'source> super::ExpressionContext<'source, '_, '_> { })) }; - let expr_scalar = match expr_inner.scalar() { + let expr_scalar = match expr_inner.automatically_convertible_scalar(&self.module.types) { Some(scalar) => scalar, None => return Err(make_error()), }; @@ -436,6 +436,32 @@ impl crate::TypeInner { | Ti::BindingArray { .. } => None, } } + + /// Return the leaf scalar type of `pointer`. + /// + /// `pointer` must be a `TypeInner` representing a pointer type. + pub fn pointer_automatically_convertible_scalar( + &self, + types: &crate::UniqueArena, + ) -> Option { + use crate::TypeInner as Ti; + match *self { + Ti::Scalar(scalar) | Ti::Vector { scalar, .. } | Ti::Matrix { scalar, .. } => { + Some(scalar) + } + Ti::Atomic(_) => None, + Ti::Pointer { base, .. } | Ti::Array { base, .. } => { + types[base].inner.automatically_convertible_scalar(types) + } + Ti::ValuePointer { scalar, .. } => Some(scalar), + Ti::Struct { .. } + | Ti::Image { .. } + | Ti::Sampler { .. } + | Ti::AccelerationStructure + | Ti::RayQuery + | Ti::BindingArray { .. } => None, + } + } } impl crate::Scalar { diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index f15bb1d68a..16d91c8b66 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1700,31 +1700,48 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { } => { let mut emitter = Emitter::default(); emitter.start(&ctx.function.expressions); + let target_span = ctx.ast_expressions.get_span(ast_target); - let target = self.expression_for_reference( - ast_target, - &mut ctx.as_expression(block, &mut emitter), - )?; - let mut value = - self.expression(value, &mut ctx.as_expression(block, &mut emitter))?; - + let mut ectx = ctx.as_expression(block, &mut emitter); + let target = self.expression_for_reference(ast_target, &mut ectx)?; let target_handle = match target { Typed::Reference(handle) => handle, Typed::Plain(handle) => { let ty = ctx.invalid_assignment_type(handle); return Err(Error::InvalidAssignment { - span: ctx.ast_expressions.get_span(ast_target), + span: target_span, ty, }); } }; + // Usually the value needs to be converted to match the type of + // the memory view you're assigning it to. The bit shift + // operators are exceptions, in that the right operand is always + // a `u32` or `vecN`. + let target_scalar = match op { + Some(crate::BinaryOperator::ShiftLeft | crate::BinaryOperator::ShiftRight) => { + Some(crate::Scalar::U32) + } + _ => resolve_inner!(ectx, target_handle) + .pointer_automatically_convertible_scalar(&ectx.module.types), + }; + + let value = self.expression_for_abstract(value, &mut ectx)?; + let mut value = match target_scalar { + Some(target_scalar) => ectx.try_automatic_conversion_for_leaf_scalar( + value, + target_scalar, + target_span, + )?, + None => value, + }; + let value = match op { Some(op) => { - let mut ctx = ctx.as_expression(block, &mut emitter); - let mut left = ctx.apply_load_rule(target)?; - ctx.binary_op_splat(op, &mut left, &mut value)?; - ctx.append_expression( + let mut left = ectx.apply_load_rule(target)?; + ectx.binary_op_splat(op, &mut left, &mut value)?; + ectx.append_expression( crate::Expression::Binary { op, left, @@ -2043,11 +2060,9 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { lowered_base.map(|base| crate::Expression::AccessIndex { base, index }) } - crate::TypeInner::Vector { .. } | crate::TypeInner::Matrix { .. } => { + crate::TypeInner::Vector { .. } => { match Components::new(field.name, field.span)? { Components::Swizzle { size, pattern } => { - // Swizzles aren't allowed on matrices, but - // validation will catch that. Typed::Plain(crate::Expression::Swizzle { size, vector: ctx.apply_load_rule(lowered_base)?, @@ -2287,22 +2302,18 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { args.finish()?; if fun == crate::MathFunction::Modf || fun == crate::MathFunction::Frexp { - if let Some((size, width)) = match *resolve_inner!(ctx, arg) { - crate::TypeInner::Scalar(crate::Scalar { width, .. }) => { - Some((None, width)) + if let Some((size, scalar)) = match *resolve_inner!(ctx, arg) { + crate::TypeInner::Scalar(scalar) => Some((None, scalar)), + crate::TypeInner::Vector { size, scalar, .. } => { + Some((Some(size), scalar)) } - crate::TypeInner::Vector { - size, - scalar: crate::Scalar { width, .. }, - .. - } => Some((Some(size), width)), _ => None, } { ctx.module.generate_predeclared_type( if fun == crate::MathFunction::Modf { - crate::PredeclaredType::ModfResult { size, width } + crate::PredeclaredType::ModfResult { size, scalar } } else { - crate::PredeclaredType::FrexpResult { size, width } + crate::PredeclaredType::FrexpResult { size, scalar } }, ); } diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 0233347c36..219bba551f 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -2,7 +2,7 @@ use crate::diagnostic_filter::{ self, DiagnosticFilter, DiagnosticFilterMap, DiagnosticFilterNode, FilterableTriggeringRule, ShouldConflictOnFullDuplicate, StandardFilterableTriggeringRule, }; -use crate::front::wgsl::error::{Error, ExpectedToken}; +use crate::front::wgsl::error::{DiagnosticAttributeNotSupportedPosition, Error, ExpectedToken}; use crate::front::wgsl::parse::directive::enable_extension::{ EnableExtension, EnableExtensions, UnimplementedEnableExtension, }; @@ -2465,17 +2465,16 @@ impl Parser { unresolved: &mut dependencies, }; let mut diagnostic_filters = DiagnosticFilterMap::new(); - let ensure_no_diag_attrs = - |on_what_plural, filters: DiagnosticFilterMap| -> Result<(), Error> { - if filters.is_empty() { - Ok(()) - } else { - Err(Error::DiagnosticAttributeNotSupported { - on_what_plural, - spans: filters.spans().collect(), - }) - } - }; + let ensure_no_diag_attrs = |on_what, filters: DiagnosticFilterMap| -> Result<(), Error> { + if filters.is_empty() { + Ok(()) + } else { + Err(Error::DiagnosticAttributeNotSupported { + on_what, + spans: filters.spans().collect(), + }) + } + }; self.push_rule_span(Rule::Attribute, lexer); while lexer.skip(Token::Attribute) { @@ -2562,14 +2561,17 @@ impl Parser { let start = lexer.start_byte_offset(); let kind = match lexer.next() { (Token::Separator(';'), _) => { - ensure_no_diag_attrs("semicolons", diagnostic_filters)?; + ensure_no_diag_attrs( + DiagnosticAttributeNotSupportedPosition::SemicolonInModulePosition, + diagnostic_filters, + )?; None } (Token::Word(word), directive_span) if DirectiveKind::from_ident(word).is_some() => { return Err(Error::DirectiveAfterFirstGlobalDecl { directive_span }); } (Token::Word("struct"), _) => { - ensure_no_diag_attrs("`struct`s", diagnostic_filters)?; + ensure_no_diag_attrs("`struct`s".into(), diagnostic_filters)?; let name = lexer.next_ident()?; @@ -2577,7 +2579,7 @@ impl Parser { Some(ast::GlobalDeclKind::Struct(ast::Struct { name, members })) } (Token::Word("alias"), _) => { - ensure_no_diag_attrs("`alias`es", diagnostic_filters)?; + ensure_no_diag_attrs("`alias`es".into(), diagnostic_filters)?; let name = lexer.next_ident()?; @@ -2587,7 +2589,7 @@ impl Parser { Some(ast::GlobalDeclKind::Type(ast::TypeAlias { name, ty })) } (Token::Word("const"), _) => { - ensure_no_diag_attrs("`const`s", diagnostic_filters)?; + ensure_no_diag_attrs("`const`s".into(), diagnostic_filters)?; let name = lexer.next_ident()?; @@ -2605,7 +2607,7 @@ impl Parser { Some(ast::GlobalDeclKind::Const(ast::Const { name, ty, init })) } (Token::Word("override"), _) => { - ensure_no_diag_attrs("`override`s", diagnostic_filters)?; + ensure_no_diag_attrs("`override`s".into(), diagnostic_filters)?; let name = lexer.next_ident()?; @@ -2631,7 +2633,7 @@ impl Parser { })) } (Token::Word("var"), _) => { - ensure_no_diag_attrs("`var`s", diagnostic_filters)?; + ensure_no_diag_attrs("`var`s".into(), diagnostic_filters)?; let mut var = self.variable_decl(lexer, &mut ctx)?; var.binding = binding.take(); @@ -2662,7 +2664,7 @@ impl Parser { })) } (Token::Word("const_assert"), _) => { - ensure_no_diag_attrs("`const_assert`s", diagnostic_filters)?; + ensure_no_diag_attrs("`const_assert`s".into(), diagnostic_filters)?; // parentheses are optional let paren = lexer.skip(Token::Paren('(')); diff --git a/naga/src/front/wgsl/tests.rs b/naga/src/front/wgsl/tests.rs index 3ae006f9d4..54d931efe2 100644 --- a/naga/src/front/wgsl/tests.rs +++ b/naga/src/front/wgsl/tests.rs @@ -399,6 +399,14 @@ fn parse_postfix() { }", ) .unwrap(); + + let err = parse_str( + "fn foo() { + let v = mat4x4().x; + }", + ) + .unwrap_err(); + assert_eq!(err.message(), "invalid field accessor `x`"); } #[test] @@ -660,6 +668,27 @@ fn parse_missing_workgroup_size() { } mod diagnostic_filter { + use crate::front::wgsl::assert_parse_err; + + #[test] + fn intended_global_directive() { + let shader = "@diagnostic(off, my.lint);"; + assert_parse_err( + shader, + "\ +error: `@diagnostic(…)` attribute(s) on semicolons are not supported + ┌─ wgsl:1:1 + │ +1 │ @diagnostic(off, my.lint); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + = note: `@diagnostic(…)` attributes are only permitted on `fn`s, some statements, and `switch`/`loop` bodies. + = note: If you meant to declare a diagnostic filter that applies to the entire module, move this line to the top of the file and remove the `@` symbol. + +" + ); + } + mod parse_sites_not_yet_supported { use crate::front::wgsl::assert_parse_err; diff --git a/naga/src/lib.rs b/naga/src/lib.rs index b9d6966b1d..5610157c5a 100644 --- a/naga/src/lib.rs +++ b/naga/src/lib.rs @@ -2265,11 +2265,11 @@ pub enum PredeclaredType { AtomicCompareExchangeWeakResult(Scalar), ModfResult { size: Option, - width: Bytes, + scalar: Scalar, }, FrexpResult { size: Option, - width: Bytes, + scalar: Scalar, }, } diff --git a/naga/src/proc/constant_evaluator.rs b/naga/src/proc/constant_evaluator.rs index 2baf918118..42ba489790 100644 --- a/naga/src/proc/constant_evaluator.rs +++ b/naga/src/proc/constant_evaluator.rs @@ -1808,29 +1808,23 @@ impl<'a> ConstantEvaluator<'a> { _ => match (left_value, right_value) { (Literal::I32(a), Literal::I32(b)) => Literal::I32(match op { - BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { - ConstantEvaluatorError::Overflow("addition".into()) - })?, - BinaryOperator::Subtract => a.checked_sub(b).ok_or_else(|| { - ConstantEvaluatorError::Overflow("subtraction".into()) - })?, - BinaryOperator::Multiply => a.checked_mul(b).ok_or_else(|| { - ConstantEvaluatorError::Overflow("multiplication".into()) - })?, - BinaryOperator::Divide => a.checked_div(b).ok_or_else(|| { + BinaryOperator::Add => a.wrapping_add(b), + BinaryOperator::Subtract => a.wrapping_sub(b), + BinaryOperator::Multiply => a.wrapping_mul(b), + BinaryOperator::Divide => { if b == 0 { - ConstantEvaluatorError::DivisionByZero + return Err(ConstantEvaluatorError::DivisionByZero); } else { - ConstantEvaluatorError::Overflow("division".into()) + a.wrapping_div(b) } - })?, - BinaryOperator::Modulo => a.checked_rem(b).ok_or_else(|| { + } + BinaryOperator::Modulo => { if b == 0 { - ConstantEvaluatorError::RemainderByZero + return Err(ConstantEvaluatorError::RemainderByZero); } else { - ConstantEvaluatorError::Overflow("remainder".into()) + a.wrapping_rem(b) } - })?, + } BinaryOperator::And => a & b, BinaryOperator::ExclusiveOr => a ^ b, BinaryOperator::InclusiveOr => a | b, @@ -1887,6 +1881,20 @@ impl<'a> ConstantEvaluator<'a> { BinaryOperator::Modulo => a % b, _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), }), + (Literal::AbstractInt(a), Literal::U32(b)) => { + Literal::AbstractInt(match op { + BinaryOperator::ShiftLeft => { + if (if a.is_negative() { !a } else { a }).leading_zeros() <= b { + return Err(ConstantEvaluatorError::Overflow( + "<<".to_string(), + )); + } + a.checked_shl(b).unwrap_or(0) + } + BinaryOperator::ShiftRight => a.checked_shr(b).unwrap_or(0), + _ => return Err(ConstantEvaluatorError::InvalidBinaryOpArgs), + }) + } (Literal::AbstractInt(a), Literal::AbstractInt(b)) => { Literal::AbstractInt(match op { BinaryOperator::Add => a.checked_add(b).ok_or_else(|| { diff --git a/naga/src/proc/typifier.rs b/naga/src/proc/typifier.rs index 1e1a4c93a4..1359289900 100644 --- a/naga/src/proc/typifier.rs +++ b/naga/src/proc/typifier.rs @@ -668,19 +668,9 @@ impl<'a> ResolveContext<'a> { | Mf::Pow | Mf::QuantizeToF16 => res_arg.clone(), Mf::Modf | Mf::Frexp => { - let (size, width) = match res_arg.inner_with(types) { - &Ti::Scalar(crate::Scalar { - kind: crate::ScalarKind::Float, - width, - }) => (None, width), - &Ti::Vector { - scalar: - crate::Scalar { - kind: crate::ScalarKind::Float, - width, - }, - size, - } => (Some(size), width), + let (size, scalar) = match res_arg.inner_with(types) { + &Ti::Scalar(scalar) => (None, scalar), + &Ti::Vector { scalar, size } => (Some(size), scalar), ref other => { return Err(ResolveError::IncompatibleOperands(format!( "{fun:?}({other:?}, _)" @@ -691,9 +681,9 @@ impl<'a> ResolveContext<'a> { .special_types .predeclared_types .get(&if fun == Mf::Modf { - crate::PredeclaredType::ModfResult { size, width } + crate::PredeclaredType::ModfResult { size, scalar } } else { - crate::PredeclaredType::FrexpResult { size, width } + crate::PredeclaredType::FrexpResult { size, scalar } }) .ok_or(ResolveError::MissingSpecialType)?; TypeResolution::Handle(*result) diff --git a/naga/src/valid/expression.rs b/naga/src/valid/expression.rs index a86f4e15dd..869a43ac25 100644 --- a/naga/src/valid/expression.rs +++ b/naga/src/valid/expression.rs @@ -54,8 +54,13 @@ pub enum ExpressionError { rhs_expr: Handle, rhs_type: crate::TypeInner, }, - #[error("Selecting is not possible")] - InvalidSelectTypes, + #[error("Expected selection argument types to match, but reject value of type {reject:?} does not match accept value of value {accept:?}")] + SelectValuesTypeMismatch { + accept: crate::TypeInner, + reject: crate::TypeInner, + }, + #[error("Expected selection condition to be a boolean value, got {actual:?}")] + SelectConditionNotABool { actual: crate::TypeInner }, #[error("Relational argument {0:?} is not a boolean vector")] InvalidBooleanVector(Handle), #[error("Relational argument {0:?} is not a float")] @@ -905,7 +910,8 @@ impl super::Validator { } => { let accept_inner = &resolver[accept]; let reject_inner = &resolver[reject]; - let condition_good = match resolver[condition] { + let condition_ty = &resolver[condition]; + let condition_good = match *condition_ty { Ti::Scalar(Sc { kind: Sk::Bool, width: _, @@ -932,8 +938,16 @@ impl super::Validator { }, _ => false, }; - if !condition_good || accept_inner != reject_inner { - return Err(ExpressionError::InvalidSelectTypes); + if accept_inner != reject_inner { + return Err(ExpressionError::SelectValuesTypeMismatch { + accept: accept_inner.clone(), + reject: reject_inner.clone(), + }); + } + if !condition_good { + return Err(ExpressionError::SelectConditionNotABool { + actual: condition_ty.clone(), + }); } ShaderStages::all() } diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index 4f233a75a9..7e031ceff7 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -1018,24 +1018,38 @@ impl super::Validator { } => { //Note: this code uses a lot of `FunctionError::InvalidImageStore`, // and could probably be refactored. - let var = match *context.get_expression(image) { + let global_var; + let image_ty; + match *context.get_expression(image) { crate::Expression::GlobalVariable(var_handle) => { - &context.global_vars[var_handle] + global_var = &context.global_vars[var_handle]; + image_ty = global_var.ty; } - // We're looking at a binding index situation, so punch through the index and look at the global behind it. + // The `image` operand is indexing into a binding array, + // so punch through the `Access`* expression and look at + // the global behind it. crate::Expression::Access { base, .. } | crate::Expression::AccessIndex { base, .. } => { - match *context.get_expression(base) { - crate::Expression::GlobalVariable(var_handle) => { - &context.global_vars[var_handle] - } - _ => { - return Err(FunctionError::InvalidImageStore( - ExpressionError::ExpectedGlobalVariable, - ) - .with_span_handle(image, context.expressions)) - } - } + let crate::Expression::GlobalVariable(var_handle) = + *context.get_expression(base) + else { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedGlobalVariable, + ) + .with_span_handle(image, context.expressions)); + }; + global_var = &context.global_vars[var_handle]; + + // The global variable must be a binding array. + let Ti::BindingArray { base, .. } = context.types[global_var.ty].inner + else { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedBindingArrayType(global_var.ty), + ) + .with_span_handle(global_var.ty, context.types)); + }; + + image_ty = base; } _ => { return Err(FunctionError::InvalidImageStore( @@ -1045,77 +1059,73 @@ impl super::Validator { } }; - // Punch through a binding array to get the underlying type - let global_ty = match context.types[var.ty].inner { - Ti::BindingArray { base, .. } => &context.types[base].inner, - ref inner => inner, + // The `image` operand must be an `Image`. + let Ti::Image { + class, + arrayed, + dim, + } = context.types[image_ty].inner + else { + return Err(FunctionError::InvalidImageStore( + ExpressionError::ExpectedImageType(global_var.ty), + ) + .with_span() + .with_handle(global_var.ty, context.types) + .with_handle(image, context.expressions)); }; - let value_ty = match *global_ty { - Ti::Image { - class, - arrayed, - dim, - } => { - match context - .resolve_type(coordinate, &self.valid_expression_set)? - .image_storage_coordinates() - { - Some(coord_dim) if coord_dim == dim => {} - _ => { - return Err(FunctionError::InvalidImageStore( - ExpressionError::InvalidImageCoordinateType( - dim, coordinate, - ), - ) - .with_span_handle(coordinate, context.expressions)); - } - }; - if arrayed != array_index.is_some() { - return Err(FunctionError::InvalidImageStore( - ExpressionError::InvalidImageArrayIndex, - ) - .with_span_handle(coordinate, context.expressions)); - } - if let Some(expr) = array_index { - match *context.resolve_type(expr, &self.valid_expression_set)? { - Ti::Scalar(crate::Scalar { - kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, - width: _, - }) => {} - _ => { - return Err(FunctionError::InvalidImageStore( - ExpressionError::InvalidImageArrayIndexType(expr), - ) - .with_span_handle(expr, context.expressions)); - } - } - } - match class { - crate::ImageClass::Storage { format, .. } => { - crate::TypeInner::Vector { - size: crate::VectorSize::Quad, - scalar: format.into(), - } - } - _ => { - return Err(FunctionError::InvalidImageStore( - ExpressionError::InvalidImageClass(class), - ) - .with_span_handle(image, context.expressions)); - } - } - } - _ => { + // It had better be a storage image, since we're writing to it. + let crate::ImageClass::Storage { format, .. } = class else { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageClass(class), + ) + .with_span_handle(image, context.expressions)); + }; + + // The `coordinate` operand must be a vector of the appropriate size. + if !context + .resolve_type(coordinate, &self.valid_expression_set)? + .image_storage_coordinates() + .is_some_and(|coord_dim| coord_dim == dim) + { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageCoordinateType(dim, coordinate), + ) + .with_span_handle(coordinate, context.expressions)); + } + + // The `array_index` operand should be present if and only if + // the image itself is arrayed. + if arrayed != array_index.is_some() { + return Err(FunctionError::InvalidImageStore( + ExpressionError::InvalidImageArrayIndex, + ) + .with_span_handle(coordinate, context.expressions)); + } + + // If present, `array_index` must be a scalar integer type. + if let Some(expr) = array_index { + if !matches!( + *context.resolve_type(expr, &self.valid_expression_set)?, + Ti::Scalar(crate::Scalar { + kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint, + width: _, + }) + ) { return Err(FunctionError::InvalidImageStore( - ExpressionError::ExpectedImageType(var.ty), + ExpressionError::InvalidImageArrayIndexType(expr), ) - .with_span() - .with_handle(var.ty, context.types) - .with_handle(image, context.expressions)) + .with_span_handle(expr, context.expressions)); } + } + + let value_ty = crate::TypeInner::Vector { + size: crate::VectorSize::Quad, + scalar: format.into(), }; + // The value we're writing had better match the scalar type + // for `image`'s format. if *context.resolve_type(value, &self.valid_expression_set)? != value_ty { return Err(FunctionError::InvalidStoreValue(value) .with_span_handle(value, context.expressions)); diff --git a/naga/tests/in/6772-unpack-expr-accesses.ron b/naga/tests/in/6772-unpack-expr-accesses.ron new file mode 100644 index 0000000000..72873dd667 --- /dev/null +++ b/naga/tests/in/6772-unpack-expr-accesses.ron @@ -0,0 +1,2 @@ +( +) diff --git a/naga/tests/in/6772-unpack-expr-accesses.wgsl b/naga/tests/in/6772-unpack-expr-accesses.wgsl new file mode 100644 index 0000000000..ad2f8b8ffc --- /dev/null +++ b/naga/tests/in/6772-unpack-expr-accesses.wgsl @@ -0,0 +1,6 @@ +@compute @workgroup_size(1, 1) +fn main() { + let idx = 2; + _ = unpack4xI8(12u)[idx]; + _ = unpack4xU8(12u)[1]; +} diff --git a/naga/tests/in/abstract-types-operators.wgsl b/naga/tests/in/abstract-types-operators.wgsl index 25f9236426..678e7f33a0 100644 --- a/naga/tests/in/abstract-types-operators.wgsl +++ b/naga/tests/in/abstract-types-operators.wgsl @@ -24,6 +24,16 @@ const bitflip_uai: u32 = ~0xffffffff & (0x100000000 - 1); const least_i32: i32 = -2147483648; const least_f32: f32 = -3.40282347e+38; +const shl_iaiai: i32 = 1 << 2; +const shl_iai_u: i32 = 1 << 2u; +const shl_uaiai: u32 = 1 << 2; +const shl_uai_u: u32 = 1 << 2u; + +const shr_iaiai: i32 = 1 >> 2; +const shr_iai_u: i32 = 1 >> 2u; +const shr_uaiai: u32 = 1 >> 2; +const shr_uai_u: u32 = 1 >> 2u; + fn runtime_values() { var f: f32 = 42; var i: i32 = 43; diff --git a/naga/tests/in/abstract-types-var.wgsl b/naga/tests/in/abstract-types-var.wgsl index c573f73d57..4f0bfe44bc 100644 --- a/naga/tests/in/abstract-types-var.wgsl +++ b/naga/tests/in/abstract-types-var.wgsl @@ -53,6 +53,7 @@ var ivfs_af = vec2(1.0); var iafafaf = array(1.0, 2.0); var iafaiai = array(1, 2); +var iaipaiai = array(1, 2); var iafpafaf = array(1.0, 2.0); var iafpaiaf = array(1, 2.0); var iafpafai = array(1.0, 2); @@ -93,13 +94,63 @@ fn all_constant_arguments() { var xai_iai: array = array(1i, 2); var xaiai_i: array = array(1, 2i); - // Ideally these would infer the var type from the initializer, - // but we don't support that yet. var xaipaiai: array = array(1, 2); var xafpaiai: array = array(1, 2); var xafpaiaf: array = array(1, 2.0); var xafpafai: array = array(1.0, 2); var xafpafaf: array = array(1.0, 2.0); + + var iaipaiai = array(1, 2); + var iafpaiaf = array(1, 2.0); + var iafpafai = array(1.0, 2); + var iafpafaf = array(1.0, 2.0); + + // Assignments to all of the above. + xvipaiai = vec2(42, 43); + xvupaiai = vec2(44, 45); + xvfpaiai = vec2(46, 47); + + xvupuai = vec2(42u, 43); + xvupaiu = vec2(42, 43u); + + xvuuai = vec2(42u, 43); + xvuaiu = vec2(42, 43u); + + xmfpaiaiaiai = mat2x2(1, 2, 3, 4); + xmfpafaiaiai = mat2x2(1.0, 2, 3, 4); + xmfpaiafaiai = mat2x2(1, 2.0, 3, 4); + xmfpaiaiafai = mat2x2(1, 2, 3.0, 4); + xmfpaiaiaiaf = mat2x2(1, 2, 3, 4.0); + + xmfp_faiaiai = mat2x2(1.0f, 2, 3, 4); + xmfpai_faiai = mat2x2(1, 2.0f, 3, 4); + xmfpaiai_fai = mat2x2(1, 2, 3.0f, 4); + xmfpaiaiai_f = mat2x2(1, 2, 3, 4.0f); + + xvispai = vec2(1); + xvfspaf = vec2(1.0); + xvis_ai = vec2(1); + xvus_ai = vec2(1); + xvfs_ai = vec2(1); + xvfs_af = vec2(1.0); + + xafafaf = array(1.0, 2.0); + xaf_faf = array(1.0f, 2.0); + xafaf_f = array(1.0, 2.0f); + xafaiai = array(1, 2); + xai_iai = array(1i, 2); + xaiai_i = array(1, 2i); + + xaipaiai = array(1, 2); + xafpaiai = array(1, 2); + xafpaiaf = array(1, 2.0); + xafpafai = array(1.0, 2); + xafpafaf = array(1.0, 2.0); + + iaipaiai = array(1, 2); + iafpaiaf = array(1, 2.0); + iafpafai = array(1.0, 2); + iafpafaf = array(1.0, 2.0); } fn mixed_constant_and_runtime_arguments() { @@ -131,4 +182,30 @@ fn mixed_constant_and_runtime_arguments() { var xafpai_f: array = array(1, f); var xaip_iai: array = array(i, 2); var xaipai_i: array = array(1, i); + + // Assignments to all of the above. + xvupuai = vec2(u, 43); + xvupaiu = vec2(42, u); + + xvuuai = vec2(u, 43); + xvuaiu = vec2(42, u); + + xmfp_faiaiai = mat2x2(f, 2, 3, 4); + xmfpai_faiai = mat2x2(1, f, 3, 4); + xmfpaiai_fai = mat2x2(1, 2, f, 4); + xmfpaiaiai_f = mat2x2(1, 2, 3, f); + + xaf_faf = array(f, 2.0); + xafaf_f = array(1.0, f); + xaf_fai = array(f, 2); + xafai_f = array(1, f); + xai_iai = array(i, 2); + xaiai_i = array(1, i); + + xafp_faf = array(f, 2.0); + xafpaf_f = array(1.0, f); + xafp_fai = array(f, 2); + xafpai_f = array(1, f); + xaip_iai = array(i, 2); + xaipai_i = array(1, i); } diff --git a/naga/tests/in/binding-arrays.param.ron b/naga/tests/in/binding-arrays.param.ron index 249a4afe2a..96807d825a 100644 --- a/naga/tests/in/binding-arrays.param.ron +++ b/naga/tests/in/binding-arrays.param.ron @@ -19,11 +19,11 @@ restrict_indexing: true ), msl: ( - lang_version: (2, 0), + lang_version: (3, 0), per_entry_point_map: { "main": ( resources: { - (group: 0, binding: 0): (texture: Some(0), binding_array_size: Some(10), mutable: false), + (group: 0, binding: 0): (buffer: Some(0), binding_array_size: Some(10), mutable: false), }, sizes_buffer: None, ) diff --git a/naga/tests/in/interface.param.ron b/naga/tests/in/interface.param.ron index 14c1cc36ab..992046970e 100644 --- a/naga/tests/in/interface.param.ron +++ b/naga/tests/in/interface.param.ron @@ -28,7 +28,7 @@ ), msl_pipeline: ( allow_and_force_point_size: true, - vertex_pulling_transform: false, - vertex_buffer_mappings: [], + vertex_pulling_transform: false, + vertex_buffer_mappings: [], ), ) diff --git a/naga/tests/in/shadow.wgsl b/naga/tests/in/shadow.wgsl index b02cf68775..86c3a4a00b 100644 --- a/naga/tests/in/shadow.wgsl +++ b/naga/tests/in/shadow.wgsl @@ -37,7 +37,7 @@ fn vs_main( let w = u_entity.world; let world_pos = u_entity.world * vec4(position); var out: VertexOutput; - out.world_normal = mat3x3(w.x.xyz, w.y.xyz, w.z.xyz) * vec3(normal.xyz); + out.world_normal = mat3x3(w[0].xyz, w[1].xyz, w[2].xyz) * vec3(normal.xyz); out.world_position = world_pos; out.proj_position = u_globals.view_proj * world_pos; return out; diff --git a/naga/tests/in/skybox.wgsl b/naga/tests/in/skybox.wgsl index f4cc37a44b..4c6a68a120 100644 --- a/naga/tests/in/skybox.wgsl +++ b/naga/tests/in/skybox.wgsl @@ -22,7 +22,7 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { 1.0, ); - let inv_model_view = transpose(mat3x3(r_data.view.x.xyz, r_data.view.y.xyz, r_data.view.z.xyz)); + let inv_model_view = transpose(mat3x3(r_data.view[0].xyz, r_data.view[1].xyz, r_data.view[2].xyz)); let unprojected = r_data.proj_inv * pos; return VertexOutput(pos, inv_model_view * unprojected.xyz); } diff --git a/naga/tests/in/spv/atomic_global_struct_field_vertex.spv b/naga/tests/in/spv/atomic_global_struct_field_vertex.spv new file mode 100644 index 0000000000..62029146f7 Binary files /dev/null and b/naga/tests/in/spv/atomic_global_struct_field_vertex.spv differ diff --git a/naga/tests/in/spv/atomic_global_struct_field_vertex.spvasm b/naga/tests/in/spv/atomic_global_struct_field_vertex.spvasm new file mode 100644 index 0000000000..e70bd951c3 --- /dev/null +++ b/naga/tests/in/spv/atomic_global_struct_field_vertex.spvasm @@ -0,0 +1,53 @@ +; SPIR-V +; Version: 1.5 +; Generator: Google rspirv; 0 +; Bound: 44 +; Schema: 0 + OpCapability Shader + OpCapability VulkanMemoryModel + OpMemoryModel Logical Vulkan + OpEntryPoint Vertex %1 "global_field_vertex" %2 %gl_Position + OpMemberDecorate %_struct_9 0 Offset 0 + OpMemberDecorate %_struct_9 1 Offset 8 + OpMemberDecorate %_struct_9 2 Offset 16 + OpDecorate %_struct_10 Block + OpMemberDecorate %_struct_10 0 Offset 0 + OpDecorate %2 Binding 0 + OpDecorate %2 DescriptorSet 0 + OpDecorate %gl_Position BuiltIn Position + %uint = OpTypeInt 32 0 + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %_struct_9 = OpTypeStruct %uint %v2float %uint + %_struct_10 = OpTypeStruct %_struct_9 +%_ptr_StorageBuffer__struct_10 = OpTypePointer StorageBuffer %_struct_10 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %18 = OpTypeFunction %void + %2 = OpVariable %_ptr_StorageBuffer__struct_10 StorageBuffer + %uint_0 = OpConstant %uint 0 +%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint + %uint_2 = OpConstant %uint 2 + %uint_5 = OpConstant %uint 5 +%_ptr_StorageBuffer_v2float = OpTypePointer StorageBuffer %v2float + %uint_1 = OpConstant %uint 1 + %float_0 = OpConstant %float 0 +%gl_Position = OpVariable %_ptr_Output_v4float Output + %1 = OpFunction %void None %18 + %28 = OpLabel + %30 = OpInBoundsAccessChain %_ptr_StorageBuffer_uint %2 %uint_0 %uint_2 + %31 = OpInBoundsAccessChain %_ptr_StorageBuffer_uint %2 %uint_0 %uint_0 + %32 = OpLoad %uint %31 + %33 = OpAtomicIAdd %uint %30 %uint_5 %uint_0 %32 + %34 = OpConvertUToF %float %33 + %35 = OpInBoundsAccessChain %_ptr_StorageBuffer_v2float %2 %uint_0 %uint_1 + %36 = OpLoad %v2float %35 + %37 = OpCompositeExtract %float %36 0 + %38 = OpCompositeExtract %float %36 1 + %39 = OpFMul %float %34 %37 + %40 = OpFMul %float %34 %38 + %43 = OpCompositeConstruct %v4float %39 %40 %float_0 %34 + OpStore %gl_Position %43 + OpReturn + OpFunctionEnd diff --git a/naga/tests/out/glsl/6772-unpack-expr-accesses.main.Compute.glsl b/naga/tests/out/glsl/6772-unpack-expr-accesses.main.Compute.glsl new file mode 100644 index 0000000000..e857de73fe --- /dev/null +++ b/naga/tests/out/glsl/6772-unpack-expr-accesses.main.Compute.glsl @@ -0,0 +1,13 @@ +#version 310 es + +precision highp float; +precision highp int; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + + +void main() { + int phony = ivec4(bitfieldExtract(int(12u), 0, 8), bitfieldExtract(int(12u), 8, 8), bitfieldExtract(int(12u), 16, 8), bitfieldExtract(int(12u), 24, 8))[2]; + uint phony_1 = uvec4(bitfieldExtract(12u, 0, 8), bitfieldExtract(12u, 8, 8), bitfieldExtract(12u, 16, 8), bitfieldExtract(12u, 24, 8)).y; +} + diff --git a/naga/tests/out/hlsl/6772-unpack-expr-accesses.hlsl b/naga/tests/out/hlsl/6772-unpack-expr-accesses.hlsl new file mode 100644 index 0000000000..e50f40c8fc --- /dev/null +++ b/naga/tests/out/hlsl/6772-unpack-expr-accesses.hlsl @@ -0,0 +1,6 @@ +[numthreads(1, 1, 1)] +void main() +{ + int phony = (int4(12u, 12u >> 8, 12u >> 16, 12u >> 24) << 24 >> 24)[2]; + uint phony_1 = (uint4(12u, 12u >> 8, 12u >> 16, 12u >> 24) << 24 >> 24).y; +} diff --git a/naga/tests/out/hlsl/6772-unpack-expr-accesses.ron b/naga/tests/out/hlsl/6772-unpack-expr-accesses.ron new file mode 100644 index 0000000000..a07b03300b --- /dev/null +++ b/naga/tests/out/hlsl/6772-unpack-expr-accesses.ron @@ -0,0 +1,12 @@ +( + vertex:[ + ], + fragment:[ + ], + compute:[ + ( + entry_point:"main", + target_profile:"cs_5_1", + ), + ], +) diff --git a/naga/tests/out/hlsl/bits.hlsl b/naga/tests/out/hlsl/bits.hlsl index c89eb19efe..1987e15514 100644 --- a/naga/tests/out/hlsl/bits.hlsl +++ b/naga/tests/out/hlsl/bits.hlsl @@ -213,9 +213,9 @@ void main() uint _e50 = u; f2_ = float2(f16tof32(_e50), f16tof32((_e50) >> 16)); uint _e52 = u; - i4_ = int4(_e52, _e52 >> 8, _e52 >> 16, _e52 >> 24) << 24 >> 24; + i4_ = (int4(_e52, _e52 >> 8, _e52 >> 16, _e52 >> 24) << 24 >> 24); uint _e54 = u; - u4_ = uint4(_e54, _e54 >> 8, _e54 >> 16, _e54 >> 24) << 24 >> 24; + u4_ = (uint4(_e54, _e54 >> 8, _e54 >> 16, _e54 >> 24) << 24 >> 24); int _e56 = i; int _e57 = i; i = naga_insertBits(_e56, _e57, 5u, 10u); diff --git a/naga/tests/out/msl/6772-unpack-expr-accesses.msl b/naga/tests/out/msl/6772-unpack-expr-accesses.msl new file mode 100644 index 0000000000..e00a1b4c54 --- /dev/null +++ b/naga/tests/out/msl/6772-unpack-expr-accesses.msl @@ -0,0 +1,12 @@ +// language: metal1.0 +#include +#include + +using metal::uint; + + +kernel void main_( +) { + int phony = (int4(12u, 12u >> 8, 12u >> 16, 12u >> 24) << 24 >> 24)[2]; + uint phony_1 = (uint4(12u, 12u >> 8, 12u >> 16, 12u >> 24) << 24 >> 24).y; +} diff --git a/naga/tests/out/msl/abstract-types-operators.msl b/naga/tests/out/msl/abstract-types-operators.msl index f273b06610..cec86a53dc 100644 --- a/naga/tests/out/msl/abstract-types-operators.msl +++ b/naga/tests/out/msl/abstract-types-operators.msl @@ -28,6 +28,14 @@ constant uint bitflip_u_u = 0u; constant uint bitflip_uai = 0u; constant int least_i32_ = -2147483648; constant float least_f32_ = -340282350000000000000000000000000000000.0; +constant int shl_iaiai = 4; +constant int shl_iai_u = 4; +constant uint shl_uaiai = 4u; +constant uint shl_uai_u = 4u; +constant int shr_iaiai = 0; +constant int shr_iai_u = 0; +constant uint shr_uaiai = 0u; +constant uint shr_uai_u = 0u; constant int wgpu_4492_ = -2147483648; constant int wgpu_4492_2_ = -2147483648; diff --git a/naga/tests/out/msl/abstract-types-var.msl b/naga/tests/out/msl/abstract-types-var.msl index 7d5623469b..1c9d6fcb25 100644 --- a/naga/tests/out/msl/abstract-types-var.msl +++ b/naga/tests/out/msl/abstract-types-var.msl @@ -46,6 +46,48 @@ void all_constant_arguments( type_7 xafpaiaf = type_7 {1.0, 2.0}; type_7 xafpafai = type_7 {1.0, 2.0}; type_7 xafpafaf = type_7 {1.0, 2.0}; + type_8 iaipaiai = type_8 {1, 2}; + type_7 iafpaiaf = type_7 {1.0, 2.0}; + type_7 iafpafai = type_7 {1.0, 2.0}; + type_7 iafpafaf = type_7 {1.0, 2.0}; + xvipaiai = metal::int2(42, 43); + xvupaiai = metal::uint2(44u, 45u); + xvfpaiai = metal::float2(46.0, 47.0); + xvupuai = metal::uint2(42u, 43u); + xvupaiu = metal::uint2(42u, 43u); + xvuuai = metal::uint2(42u, 43u); + xvuaiu = metal::uint2(42u, 43u); + xmfpaiaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpafaiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpaiafaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpaiaiafai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpaiaiaiaf = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfp_faiaiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpai_faiai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpaiai_fai = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xmfpaiaiai_f = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, 4.0)); + xvispai = metal::int2(1); + xvfspaf = metal::float2(1.0); + xvis_ai = metal::int2(1); + xvus_ai = metal::uint2(1u); + xvfs_ai = metal::float2(1.0); + xvfs_af = metal::float2(1.0); + xafafaf = type_7 {1.0, 2.0}; + xaf_faf = type_7 {1.0, 2.0}; + xafaf_f = type_7 {1.0, 2.0}; + xafaiai = type_7 {1.0, 2.0}; + xai_iai = type_8 {1, 2}; + xaiai_i = type_8 {1, 2}; + xaipaiai = type_8 {1, 2}; + xafpaiai = type_7 {1.0, 2.0}; + xafpaiaf = type_7 {1.0, 2.0}; + xafpafai = type_7 {1.0, 2.0}; + xafpafaf = type_7 {1.0, 2.0}; + iaipaiai = type_8 {1, 2}; + iafpaiaf = type_7 {1.0, 2.0}; + iafpafai = type_7 {1.0, 2.0}; + iafpafaf = type_7 {1.0, 2.0}; + return; } void mixed_constant_and_runtime_arguments( @@ -113,5 +155,45 @@ void mixed_constant_and_runtime_arguments( xaip_iai = type_8 {_e91, 2}; int _e95 = i; xaipai_i = type_8 {1, _e95}; + uint _e99 = u; + xvupuai_1 = metal::uint2(_e99, 43u); + uint _e102 = u; + xvupaiu_1 = metal::uint2(42u, _e102); + uint _e105 = u; + xvuuai_1 = metal::uint2(_e105, 43u); + uint _e108 = u; + xvuaiu_1 = metal::uint2(42u, _e108); + float _e111 = f; + xmfp_faiaiai_1 = metal::float2x2(metal::float2(_e111, 2.0), metal::float2(3.0, 4.0)); + float _e118 = f; + xmfpai_faiai_1 = metal::float2x2(metal::float2(1.0, _e118), metal::float2(3.0, 4.0)); + float _e125 = f; + xmfpaiai_fai_1 = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(_e125, 4.0)); + float _e132 = f; + xmfpaiaiai_f_1 = metal::float2x2(metal::float2(1.0, 2.0), metal::float2(3.0, _e132)); + float _e139 = f; + xaf_faf_1 = type_7 {_e139, 2.0}; + float _e142 = f; + xafaf_f_1 = type_7 {1.0, _e142}; + float _e145 = f; + xaf_fai = type_7 {_e145, 2.0}; + float _e148 = f; + xafai_f = type_7 {1.0, _e148}; + int _e151 = i; + xai_iai_1 = type_8 {_e151, 2}; + int _e154 = i; + xaiai_i_1 = type_8 {1, _e154}; + float _e157 = f; + xafp_faf = type_7 {_e157, 2.0}; + float _e160 = f; + xafpaf_f = type_7 {1.0, _e160}; + float _e163 = f; + xafp_fai = type_7 {_e163, 2.0}; + float _e166 = f; + xafpai_f = type_7 {1.0, _e166}; + int _e169 = i; + xaip_iai = type_8 {_e169, 2}; + int _e172 = i; + xaipai_i = type_8 {1, _e172}; return; } diff --git a/naga/tests/out/msl/binding-arrays.msl b/naga/tests/out/msl/binding-arrays.msl index 75f787a9f2..f62546241a 100644 --- a/naga/tests/out/msl/binding-arrays.msl +++ b/naga/tests/out/msl/binding-arrays.msl @@ -1,4 +1,4 @@ -// language: metal2.0 +// language: metal3.0 #include #include @@ -13,6 +13,10 @@ struct DefaultConstructible { struct UniformIndex { uint index; }; +template +struct NagaArgumentBufferWrapper { + T inner; +}; struct FragmentIn { uint index; }; @@ -25,14 +29,14 @@ struct main_Output { }; fragment main_Output main_( main_Input varyings [[stage_in]] -, metal::array, 10> texture_array_unbounded [[texture(0)]] -, metal::array, 5> texture_array_bounded [[user(fake0)]] -, metal::array, 5> texture_array_2darray [[user(fake0)]] -, metal::array, 5> texture_array_multisampled [[user(fake0)]] -, metal::array, 5> texture_array_depth [[user(fake0)]] -, metal::array, 5> texture_array_storage [[user(fake0)]] -, metal::array samp [[user(fake0)]] -, metal::array samp_comp [[user(fake0)]] +, constant NagaArgumentBufferWrapper>* texture_array_unbounded [[buffer(0)]] +, constant NagaArgumentBufferWrapper>* texture_array_bounded [[user(fake0)]] +, constant NagaArgumentBufferWrapper>* texture_array_2darray [[user(fake0)]] +, constant NagaArgumentBufferWrapper>* texture_array_multisampled [[user(fake0)]] +, constant NagaArgumentBufferWrapper>* texture_array_depth [[user(fake0)]] +, constant NagaArgumentBufferWrapper>* texture_array_storage [[user(fake0)]] +, constant NagaArgumentBufferWrapper* samp [[user(fake0)]] +, constant NagaArgumentBufferWrapper* samp_comp [[user(fake0)]] , constant UniformIndex& uni [[user(fake0)]] ) { const FragmentIn fragment_in = { varyings.index }; @@ -45,116 +49,116 @@ fragment main_Output main_( metal::float2 uv = metal::float2(0.0); metal::int2 pix = metal::int2(0); metal::uint2 _e22 = u2_; - u2_ = _e22 + metal::uint2(texture_array_unbounded[0].get_width(), texture_array_unbounded[0].get_height()); + u2_ = _e22 + metal::uint2(texture_array_unbounded[0].inner.get_width(), texture_array_unbounded[0].inner.get_height()); metal::uint2 _e27 = u2_; - u2_ = _e27 + metal::uint2(texture_array_unbounded[uniform_index].get_width(), texture_array_unbounded[uniform_index].get_height()); + u2_ = _e27 + metal::uint2(texture_array_unbounded[uniform_index].inner.get_width(), texture_array_unbounded[uniform_index].inner.get_height()); metal::uint2 _e32 = u2_; - u2_ = _e32 + metal::uint2(texture_array_unbounded[non_uniform_index].get_width(), texture_array_unbounded[non_uniform_index].get_height()); - metal::float4 _e38 = texture_array_bounded[0].gather(samp[0], uv); + u2_ = _e32 + metal::uint2(texture_array_unbounded[non_uniform_index].inner.get_width(), texture_array_unbounded[non_uniform_index].inner.get_height()); + metal::float4 _e38 = texture_array_bounded[0].inner.gather(samp[0].inner, uv); metal::float4 _e39 = v4_; v4_ = _e39 + _e38; - metal::float4 _e45 = texture_array_bounded[uniform_index].gather(samp[uniform_index], uv); + metal::float4 _e45 = texture_array_bounded[uniform_index].inner.gather(samp[uniform_index].inner, uv); metal::float4 _e46 = v4_; v4_ = _e46 + _e45; - metal::float4 _e52 = texture_array_bounded[non_uniform_index].gather(samp[non_uniform_index], uv); + metal::float4 _e52 = texture_array_bounded[non_uniform_index].inner.gather(samp[non_uniform_index].inner, uv); metal::float4 _e53 = v4_; v4_ = _e53 + _e52; - metal::float4 _e60 = texture_array_depth[0].gather_compare(samp_comp[0], uv, 0.0); + metal::float4 _e60 = texture_array_depth[0].inner.gather_compare(samp_comp[0].inner, uv, 0.0); metal::float4 _e61 = v4_; v4_ = _e61 + _e60; - metal::float4 _e68 = texture_array_depth[uniform_index].gather_compare(samp_comp[uniform_index], uv, 0.0); + metal::float4 _e68 = texture_array_depth[uniform_index].inner.gather_compare(samp_comp[uniform_index].inner, uv, 0.0); metal::float4 _e69 = v4_; v4_ = _e69 + _e68; - metal::float4 _e76 = texture_array_depth[non_uniform_index].gather_compare(samp_comp[non_uniform_index], uv, 0.0); + metal::float4 _e76 = texture_array_depth[non_uniform_index].inner.gather_compare(samp_comp[non_uniform_index].inner, uv, 0.0); metal::float4 _e77 = v4_; v4_ = _e77 + _e76; - metal::float4 _e82 = (uint(0) < texture_array_unbounded[0].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[0].get_width(0), texture_array_unbounded[0].get_height(0))) ? texture_array_unbounded[0].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e82 = (uint(0) < texture_array_unbounded[0].inner.get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[0].inner.get_width(0), texture_array_unbounded[0].inner.get_height(0))) ? texture_array_unbounded[0].inner.read(metal::uint2(pix), 0): DefaultConstructible()); metal::float4 _e83 = v4_; v4_ = _e83 + _e82; - metal::float4 _e88 = (uint(0) < texture_array_unbounded[uniform_index].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[uniform_index].get_width(0), texture_array_unbounded[uniform_index].get_height(0))) ? texture_array_unbounded[uniform_index].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e88 = (uint(0) < texture_array_unbounded[uniform_index].inner.get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[uniform_index].inner.get_width(0), texture_array_unbounded[uniform_index].inner.get_height(0))) ? texture_array_unbounded[uniform_index].inner.read(metal::uint2(pix), 0): DefaultConstructible()); metal::float4 _e89 = v4_; v4_ = _e89 + _e88; - metal::float4 _e94 = (uint(0) < texture_array_unbounded[non_uniform_index].get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[non_uniform_index].get_width(0), texture_array_unbounded[non_uniform_index].get_height(0))) ? texture_array_unbounded[non_uniform_index].read(metal::uint2(pix), 0): DefaultConstructible()); + metal::float4 _e94 = (uint(0) < texture_array_unbounded[non_uniform_index].inner.get_num_mip_levels() && metal::all(metal::uint2(pix) < metal::uint2(texture_array_unbounded[non_uniform_index].inner.get_width(0), texture_array_unbounded[non_uniform_index].inner.get_height(0))) ? texture_array_unbounded[non_uniform_index].inner.read(metal::uint2(pix), 0): DefaultConstructible()); metal::float4 _e95 = v4_; v4_ = _e95 + _e94; uint _e100 = u1_; - u1_ = _e100 + texture_array_2darray[0].get_array_size(); + u1_ = _e100 + texture_array_2darray[0].inner.get_array_size(); uint _e105 = u1_; - u1_ = _e105 + texture_array_2darray[uniform_index].get_array_size(); + u1_ = _e105 + texture_array_2darray[uniform_index].inner.get_array_size(); uint _e110 = u1_; - u1_ = _e110 + texture_array_2darray[non_uniform_index].get_array_size(); + u1_ = _e110 + texture_array_2darray[non_uniform_index].inner.get_array_size(); uint _e115 = u1_; - u1_ = _e115 + texture_array_bounded[0].get_num_mip_levels(); + u1_ = _e115 + texture_array_bounded[0].inner.get_num_mip_levels(); uint _e120 = u1_; - u1_ = _e120 + texture_array_bounded[uniform_index].get_num_mip_levels(); + u1_ = _e120 + texture_array_bounded[uniform_index].inner.get_num_mip_levels(); uint _e125 = u1_; - u1_ = _e125 + texture_array_bounded[non_uniform_index].get_num_mip_levels(); + u1_ = _e125 + texture_array_bounded[non_uniform_index].inner.get_num_mip_levels(); uint _e130 = u1_; - u1_ = _e130 + texture_array_multisampled[0].get_num_samples(); + u1_ = _e130 + texture_array_multisampled[0].inner.get_num_samples(); uint _e135 = u1_; - u1_ = _e135 + texture_array_multisampled[uniform_index].get_num_samples(); + u1_ = _e135 + texture_array_multisampled[uniform_index].inner.get_num_samples(); uint _e140 = u1_; - u1_ = _e140 + texture_array_multisampled[non_uniform_index].get_num_samples(); - metal::float4 _e146 = texture_array_bounded[0].sample(samp[0], uv); + u1_ = _e140 + texture_array_multisampled[non_uniform_index].inner.get_num_samples(); + metal::float4 _e146 = texture_array_bounded[0].inner.sample(samp[0].inner, uv); metal::float4 _e147 = v4_; v4_ = _e147 + _e146; - metal::float4 _e153 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv); + metal::float4 _e153 = texture_array_bounded[uniform_index].inner.sample(samp[uniform_index].inner, uv); metal::float4 _e154 = v4_; v4_ = _e154 + _e153; - metal::float4 _e160 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv); + metal::float4 _e160 = texture_array_bounded[non_uniform_index].inner.sample(samp[non_uniform_index].inner, uv); metal::float4 _e161 = v4_; v4_ = _e161 + _e160; - metal::float4 _e168 = texture_array_bounded[0].sample(samp[0], uv, metal::bias(0.0)); + metal::float4 _e168 = texture_array_bounded[0].inner.sample(samp[0].inner, uv, metal::bias(0.0)); metal::float4 _e169 = v4_; v4_ = _e169 + _e168; - metal::float4 _e176 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::bias(0.0)); + metal::float4 _e176 = texture_array_bounded[uniform_index].inner.sample(samp[uniform_index].inner, uv, metal::bias(0.0)); metal::float4 _e177 = v4_; v4_ = _e177 + _e176; - metal::float4 _e184 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::bias(0.0)); + metal::float4 _e184 = texture_array_bounded[non_uniform_index].inner.sample(samp[non_uniform_index].inner, uv, metal::bias(0.0)); metal::float4 _e185 = v4_; v4_ = _e185 + _e184; - float _e192 = texture_array_depth[0].sample_compare(samp_comp[0], uv, 0.0); + float _e192 = texture_array_depth[0].inner.sample_compare(samp_comp[0].inner, uv, 0.0); float _e193 = v1_; v1_ = _e193 + _e192; - float _e200 = texture_array_depth[uniform_index].sample_compare(samp_comp[uniform_index], uv, 0.0); + float _e200 = texture_array_depth[uniform_index].inner.sample_compare(samp_comp[uniform_index].inner, uv, 0.0); float _e201 = v1_; v1_ = _e201 + _e200; - float _e208 = texture_array_depth[non_uniform_index].sample_compare(samp_comp[non_uniform_index], uv, 0.0); + float _e208 = texture_array_depth[non_uniform_index].inner.sample_compare(samp_comp[non_uniform_index].inner, uv, 0.0); float _e209 = v1_; v1_ = _e209 + _e208; - float _e216 = texture_array_depth[0].sample_compare(samp_comp[0], uv, 0.0); + float _e216 = texture_array_depth[0].inner.sample_compare(samp_comp[0].inner, uv, 0.0); float _e217 = v1_; v1_ = _e217 + _e216; - float _e224 = texture_array_depth[uniform_index].sample_compare(samp_comp[uniform_index], uv, 0.0); + float _e224 = texture_array_depth[uniform_index].inner.sample_compare(samp_comp[uniform_index].inner, uv, 0.0); float _e225 = v1_; v1_ = _e225 + _e224; - float _e232 = texture_array_depth[non_uniform_index].sample_compare(samp_comp[non_uniform_index], uv, 0.0); + float _e232 = texture_array_depth[non_uniform_index].inner.sample_compare(samp_comp[non_uniform_index].inner, uv, 0.0); float _e233 = v1_; v1_ = _e233 + _e232; - metal::float4 _e239 = texture_array_bounded[0].sample(samp[0], uv, metal::gradient2d(uv, uv)); + metal::float4 _e239 = texture_array_bounded[0].inner.sample(samp[0].inner, uv, metal::gradient2d(uv, uv)); metal::float4 _e240 = v4_; v4_ = _e240 + _e239; - metal::float4 _e246 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::gradient2d(uv, uv)); + metal::float4 _e246 = texture_array_bounded[uniform_index].inner.sample(samp[uniform_index].inner, uv, metal::gradient2d(uv, uv)); metal::float4 _e247 = v4_; v4_ = _e247 + _e246; - metal::float4 _e253 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::gradient2d(uv, uv)); + metal::float4 _e253 = texture_array_bounded[non_uniform_index].inner.sample(samp[non_uniform_index].inner, uv, metal::gradient2d(uv, uv)); metal::float4 _e254 = v4_; v4_ = _e254 + _e253; - metal::float4 _e261 = texture_array_bounded[0].sample(samp[0], uv, metal::level(0.0)); + metal::float4 _e261 = texture_array_bounded[0].inner.sample(samp[0].inner, uv, metal::level(0.0)); metal::float4 _e262 = v4_; v4_ = _e262 + _e261; - metal::float4 _e269 = texture_array_bounded[uniform_index].sample(samp[uniform_index], uv, metal::level(0.0)); + metal::float4 _e269 = texture_array_bounded[uniform_index].inner.sample(samp[uniform_index].inner, uv, metal::level(0.0)); metal::float4 _e270 = v4_; v4_ = _e270 + _e269; - metal::float4 _e277 = texture_array_bounded[non_uniform_index].sample(samp[non_uniform_index], uv, metal::level(0.0)); + metal::float4 _e277 = texture_array_bounded[non_uniform_index].inner.sample(samp[non_uniform_index].inner, uv, metal::level(0.0)); metal::float4 _e278 = v4_; v4_ = _e278 + _e277; metal::float4 _e282 = v4_; - texture_array_storage[0].write(_e282, metal::uint2(pix)); + texture_array_storage[0].inner.write(_e282, metal::uint2(pix)); metal::float4 _e285 = v4_; - texture_array_storage[uniform_index].write(_e285, metal::uint2(pix)); + texture_array_storage[uniform_index].inner.write(_e285, metal::uint2(pix)); metal::float4 _e288 = v4_; - texture_array_storage[non_uniform_index].write(_e288, metal::uint2(pix)); + texture_array_storage[non_uniform_index].inner.write(_e288, metal::uint2(pix)); metal::uint2 _e289 = u2_; uint _e290 = u1_; metal::float2 v2_ = static_cast(_e289 + metal::uint2(_e290)); diff --git a/naga/tests/out/msl/bits.msl b/naga/tests/out/msl/bits.msl index 02613fcc04..a0138862d5 100644 --- a/naga/tests/out/msl/bits.msl +++ b/naga/tests/out/msl/bits.msl @@ -42,9 +42,9 @@ kernel void main_( uint _e50 = u; f2_ = float2(as_type(_e50)); uint _e52 = u; - i4_ = int4(_e52, _e52 >> 8, _e52 >> 16, _e52 >> 24) << 24 >> 24; + i4_ = (int4(_e52, _e52 >> 8, _e52 >> 16, _e52 >> 24) << 24 >> 24); uint _e54 = u; - u4_ = uint4(_e54, _e54 >> 8, _e54 >> 16, _e54 >> 24) << 24 >> 24; + u4_ = (uint4(_e54, _e54 >> 8, _e54 >> 16, _e54 >> 24) << 24 >> 24); int _e56 = i; int _e57 = i; i = metal::insert_bits(_e56, _e57, metal::min(5u, 32u), metal::min(10u, 32u - metal::min(5u, 32u))); diff --git a/naga/tests/out/spv/6772-unpack-expr-accesses.spvasm b/naga/tests/out/spv/6772-unpack-expr-accesses.spvasm new file mode 100644 index 0000000000..973557789e --- /dev/null +++ b/naga/tests/out/spv/6772-unpack-expr-accesses.spvasm @@ -0,0 +1,40 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 30 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %4 "main" +OpExecutionMode %4 LocalSize 1 1 1 +%2 = OpTypeVoid +%5 = OpTypeFunction %2 +%6 = OpTypeInt 32 1 +%7 = OpConstant %6 2 +%8 = OpTypeInt 32 0 +%9 = OpConstant %8 12 +%11 = OpTypeVector %6 4 +%13 = OpConstant %8 8 +%19 = OpConstant %8 0 +%20 = OpConstant %8 16 +%21 = OpConstant %8 24 +%23 = OpTypeVector %8 4 +%4 = OpFunction %2 None %5 +%3 = OpLabel +OpBranch %10 +%10 = OpLabel +%14 = OpBitcast %6 %9 +%15 = OpBitFieldSExtract %6 %14 %19 %13 +%16 = OpBitFieldSExtract %6 %14 %13 %13 +%17 = OpBitFieldSExtract %6 %14 %20 %13 +%18 = OpBitFieldSExtract %6 %14 %21 %13 +%12 = OpCompositeConstruct %11 %15 %16 %17 %18 +%22 = OpCompositeExtract %6 %12 2 +%25 = OpBitFieldUExtract %8 %9 %19 %13 +%26 = OpBitFieldUExtract %8 %9 %13 %13 +%27 = OpBitFieldUExtract %8 %9 %20 %13 +%28 = OpBitFieldUExtract %8 %9 %21 %13 +%24 = OpCompositeConstruct %23 %25 %26 %27 %28 +%29 = OpCompositeExtract %8 %24 1 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/abstract-types-operators.spvasm b/naga/tests/out/spv/abstract-types-operators.spvasm index d1ecc56397..e954bff433 100644 --- a/naga/tests/out/spv/abstract-types-operators.spvasm +++ b/naga/tests/out/spv/abstract-types-operators.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.1 ; Generator: rspirv -; Bound: 100 +; Bound: 103 OpCapability Shader OpCapability Linkage %1 = OpExtInstImport "GLSL.std.450" @@ -19,109 +19,112 @@ OpDecorate %6 ArrayStride 4 %11 = OpConstant %5 0 %12 = OpConstant %4 -2147483648 %13 = OpConstant %3 -3.4028235e38 -%15 = OpTypePointer Workgroup %6 -%14 = OpVariable %15 Workgroup -%18 = OpTypeFunction %2 -%19 = OpConstant %3 42.0 -%20 = OpConstant %4 43 -%21 = OpConstant %5 44 -%22 = OpConstant %3 1.0 -%23 = OpConstant %3 2.0 -%24 = OpConstant %4 1 -%25 = OpConstant %4 2 -%26 = OpConstant %5 1 -%27 = OpConstant %5 2 -%29 = OpTypePointer Function %3 -%31 = OpTypePointer Function %4 -%33 = OpTypePointer Function %5 -%37 = OpConstantNull %3 -%41 = OpConstantNull %3 -%43 = OpConstantNull %3 -%45 = OpConstantNull %3 -%47 = OpConstantNull %3 -%50 = OpConstantNull %4 -%52 = OpConstantNull %4 -%54 = OpConstantNull %4 -%57 = OpConstantNull %5 -%59 = OpConstantNull %5 -%61 = OpConstantNull %5 -%90 = OpConstant %3 5.0 -%91 = OpConstant %3 7.0 -%97 = OpTypePointer Workgroup %5 -%17 = OpFunction %2 None %18 -%16 = OpLabel -%60 = OpVariable %33 Function %61 -%55 = OpVariable %33 Function %10 -%49 = OpVariable %31 Function %50 -%44 = OpVariable %29 Function %45 -%39 = OpVariable %29 Function %8 -%35 = OpVariable %29 Function %8 -%30 = OpVariable %31 Function %20 -%58 = OpVariable %33 Function %59 -%53 = OpVariable %31 Function %54 -%48 = OpVariable %31 Function %9 -%42 = OpVariable %29 Function %43 -%38 = OpVariable %29 Function %8 -%34 = OpVariable %29 Function %8 -%28 = OpVariable %29 Function %19 -%56 = OpVariable %33 Function %57 -%51 = OpVariable %31 Function %52 -%46 = OpVariable %29 Function %47 -%40 = OpVariable %29 Function %41 -%36 = OpVariable %29 Function %37 -%32 = OpVariable %33 Function %21 -OpBranch %62 -%62 = OpLabel -%63 = OpLoad %3 %28 -%64 = OpFAdd %3 %22 %63 -OpStore %36 %64 -%65 = OpLoad %3 %28 -%66 = OpFAdd %3 %22 %65 -OpStore %40 %66 -%67 = OpLoad %3 %28 -%68 = OpFAdd %3 %67 %23 -OpStore %42 %68 -%69 = OpLoad %3 %28 -%70 = OpFAdd %3 %69 %23 -OpStore %44 %70 -%71 = OpLoad %3 %28 -%72 = OpLoad %3 %28 -%73 = OpFAdd %3 %71 %72 -OpStore %46 %73 -%74 = OpLoad %4 %30 -%75 = OpIAdd %4 %24 %74 -OpStore %49 %75 -%76 = OpLoad %4 %30 -%77 = OpIAdd %4 %76 %25 -OpStore %51 %77 -%78 = OpLoad %4 %30 -%79 = OpLoad %4 %30 -%80 = OpIAdd %4 %78 %79 -OpStore %53 %80 -%81 = OpLoad %5 %32 -%82 = OpIAdd %5 %26 %81 -OpStore %56 %82 -%83 = OpLoad %5 %32 -%84 = OpIAdd %5 %83 %27 -OpStore %58 %84 -%85 = OpLoad %5 %32 -%86 = OpLoad %5 %32 -%87 = OpIAdd %5 %85 %86 -OpStore %60 %87 +%14 = OpConstant %4 4 +%15 = OpConstant %5 4 +%16 = OpConstant %4 0 +%18 = OpTypePointer Workgroup %6 +%17 = OpVariable %18 Workgroup +%21 = OpTypeFunction %2 +%22 = OpConstant %3 42.0 +%23 = OpConstant %4 43 +%24 = OpConstant %5 44 +%25 = OpConstant %3 1.0 +%26 = OpConstant %3 2.0 +%27 = OpConstant %4 1 +%28 = OpConstant %4 2 +%29 = OpConstant %5 1 +%30 = OpConstant %5 2 +%32 = OpTypePointer Function %3 +%34 = OpTypePointer Function %4 +%36 = OpTypePointer Function %5 +%40 = OpConstantNull %3 +%44 = OpConstantNull %3 +%46 = OpConstantNull %3 +%48 = OpConstantNull %3 +%50 = OpConstantNull %3 +%53 = OpConstantNull %4 +%55 = OpConstantNull %4 +%57 = OpConstantNull %4 +%60 = OpConstantNull %5 +%62 = OpConstantNull %5 +%64 = OpConstantNull %5 +%93 = OpConstant %3 5.0 +%94 = OpConstant %3 7.0 +%100 = OpTypePointer Workgroup %5 +%20 = OpFunction %2 None %21 +%19 = OpLabel +%63 = OpVariable %36 Function %64 +%58 = OpVariable %36 Function %10 +%52 = OpVariable %34 Function %53 +%47 = OpVariable %32 Function %48 +%42 = OpVariable %32 Function %8 +%38 = OpVariable %32 Function %8 +%33 = OpVariable %34 Function %23 +%61 = OpVariable %36 Function %62 +%56 = OpVariable %34 Function %57 +%51 = OpVariable %34 Function %9 +%45 = OpVariable %32 Function %46 +%41 = OpVariable %32 Function %8 +%37 = OpVariable %32 Function %8 +%31 = OpVariable %32 Function %22 +%59 = OpVariable %36 Function %60 +%54 = OpVariable %34 Function %55 +%49 = OpVariable %32 Function %50 +%43 = OpVariable %32 Function %44 +%39 = OpVariable %32 Function %40 +%35 = OpVariable %36 Function %24 +OpBranch %65 +%65 = OpLabel +%66 = OpLoad %3 %31 +%67 = OpFAdd %3 %25 %66 +OpStore %39 %67 +%68 = OpLoad %3 %31 +%69 = OpFAdd %3 %25 %68 +OpStore %43 %69 +%70 = OpLoad %3 %31 +%71 = OpFAdd %3 %70 %26 +OpStore %45 %71 +%72 = OpLoad %3 %31 +%73 = OpFAdd %3 %72 %26 +OpStore %47 %73 +%74 = OpLoad %3 %31 +%75 = OpLoad %3 %31 +%76 = OpFAdd %3 %74 %75 +OpStore %49 %76 +%77 = OpLoad %4 %33 +%78 = OpIAdd %4 %27 %77 +OpStore %52 %78 +%79 = OpLoad %4 %33 +%80 = OpIAdd %4 %79 %28 +OpStore %54 %80 +%81 = OpLoad %4 %33 +%82 = OpLoad %4 %33 +%83 = OpIAdd %4 %81 %82 +OpStore %56 %83 +%84 = OpLoad %5 %35 +%85 = OpIAdd %5 %29 %84 +OpStore %59 %85 +%86 = OpLoad %5 %35 +%87 = OpIAdd %5 %86 %30 +OpStore %61 %87 +%88 = OpLoad %5 %35 +%89 = OpLoad %5 %35 +%90 = OpIAdd %5 %88 %89 +OpStore %63 %90 OpReturn OpFunctionEnd -%89 = OpFunction %2 None %18 -%88 = OpLabel -OpBranch %92 -%92 = OpLabel -OpReturn -OpFunctionEnd -%94 = OpFunction %2 None %18 -%93 = OpLabel +%92 = OpFunction %2 None %21 +%91 = OpLabel OpBranch %95 %95 = OpLabel -%96 = OpISub %4 %24 %24 -%98 = OpAccessChain %97 %14 %96 -%99 = OpLoad %5 %98 +OpReturn +OpFunctionEnd +%97 = OpFunction %2 None %21 +%96 = OpLabel +OpBranch %98 +%98 = OpLabel +%99 = OpISub %4 %27 %27 +%101 = OpAccessChain %100 %17 %99 +%102 = OpLoad %5 %101 OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/abstract-types-var.spvasm b/naga/tests/out/spv/abstract-types-var.spvasm index 59dba569cd..47249bf278 100644 --- a/naga/tests/out/spv/abstract-types-var.spvasm +++ b/naga/tests/out/spv/abstract-types-var.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.1 ; Generator: rspirv -; Bound: 220 +; Bound: 269 OpCapability Shader OpCapability Linkage %1 = OpExtInstImport "GLSL.std.450" @@ -84,171 +84,277 @@ OpDecorate %12 ArrayStride 4 %75 = OpVariable %45 Private %34 %76 = OpVariable %63 Private %37 %77 = OpVariable %63 Private %37 -%78 = OpVariable %63 Private %37 +%78 = OpVariable %66 Private %39 %79 = OpVariable %63 Private %37 %80 = OpVariable %63 Private %37 -%83 = OpTypeFunction %2 -%85 = OpTypePointer Function %4 -%87 = OpTypePointer Function %6 -%89 = OpTypePointer Function %8 -%95 = OpTypePointer Function %9 -%111 = OpTypePointer Function %10 -%116 = OpTypePointer Function %12 -%127 = OpTypePointer Function %5 -%128 = OpConstantNull %5 -%130 = OpTypePointer Function %3 -%131 = OpConstantNull %3 -%133 = OpTypePointer Function %7 -%134 = OpConstantNull %7 -%136 = OpConstantNull %6 -%138 = OpConstantNull %6 -%140 = OpConstantNull %6 -%142 = OpConstantNull %6 -%144 = OpConstantNull %9 -%146 = OpConstantNull %9 -%148 = OpConstantNull %9 -%150 = OpConstantNull %9 -%152 = OpConstantNull %10 -%154 = OpConstantNull %10 -%156 = OpConstantNull %10 -%158 = OpConstantNull %10 -%160 = OpConstantNull %12 -%162 = OpConstantNull %12 -%164 = OpConstantNull %10 -%166 = OpConstantNull %10 -%168 = OpConstantNull %10 -%170 = OpConstantNull %10 -%172 = OpConstantNull %12 -%174 = OpConstantNull %12 -%82 = OpFunction %2 None %83 -%81 = OpLabel -%120 = OpVariable %111 Function %37 -%117 = OpVariable %116 Function %39 -%113 = OpVariable %111 Function %37 -%109 = OpVariable %89 Function %34 -%106 = OpVariable %85 Function %33 -%103 = OpVariable %95 Function %31 -%100 = OpVariable %95 Function %31 -%97 = OpVariable %95 Function %31 -%93 = OpVariable %87 Function %24 -%90 = OpVariable %87 Function %24 -%84 = OpVariable %85 Function %15 -%121 = OpVariable %111 Function %37 -%118 = OpVariable %116 Function %39 -%114 = OpVariable %111 Function %37 -%110 = OpVariable %111 Function %37 -%107 = OpVariable %87 Function %36 -%104 = OpVariable %85 Function %33 -%101 = OpVariable %95 Function %31 -%98 = OpVariable %95 Function %31 -%94 = OpVariable %95 Function %31 -%91 = OpVariable %87 Function %24 -%86 = OpVariable %87 Function %18 -%122 = OpVariable %111 Function %37 -%119 = OpVariable %111 Function %37 -%115 = OpVariable %116 Function %39 -%112 = OpVariable %111 Function %37 -%108 = OpVariable %89 Function %34 -%105 = OpVariable %89 Function %34 -%102 = OpVariable %95 Function %31 -%99 = OpVariable %95 Function %31 -%96 = OpVariable %95 Function %31 -%92 = OpVariable %87 Function %24 -%88 = OpVariable %89 Function %21 -OpBranch %123 -%123 = OpLabel +%81 = OpVariable %63 Private %37 +%84 = OpTypeFunction %2 +%86 = OpTypePointer Function %4 +%88 = OpTypePointer Function %6 +%90 = OpTypePointer Function %8 +%96 = OpTypePointer Function %9 +%112 = OpTypePointer Function %10 +%117 = OpTypePointer Function %12 +%132 = OpTypePointer Function %5 +%133 = OpConstantNull %5 +%135 = OpTypePointer Function %3 +%136 = OpConstantNull %3 +%138 = OpTypePointer Function %7 +%139 = OpConstantNull %7 +%141 = OpConstantNull %6 +%143 = OpConstantNull %6 +%145 = OpConstantNull %6 +%147 = OpConstantNull %6 +%149 = OpConstantNull %9 +%151 = OpConstantNull %9 +%153 = OpConstantNull %9 +%155 = OpConstantNull %9 +%157 = OpConstantNull %10 +%159 = OpConstantNull %10 +%161 = OpConstantNull %10 +%163 = OpConstantNull %10 +%165 = OpConstantNull %12 +%167 = OpConstantNull %12 +%169 = OpConstantNull %10 +%171 = OpConstantNull %10 +%173 = OpConstantNull %10 +%175 = OpConstantNull %10 +%177 = OpConstantNull %12 +%179 = OpConstantNull %12 +%83 = OpFunction %2 None %84 +%82 = OpLabel +%127 = OpVariable %112 Function %37 +%124 = OpVariable %117 Function %39 +%121 = OpVariable %112 Function %37 +%118 = OpVariable %117 Function %39 +%114 = OpVariable %112 Function %37 +%110 = OpVariable %90 Function %34 +%107 = OpVariable %86 Function %33 +%104 = OpVariable %96 Function %31 +%101 = OpVariable %96 Function %31 +%98 = OpVariable %96 Function %31 +%94 = OpVariable %88 Function %24 +%91 = OpVariable %88 Function %24 +%85 = OpVariable %86 Function %15 +%125 = OpVariable %112 Function %37 +%122 = OpVariable %112 Function %37 +%119 = OpVariable %117 Function %39 +%115 = OpVariable %112 Function %37 +%111 = OpVariable %112 Function %37 +%108 = OpVariable %88 Function %36 +%105 = OpVariable %86 Function %33 +%102 = OpVariable %96 Function %31 +%99 = OpVariable %96 Function %31 +%95 = OpVariable %96 Function %31 +%92 = OpVariable %88 Function %24 +%87 = OpVariable %88 Function %18 +%126 = OpVariable %112 Function %37 +%123 = OpVariable %112 Function %37 +%120 = OpVariable %112 Function %37 +%116 = OpVariable %117 Function %39 +%113 = OpVariable %112 Function %37 +%109 = OpVariable %90 Function %34 +%106 = OpVariable %90 Function %34 +%103 = OpVariable %96 Function %31 +%100 = OpVariable %96 Function %31 +%97 = OpVariable %96 Function %31 +%93 = OpVariable %88 Function %24 +%89 = OpVariable %90 Function %21 +OpBranch %128 +%128 = OpLabel +OpStore %85 %15 +OpStore %87 %18 +OpStore %89 %21 +OpStore %91 %24 +OpStore %92 %24 +OpStore %93 %24 +OpStore %94 %24 +OpStore %95 %31 +OpStore %97 %31 +OpStore %98 %31 +OpStore %99 %31 +OpStore %100 %31 +OpStore %101 %31 +OpStore %102 %31 +OpStore %103 %31 +OpStore %104 %31 +OpStore %105 %33 +OpStore %106 %34 +OpStore %107 %33 +OpStore %108 %36 +OpStore %109 %34 +OpStore %110 %34 +OpStore %111 %37 +OpStore %113 %37 +OpStore %114 %37 +OpStore %115 %37 +OpStore %116 %39 +OpStore %118 %39 +OpStore %119 %39 +OpStore %120 %37 +OpStore %121 %37 +OpStore %122 %37 +OpStore %123 %37 +OpStore %124 %39 +OpStore %125 %37 +OpStore %126 %37 +OpStore %127 %37 OpReturn OpFunctionEnd -%125 = OpFunction %2 None %83 -%124 = OpLabel -%173 = OpVariable %116 Function %174 -%167 = OpVariable %111 Function %168 -%161 = OpVariable %116 Function %162 -%155 = OpVariable %111 Function %156 -%149 = OpVariable %95 Function %150 -%143 = OpVariable %95 Function %144 -%137 = OpVariable %87 Function %138 -%129 = OpVariable %130 Function %131 -%171 = OpVariable %116 Function %172 -%165 = OpVariable %111 Function %166 -%159 = OpVariable %116 Function %160 -%153 = OpVariable %111 Function %154 -%147 = OpVariable %95 Function %148 -%141 = OpVariable %87 Function %142 -%135 = OpVariable %87 Function %136 -%126 = OpVariable %127 Function %128 -%169 = OpVariable %111 Function %170 -%163 = OpVariable %111 Function %164 -%157 = OpVariable %111 Function %158 -%151 = OpVariable %111 Function %152 -%145 = OpVariable %95 Function %146 -%139 = OpVariable %87 Function %140 -%132 = OpVariable %133 Function %134 -OpBranch %175 -%175 = OpLabel -%176 = OpLoad %5 %126 -%177 = OpCompositeConstruct %6 %176 %23 -OpStore %135 %177 -%178 = OpLoad %5 %126 -%179 = OpCompositeConstruct %6 %22 %178 -OpStore %137 %179 -%180 = OpLoad %5 %126 -%181 = OpCompositeConstruct %6 %180 %23 -OpStore %139 %181 -%182 = OpLoad %5 %126 -%183 = OpCompositeConstruct %6 %22 %182 -OpStore %141 %183 -%184 = OpLoad %7 %132 -%185 = OpCompositeConstruct %8 %184 %26 -%186 = OpCompositeConstruct %9 %185 %30 -OpStore %143 %186 -%187 = OpLoad %7 %132 -%188 = OpCompositeConstruct %8 %25 %187 -%189 = OpCompositeConstruct %9 %188 %30 -OpStore %145 %189 -%190 = OpLoad %7 %132 -%191 = OpCompositeConstruct %8 %190 %29 -%192 = OpCompositeConstruct %9 %27 %191 -OpStore %147 %192 -%193 = OpLoad %7 %132 -%194 = OpCompositeConstruct %8 %28 %193 -%195 = OpCompositeConstruct %9 %27 %194 -OpStore %149 %195 -%196 = OpLoad %7 %132 -%197 = OpCompositeConstruct %10 %196 %26 -OpStore %151 %197 -%198 = OpLoad %7 %132 -%199 = OpCompositeConstruct %10 %25 %198 -OpStore %153 %199 -%200 = OpLoad %7 %132 -%201 = OpCompositeConstruct %10 %200 %26 -OpStore %155 %201 -%202 = OpLoad %7 %132 -%203 = OpCompositeConstruct %10 %25 %202 -OpStore %157 %203 -%204 = OpLoad %3 %129 -%205 = OpCompositeConstruct %12 %204 %38 -OpStore %159 %205 -%206 = OpLoad %3 %129 -%207 = OpCompositeConstruct %12 %32 %206 -OpStore %161 %207 -%208 = OpLoad %7 %132 -%209 = OpCompositeConstruct %10 %208 %26 -OpStore %163 %209 -%210 = OpLoad %7 %132 -%211 = OpCompositeConstruct %10 %25 %210 -OpStore %165 %211 -%212 = OpLoad %7 %132 -%213 = OpCompositeConstruct %10 %212 %26 -OpStore %167 %213 -%214 = OpLoad %7 %132 -%215 = OpCompositeConstruct %10 %25 %214 -OpStore %169 %215 -%216 = OpLoad %3 %129 -%217 = OpCompositeConstruct %12 %216 %38 -OpStore %171 %217 -%218 = OpLoad %3 %129 -%219 = OpCompositeConstruct %12 %32 %218 -OpStore %173 %219 +%130 = OpFunction %2 None %84 +%129 = OpLabel +%178 = OpVariable %117 Function %179 +%172 = OpVariable %112 Function %173 +%166 = OpVariable %117 Function %167 +%160 = OpVariable %112 Function %161 +%154 = OpVariable %96 Function %155 +%148 = OpVariable %96 Function %149 +%142 = OpVariable %88 Function %143 +%134 = OpVariable %135 Function %136 +%176 = OpVariable %117 Function %177 +%170 = OpVariable %112 Function %171 +%164 = OpVariable %117 Function %165 +%158 = OpVariable %112 Function %159 +%152 = OpVariable %96 Function %153 +%146 = OpVariable %88 Function %147 +%140 = OpVariable %88 Function %141 +%131 = OpVariable %132 Function %133 +%174 = OpVariable %112 Function %175 +%168 = OpVariable %112 Function %169 +%162 = OpVariable %112 Function %163 +%156 = OpVariable %112 Function %157 +%150 = OpVariable %96 Function %151 +%144 = OpVariable %88 Function %145 +%137 = OpVariable %138 Function %139 +OpBranch %180 +%180 = OpLabel +%181 = OpLoad %5 %131 +%182 = OpCompositeConstruct %6 %181 %23 +OpStore %140 %182 +%183 = OpLoad %5 %131 +%184 = OpCompositeConstruct %6 %22 %183 +OpStore %142 %184 +%185 = OpLoad %5 %131 +%186 = OpCompositeConstruct %6 %185 %23 +OpStore %144 %186 +%187 = OpLoad %5 %131 +%188 = OpCompositeConstruct %6 %22 %187 +OpStore %146 %188 +%189 = OpLoad %7 %137 +%190 = OpCompositeConstruct %8 %189 %26 +%191 = OpCompositeConstruct %9 %190 %30 +OpStore %148 %191 +%192 = OpLoad %7 %137 +%193 = OpCompositeConstruct %8 %25 %192 +%194 = OpCompositeConstruct %9 %193 %30 +OpStore %150 %194 +%195 = OpLoad %7 %137 +%196 = OpCompositeConstruct %8 %195 %29 +%197 = OpCompositeConstruct %9 %27 %196 +OpStore %152 %197 +%198 = OpLoad %7 %137 +%199 = OpCompositeConstruct %8 %28 %198 +%200 = OpCompositeConstruct %9 %27 %199 +OpStore %154 %200 +%201 = OpLoad %7 %137 +%202 = OpCompositeConstruct %10 %201 %26 +OpStore %156 %202 +%203 = OpLoad %7 %137 +%204 = OpCompositeConstruct %10 %25 %203 +OpStore %158 %204 +%205 = OpLoad %7 %137 +%206 = OpCompositeConstruct %10 %205 %26 +OpStore %160 %206 +%207 = OpLoad %7 %137 +%208 = OpCompositeConstruct %10 %25 %207 +OpStore %162 %208 +%209 = OpLoad %3 %134 +%210 = OpCompositeConstruct %12 %209 %38 +OpStore %164 %210 +%211 = OpLoad %3 %134 +%212 = OpCompositeConstruct %12 %32 %211 +OpStore %166 %212 +%213 = OpLoad %7 %137 +%214 = OpCompositeConstruct %10 %213 %26 +OpStore %168 %214 +%215 = OpLoad %7 %137 +%216 = OpCompositeConstruct %10 %25 %215 +OpStore %170 %216 +%217 = OpLoad %7 %137 +%218 = OpCompositeConstruct %10 %217 %26 +OpStore %172 %218 +%219 = OpLoad %7 %137 +%220 = OpCompositeConstruct %10 %25 %219 +OpStore %174 %220 +%221 = OpLoad %3 %134 +%222 = OpCompositeConstruct %12 %221 %38 +OpStore %176 %222 +%223 = OpLoad %3 %134 +%224 = OpCompositeConstruct %12 %32 %223 +OpStore %178 %224 +%225 = OpLoad %5 %131 +%226 = OpCompositeConstruct %6 %225 %23 +OpStore %140 %226 +%227 = OpLoad %5 %131 +%228 = OpCompositeConstruct %6 %22 %227 +OpStore %142 %228 +%229 = OpLoad %5 %131 +%230 = OpCompositeConstruct %6 %229 %23 +OpStore %144 %230 +%231 = OpLoad %5 %131 +%232 = OpCompositeConstruct %6 %22 %231 +OpStore %146 %232 +%233 = OpLoad %7 %137 +%234 = OpCompositeConstruct %8 %233 %26 +%235 = OpCompositeConstruct %9 %234 %30 +OpStore %148 %235 +%236 = OpLoad %7 %137 +%237 = OpCompositeConstruct %8 %25 %236 +%238 = OpCompositeConstruct %9 %237 %30 +OpStore %150 %238 +%239 = OpLoad %7 %137 +%240 = OpCompositeConstruct %8 %239 %29 +%241 = OpCompositeConstruct %9 %27 %240 +OpStore %152 %241 +%242 = OpLoad %7 %137 +%243 = OpCompositeConstruct %8 %28 %242 +%244 = OpCompositeConstruct %9 %27 %243 +OpStore %154 %244 +%245 = OpLoad %7 %137 +%246 = OpCompositeConstruct %10 %245 %26 +OpStore %156 %246 +%247 = OpLoad %7 %137 +%248 = OpCompositeConstruct %10 %25 %247 +OpStore %158 %248 +%249 = OpLoad %7 %137 +%250 = OpCompositeConstruct %10 %249 %26 +OpStore %160 %250 +%251 = OpLoad %7 %137 +%252 = OpCompositeConstruct %10 %25 %251 +OpStore %162 %252 +%253 = OpLoad %3 %134 +%254 = OpCompositeConstruct %12 %253 %38 +OpStore %164 %254 +%255 = OpLoad %3 %134 +%256 = OpCompositeConstruct %12 %32 %255 +OpStore %166 %256 +%257 = OpLoad %7 %137 +%258 = OpCompositeConstruct %10 %257 %26 +OpStore %168 %258 +%259 = OpLoad %7 %137 +%260 = OpCompositeConstruct %10 %25 %259 +OpStore %170 %260 +%261 = OpLoad %7 %137 +%262 = OpCompositeConstruct %10 %261 %26 +OpStore %172 %262 +%263 = OpLoad %7 %137 +%264 = OpCompositeConstruct %10 %25 %263 +OpStore %174 %264 +%265 = OpLoad %3 %134 +%266 = OpCompositeConstruct %12 %265 %38 +OpStore %176 %266 +%267 = OpLoad %3 %134 +%268 = OpCompositeConstruct %12 %32 %267 +OpStore %178 %268 OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/wgsl/6772-unpack-expr-accesses.wgsl b/naga/tests/out/wgsl/6772-unpack-expr-accesses.wgsl new file mode 100644 index 0000000000..4c13bc50f3 --- /dev/null +++ b/naga/tests/out/wgsl/6772-unpack-expr-accesses.wgsl @@ -0,0 +1,5 @@ +@compute @workgroup_size(1, 1, 1) +fn main() { + let phony = unpack4xI8(12u)[2i]; + let phony_1 = unpack4xU8(12u).y; +} diff --git a/naga/tests/out/wgsl/abstract-types-operators.wgsl b/naga/tests/out/wgsl/abstract-types-operators.wgsl index de2b073074..caafb535ce 100644 --- a/naga/tests/out/wgsl/abstract-types-operators.wgsl +++ b/naga/tests/out/wgsl/abstract-types-operators.wgsl @@ -19,6 +19,14 @@ const bitflip_u_u: u32 = 0u; const bitflip_uai: u32 = 0u; const least_i32_: i32 = i32(-2147483648); const least_f32_: f32 = -340282350000000000000000000000000000000f; +const shl_iaiai: i32 = 4i; +const shl_iai_u: i32 = 4i; +const shl_uaiai: u32 = 4u; +const shl_uai_u: u32 = 4u; +const shr_iaiai: i32 = 0i; +const shr_iai_u: i32 = 0i; +const shr_uaiai: u32 = 0u; +const shr_uai_u: u32 = 0u; const wgpu_4492_: i32 = i32(-2147483648); const wgpu_4492_2_: i32 = i32(-2147483648); diff --git a/naga/tests/out/wgsl/abstract-types-var.wgsl b/naga/tests/out/wgsl/abstract-types-var.wgsl index 596595ba44..cd35501732 100644 --- a/naga/tests/out/wgsl/abstract-types-var.wgsl +++ b/naga/tests/out/wgsl/abstract-types-var.wgsl @@ -30,9 +30,10 @@ var ivfs_ai: vec2 = vec2(1f); var ivfs_af: vec2 = vec2(1f); var iafafaf: array = array(1f, 2f); var iafaiai: array = array(1f, 2f); -var iafpafaf: array = array(1f, 2f); -var iafpaiaf: array = array(1f, 2f); -var iafpafai: array = array(1f, 2f); +var iaipaiai_1: array = array(1i, 2i); +var iafpafaf_1: array = array(1f, 2f); +var iafpaiaf_1: array = array(1f, 2f); +var iafpafai_1: array = array(1f, 2f); fn all_constant_arguments() { var xvipaiai: vec2 = vec2(42i, 43i); @@ -68,7 +69,49 @@ fn all_constant_arguments() { var xafpaiaf: array = array(1f, 2f); var xafpafai: array = array(1f, 2f); var xafpafaf: array = array(1f, 2f); + var iaipaiai: array = array(1i, 2i); + var iafpaiaf: array = array(1f, 2f); + var iafpafai: array = array(1f, 2f); + var iafpafaf: array = array(1f, 2f); + xvipaiai = vec2(42i, 43i); + xvupaiai = vec2(44u, 45u); + xvfpaiai = vec2(46f, 47f); + xvupuai = vec2(42u, 43u); + xvupaiu = vec2(42u, 43u); + xvuuai = vec2(42u, 43u); + xvuaiu = vec2(42u, 43u); + xmfpaiaiaiai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpafaiaiai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpaiafaiai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpaiaiafai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpaiaiaiaf = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfp_faiaiai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpai_faiai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpaiai_fai = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xmfpaiaiai_f = mat2x2(vec2(1f, 2f), vec2(3f, 4f)); + xvispai = vec2(1i); + xvfspaf = vec2(1f); + xvis_ai = vec2(1i); + xvus_ai = vec2(1u); + xvfs_ai = vec2(1f); + xvfs_af = vec2(1f); + xafafaf = array(1f, 2f); + xaf_faf = array(1f, 2f); + xafaf_f = array(1f, 2f); + xafaiai = array(1f, 2f); + xai_iai = array(1i, 2i); + xaiai_i = array(1i, 2i); + xaipaiai = array(1i, 2i); + xafpaiai = array(1f, 2f); + xafpaiaf = array(1f, 2f); + xafpafai = array(1f, 2f); + xafpafaf = array(1f, 2f); + iaipaiai = array(1i, 2i); + iafpaiaf = array(1f, 2f); + iafpafai = array(1f, 2f); + iafpafaf = array(1f, 2f); + return; } fn mixed_constant_and_runtime_arguments() { @@ -136,6 +179,46 @@ fn mixed_constant_and_runtime_arguments() { xaip_iai = array(_e91, 2i); let _e95 = i; xaipai_i = array(1i, _e95); + let _e99 = u; + xvupuai_1 = vec2(_e99, 43u); + let _e102 = u; + xvupaiu_1 = vec2(42u, _e102); + let _e105 = u; + xvuuai_1 = vec2(_e105, 43u); + let _e108 = u; + xvuaiu_1 = vec2(42u, _e108); + let _e111 = f; + xmfp_faiaiai_1 = mat2x2(vec2(_e111, 2f), vec2(3f, 4f)); + let _e118 = f; + xmfpai_faiai_1 = mat2x2(vec2(1f, _e118), vec2(3f, 4f)); + let _e125 = f; + xmfpaiai_fai_1 = mat2x2(vec2(1f, 2f), vec2(_e125, 4f)); + let _e132 = f; + xmfpaiaiai_f_1 = mat2x2(vec2(1f, 2f), vec2(3f, _e132)); + let _e139 = f; + xaf_faf_1 = array(_e139, 2f); + let _e142 = f; + xafaf_f_1 = array(1f, _e142); + let _e145 = f; + xaf_fai = array(_e145, 2f); + let _e148 = f; + xafai_f = array(1f, _e148); + let _e151 = i; + xai_iai_1 = array(_e151, 2i); + let _e154 = i; + xaiai_i_1 = array(1i, _e154); + let _e157 = f; + xafp_faf = array(_e157, 2f); + let _e160 = f; + xafpaf_f = array(1f, _e160); + let _e163 = f; + xafp_fai = array(_e163, 2f); + let _e166 = f; + xafpai_f = array(1f, _e166); + let _e169 = i; + xaip_iai = array(_e169, 2i); + let _e172 = i; + xaipai_i = array(1i, _e172); return; } diff --git a/naga/tests/out/wgsl/atomic_global_struct_field_vertex.wgsl b/naga/tests/out/wgsl/atomic_global_struct_field_vertex.wgsl new file mode 100644 index 0000000000..c3369e241b --- /dev/null +++ b/naga/tests/out/wgsl/atomic_global_struct_field_vertex.wgsl @@ -0,0 +1,29 @@ +struct type_5 { + member: u32, + member_1: vec2, + member_2: atomic, +} + +struct type_6 { + member: type_5, +} + +@group(0) @binding(0) +var global: type_6; +var global_1: vec4 = vec4(0f, 0f, 0f, 1f); + +fn function() { + let _e7 = global.member.member; + let _e8 = atomicAdd((&global.member.member_2), _e7); + let _e9 = f32(_e8); + let _e12 = global.member.member_1; + global_1 = vec4((_e9 * _e12.x), (_e9 * _e12.y), 0f, _e9); + return; +} + +@vertex +fn global_field_vertex() -> @builtin(position) vec4 { + function(); + let _e1 = global_1; + return _e1; +} diff --git a/naga/tests/snapshots.rs b/naga/tests/snapshots.rs index 93d131b739..bb0e22f69c 100644 --- a/naga/tests/snapshots.rs +++ b/naga/tests/snapshots.rs @@ -949,6 +949,10 @@ fn convert_wgsl() { Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, ), ("diagnostic-filter", Targets::IR), + ( + "6772-unpack-expr-accesses", + Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL, + ), ]; for &(name, targets) in inputs.iter() { @@ -1081,6 +1085,7 @@ fn convert_spv_all() { convert_spv("atomic_compare_exchange", false, Targets::WGSL); convert_spv("atomic_i_decrement", false, Targets::WGSL); convert_spv("atomic_i_add_sub", false, Targets::WGSL); + convert_spv("atomic_global_struct_field_vertex", false, Targets::WGSL); convert_spv( "fetch_depth", false, diff --git a/naga/tests/validation.rs b/naga/tests/validation.rs index df8fad2ace..3ffb231e25 100644 --- a/naga/tests/validation.rs +++ b/naga/tests/validation.rs @@ -610,8 +610,9 @@ fn binding_arrays_cannot_hold_scalars() { #[cfg(feature = "wgsl-in")] #[test] fn validation_error_messages() { - let cases = [( - r#"@group(0) @binding(0) var my_sampler: sampler; + let cases = [ + ( + r#"@group(0) @binding(0) var my_sampler: sampler; fn foo(tex: texture_2d) -> vec4 { return textureSampleLevel(tex, my_sampler, vec2f(0, 0), 0.0); @@ -621,7 +622,7 @@ fn validation_error_messages() { foo(); } "#, - "\ + "\ error: Function [1] 'main' is invalid ┌─ wgsl:7:17 │ \n7 │ ╭ fn main() { @@ -632,7 +633,48 @@ error: Function [1] 'main' is invalid = Requires 1 arguments, but 0 are provided ", - )]; + ), + ( + "\ +@compute @workgroup_size(1, 1) +fn main() { + // Bad: `9001` isn't a `bool`. + _ = select(1, 2, 9001); +} +", + "\ +error: Entry point main at Compute is invalid + ┌─ wgsl:4:9 + │ +4 │ _ = select(1, 2, 9001); + │ ^^^^^^ naga::Expression [3] + │ + = Expression [3] is invalid + = Expected selection condition to be a boolean value, got Scalar(Scalar { kind: Sint, width: 4 }) + +", + ), + ( + "\ +@compute @workgroup_size(1, 1) +fn main() { + // Bad: `bool` and abstract int args. don't match. + _ = select(true, 1, false); +} +", + "\ +error: Entry point main at Compute is invalid + ┌─ wgsl:4:9 + │ +4 │ _ = select(true, 1, false); + │ ^^^^^^ naga::Expression [3] + │ + = Expression [3] is invalid + = Expected selection argument types to match, but reject value of type Scalar(Scalar { kind: Bool, width: 1 }) does not match accept value of value Scalar(Scalar { kind: Sint, width: 4 }) + +", + ), + ]; for (source, expected_err) in cases { let module = naga::front::wgsl::parse_str(source).unwrap(); diff --git a/naga/tests/wgsl_errors.rs b/naga/tests/wgsl_errors.rs index acc2f32cbf..abba829d5f 100644 --- a/naga/tests/wgsl_errors.rs +++ b/naga/tests/wgsl_errors.rs @@ -1238,8 +1238,8 @@ fn pointer_type_equivalence() { fn g() { var m: mat2x2; - let pv: ptr> = &m.x; - let pf: ptr = &m.x.x; + let pv: ptr> = &m[0]; + let pf: ptr = &m[0].x; f(pv, pf); } @@ -1543,7 +1543,7 @@ fn select() { naga::valid::ValidationError::Function { name, source: naga::valid::FunctionError::Expression { - source: naga::valid::ExpressionError::InvalidSelectTypes, + source: naga::valid::ExpressionError::SelectConditionNotABool { .. }, .. }, .. @@ -2108,10 +2108,10 @@ fn compaction_preserves_spans() { let source = r#" fn f() { var a: i32 = -(-(-(-42i))); - var x: i32; - x = 42u; + var x: array; + var y = x[1.0]; } - "#; // ^^^ correct error span: 95..98 + "#; // ^^^ correct error span: 108..114 let mut module = naga::front::wgsl::parse_str(source).expect("source ought to parse"); naga::compact::compact(&mut module); let err = naga::valid::Validator::new( @@ -2135,7 +2135,10 @@ fn compaction_preserves_spans() { .0; if !matches!( dest_span.to_range(), - Some(std::ops::Range { start: 95, end: 98 }) + Some(std::ops::Range { + start: 108, + end: 114 + }) ) { panic!("Error message has wrong span:\n\n{err:#?}"); } diff --git a/naga/xtask/src/validate.rs b/naga/xtask/src/validate.rs index fa330f0a96..99defa3303 100644 --- a/naga/xtask/src/validate.rs +++ b/naga/xtask/src/validate.rs @@ -159,7 +159,7 @@ fn collect_validation_jobs(jobs: &mut Vec, cmd: ValidateSubcommand) -> anyh // that to Windows as well. ValidateSubcommand::Hlsl( ValidateHlslCommand::Dxc | ValidateHlslCommand::Fxc, - ) => cfg!(os = "windows"), + ) => cfg!(target_os = "windows"), ValidateSubcommand::All => continue, }; if should_validate { diff --git a/player/src/bin/play.rs b/player/src/bin/play.rs index 842a05148d..f5bafe57e4 100644 --- a/player/src/bin/play.rs +++ b/player/src/bin/play.rs @@ -48,7 +48,7 @@ fn main() { .build(&event_loop) .unwrap(); - let global = wgc::global::Global::new("player", wgt::InstanceDescriptor::default()); + let global = wgc::global::Global::new("player", &wgt::InstanceDescriptor::default()); let mut command_buffer_id_manager = wgc::identity::IdentityManager::new(); #[cfg(feature = "winit")] diff --git a/player/tests/data/quad.ron b/player/tests/data/quad.ron index 04df018993..7bb56a6be0 100644 --- a/player/tests/data/quad.ron +++ b/player/tests/data/quad.ron @@ -100,17 +100,13 @@ Some(( view: Id(0, 1), resolve_target: None, - channel: ( - load_op: clear, - store_op: store, - clear_value: ( - r: 0, - g: 0, - b: 0, - a: 1, - ), - read_only: false, - ), + load_op: clear(Color( + r: 0, + g: 0, + b: 0, + a: 1, + )), + store_op: store, )), ], target_depth_stencil: None, diff --git a/player/tests/data/zero-init-texture-rendertarget.ron b/player/tests/data/zero-init-texture-rendertarget.ron index bcd4fccbf0..7f06de9b4e 100644 --- a/player/tests/data/zero-init-texture-rendertarget.ron +++ b/player/tests/data/zero-init-texture-rendertarget.ron @@ -50,13 +50,10 @@ Some(( view: Id(0, 1), resolve_target: None, - channel: ( - load_op: load, - store_op: store, - clear_value: ( - r: 1, g: 1, b: 1, a: 1, - ), - read_only: false, + load_op: load, + store_op: store, + clear_value: ( + r: 1, g: 1, b: 1, a: 1, ), )), ], diff --git a/player/tests/test.rs b/player/tests/test.rs index f9cad8b4ea..cc3106f20d 100644 --- a/player/tests/test.rs +++ b/player/tests/test.rs @@ -201,7 +201,7 @@ impl Corpus { let global = wgc::global::Global::new( "test", - wgt::InstanceDescriptor { + &wgt::InstanceDescriptor { backends: backend.into(), flags: wgt::InstanceFlags::debugging(), dx12_shader_compiler: wgt::Dx12Compiler::Fxc, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 45bb8d6d51..a0fae34914 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.76" +channel = "1.83" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index db91fb8665..410de588f9 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -34,6 +34,7 @@ ctor.workspace = true futures-lite.workspace = true glam.workspace = true itertools.workspace = true +image.workspace = true libtest-mimic.workspace = true log.workspace = true parking_lot.workspace = true @@ -44,7 +45,7 @@ serde_json.workspace = true serde.workspace = true strum = { workspace = true, features = ["derive"] } trybuild.workspace = true -wgpu = { workspace = true, features = ["wgsl"] } +wgpu = { workspace = true, features = ["wgsl", "static-dxc"] } wgpu-macros.workspace = true wgt = { workspace = true, features = ["serde"] } diff --git a/tests/compile_tests/fail/cpass_lifetime.rs b/tests/compile_tests/fail/cpass_lifetime.rs index 3a26367187..9384348e4b 100644 --- a/tests/compile_tests/fail/cpass_lifetime.rs +++ b/tests/compile_tests/fail/cpass_lifetime.rs @@ -4,7 +4,7 @@ // See #6145 for more info. fn main() { - let instance = wgpu::Instance::new(Default::default()); + let instance = wgpu::Instance::default(); let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap(); let (device, queue) = pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap(); diff --git a/tests/compile_tests/fail/rpass_lifetime.rs b/tests/compile_tests/fail/rpass_lifetime.rs index 781460a8e1..4a505bdb1f 100644 --- a/tests/compile_tests/fail/rpass_lifetime.rs +++ b/tests/compile_tests/fail/rpass_lifetime.rs @@ -4,7 +4,7 @@ // See #6145 for more info. fn main() { - let instance = wgpu::Instance::new(Default::default()); + let instance = wgpu::Instance::default(); let adapter = pollster::block_on(instance.request_adapter(&Default::default())).unwrap(); let (device, queue) = pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap(); diff --git a/tests/src/init.rs b/tests/src/init.rs index 3644655bec..f710cfb7cb 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -34,10 +34,10 @@ pub fn initialize_instance(backends: wgpu::Backends, force_fxc: bool) -> Instanc let dx12_shader_compiler = if force_fxc { wgpu::Dx12Compiler::Fxc } else { - wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default() + wgpu::util::dx12_shader_compiler_from_env().unwrap_or(wgpu::Dx12Compiler::StaticDxc) }; let gles_minor_version = wgpu::util::gles_minor_version_from_env().unwrap_or_default(); - Instance::new(wgpu::InstanceDescriptor { + Instance::new(&wgpu::InstanceDescriptor { backends, flags: wgpu::InstanceFlags::debugging().with_env(), dx12_shader_compiler, diff --git a/tests/tests/bgra8unorm_storage.rs b/tests/tests/bgra8unorm_storage.rs index 30babe0d82..698a4988b7 100644 --- a/tests/tests/bgra8unorm_storage.rs +++ b/tests/tests/bgra8unorm_storage.rs @@ -44,6 +44,7 @@ static BGRA8_UNORM_STORAGE: GpuTestConfiguration = GpuTestConfiguration::new() label: None, format: None, dimension: None, + usage: None, aspect: wgpu::TextureAspect::All, base_mip_level: 0, base_array_layer: 0, diff --git a/tests/tests/binding_array/buffers.rs b/tests/tests/binding_array/buffers.rs new file mode 100644 index 0000000000..1ef9818302 --- /dev/null +++ b/tests/tests/binding_array/buffers.rs @@ -0,0 +1,265 @@ +use std::num::{NonZeroU32, NonZeroU64}; + +use wgpu::*; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; + +#[gpu_test] +static BINDING_ARRAY_UNIFORM_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::BUFFER_BINDING_ARRAY + | Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_uniform_buffers_per_shader_stage: 16, + ..Limits::default() + }) + // Naga bug on vulkan: https://github.com/gfx-rs/wgpu/issues/6733 + // + // Causes varying errors on different devices, so we don't match more closely. + .expect_fail(FailureCase::backend(Backends::VULKAN)) + // These issues cause a segfault on lavapipe + .skip(FailureCase::backend_adapter(Backends::VULKAN, "llvmpipe")), + ) + .run_async(|ctx| async move { binding_array_buffers(ctx, BufferType::Uniform, false).await }); + +#[gpu_test] +static PARTIAL_BINDING_ARRAY_UNIFORM_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::BUFFER_BINDING_ARRAY + | Features::PARTIALLY_BOUND_BINDING_ARRAY + | Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_uniform_buffers_per_shader_stage: 32, + ..Limits::default() + }) + // Naga bug on vulkan: https://github.com/gfx-rs/wgpu/issues/6733 + // + // Causes varying errors on different devices, so we don't match more closely. + .expect_fail(FailureCase::backend(Backends::VULKAN)) + // These issues cause a segfault on lavapipe + .skip(FailureCase::backend_adapter(Backends::VULKAN, "llvmpipe")), + ) + .run_async(|ctx| async move { binding_array_buffers(ctx, BufferType::Uniform, true).await }); + +#[gpu_test] +static BINDING_ARRAY_STORAGE_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::BUFFER_BINDING_ARRAY + | Features::STORAGE_RESOURCE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_storage_buffers_per_shader_stage: 17, + ..Limits::default() + }) + // See https://github.com/gfx-rs/wgpu/issues/6745. + .expect_fail(FailureCase::molten_vk()), + ) + .run_async(|ctx| async move { binding_array_buffers(ctx, BufferType::Storage, false).await }); + +#[gpu_test] +static PARTIAL_BINDING_ARRAY_STORAGE_BUFFERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::BUFFER_BINDING_ARRAY + | Features::PARTIALLY_BOUND_BINDING_ARRAY + | Features::STORAGE_RESOURCE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_storage_buffers_per_shader_stage: 33, + ..Limits::default() + }) + // See https://github.com/gfx-rs/wgpu/issues/6745. + .expect_fail(FailureCase::molten_vk()), + ) + .run_async(|ctx| async move { binding_array_buffers(ctx, BufferType::Storage, true).await }); + +enum BufferType { + Storage, + Uniform, +} + +async fn binding_array_buffers( + ctx: TestingContext, + buffer_type: BufferType, + partial_binding: bool, +) { + let storage_mode = match buffer_type { + BufferType::Storage => "storage", + BufferType::Uniform => "uniform", + }; + + let shader = r#" + struct ImAU32 { + value: u32, + _padding: u32, + _padding2: u32, + _padding3: u32, + }; + + @group(0) @binding(0) + var<{storage_mode}> buffers: binding_array; + + @group(0) @binding(1) + var output_buffer: array; + + @compute + @workgroup_size(16, 1, 1) + fn compMain(@builtin(global_invocation_id) id: vec3u) { + output_buffer[id.x] = buffers[id.x].value; + } + "#; + let shader = shader.replace("{storage_mode}", storage_mode); + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Binding Array Buffer"), + source: wgpu::ShaderSource::Wgsl(shader.into()), + }); + + let image = image::load_from_memory(include_bytes!("../3x3_colors.png")).unwrap(); + // Resize image to 4x4 + let image = image + .resize_exact(4, 4, image::imageops::FilterType::Gaussian) + .into_rgba8(); + + // Create one buffer for each pixel + let mut buffers = Vec::with_capacity(64); + for data in image.pixels() { + let buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + usage: match buffer_type { + BufferType::Storage => BufferUsages::STORAGE | BufferUsages::COPY_DST, + BufferType::Uniform => BufferUsages::UNIFORM | BufferUsages::COPY_DST, + }, + // 16 to allow padding for uniform buffers + size: 16, + mapped_at_creation: true, + }); + buffer.slice(..).get_mapped_range_mut()[0..4].copy_from_slice(&data.0); + buffer.unmap(); + buffers.push(buffer); + } + + let output_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + size: 4 * 4 * 4, + usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let multiplier = if partial_binding { 2 } else { 1 }; + + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: match buffer_type { + BufferType::Storage => BufferBindingType::Storage { read_only: true }, + BufferType::Uniform => BufferBindingType::Uniform, + }, + has_dynamic_offset: false, + min_binding_size: Some(NonZeroU64::new(16).unwrap()), + }, + count: Some(NonZeroU32::new(16 * multiplier).unwrap()), + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: Some(NonZeroU64::new(4).unwrap()), + }, + count: None, + }, + ], + }); + + let buffer_references: Vec<_> = buffers + .iter() + .map(|b| b.as_entire_buffer_binding()) + .collect(); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: Some("Bind Group"), + layout: &bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::BufferArray(&buffer_references), + }, + BindGroupEntry { + binding: 1, + resource: output_buffer.as_entire_binding(), + }, + ], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("Compute Pipeline"), + layout: Some(&pipeline_layout), + module: &module, + entry_point: Some("compMain"), + compilation_options: Default::default(), + cache: None, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_compute_pass(&ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(&pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.dispatch_workgroups(1, 1, 1); + } + + let readback_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + size: 4 * 4 * 4, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + encoder.copy_buffer_to_buffer(&output_buffer, 0, &readback_buffer, 0, 4 * 4 * 4); + + ctx.queue.submit(Some(encoder.finish())); + + let slice = readback_buffer.slice(..); + slice.map_async(MapMode::Read, |_| {}); + + ctx.device.poll(Maintain::Wait); + + let data = slice.get_mapped_range(); + + assert_eq!(&data[..], &*image); +} diff --git a/tests/tests/binding_array/mod.rs b/tests/tests/binding_array/mod.rs new file mode 100644 index 0000000000..4b8972fcdb --- /dev/null +++ b/tests/tests/binding_array/mod.rs @@ -0,0 +1,4 @@ +mod buffers; +mod sampled_textures; +mod samplers; +mod storage_textures; diff --git a/tests/tests/binding_array/sampled_textures.rs b/tests/tests/binding_array/sampled_textures.rs new file mode 100644 index 0000000000..e95476e876 --- /dev/null +++ b/tests/tests/binding_array/sampled_textures.rs @@ -0,0 +1,234 @@ +use std::num::NonZeroU32; + +use wgpu::*; +use wgpu_test::{ + gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters, TestingContext, +}; + +#[gpu_test] +static BINDING_ARRAY_SAMPLED_TEXTURES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_sampled_textures_per_shader_stage: 16, + ..Limits::default() + }), + ) + .run_async(|ctx| async move { binding_array_sampled_textures(ctx, false).await }); + +#[gpu_test] +static PARTIAL_BINDING_ARRAY_SAMPLED_TEXTURES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING + | Features::PARTIALLY_BOUND_BINDING_ARRAY, + ) + .limits(Limits { + max_sampled_textures_per_shader_stage: 32, + ..Limits::default() + }), + ) + .run_async(|ctx| async move { binding_array_sampled_textures(ctx, false).await }); + +/// Test to see how texture bindings array work and additionally making sure +/// that non-uniform indexing is working correctly. +/// +/// If non-uniform indexing is not working correctly, AMD will produce the wrong +/// output due to non-native support for non-uniform indexing within a WARP. +async fn binding_array_sampled_textures(ctx: TestingContext, partially_bound: bool) { + let shader = r#" + @group(0) @binding(0) + var textures: binding_array>; + + @vertex + fn vertMain(@builtin(vertex_index) id: u32) -> @builtin(position) vec4f { + var positions = array( + vec2f(-1.0, -1.0), + vec2f(3.0, -1.0), + vec2f(-1.0, 3.0) + ); + + return vec4(positions[id], 0.0, 1.0); + } + + @fragment + fn fragMain(@builtin(position) pos: vec4f) -> @location(0) vec4f { + let pixel = vec2u(floor(pos.xy)); + let index = pixel.y * 4 + pixel.x; + + return textureLoad(textures[index], vec2u(0), 0); + } + "#; + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Binding Array Texture"), + source: wgpu::ShaderSource::Wgsl(shader.into()), + }); + + let image = image::load_from_memory(include_bytes!("../3x3_colors.png")).unwrap(); + // Resize image to 4x4 + let image = image + .resize_exact(4, 4, image::imageops::FilterType::Gaussian) + .into_rgba8(); + + // Create one texture for each pixel + let mut input_views = Vec::with_capacity(64); + for data in image.pixels() { + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + view_formats: &[], + }); + + ctx.queue.write_texture( + TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }, + &data.0, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: Some(1), + }, + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + ); + + input_views.push(texture.create_view(&TextureViewDescriptor::default())); + } + + let output_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("Output Texture"), + size: Extent3d { + width: 4, + height: 4, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let output_view = output_texture.create_view(&TextureViewDescriptor::default()); + + let count = if partially_bound { 32 } else { 16 }; + + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: Some(NonZeroU32::new(count).unwrap()), + }], + }); + + let input_view_references: Vec<_> = input_views.iter().collect(); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: Some("Bind Group"), + layout: &bind_group_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureViewArray(&input_view_references), + }], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = ctx + .device + .create_render_pipeline(&RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&pipeline_layout), + vertex: VertexState { + module: &module, + entry_point: Some("vertMain"), + buffers: &[], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(FragmentState { + module: &module, + entry_point: Some("fragMain"), + targets: &[Some(ColorTargetState { + format: TextureFormat::Rgba8UnormSrgb, + blend: None, + write_mask: ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + cache: None, + multiview: None, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &output_view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Color::BLACK), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + + let readback_buffers = ReadbackBuffers::new(&ctx.device, &output_texture); + readback_buffers.copy_from(&ctx.device, &mut encoder, &output_texture); + + ctx.queue.submit(Some(encoder.finish())); + + readback_buffers.assert_buffer_contents(&ctx, &image).await; +} diff --git a/tests/tests/binding_array/samplers.rs b/tests/tests/binding_array/samplers.rs new file mode 100644 index 0000000000..d4ff2a24b5 --- /dev/null +++ b/tests/tests/binding_array/samplers.rs @@ -0,0 +1,251 @@ +use std::num::{NonZeroU32, NonZeroU64}; + +use wgpu::*; +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +#[gpu_test] +static BINDING_ARRAY_SAMPLERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, + ) + .limits(Limits { + max_samplers_per_shader_stage: 2, + ..Limits::default() + }), + ) + .run_async(|ctx| async move { binding_array_samplers(ctx, false).await }); + +#[gpu_test] +static PARTIAL_BINDING_ARRAY_SAMPLERS: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING + | Features::PARTIALLY_BOUND_BINDING_ARRAY, + ) + .limits(Limits { + max_samplers_per_shader_stage: 4, + ..Limits::default() + }), + ) + .run_async(|ctx| async move { binding_array_samplers(ctx, true).await }); + +async fn binding_array_samplers(ctx: TestingContext, partially_bound: bool) { + let shader = r#" + @group(0) @binding(0) + var samplers: binding_array; + @group(0) @binding(1) + var texture: texture_2d; + @group(0) @binding(2) + var output_values: array; + + @compute + @workgroup_size(2, 1, 1) + fn compMain(@builtin(global_invocation_id) id: vec3u) { + output_values[id.x] = pack4x8unorm(textureSampleLevel(texture, samplers[id.x], vec2f(0.25 + (0.5 * 0.25), 0.5), 0.0)); + } + "#; + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Binding Array Texture"), + source: wgpu::ShaderSource::Wgsl(shader.into()), + }); + + let input_image: [u8; 8] = [ + 255, 0, 0, 255, // + 0, 255, 0, 255, // + ]; + + let expected_output: [u8; 8] = [ + 191, 64, 0, 255, // + 255, 0, 0, 255, // + ]; + + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: Extent3d { + width: 2, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + view_formats: &[], + }); + + ctx.queue.write_texture( + TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }, + &input_image, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(8), + rows_per_image: Some(1), + }, + Extent3d { + width: 2, + height: 1, + depth_or_array_layers: 1, + }, + ); + + let input_view = texture.create_view(&TextureViewDescriptor::default()); + + let samplers = [ + ctx.device.create_sampler(&SamplerDescriptor { + label: None, + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Linear, + lod_min_clamp: 0.0, + lod_max_clamp: 1000.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + }), + ctx.device.create_sampler(&SamplerDescriptor { + label: None, + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Nearest, + min_filter: FilterMode::Nearest, + mipmap_filter: FilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: 1000.0, + compare: None, + anisotropy_clamp: 1, + border_color: None, + }), + ]; + + let output_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + size: 4 * 2, + usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let multiplier = if partially_bound { 2 } else { 1 }; + + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: Some(NonZeroU32::new(2 * multiplier).unwrap()), + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::COMPUTE, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: Some(NonZeroU64::new(4).unwrap()), + }, + count: None, + }, + ], + }); + + let sampler_references: Vec<_> = samplers.iter().collect(); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: Some("Bind Group"), + layout: &bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::SamplerArray(&sampler_references), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(&input_view), + }, + BindGroupEntry { + binding: 2, + resource: output_buffer.as_entire_binding(), + }, + ], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("Compute Pipeline"), + layout: Some(&pipeline_layout), + module: &module, + entry_point: Some("compMain"), + compilation_options: Default::default(), + cache: None, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_compute_pass(&ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(&pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.dispatch_workgroups(1, 1, 1); + } + + let readback_buffer = ctx.device.create_buffer(&BufferDescriptor { + label: None, + size: 4 * 2, + usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + encoder.copy_buffer_to_buffer(&output_buffer, 0, &readback_buffer, 0, 4 * 2); + + ctx.queue.submit(Some(encoder.finish())); + + readback_buffer.slice(..).map_async(MapMode::Read, |_| {}); + ctx.device.poll(Maintain::Wait); + + let readback_buffer_slice = readback_buffer.slice(..).get_mapped_range(); + + assert_eq!(&readback_buffer_slice[0..8], &expected_output[..]); +} diff --git a/tests/tests/binding_array/storage_textures.rs b/tests/tests/binding_array/storage_textures.rs new file mode 100644 index 0000000000..ed8e6b4edb --- /dev/null +++ b/tests/tests/binding_array/storage_textures.rs @@ -0,0 +1,203 @@ +use std::num::NonZeroU32; + +use wgpu::*; +use wgpu_test::{ + gpu_test, image::ReadbackBuffers, FailureCase, GpuTestConfiguration, TestParameters, + TestingContext, +}; + +#[gpu_test] +static BINDING_ARRAY_STORAGE_TEXTURES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::STORAGE_RESOURCE_BINDING_ARRAY + | Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING + | Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + ) + .limits(Limits { + max_storage_textures_per_shader_stage: 17, + ..Limits::default() + }) + .expect_fail(FailureCase::backend(Backends::METAL)), + ) + .run_async(|ctx| async move { binding_array_storage_textures(ctx, false).await }); + +#[gpu_test] +static PARTIAL_BINDING_ARRAY_STORAGE_TEXTURES: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features( + Features::TEXTURE_BINDING_ARRAY + | Features::PARTIALLY_BOUND_BINDING_ARRAY + | Features::STORAGE_RESOURCE_BINDING_ARRAY + | Features::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING + | Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + ) + .limits(Limits { + max_storage_textures_per_shader_stage: 33, + ..Limits::default() + }) + .expect_fail(FailureCase::backend(Backends::METAL)), + ) + .run_async(|ctx| async move { binding_array_storage_textures(ctx, true).await }); + +async fn binding_array_storage_textures(ctx: TestingContext, partially_bound: bool) { + let shader = r#" + @group(0) @binding(0) + var textures: binding_array >; + + @compute + @workgroup_size(4, 4, 1) + fn compMain(@builtin(global_invocation_id) id: vec3u) { + // Read from the 4x4 textures in 0-15, then write to the 4x4 texture in 16 + + let pixel = vec2u(id.xy); + let index = pixel.y * 4 + pixel.x; + + let color = textureLoad(textures[index], vec2u(0)); + textureStore(textures[16], pixel, color); + } + "#; + + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Binding Array Texture"), + source: wgpu::ShaderSource::Wgsl(shader.into()), + }); + + let image = image::load_from_memory(include_bytes!("../3x3_colors.png")).unwrap(); + // Resize image to 4x4 + let image = image + .resize_exact(4, 4, image::imageops::FilterType::Gaussian) + .into_rgba8(); + + // Create one texture for each pixel + let mut input_views = Vec::with_capacity(64); + for data in image.pixels() { + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::STORAGE_BINDING | TextureUsages::COPY_DST, + view_formats: &[], + }); + + ctx.queue.write_texture( + TexelCopyTextureInfo { + texture: &texture, + mip_level: 0, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }, + &data.0, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: Some(1), + }, + Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + ); + + input_views.push(texture.create_view(&TextureViewDescriptor::default())); + } + + let output_texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("Output Texture"), + size: Extent3d { + width: 4, + height: 4, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::Rgba8Unorm, + usage: TextureUsages::STORAGE_BINDING | TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let output_view = output_texture.create_view(&TextureViewDescriptor::default()); + + let multiplier = if partially_bound { 2 } else { 1 }; + + let bind_group_layout = ctx + .device + .create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::COMPUTE, + ty: BindingType::StorageTexture { + access: StorageTextureAccess::ReadWrite, + format: TextureFormat::Rgba8Unorm, + view_dimension: TextureViewDimension::D2, + }, + count: Some(NonZeroU32::new(4 * 4 * multiplier + 1).unwrap()), + }], + }); + + let mut input_view_references: Vec<_> = input_views.iter().collect(); + input_view_references.push(&output_view); + + let bind_group = ctx.device.create_bind_group(&BindGroupDescriptor { + label: Some("Bind Group"), + layout: &bind_group_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: BindingResource::TextureViewArray(&input_view_references), + }], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&ComputePipelineDescriptor { + label: Some("Compute Pipeline"), + layout: Some(&pipeline_layout), + module: &module, + entry_point: Some("compMain"), + compilation_options: Default::default(), + cache: None, + }); + + let mut encoder = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_compute_pass(&ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(&pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.dispatch_workgroups(1, 1, 1); + } + + let readback_buffers = ReadbackBuffers::new(&ctx.device, &output_texture); + readback_buffers.copy_from(&ctx.device, &mut encoder, &output_texture); + + ctx.queue.submit(Some(encoder.finish())); + + readback_buffers.assert_buffer_contents(&ctx, &image).await; +} diff --git a/tests/tests/cloneable_types.rs b/tests/tests/cloneable_types.rs new file mode 100644 index 0000000000..91ee686cff --- /dev/null +++ b/tests/tests/cloneable_types.rs @@ -0,0 +1,43 @@ +use wgpu_test::{gpu_test, TestingContext}; + +#[gpu_test] +static CLONEABLE_BUFFERS: GpuTestConfiguration = + wgpu_test::GpuTestConfiguration::new().run_sync(cloneable_buffers); + +// Test a basic case of cloneable types where you clone the buffer to be able +// to access the buffer inside the callback as well as outside. +fn cloneable_buffers(ctx: TestingContext) { + let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 32, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: true, + }); + + let buffer_contents: Vec = (0..32).collect(); + + buffer + .slice(..) + .get_mapped_range_mut() + .copy_from_slice(&buffer_contents); + + buffer.unmap(); + + // This is actually a bug, we should not need to call submit to make the buffer contents visible. + ctx.queue.submit([]); + + let cloned_buffer = buffer.clone(); + let cloned_buffer_contents = buffer_contents.clone(); + + buffer.slice(..).map_async(wgpu::MapMode::Read, move |_| { + let data = cloned_buffer.slice(..).get_mapped_range(); + + assert_eq!(&*data, &cloned_buffer_contents); + }); + + ctx.device.poll(wgpu::Maintain::Wait); + + let data = buffer.slice(..).get_mapped_range(); + + assert_eq!(&*data, &buffer_contents); +} diff --git a/tests/tests/device.rs b/tests/tests/device.rs index 896ac49349..41efc9b565 100644 --- a/tests/tests/device.rs +++ b/tests/tests/device.rs @@ -688,6 +688,7 @@ static DIFFERENT_BGL_ORDER_BW_SHADER_AND_API: GpuTestConfiguration = GpuTestConf label: None, format: None, dimension: None, + usage: None, aspect: wgt::TextureAspect::All, base_mip_level: 0, mip_level_count: None, diff --git a/tests/tests/dispatch_workgroups_indirect.rs b/tests/tests/dispatch_workgroups_indirect.rs index 0f7d24e4ed..cd83b291c8 100644 --- a/tests/tests/dispatch_workgroups_indirect.rs +++ b/tests/tests/dispatch_workgroups_indirect.rs @@ -88,7 +88,7 @@ static RESET_BIND_GROUPS: GpuTestConfiguration = GpuTestConfiguration::new() ctx.queue.submit(Some(encoder.finish())); let error = pollster::block_on(ctx.device.pop_error_scope()); - assert!(error.map_or(false, |error| { + assert!(error.is_some_and(|error| { format!("{error}").contains("The current set ComputePipeline with '' label expects a BindGroup to be set at index 0") })); }); @@ -130,7 +130,7 @@ static ZERO_SIZED_BUFFER: GpuTestConfiguration = GpuTestConfiguration::new() ctx.queue.submit(Some(encoder.finish())); let error = pollster::block_on(ctx.device.pop_error_scope()); - assert!(error.map_or(false, |error| { + assert!(error.is_some_and(|error| { format!("{error}").contains( "Indirect buffer uses bytes 0..12 which overruns indirect buffer of size 0", ) diff --git a/tests/tests/multi-instance.rs b/tests/tests/multi-instance.rs index 196d791765..d6e6f7df81 100644 --- a/tests/tests/multi-instance.rs +++ b/tests/tests/multi-instance.rs @@ -2,7 +2,7 @@ async fn get() -> wgpu::Adapter { let adapter = { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::util::backend_bits_from_env().unwrap_or_default(), ..Default::default() }); diff --git a/tests/tests/partially_bounded_arrays/mod.rs b/tests/tests/partially_bounded_arrays/mod.rs deleted file mode 100644 index 669c13c511..0000000000 --- a/tests/tests/partially_bounded_arrays/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::num::NonZeroU32; - -use wgpu_test::{gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters}; - -#[gpu_test] -static PARTIALLY_BOUNDED_ARRAY: GpuTestConfiguration = GpuTestConfiguration::new() - .parameters( - TestParameters::default() - .features( - wgpu::Features::TEXTURE_BINDING_ARRAY - | wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY - | wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY - | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, - ) - .limits(wgpu::Limits::downlevel_defaults()), - ) - .run_async(|ctx| async move { - let device = &ctx.device; - - let texture_extent = wgpu::Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }; - let storage_texture = device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: texture_extent, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba32Float, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::STORAGE_BINDING - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let texture_view = storage_texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::StorageTexture { - access: wgpu::StorageTextureAccess::WriteOnly, - format: wgpu::TextureFormat::Rgba32Float, - view_dimension: wgpu::TextureViewDimension::D2, - }, - - count: NonZeroU32::new(4), - }], - }); - - let cs_module = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("main"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - }); - - let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: Some(&pipeline_layout), - module: &cs_module, - entry_point: Some("main"), - compilation_options: Default::default(), - cache: None, - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureViewArray(&[&texture_view]), - }], - layout: &bind_group_layout, - label: Some("bind group"), - }); - - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: None, - timestamp_writes: None, - }); - cpass.set_pipeline(&compute_pipeline); - cpass.set_bind_group(0, &bind_group, &[]); - cpass.dispatch_workgroups(1, 1, 1); - } - - let readback_buffers = ReadbackBuffers::new(&ctx.device, &storage_texture); - readback_buffers.copy_from(&ctx.device, &mut encoder, &storage_texture); - - ctx.queue.submit(Some(encoder.finish())); - - readback_buffers - .assert_buffer_contents(&ctx, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])) - .await; - }); diff --git a/tests/tests/partially_bounded_arrays/shader.wgsl b/tests/tests/partially_bounded_arrays/shader.wgsl deleted file mode 100644 index 7d475800fa..0000000000 --- a/tests/tests/partially_bounded_arrays/shader.wgsl +++ /dev/null @@ -1,11 +0,0 @@ -@group(0) -@binding(0) -var texture_array_storage: binding_array,1>; - -@compute -@workgroup_size(1) -fn main(@builtin(global_invocation_id) global_id: vec3) { - - textureStore(texture_array_storage[0],vec2(0,0), vec4(4.0,3.0,2.0,1.0)); - -} diff --git a/tests/tests/ray_tracing/as_build.rs b/tests/tests/ray_tracing/as_build.rs index 1b52678128..a75b280724 100644 --- a/tests/tests/ray_tracing/as_build.rs +++ b/tests/tests/ray_tracing/as_build.rs @@ -4,7 +4,9 @@ use wgpu::{ util::{BufferInitDescriptor, DeviceExt}, *, }; -use wgpu_test::{fail, gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; +use wgpu_test::{ + fail, gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext, +}; struct AsBuildContext { vertices: Buffer, @@ -86,7 +88,9 @@ static UNBUILT_BLAS: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( TestParameters::default() .test_features_limits() - .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), ) .run_sync(unbuilt_blas); @@ -114,7 +118,9 @@ static OUT_OF_ORDER_AS_BUILD: GpuTestConfiguration = GpuTestConfiguration::new() .parameters( TestParameters::default() .test_features_limits() - .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), ) .run_sync(out_of_order_as_build); @@ -184,10 +190,16 @@ fn out_of_order_as_build(ctx: TestingContext) { #[gpu_test] static OUT_OF_ORDER_AS_BUILD_USE: GpuTestConfiguration = GpuTestConfiguration::new() - .parameters(TestParameters::default().test_features_limits().features( - wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE - | wgpu::Features::EXPERIMENTAL_RAY_QUERY, - )) + .parameters( + TestParameters::default() + .test_features_limits() + .features( + wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE + | wgpu::Features::EXPERIMENTAL_RAY_QUERY, + ) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), + ) .run_sync(out_of_order_as_build_use); fn out_of_order_as_build_use(ctx: TestingContext) { @@ -316,3 +328,90 @@ fn empty_build(ctx: TestingContext) { ctx.queue .submit([encoder_safe.finish(), encoder_unsafe.finish()]); } + +#[gpu_test] +static BUILD_WITH_TRANSFORM: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .test_features_limits() + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), + ) + .run_sync(build_with_transform); + +fn build_with_transform(ctx: TestingContext) { + let vertices = ctx.device.create_buffer_init(&BufferInitDescriptor { + label: None, + contents: &[0; mem::size_of::<[[f32; 3]; 3]>()], + usage: BufferUsages::BLAS_INPUT, + }); + + let transform = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&[ + 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, + ]), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::BLAS_INPUT, + }); + + let blas_size = BlasTriangleGeometrySizeDescriptor { + vertex_format: VertexFormat::Float32x3, + vertex_count: 3, + index_format: None, + index_count: None, + flags: AccelerationStructureGeometryFlags::empty(), + }; + + let blas = ctx.device.create_blas( + &CreateBlasDescriptor { + label: Some("BLAS"), + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }, + BlasGeometrySizeDescriptors::Triangles { + descriptors: vec![blas_size.clone()], + }, + ); + + let tlas = ctx.device.create_tlas(&CreateTlasDescriptor { + label: Some("TLAS"), + max_instances: 1, + flags: AccelerationStructureFlags::PREFER_FAST_TRACE, + update_mode: AccelerationStructureUpdateMode::Build, + }); + + let mut tlas_package = TlasPackage::new(tlas); + tlas_package[0] = Some(TlasInstance::new( + &blas, + [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], + 0, + 0xFF, + )); + + let mut encoder_build = ctx + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("BUILD 1"), + }); + + encoder_build.build_acceleration_structures( + [&BlasBuildEntry { + blas: &blas, + geometry: BlasGeometries::TriangleGeometries(vec![BlasTriangleGeometry { + size: &blas_size, + vertex_buffer: &vertices, + first_vertex: 0, + vertex_stride: mem::size_of::<[f32; 3]>() as BufferAddress, + index_buffer: None, + index_buffer_offset: None, + transform_buffer: Some(&transform), + transform_buffer_offset: Some(0), + }]), + }], + [&tlas_package], + ); + ctx.queue.submit([encoder_build.finish()]); +} diff --git a/tests/tests/ray_tracing/as_use_after_free.rs b/tests/tests/ray_tracing/as_use_after_free.rs index c0df9d385e..ae6c49da28 100644 --- a/tests/tests/ray_tracing/as_use_after_free.rs +++ b/tests/tests/ray_tracing/as_use_after_free.rs @@ -10,7 +10,7 @@ use wgpu::{ Maintain, TlasInstance, TlasPackage, VertexFormat, }; use wgpu_macros::gpu_test; -use wgpu_test::{GpuTestConfiguration, TestParameters, TestingContext}; +use wgpu_test::{FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; fn required_features() -> wgpu::Features { wgpu::Features::EXPERIMENTAL_RAY_QUERY @@ -147,6 +147,8 @@ static ACCELERATION_STRUCTURE_USE_AFTER_FREE: GpuTestConfiguration = GpuTestConf .parameters( TestParameters::default() .test_features_limits() - .features(required_features()), + .features(required_features()) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), ) .run_sync(acceleration_structure_use_after_free); diff --git a/tests/tests/ray_tracing/scene/mod.rs b/tests/tests/ray_tracing/scene/mod.rs index 20cc321d44..299323af58 100644 --- a/tests/tests/ray_tracing/scene/mod.rs +++ b/tests/tests/ray_tracing/scene/mod.rs @@ -1,6 +1,6 @@ use std::{iter, mem}; -use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; +use wgpu_test::{gpu_test, FailureCase, GpuTestConfiguration, TestParameters, TestingContext}; use wgpu::util::DeviceExt; @@ -103,7 +103,9 @@ static ACCELERATION_STRUCTURE_BUILD_NO_INDEX: GpuTestConfiguration = GpuTestConf .parameters( TestParameters::default() .test_features_limits() - .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), ) .run_sync(|ctx| { acceleration_structure_build(&ctx, false); @@ -114,7 +116,9 @@ static ACCELERATION_STRUCTURE_BUILD_WITH_INDEX: GpuTestConfiguration = GpuTestCo .parameters( TestParameters::default() .test_features_limits() - .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE), + .features(wgpu::Features::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE) + // https://github.com/gfx-rs/wgpu/issues/6727 + .skip(FailureCase::backend_adapter(wgpu::Backends::VULKAN, "AMD")), ) .run_sync(|ctx| { acceleration_structure_build(&ctx, true); diff --git a/tests/tests/regression/issue_6467.rs b/tests/tests/regression/issue_6467.rs index da40a791d3..75ccc43a99 100644 --- a/tests/tests/regression/issue_6467.rs +++ b/tests/tests/regression/issue_6467.rs @@ -1,14 +1,14 @@ use wgpu::util::DeviceExt; use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters}; -/// Running a compute shader with one or more of the workgroup sizes set to 0 implies that no work +/// Running a compute shader with a total workgroup count of zero implies that no work /// should be done, and is a user error. Vulkan and DX12 accept this invalid input with grace, but -/// Metal does not guard against this and eventually the machine will crash. Since this is a public -/// API that may be given untrusted values in a browser, this must be protected again. +/// Metal and some OpenGL drivers do not guard against this and eventually the machine will crash. +/// Since this is a public API that may be given untrusted values in a browser, this must be protected again. /// /// The following test should successfully do nothing on all platforms. #[gpu_test] -static ZERO_WORKGROUP_SIZE: GpuTestConfiguration = GpuTestConfiguration::new() +static ZERO_WORKGROUP_COUNT: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default().limits(wgpu::Limits::default())) .run_async(|ctx| async move { let module = ctx diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 84f1e48f9f..ee2539c5f1 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -13,10 +13,12 @@ mod regression { mod bgra8unorm_storage; mod bind_group_layout_dedup; mod bind_groups; +mod binding_array; mod buffer; mod buffer_copy; mod buffer_usages; mod clear_texture; +mod cloneable_types; mod compute_pass_ownership; mod create_surface_error; mod device; @@ -31,7 +33,6 @@ mod mem_leaks; mod nv12_texture; mod occlusion_query; mod oob_indexing; -mod partially_bounded_arrays; mod pipeline; mod pipeline_cache; mod poll; diff --git a/tests/tests/vertex_formats/draw.vert.wgsl b/tests/tests/vertex_formats/draw.vert.wgsl index bf6a08aac6..33fc05eb21 100644 --- a/tests/tests/vertex_formats/draw.vert.wgsl +++ b/tests/tests/vertex_formats/draw.vert.wgsl @@ -225,6 +225,7 @@ struct AttributeBlock4{ @location(3) float32x4: vec4, @location(4) float16x2: vec2, @location(5) float16x4: vec4, + @location(6) float16: f32, } @vertex @@ -260,6 +261,8 @@ fn vertex_block_4(v_in: AttributeBlock4) -> @builtin(position) vec4 all_float16 = accumulate_float16(all_float16, v_in.float16x4.z); all_float16 = accumulate_float16(all_float16, v_in.float16x4.w); + all_float16 = accumulate_float16(all_float16, v_in.float16); + checksums[index_float16] = f32(all_float16); return vec4(0.0); @@ -286,6 +289,70 @@ fn vertex_block_5(v_in: AttributeBlock5) -> @builtin(position) vec4 return vec4(0.0); } +struct AttributeBlock6 { + @location(0) uint16: u32, + @location(1) sint16: i32, + @location(2) unorm16: f32, + @location(3) snorm16: f32, + @location(4) uint8: u32, + @location(5) sint8: i32, + @location(6) unorm8: f32, + @location(7) snorm8: f32, +} + +@vertex +fn vertex_block_6(v_in: AttributeBlock6) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all unorm into one checksum value. + var all_unorm: f32 = 0.0; + all_unorm = accumulate_unorm(all_unorm, v_in.unorm16); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8); + checksums[index_unorm] = f32(all_unorm); + + // Accumulate all snorm into one checksum value. + var all_snorm: f32 = 0.0; + all_snorm = accumulate_snorm(all_snorm, v_in.snorm16); + all_snorm = accumulate_snorm(all_snorm, v_in.snorm8); + checksums[index_snorm] = f32(all_snorm); + + // Accumulate all uint into one checksum value. + var all_uint: u32 = 0; + all_uint = accumulate_uint(all_uint, v_in.uint16); + all_uint = accumulate_uint(all_uint, v_in.uint8); + checksums[index_uint] = f32(all_uint); + + // Accumulate all sint into one checksum value. + var all_sint: i32 = 0; + all_sint = accumulate_sint(all_sint, v_in.sint16); + all_sint = accumulate_sint(all_sint, v_in.sint8); + checksums[index_sint] = f32(all_sint); + + return vec4(0.0); +} + +struct AttributeBlock7 { + @location(0) unorm8x4_bgra: vec4, +} + +@vertex +fn vertex_block_7(v_in: AttributeBlock7) -> @builtin(position) vec4 +{ + init_checksums(); + + // Accumulate all unorm into one checksum value. + var all_unorm: f32 = 0.0; + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4_bgra.r); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4_bgra.g); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4_bgra.b); + all_unorm = accumulate_unorm(all_unorm, v_in.unorm8x4_bgra.a); + + checksums[index_unorm] = f32(all_unorm); + + return vec4(0.0); +} + fn accumulate_uint(accum: u32, val: u32) -> u32 { return accum + val; } diff --git a/tests/tests/vertex_formats/mod.rs b/tests/tests/vertex_formats/mod.rs index e956455786..c8a538c4f6 100644 --- a/tests/tests/vertex_formats/mod.rs +++ b/tests/tests/vertex_formats/mod.rs @@ -14,6 +14,8 @@ enum TestCase { SintsBig, Floats, Unorm1010102, + SingleSmallNormsAndInts, + Unorm8x4Bgra, } struct Test<'a> { @@ -68,6 +70,22 @@ async fn vertex_formats_all(ctx: TestingContext) { 3 => Float32x4, 4 => Float16x2, 5 => Float16x4, + 6 => Float16, + ]; + + let attributes_block_6 = &wgpu::vertex_attr_array![ + 0 => Uint16, + 1 => Sint16, + 2 => Unorm16, + 3 => Snorm16, + 4 => Uint8, + 5 => Sint8, + 6 => Unorm8, + 7 => Snorm8, + ]; + + let attributes_block_7 = &wgpu::vertex_attr_array![ + 0 => Unorm8x4Bgra, ]; let tests = vec![ @@ -145,11 +163,37 @@ async fn vertex_formats_all(ctx: TestingContext) { 66u8, // Float32x3 (-2.0, -102.0, 100.0) 0u8, 0u8, 92u8, 66u8, 0u8, 0u8, 72u8, 194u8, 0u8, 0u8, 32u8, 65u8, 0u8, 0u8, 128u8, 63u8, // Float32x4 (55.0, -50.0, 10.0, 1.0) + 0u8, 68u8, // Float16 (4.0) 0u8, 60u8, 72u8, 53u8, // Float16x2 (1.0, 0.33) 72u8, 57u8, 0u8, 192u8, 0u8, 188u8, 0u8, 184u8, // Float16x4 (0.66, -2.0, -1.0, -0.5) ], - checksums: &[0.0, 0.0, 0.0, 0.0, -1.5, 16.0], + checksums: &[0.0, 0.0, 0.0, 0.0, 2.5, 16.0], + }, + Test { + case: TestCase::SingleSmallNormsAndInts, + entry_point: "vertex_block_6", + attributes: attributes_block_6, + input: &[ + 1u8, 2u8, // Uint16 (513) + 1u8, 2u8, // Sint16 (513) + 0u8, 64u8, // Unorm16 (0.25) + 0u8, 64u8, // Snorm16 (0.5) + 32u8, // Uint8 (32) + 255u8, // Sint8 (-1) + 128u8, // Unorm8 (0.5) + 128u8, // Snorm8 (-1) + ], + checksums: &[513.0 + 32.0, 513.0 - 1.0, 0.25 + 0.5, 0.5 - 1.0, 0.0, 0.0], + }, + Test { + case: TestCase::Unorm8x4Bgra, + entry_point: "vertex_block_7", + attributes: attributes_block_7, + input: &[ + 128u8, 85u8, 170u8, 64u8, // Unorm8x4Bgra (0.67, 0.33, 0.5, 0.25) + ], + checksums: &[0.0, 0.0, 1.75, 0.0, 0.0, 0.0], }, ]; diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index 31606b9b62..b2831a67f8 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -3,7 +3,7 @@ name = "wgpu-core" version = "23.0.1" authors = ["gfx-rs developers"] edition = "2021" -description = "WebGPU core logic on wgpu-hal" +description = "Core implementation logic of wgpu, the cross-platform, safe, pure-rust graphics API" homepage = "https://wgpu.rs/" repository = "https://github.com/gfx-rs/wgpu" keywords = ["graphics"] @@ -29,6 +29,9 @@ targets = [ # Cargo machete can't check build.rs dependencies. See https://github.com/bnjbvr/cargo-machete/issues/100 ignored = ["cfg_aliases"] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wgpu_validate_locks)'] } + [lib] [features] diff --git a/wgpu-core/src/command/bundle.rs b/wgpu-core/src/command/bundle.rs index ac7a5280bf..c7f433c3a0 100644 --- a/wgpu-core/src/command/bundle.rs +++ b/wgpu-core/src/command/bundle.rs @@ -484,10 +484,10 @@ impl RenderBundleEncoder { ) .map_pass_err(scope)?; } - RenderCommand::MultiDrawIndirect { + RenderCommand::DrawIndirect { buffer_id, offset, - count: None, + count: 1, indexed, } => { let scope = PassErrorScope::Draw { @@ -504,7 +504,7 @@ impl RenderBundleEncoder { ) .map_pass_err(scope)?; } - RenderCommand::MultiDrawIndirect { .. } + RenderCommand::DrawIndirect { .. } | RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(), RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(), RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(), @@ -887,10 +887,10 @@ fn multi_draw_indirect( state.flush_vertices(); state.flush_binds(used_bind_groups, dynamic_offsets); - state.commands.push(ArcRenderCommand::MultiDrawIndirect { + state.commands.push(ArcRenderCommand::DrawIndirect { buffer, offset, - count: None, + count: 1, indexed, }); Ok(()) @@ -1101,25 +1101,25 @@ impl RenderBundle { ) }; } - Cmd::MultiDrawIndirect { + Cmd::DrawIndirect { buffer, offset, - count: None, + count: 1, indexed: false, } => { let buffer = buffer.try_raw(snatch_guard)?; unsafe { raw.draw_indirect(buffer, *offset, 1) }; } - Cmd::MultiDrawIndirect { + Cmd::DrawIndirect { buffer, offset, - count: None, + count: 1, indexed: true, } => { let buffer = buffer.try_raw(snatch_guard)?; unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) }; } - Cmd::MultiDrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => { + Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => { return Err(ExecutionError::Unimplemented("multi-draw-indirect")) } Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => { @@ -1727,10 +1727,10 @@ pub mod bundle_ffi { buffer_id: id::BufferId, offset: BufferAddress, ) { - bundle.base.commands.push(RenderCommand::MultiDrawIndirect { + bundle.base.commands.push(RenderCommand::DrawIndirect { buffer_id, offset, - count: None, + count: 1, indexed: false, }); } @@ -1740,10 +1740,10 @@ pub mod bundle_ffi { buffer_id: id::BufferId, offset: BufferAddress, ) { - bundle.base.commands.push(RenderCommand::MultiDrawIndirect { + bundle.base.commands.push(RenderCommand::DrawIndirect { buffer_id, offset, - count: None, + count: 1, indexed: true, }); } diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index 9b7bc9726a..0811c2ac42 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -155,7 +155,7 @@ impl Global { // actual hal barrier & operation let dst_barrier = dst_pending.map(|pending| pending.into_hal(&dst_buffer, &snatch_guard)); - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(dst_barrier.as_slice()); cmd_buf_raw.clear_buffer(dst_raw, offset..end_offset); @@ -235,7 +235,7 @@ impl Global { let device = &cmd_buf.device; device.check_is_valid()?; - let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker(&cmd_buf.device)?; + let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker()?; let snatch_guard = device.snatchable_lock.read(); clear_texture( diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index 44b65b6d6e..0fa6845d28 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -415,8 +415,10 @@ impl Global { // We automatically keep extending command buffers over time, and because // we want to insert a command buffer _before_ what we're about to record, // we need to make sure to close the previous one. - encoder.close(&cmd_buf.device).map_pass_err(pass_scope)?; - let raw_encoder = encoder.open(&cmd_buf.device).map_pass_err(pass_scope)?; + encoder.close_if_open().map_pass_err(pass_scope)?; + let raw_encoder = encoder + .open_pass(base.label.as_deref()) + .map_pass_err(pass_scope)?; let mut state = State { binder: Binder::new(), @@ -594,12 +596,14 @@ impl Global { } = state; // Stop the current command buffer. - encoder.close(&cmd_buf.device).map_pass_err(pass_scope)?; + encoder.close().map_pass_err(pass_scope)?; // Create a new command buffer, which we will insert _before_ the body of the compute pass. // // Use that buffer to insert barriers and clear discarded images. - let transit = encoder.open(&cmd_buf.device).map_pass_err(pass_scope)?; + let transit = encoder + .open_pass(Some("(wgpu internal) Pre Pass")) + .map_pass_err(pass_scope)?; fixup_discarded_surfaces( pending_discard_init_fixups.into_iter(), transit, @@ -614,9 +618,7 @@ impl Global { &snatch_guard, ); // Close the command buffer, and swap it with the previous. - encoder - .close_and_swap(&cmd_buf.device) - .map_pass_err(pass_scope)?; + encoder.close_and_swap().map_pass_err(pass_scope)?; cmd_buf_data_guard.mark_successful(); Ok(()) diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index b11a8595fa..88cf874d3a 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -37,8 +37,8 @@ use crate::ray_tracing::{BlasAction, TlasAction}; use crate::resource::{Fallible, InvalidResourceError, Labeled, ParentDevice as _, QuerySet}; use crate::storage::Storage; use crate::track::{DeviceTracker, Tracker, UsageScope}; -use crate::LabelHelpers; use crate::{api_log, global::Global, id, resource_log, Label}; +use crate::{hal_label, LabelHelpers}; use thiserror::Error; @@ -150,10 +150,10 @@ impl CommandEncoderStatus { } } - fn finish(&mut self, device: &Device) -> Result<(), CommandEncoderError> { + fn finish(&mut self) -> Result<(), CommandEncoderError> { match mem::replace(self, Self::Error) { Self::Recording(mut inner) => { - if let Err(e) = inner.encoder.close(device) { + if let Err(e) = inner.encoder.close_if_open() { Err(e.into()) } else { *self = Self::Finished(inner); @@ -276,14 +276,9 @@ pub(crate) struct CommandEncoder { pub(crate) hal_label: Option, } -//TODO: handle errors better impl CommandEncoder { - /// Finish the current command buffer, if any, and place it - /// at the second-to-last position in our list. - /// - /// If we have opened this command encoder, finish its current - /// command buffer, and insert it just before the last element in - /// [`self.list`][l]. If this command buffer is closed, do nothing. + /// Finish the current command buffer and insert it just before + /// the last element in [`self.list`][l]. /// /// On return, the underlying hal encoder is closed. /// @@ -300,15 +295,62 @@ impl CommandEncoder { /// in just before the pass's. This is the function that jams in the /// transitions' command buffer. /// + /// # Panics + /// + /// - If the encoder is not open. + /// /// [l]: CommandEncoder::list /// [`transition_buffers`]: hal::CommandEncoder::transition_buffers /// [`transition_textures`]: hal::CommandEncoder::transition_textures - fn close_and_swap(&mut self, device: &Device) -> Result<(), DeviceError> { - if self.is_open { - self.is_open = false; - let new = unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?; - self.list.insert(self.list.len() - 1, new); - } + fn close_and_swap(&mut self) -> Result<(), DeviceError> { + assert!(self.is_open); + self.is_open = false; + + let new = + unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; + self.list.insert(self.list.len() - 1, new); + + Ok(()) + } + + /// Finish the current command buffer and insert it at the beginning + /// of [`self.list`][l]. + /// + /// On return, the underlying hal encoder is closed. + /// + /// # Panics + /// + /// - If the encoder is not open. + /// + /// [l]: CommandEncoder::list + pub(crate) fn close_and_push_front(&mut self) -> Result<(), DeviceError> { + assert!(self.is_open); + self.is_open = false; + + let new = + unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; + self.list.insert(0, new); + + Ok(()) + } + + /// Finish the current command buffer, and push it onto + /// the end of [`self.list`][l]. + /// + /// On return, the underlying hal encoder is closed. + /// + /// # Panics + /// + /// - If the encoder is not open. + /// + /// [l]: CommandEncoder::list + pub(crate) fn close(&mut self) -> Result<(), DeviceError> { + assert!(self.is_open); + self.is_open = false; + + let cmd_buf = + unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; + self.list.push(cmd_buf); Ok(()) } @@ -323,11 +365,11 @@ impl CommandEncoder { /// On return, the underlying hal encoder is closed. /// /// [l]: CommandEncoder::list - fn close(&mut self, device: &Device) -> Result<(), DeviceError> { + fn close_if_open(&mut self) -> Result<(), DeviceError> { if self.is_open { self.is_open = false; let cmd_buf = - unsafe { self.raw.end_encoding() }.map_err(|e| device.handle_hal_error(e))?; + unsafe { self.raw.end_encoding() }.map_err(|e| self.device.handle_hal_error(e))?; self.list.push(cmd_buf); } @@ -337,15 +379,12 @@ impl CommandEncoder { /// Begin recording a new command buffer, if we haven't already. /// /// The underlying hal encoder is put in the "recording" state. - pub(crate) fn open( - &mut self, - device: &Device, - ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { + pub(crate) fn open(&mut self) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { if !self.is_open { self.is_open = true; let hal_label = self.hal_label.as_deref(); unsafe { self.raw.begin_encoding(hal_label) } - .map_err(|e| device.handle_hal_error(e))?; + .map_err(|e| self.device.handle_hal_error(e))?; } Ok(self.raw.as_mut()) @@ -355,11 +394,22 @@ impl CommandEncoder { /// its own label. /// /// The underlying hal encoder is put in the "recording" state. - fn open_pass(&mut self, hal_label: Option<&str>, device: &Device) -> Result<(), DeviceError> { + /// + /// # Panics + /// + /// - If the encoder is already open. + pub(crate) fn open_pass( + &mut self, + label: Option<&str>, + ) -> Result<&mut dyn hal::DynCommandEncoder, DeviceError> { + assert!(!self.is_open); self.is_open = true; - unsafe { self.raw.begin_encoding(hal_label) }.map_err(|e| device.handle_hal_error(e))?; - Ok(()) + let hal_label = hal_label(label, self.device.instance_flags); + unsafe { self.raw.begin_encoding(hal_label) } + .map_err(|e| self.device.handle_hal_error(e))?; + + Ok(self.raw.as_mut()) } } @@ -418,9 +468,8 @@ pub struct CommandBufferMutable { impl CommandBufferMutable { pub(crate) fn open_encoder_and_tracker( &mut self, - device: &Device, ) -> Result<(&mut dyn hal::DynCommandEncoder, &mut Tracker), DeviceError> { - let encoder = self.encoder.open(device)?; + let encoder = self.encoder.open()?; let tracker = &mut self.trackers; Ok((encoder, tracker)) @@ -676,6 +725,8 @@ pub enum CommandEncoderError { #[error(transparent)] InvalidColorAttachment(#[from] ColorAttachmentError), #[error(transparent)] + InvalidAttachment(#[from] AttachmentError), + #[error(transparent)] InvalidResource(#[from] InvalidResourceError), #[error(transparent)] MissingFeatures(#[from] MissingFeatures), @@ -701,7 +752,7 @@ impl Global { let cmd_buf = hub.command_buffers.get(encoder_id.into_command_buffer_id()); - let error = match cmd_buf.data.lock().finish(&cmd_buf.device) { + let error = match cmd_buf.data.lock().finish() { Ok(_) => None, Err(e) => Some(e), }; @@ -729,7 +780,7 @@ impl Global { list.push(TraceCommand::PushDebugGroup(label.to_string())); } - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; if !cmd_buf .device .instance_flags @@ -769,7 +820,7 @@ impl Global { .instance_flags .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS) { - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.insert_debug_marker(label); } @@ -798,7 +849,7 @@ impl Global { list.push(TraceCommand::PopDebugGroup); } - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; if !cmd_buf .device .instance_flags diff --git a/wgpu-core/src/command/query.rs b/wgpu-core/src/command/query.rs index 56ca3339de..c2444aa129 100644 --- a/wgpu-core/src/command/query.rs +++ b/wgpu-core/src/command/query.rs @@ -337,7 +337,7 @@ impl Global { }); } - let raw_encoder = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let raw_encoder = cmd_buf_data.encoder.open()?; let query_set = hub.query_sets.get(query_set_id).get()?; @@ -447,7 +447,7 @@ impl Global { ); let raw_dst_buffer = dst_buffer.try_raw(&snatch_guard)?; - let raw_encoder = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let raw_encoder = cmd_buf_data.encoder.open()?; unsafe { raw_encoder.transition_buffers(dst_barrier.as_slice()); raw_encoder.copy_query_results( diff --git a/wgpu-core/src/command/ray_tracing.rs b/wgpu-core/src/command/ray_tracing.rs index 35fc581127..9395c20fc1 100644 --- a/wgpu-core/src/command/ray_tracing.rs +++ b/wgpu-core/src/command/ray_tracing.rs @@ -290,8 +290,8 @@ impl Global { let scratch_buffer_barrier = hal::BufferBarrier:: { buffer: scratch_buffer.raw(), usage: hal::StateTransition { - from: hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH, - to: hal::BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + from: BufferUses::ACCELERATION_STRUCTURE_SCRATCH, + to: BufferUses::ACCELERATION_STRUCTURE_SCRATCH, }, }; @@ -322,7 +322,7 @@ impl Global { let blas_present = !blas_storage.is_empty(); let tlas_present = !tlas_storage.is_empty(); - let cmd_buf_raw = cmd_buf_data.encoder.open(device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; let mut descriptors = Vec::new(); @@ -674,7 +674,7 @@ impl Global { let blas_present = !blas_storage.is_empty(); let tlas_present = !tlas_storage.is_empty(); - let cmd_buf_raw = cmd_buf_data.encoder.open(device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; let mut descriptors = Vec::new(); @@ -1169,24 +1169,27 @@ fn iter_buffers<'a, 'b>( { input_barriers.push(barrier); } - if mesh.transform_buffer_offset.unwrap() % wgt::TRANSFORM_BUFFER_ALIGNMENT != 0 { + + let offset = mesh.transform_buffer_offset.unwrap(); + + if offset % wgt::TRANSFORM_BUFFER_ALIGNMENT != 0 { return Err( BuildAccelerationStructureError::UnalignedTransformBufferOffset( transform_buffer.error_ident(), ), ); } - if transform_buffer.size < 48 + mesh.transform_buffer_offset.unwrap() { + if transform_buffer.size < 48 + offset { return Err(BuildAccelerationStructureError::InsufficientBufferSize( transform_buffer.error_ident(), transform_buffer.size, - 48 + mesh.transform_buffer_offset.unwrap(), + 48 + offset, )); } cmd_buf_data.buffer_memory_init_actions.extend( transform_buffer.initialization_status.read().create_action( transform_buffer, - mesh.transform_buffer_offset.unwrap()..(mesh.index_buffer_offset.unwrap() + 48), + offset..(offset + 48), MemoryInitKind::NeedsInitializedMemory, ), ); diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index c35998b807..abbbcfb46a 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -54,30 +54,20 @@ use super::{ }; use super::{DrawKind, Rect}; -/// Operation to perform to the output attachment at the start of a renderpass. -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum LoadOp { - /// Clear the output attachment with the clear color. Clearing is faster than loading. - Clear = 0, - /// Do not clear output attachment. - Load = 1, +pub use wgt::{LoadOp, StoreOp}; + +fn load_hal_ops(load: LoadOp) -> hal::AttachmentOps { + match load { + LoadOp::Load => hal::AttachmentOps::LOAD, + LoadOp::Clear(_) => hal::AttachmentOps::empty(), + } } -/// Operation to perform to the output attachment at the end of a renderpass. -#[repr(C)] -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] -pub enum StoreOp { - /// Discards the content of the render target. - /// - /// If you don't care about the contents of the target, this can be faster. - Discard = 0, - /// Store the result of the renderpass. - Store = 1, +fn store_hal_ops(store: StoreOp) -> hal::AttachmentOps { + match store { + StoreOp::Store => hal::AttachmentOps::STORE, + StoreOp::Discard => hal::AttachmentOps::empty(), + } } /// Describes an individual channel within a render pass, such as color, depth, or stencil. @@ -90,30 +80,77 @@ pub struct PassChannel { /// /// This must be clear if it is the first renderpass rendering to a swap /// chain image. - pub load_op: LoadOp, + pub load_op: Option>, /// Operation to perform to the output attachment at the end of a renderpass. - pub store_op: StoreOp, - /// If load_op is [`LoadOp::Clear`], the attachment will be cleared to this - /// color. - pub clear_value: V, + pub store_op: Option, /// If true, the relevant channel is not changed by a renderpass, and the /// corresponding attachment can be used inside the pass by other read-only /// usages. pub read_only: bool, } -impl PassChannel { +impl PassChannel> { + fn resolve( + &self, + handle_clear: impl Fn(Option) -> Result, + ) -> Result, AttachmentError> { + if self.read_only { + if self.load_op.is_some() { + return Err(AttachmentError::ReadOnlyWithLoad); + } + if self.store_op.is_some() { + return Err(AttachmentError::ReadOnlyWithStore); + } + Ok(ResolvedPassChannel::ReadOnly) + } else { + Ok(ResolvedPassChannel::Operational(wgt::Operations { + load: match self.load_op.ok_or(AttachmentError::NoLoad)? { + LoadOp::Clear(clear_value) => LoadOp::Clear(handle_clear(clear_value)?), + LoadOp::Load => LoadOp::Load, + }, + store: self.store_op.ok_or(AttachmentError::NoStore)?, + })) + } + } +} + +#[derive(Debug)] +pub enum ResolvedPassChannel { + ReadOnly, + Operational(wgt::Operations), +} + +impl ResolvedPassChannel { + fn load_op(&self) -> LoadOp { + match self { + ResolvedPassChannel::ReadOnly => LoadOp::Load, + ResolvedPassChannel::Operational(wgt::Operations { load, .. }) => *load, + } + } + + fn store_op(&self) -> StoreOp { + match self { + ResolvedPassChannel::ReadOnly => StoreOp::Store, + ResolvedPassChannel::Operational(wgt::Operations { store, .. }) => *store, + } + } + + fn clear_value(&self) -> V { + match self { + Self::Operational(wgt::Operations { + load: LoadOp::Clear(clear_value), + .. + }) => *clear_value, + _ => Default::default(), + } + } + + fn is_readonly(&self) -> bool { + matches!(self, Self::ReadOnly) + } + fn hal_ops(&self) -> hal::AttachmentOps { - let mut ops = hal::AttachmentOps::empty(); - match self.load_op { - LoadOp::Load => ops |= hal::AttachmentOps::LOAD, - LoadOp::Clear => (), - }; - match self.store_op { - StoreOp::Store => ops |= hal::AttachmentOps::STORE, - StoreOp::Discard => (), - }; - ops + load_hal_ops(self.load_op()) | store_hal_ops(self.store_op()) } } @@ -126,8 +163,14 @@ pub struct RenderPassColorAttachment { pub view: id::TextureViewId, /// The view that will receive the resolved output if multisampling is used. pub resolve_target: Option, - /// What operations will be performed on this color attachment. - pub channel: PassChannel, + /// Operation to perform to the output attachment at the start of a + /// renderpass. + /// + /// This must be clear if it is the first renderpass rendering to a swap + /// chain image. + pub load_op: LoadOp, + /// Operation to perform to the output attachment at the end of a renderpass. + pub store_op: StoreOp, } /// Describes a color attachment to a render pass. @@ -137,8 +180,26 @@ struct ArcRenderPassColorAttachment { pub view: Arc, /// The view that will receive the resolved output if multisampling is used. pub resolve_target: Option>, - /// What operations will be performed on this color attachment. - pub channel: PassChannel, + /// Operation to perform to the output attachment at the start of a + /// renderpass. + /// + /// This must be clear if it is the first renderpass rendering to a swap + /// chain image. + pub load_op: LoadOp, + /// Operation to perform to the output attachment at the end of a renderpass. + pub store_op: StoreOp, +} +impl ArcRenderPassColorAttachment { + fn hal_ops(&self) -> hal::AttachmentOps { + load_hal_ops(self.load_op) | store_hal_ops(self.store_op) + } + + fn clear_value(&self) -> Color { + match self.load_op { + LoadOp::Clear(clear_value) => clear_value, + LoadOp::Load => Color::default(), + } + } } /// Describes a depth/stencil attachment to a render pass. @@ -149,57 +210,20 @@ pub struct RenderPassDepthStencilAttachment { /// The view to use as an attachment. pub view: id::TextureViewId, /// What operations will be performed on the depth part of the attachment. - pub depth: PassChannel, + pub depth: PassChannel>, /// What operations will be performed on the stencil part of the attachment. - pub stencil: PassChannel, + pub stencil: PassChannel>, } + /// Describes a depth/stencil attachment to a render pass. #[derive(Debug)] pub struct ArcRenderPassDepthStencilAttachment { /// The view to use as an attachment. pub view: Arc, /// What operations will be performed on the depth part of the attachment. - pub depth: PassChannel, + pub depth: ResolvedPassChannel, /// What operations will be performed on the stencil part of the attachment. - pub stencil: PassChannel, -} - -impl ArcRenderPassDepthStencilAttachment { - /// Validate the given aspects' read-only flags against their load - /// and store ops. - /// - /// When an aspect is read-only, its load and store ops must be - /// `LoadOp::Load` and `StoreOp::Store`. - /// - /// On success, return a pair `(depth, stencil)` indicating - /// whether the depth and stencil passes are read-only. - fn depth_stencil_read_only( - &self, - aspects: hal::FormatAspects, - ) -> Result<(bool, bool), RenderPassErrorInner> { - let mut depth_read_only = true; - let mut stencil_read_only = true; - - if aspects.contains(hal::FormatAspects::DEPTH) { - if self.depth.read_only - && (self.depth.load_op, self.depth.store_op) != (LoadOp::Load, StoreOp::Store) - { - return Err(RenderPassErrorInner::InvalidDepthOps); - } - depth_read_only = self.depth.read_only; - } - - if aspects.contains(hal::FormatAspects::STENCIL) { - if self.stencil.read_only - && (self.stencil.load_op, self.stencil.store_op) != (LoadOp::Load, StoreOp::Store) - { - return Err(RenderPassErrorInner::InvalidStencilOps); - } - stencil_read_only = self.stencil.read_only; - } - - Ok((depth_read_only, stencil_read_only)) - } + pub stencil: ResolvedPassChannel, } /// Describes the attachments of a render pass. @@ -569,6 +593,25 @@ pub enum ColorAttachmentError { TooManyBytesPerSample { total: u32, limit: u32 }, } +#[derive(Clone, Debug, Error)] +#[non_exhaustive] +pub enum AttachmentError { + #[error("The format of the depth-stencil attachment ({0:?}) is not a depth-or-stencil format")] + InvalidDepthStencilAttachmentFormat(wgt::TextureFormat), + #[error("Read-only attachment with load")] + ReadOnlyWithLoad, + #[error("Read-only attachment with store")] + ReadOnlyWithStore, + #[error("Attachment without load")] + NoLoad, + #[error("Attachment without store")] + NoStore, + #[error("LoadOp is `Clear` but no clear value was provided")] + NoClearValue, + #[error("Clear value ({0}) must be between 0.0 and 1.0, inclusive")] + ClearValueOutOfRange(f32), +} + /// Error encountered when performing a render pass. #[derive(Clone, Debug, Error)] pub enum RenderPassErrorInner { @@ -580,8 +623,6 @@ pub enum RenderPassErrorInner { Encoder(#[from] CommandEncoderError), #[error("Parent encoder is invalid")] InvalidParentEncoder, - #[error("The format of the depth-stencil attachment ({0:?}) is not a depth-stencil format")] - InvalidDepthStencilAttachmentFormat(wgt::TextureFormat), #[error("The format of the {location} ({format:?}) is not resolvable")] UnsupportedResolveTargetFormat { location: AttachmentErrorLocation, @@ -635,10 +676,9 @@ pub enum RenderPassErrorInner { MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error("Indirect buffer offset {0:?} is not a multiple of 4")] UnalignedIndirectBufferOffset(BufferAddress), - #[error("Indirect draw uses bytes {offset}..{end_offset} {} which overruns indirect buffer of size {buffer_size}", - count.map_or_else(String::new, |v| format!("(using count {v})")))] + #[error("Indirect draw uses bytes {offset}..{end_offset} using count {count} which overruns indirect buffer of size {buffer_size}")] IndirectBufferOverrun { - count: Option, + count: u32, offset: u64, end_offset: u64, buffer_size: u64, @@ -771,12 +811,13 @@ struct RenderPassInfo<'d> { impl<'d> RenderPassInfo<'d> { fn add_pass_texture_init_actions( - channel: &PassChannel, + load_op: LoadOp, + store_op: StoreOp, texture_memory_actions: &mut CommandBufferTextureMemoryActions, view: &TextureView, pending_discard_init_fixups: &mut SurfacesInDiscardState, ) { - if channel.load_op == LoadOp::Load { + if matches!(load_op, LoadOp::Load) { pending_discard_init_fixups.extend(texture_memory_actions.register_init_action( &TextureInitTrackerAction { texture: view.parent.clone(), @@ -785,14 +826,14 @@ impl<'d> RenderPassInfo<'d> { kind: MemoryInitKind::NeedsInitializedMemory, }, )); - } else if channel.store_op == StoreOp::Store { + } else if store_op == StoreOp::Store { // Clear + Store texture_memory_actions.register_implicit_init( &view.parent, TextureInitRange::from(view.selector.clone()), ); } - if channel.store_op == StoreOp::Discard { + if store_op == StoreOp::Discard { // the discard happens at the *end* of a pass, but recording the // discard right away be alright since the texture can't be used // during the pass anyways @@ -907,30 +948,26 @@ impl<'d> RenderPassInfo<'d> { if let Some(at) = depth_stencil_attachment.as_ref() { let view = &at.view; - view.same_device(device)?; check_multiview(view)?; add_view(view, AttachmentErrorLocation::Depth)?; let ds_aspects = view.desc.aspects(); - if ds_aspects.contains(hal::FormatAspects::COLOR) { - return Err(RenderPassErrorInner::InvalidDepthStencilAttachmentFormat( - view.desc.format, - )); - } if !ds_aspects.contains(hal::FormatAspects::STENCIL) - || (at.stencil.load_op == at.depth.load_op - && at.stencil.store_op == at.depth.store_op) + || (at.stencil.load_op().eq_variant(at.depth.load_op()) + && at.stencil.store_op() == at.depth.store_op()) { Self::add_pass_texture_init_actions( - &at.depth, + at.depth.load_op(), + at.depth.store_op(), texture_memory_actions, view, &mut pending_discard_init_fixups, ); } else if !ds_aspects.contains(hal::FormatAspects::DEPTH) { Self::add_pass_texture_init_actions( - &at.stencil, + at.stencil.load_op(), + at.stencil.store_op(), texture_memory_actions, view, &mut pending_discard_init_fixups, @@ -958,7 +995,7 @@ impl<'d> RenderPassInfo<'d> { // NeedsInitializedMemory should know that it doesn't need to // clear the aspect that was set to C) let need_init_beforehand = - at.depth.load_op == LoadOp::Load || at.stencil.load_op == LoadOp::Load; + at.depth.load_op() == LoadOp::Load || at.stencil.load_op() == LoadOp::Load; if need_init_beforehand { pending_discard_init_fixups.extend( texture_memory_actions.register_init_action(&TextureInitTrackerAction { @@ -977,7 +1014,7 @@ impl<'d> RenderPassInfo<'d> { // it isn't already set to NeedsInitializedMemory). // // (possible optimization: Delay and potentially drop this zeroing) - if at.depth.store_op != at.stencil.store_op { + if at.depth.store_op() != at.stencil.store_op() { if !need_init_beforehand { texture_memory_actions.register_implicit_init( &view.parent, @@ -985,14 +1022,14 @@ impl<'d> RenderPassInfo<'d> { ); } divergent_discarded_depth_stencil_aspect = Some(( - if at.depth.store_op == StoreOp::Discard { + if at.depth.store_op() == StoreOp::Discard { wgt::TextureAspect::DepthOnly } else { wgt::TextureAspect::StencilOnly }, view.clone(), )); - } else if at.depth.store_op == StoreOp::Discard { + } else if at.depth.store_op() == StoreOp::Discard { // Both are discarded using the regular path. discarded_surfaces.push(TextureSurfaceDiscard { texture: view.parent.clone(), @@ -1002,7 +1039,8 @@ impl<'d> RenderPassInfo<'d> { } } - (is_depth_read_only, is_stencil_read_only) = at.depth_stencil_read_only(ds_aspects)?; + is_depth_read_only = at.depth.is_readonly(); + is_stencil_read_only = at.stencil.is_readonly(); let usage = if is_depth_read_only && is_stencil_read_only @@ -1024,7 +1062,7 @@ impl<'d> RenderPassInfo<'d> { }, depth_ops: at.depth.hal_ops(), stencil_ops: at.stencil.hal_ops(), - clear_value: (at.depth.clear_value, at.stencil.clear_value), + clear_value: (at.depth.clear_value(), at.stencil.clear_value()), }); } @@ -1059,7 +1097,8 @@ impl<'d> RenderPassInfo<'d> { } Self::add_pass_texture_init_actions( - &at.channel, + at.load_op, + at.store_op, texture_memory_actions, color_view, &mut pending_discard_init_fixups, @@ -1135,8 +1174,8 @@ impl<'d> RenderPassInfo<'d> { usage: hal::TextureUses::COLOR_TARGET, }, resolve_target: hal_resolve_target, - ops: at.channel.hal_ops(), - clear_value: at.channel.clear_value, + ops: at.hal_ops(), + clear_value: at.clear_value(), })); } @@ -1351,7 +1390,8 @@ impl Global { if let Some(RenderPassColorAttachment { view: view_id, resolve_target, - channel, + load_op, + store_op, }) = color_attachment { let view = texture_views.get(*view_id).get()?; @@ -1371,7 +1411,8 @@ impl Global { .push(Some(ArcRenderPassColorAttachment { view, resolve_target, - channel: channel.clone(), + load_op: *load_op, + store_op: *store_op, })); } else { arc_desc.color_attachments.push(None); @@ -1379,14 +1420,39 @@ impl Global { } arc_desc.depth_stencil_attachment = + // https://gpuweb.github.io/gpuweb/#abstract-opdef-gpurenderpassdepthstencilattachment-gpurenderpassdepthstencilattachment-valid-usage if let Some(depth_stencil_attachment) = desc.depth_stencil_attachment { let view = texture_views.get(depth_stencil_attachment.view).get()?; view.same_device(device)?; + let format = view.desc.format; + if !format.is_depth_stencil_format() { + return Err(CommandEncoderError::InvalidAttachment(AttachmentError::InvalidDepthStencilAttachmentFormat( + view.desc.format, + ))); + } + Some(ArcRenderPassDepthStencilAttachment { view, - depth: depth_stencil_attachment.depth.clone(), - stencil: depth_stencil_attachment.stencil.clone(), + depth: if format.has_depth_aspect() { + depth_stencil_attachment.depth.resolve(|clear| if let Some(clear) = clear { + // If this.depthLoadOp is "clear", this.depthClearValue must be provided and must be between 0.0 and 1.0, inclusive. + if !(0.0..=1.0).contains(&clear) { + Err(AttachmentError::ClearValueOutOfRange(clear)) + } else { + Ok(clear) + } + } else { + Err(AttachmentError::NoClearValue) + })? + } else { + ResolvedPassChannel::ReadOnly + }, + stencil: if format.has_stencil_aspect() { + depth_stencil_attachment.stencil.resolve(|clear| Ok(clear.unwrap_or_default()))? + } else { + ResolvedPassChannel::ReadOnly + }, }) } else { None @@ -1537,8 +1603,6 @@ impl Global { let device = &cmd_buf.device; let snatch_guard = &device.snatchable_lock.read(); - let hal_label = hal_label(base.label.as_deref(), device.instance_flags); - let (scope, pending_discard_init_fixups) = { device.check_is_valid().map_pass_err(pass_scope)?; @@ -1551,14 +1615,14 @@ impl Global { // We automatically keep extending command buffers over time, and because // we want to insert a command buffer _before_ what we're about to record, // we need to make sure to close the previous one. - encoder.close(&cmd_buf.device).map_pass_err(pass_scope)?; + encoder.close_if_open().map_pass_err(pass_scope)?; encoder - .open_pass(hal_label, &cmd_buf.device) + .open_pass(base.label.as_deref()) .map_pass_err(pass_scope)?; let info = RenderPassInfo::start( device, - hal_label, + hal_label(base.label.as_deref(), device.instance_flags), pass.color_attachments.take(), pass.depth_stencil_attachment.take(), pass.timestamp_writes.take(), @@ -1722,14 +1786,14 @@ impl Global { ) .map_pass_err(scope)?; } - ArcRenderCommand::MultiDrawIndirect { + ArcRenderCommand::DrawIndirect { buffer, offset, count, indexed, } => { let scope = PassErrorScope::Draw { - kind: if count.is_some() { + kind: if count != 1 { DrawKind::MultiDrawIndirect } else { DrawKind::DrawIndirect @@ -1857,7 +1921,7 @@ impl Global { .finish(state.raw_encoder, state.snatch_guard) .map_pass_err(pass_scope)?; - encoder.close(&cmd_buf.device).map_pass_err(pass_scope)?; + encoder.close().map_pass_err(pass_scope)?; (trackers, pending_discard_init_fixups) }; @@ -1865,7 +1929,9 @@ impl Global { let tracker = &mut cmd_buf_data.trackers; { - let transit = encoder.open(&cmd_buf.device).map_pass_err(pass_scope)?; + let transit = encoder + .open_pass(Some("(wgpu internal) Pre Pass")) + .map_pass_err(pass_scope)?; fixup_discarded_surfaces( pending_discard_init_fixups.into_iter(), @@ -1880,9 +1946,7 @@ impl Global { CommandBuffer::insert_barriers_from_scope(transit, tracker, &scope, snatch_guard); } - encoder - .close_and_swap(&cmd_buf.device) - .map_pass_err(pass_scope)?; + encoder.close_and_swap().map_pass_err(pass_scope)?; cmd_buf_data_guard.mark_successful(); Ok(()) @@ -2402,7 +2466,7 @@ fn multi_draw_indirect( cmd_buf: &Arc, indirect_buffer: Arc, offset: u64, - count: Option, + count: u32, indexed: bool, ) -> Result<(), RenderPassErrorInner> { api_log!( @@ -2417,7 +2481,7 @@ fn multi_draw_indirect( true => size_of::(), }; - if count.is_some() { + if count != 1 { state .device .require_features(wgt::Features::MULTI_DRAW_INDIRECT)?; @@ -2437,13 +2501,11 @@ fn multi_draw_indirect( indirect_buffer.check_usage(BufferUsages::INDIRECT)?; let indirect_raw = indirect_buffer.try_raw(state.snatch_guard)?; - let actual_count = count.map_or(1, |c| c.get()); - if offset % 4 != 0 { return Err(RenderPassErrorInner::UnalignedIndirectBufferOffset(offset)); } - let end_offset = offset + stride as u64 * actual_count as u64; + let end_offset = offset + stride as u64 * count as u64; if end_offset > indirect_buffer.size { return Err(RenderPassErrorInner::IndirectBufferOverrun { count, @@ -2463,14 +2525,12 @@ fn multi_draw_indirect( match indexed { false => unsafe { - state - .raw_encoder - .draw_indirect(indirect_raw, offset, actual_count); + state.raw_encoder.draw_indirect(indirect_raw, offset, count); }, true => unsafe { state .raw_encoder - .draw_indexed_indirect(indirect_raw, offset, actual_count); + .draw_indexed_indirect(indirect_raw, offset, count); }, } Ok(()) @@ -2534,7 +2594,7 @@ fn multi_draw_indirect_count( let end_offset = offset + stride * max_count as u64; if end_offset > indirect_buffer.size { return Err(RenderPassErrorInner::IndirectBufferOverrun { - count: None, + count: 1, offset, end_offset, buffer_size: indirect_buffer.size, @@ -3038,10 +3098,10 @@ impl Global { }; let base = pass.base_mut(scope)?; - base.commands.push(ArcRenderCommand::MultiDrawIndirect { + base.commands.push(ArcRenderCommand::DrawIndirect { buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, offset, - count: None, + count: 1, indexed: false, }); @@ -3060,10 +3120,10 @@ impl Global { }; let base = pass.base_mut(scope)?; - base.commands.push(ArcRenderCommand::MultiDrawIndirect { + base.commands.push(ArcRenderCommand::DrawIndirect { buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, offset, - count: None, + count: 1, indexed: true, }); @@ -3083,10 +3143,10 @@ impl Global { }; let base = pass.base_mut(scope)?; - base.commands.push(ArcRenderCommand::MultiDrawIndirect { + base.commands.push(ArcRenderCommand::DrawIndirect { buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, offset, - count: NonZeroU32::new(count), + count, indexed: false, }); @@ -3106,10 +3166,10 @@ impl Global { }; let base = pass.base_mut(scope)?; - base.commands.push(ArcRenderCommand::MultiDrawIndirect { + base.commands.push(ArcRenderCommand::DrawIndirect { buffer: self.resolve_render_pass_buffer_id(scope, buffer_id)?, offset, - count: NonZeroU32::new(count), + count, indexed: true, }); diff --git a/wgpu-core/src/command/render_command.rs b/wgpu-core/src/command/render_command.rs index d4e2689d27..549d140bb5 100644 --- a/wgpu-core/src/command/render_command.rs +++ b/wgpu-core/src/command/render_command.rs @@ -6,7 +6,7 @@ use crate::{ }; use wgt::{BufferAddress, BufferSize, Color}; -use std::{num::NonZeroU32, sync::Arc}; +use std::sync::Arc; use super::{Rect, RenderBundle}; @@ -82,11 +82,10 @@ pub enum RenderCommand { base_vertex: i32, first_instance: u32, }, - MultiDrawIndirect { + DrawIndirect { buffer_id: id::BufferId, offset: BufferAddress, - /// Count of `None` represents a non-multi call. - count: Option, + count: u32, indexed: bool, }, MultiDrawIndirectCount { @@ -311,16 +310,16 @@ impl RenderCommand { first_instance, }, - RenderCommand::MultiDrawIndirect { + RenderCommand::DrawIndirect { buffer_id, offset, count, indexed, - } => ArcRenderCommand::MultiDrawIndirect { + } => ArcRenderCommand::DrawIndirect { buffer: buffers_guard.get(buffer_id).get().map_err(|e| { RenderPassError { scope: PassErrorScope::Draw { - kind: if count.is_some() { + kind: if count != 1 { DrawKind::MultiDrawIndirect } else { DrawKind::DrawIndirect @@ -459,11 +458,10 @@ pub enum ArcRenderCommand { base_vertex: i32, first_instance: u32, }, - MultiDrawIndirect { + DrawIndirect { buffer: Arc, offset: BufferAddress, - /// Count of `None` represents a non-multi call. - count: Option, + count: u32, indexed: bool, }, MultiDrawIndirectCount { diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 3ecff5ca43..291c44bd2c 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -444,7 +444,7 @@ fn handle_texture_init( // In rare cases we may need to insert an init operation immediately onto the command buffer. if !immediate_inits.is_empty() { - let cmd_buf_raw = cmd_buf_data.encoder.open(device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; for init in immediate_inits { clear_texture( &init.texture, @@ -678,7 +678,7 @@ impl Global { dst_offset: destination_offset, size: wgt::BufferSize::new(size).unwrap(), }; - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; let barriers = src_barrier .into_iter() .chain(dst_barrier) @@ -838,7 +838,7 @@ impl Global { }) .collect::>(); - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_textures(&dst_barrier); cmd_buf_raw.transition_buffers(src_barrier.as_slice()); @@ -1004,7 +1004,7 @@ impl Global { } }) .collect::>(); - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_buffers(dst_barrier.as_slice()); cmd_buf_raw.transition_textures(&src_barrier); @@ -1168,7 +1168,7 @@ impl Global { } }) .collect::>(); - let cmd_buf_raw = cmd_buf_data.encoder.open(&cmd_buf.device)?; + let cmd_buf_raw = cmd_buf_data.encoder.open()?; unsafe { cmd_buf_raw.transition_textures(&barriers); cmd_buf_raw.copy_texture_to_texture( diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 3b2ab44c32..eff2e811be 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -331,8 +331,6 @@ impl Global { return (id, None); }; - log::error!("Device::create_texture error: {error}"); - let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); (id, Some(error)) } @@ -376,8 +374,6 @@ impl Global { return (id, None); }; - log::error!("Device::create_texture error: {error}"); - let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); (id, Some(error)) } @@ -487,7 +483,6 @@ impl Global { return (id, None); }; - log::error!("Texture::create_view({texture_id:?}) error: {error}"); let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); (id, Some(error)) } @@ -942,8 +937,6 @@ impl Global { return (id, None); }; - log::error!("Device::create_shader_module error: {error}"); - let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); (id, Some(error)) } @@ -993,8 +986,6 @@ impl Global { return (id, None); }; - log::error!("Device::create_shader_module_spirv error: {error}"); - let id = fid.assign(Fallible::Invalid(Arc::new(desc.label.to_string()))); (id, Some(error)) } @@ -1375,8 +1366,6 @@ impl Global { } } - log::error!("Device::create_render_pipeline error: {error}"); - (id, Some(error)) } @@ -2174,7 +2163,6 @@ impl Global { if let Some(callback) = operation.callback.take() { callback(Err(err.clone())); } - log::error!("Buffer::map_async error: {err}"); Err(err) } } diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index bf202702e9..6811d86dcb 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -340,8 +340,7 @@ pub struct MissingFeatures(pub wgt::Features); #[derive(Clone, Debug, Error)] #[error( - "Downlevel flags {0:?} are required but not supported on the device.\n{}", - DOWNLEVEL_ERROR_MESSAGE + "Downlevel flags {0:?} are required but not supported on the device.\n{DOWNLEVEL_ERROR_MESSAGE}", )] pub struct MissingDownlevelFlags(pub wgt::DownlevelFlags); diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 6ba9728e9e..cd6731ae04 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -11,7 +11,6 @@ use crate::{ device::{DeviceError, WaitIdleError}, get_lowest_common_denom, global::Global, - hal_label, id::{self, QueueId}, init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange}, lock::{rank, Mutex, MutexGuard, RwLockWriteGuard}, @@ -1165,14 +1164,7 @@ impl Queue { }; // execute resource transitions - if let Err(e) = unsafe { - baked.encoder.raw.begin_encoding(hal_label( - Some("(wgpu internal) Transit"), - self.device.instance_flags, - )) - } - .map_err(|e| self.device.handle_hal_error(e)) - { + if let Err(e) = baked.encoder.open_pass(Some("(wgpu internal) Transit")) { break 'error Err(e.into()); } @@ -1199,20 +1191,15 @@ impl Queue { &snatch_guard, ); - let transit = unsafe { baked.encoder.raw.end_encoding().unwrap() }; - baked.encoder.list.insert(0, transit); + if let Err(e) = baked.encoder.close_and_push_front() { + break 'error Err(e.into()); + } // Transition surface textures into `Present` state. // Note: we could technically do it after all of the command buffers, // but here we have a command encoder by hand, so it's easier to use it. if !used_surface_textures.is_empty() { - if let Err(e) = unsafe { - baked.encoder.raw.begin_encoding(hal_label( - Some("(wgpu internal) Present"), - self.device.instance_flags, - )) - } - .map_err(|e| self.device.handle_hal_error(e)) + if let Err(e) = baked.encoder.open_pass(Some("(wgpu internal) Present")) { break 'error Err(e.into()); } @@ -1223,11 +1210,12 @@ impl Queue { &snatch_guard, ) .collect::>(); - let present = unsafe { + unsafe { baked.encoder.raw.transition_textures(&texture_barriers); - baked.encoder.raw.end_encoding().unwrap() }; - baked.encoder.list.push(present); + if let Err(e) = baked.encoder.close() { + break 'error Err(e.into()); + } used_surface_textures = track::TextureUsageScope::default(); } @@ -1568,8 +1556,7 @@ fn validate_command_buffer( TextureInner::Native { .. } => false, TextureInner::Surface { .. } => { // Compare the Arcs by pointer as Textures don't implement Eq. - submit_surface_textures_owned - .insert(Arc::as_ptr(&texture), texture.clone()); + submit_surface_textures_owned.insert(Arc::as_ptr(texture), texture.clone()); true } @@ -1577,7 +1564,7 @@ fn validate_command_buffer( if should_extend { unsafe { used_surface_textures - .merge_single(&texture, None, hal::TextureUses::PRESENT) + .merge_single(texture, None, hal::TextureUses::PRESENT) .unwrap(); }; } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 937032c0ee..838e822d50 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -1086,6 +1086,39 @@ impl Device { .saturating_sub(desc.range.base_array_layer), }); + let resolved_usage = { + let usage = desc.usage.unwrap_or(wgt::TextureUsages::empty()); + if usage.is_empty() { + texture.desc.usage + } else if texture.desc.usage.contains(usage) { + usage + } else { + return Err(resource::CreateTextureViewError::InvalidTextureViewUsage { + view: usage, + texture: texture.desc.usage, + }); + } + }; + + let allowed_format_usages = self + .describe_format_features(resolved_format)? + .allowed_usages; + if resolved_usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) + && !allowed_format_usages.contains(wgt::TextureUsages::RENDER_ATTACHMENT) + { + return Err( + resource::CreateTextureViewError::TextureViewFormatNotRenderable(resolved_format), + ); + } + + if resolved_usage.contains(wgt::TextureUsages::STORAGE_BINDING) + && !allowed_format_usages.contains(wgt::TextureUsages::STORAGE_BINDING) + { + return Err( + resource::CreateTextureViewError::TextureViewFormatNotStorage(resolved_format), + ); + } + // validate TextureViewDescriptor let aspects = hal::FormatAspects::new(texture.desc.format, desc.range.aspect); @@ -1207,12 +1240,8 @@ impl Device { // https://gpuweb.github.io/gpuweb/#abstract-opdef-renderable-texture-view let render_extent = 'error: { - if !texture - .desc - .usage - .contains(wgt::TextureUsages::RENDER_ATTACHMENT) - { - break 'error Err(TextureViewNotRenderableReason::Usage(texture.desc.usage)); + if !resolved_usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { + break 'error Err(TextureViewNotRenderableReason::Usage(resolved_usage)); } if !(resolved_dimension == TextureViewDimension::D2 @@ -1309,6 +1338,7 @@ impl Device { texture_format: texture.desc.format, format: resolved_format, dimension: resolved_dimension, + usage: resolved_usage, range: resolved_range, }, format_features: texture.format_features, @@ -1539,7 +1569,7 @@ impl Device { }); let hal_desc = hal::ShaderModuleDescriptor { label: desc.label.to_hal(self.instance_flags), - runtime_checks: desc.shader_bound_checks.runtime_checks(), + runtime_checks: desc.runtime_checks, }; let raw = match unsafe { self.raw().create_shader_module(&hal_desc, hal_shader) } { Ok(raw) => raw, @@ -1579,7 +1609,7 @@ impl Device { self.require_features(wgt::Features::SPIRV_SHADER_PASSTHROUGH)?; let hal_desc = hal::ShaderModuleDescriptor { label: desc.label.to_hal(self.instance_flags), - runtime_checks: desc.shader_bound_checks.runtime_checks(), + runtime_checks: desc.runtime_checks, }; let hal_shader = hal::ShaderInput::SpirV(source); let raw = match unsafe { self.raw().create_shader_module(&hal_desc, hal_shader) } { @@ -2102,7 +2132,7 @@ impl Device { { view.same_device(self)?; - let (pub_usage, internal_use) = self.texture_use_parameters( + let internal_use = self.texture_use_parameters( binding, decl, view, @@ -2112,7 +2142,6 @@ impl Device { used.views.insert_single(view.clone(), internal_use); let texture = &view.parent; - texture.check_usage(pub_usage)?; used_texture_ranges.push(TextureInitTrackerAction { texture: texture.clone(), @@ -2411,7 +2440,7 @@ impl Device { decl: &wgt::BindGroupLayoutEntry, view: &TextureView, expected: &'static str, - ) -> Result<(wgt::TextureUsages, hal::TextureUses), binding_model::CreateBindGroupError> { + ) -> Result { use crate::binding_model::CreateBindGroupError as Error; if view .desc @@ -2470,10 +2499,8 @@ impl Device { view_dimension: view.desc.dimension, }); } - Ok(( - wgt::TextureUsages::TEXTURE_BINDING, - hal::TextureUses::RESOURCE, - )) + view.check_usage(wgt::TextureUsages::TEXTURE_BINDING)?; + Ok(hal::TextureUses::RESOURCE) } wgt::BindingType::StorageTexture { access, @@ -2547,7 +2574,8 @@ impl Device { hal::TextureUses::STORAGE_ATOMIC } }; - Ok((wgt::TextureUsages::STORAGE_BINDING, internal_use)) + view.check_usage(wgt::TextureUsages::STORAGE_BINDING)?; + Ok(internal_use) } _ => Err(Error::WrongBindingType { binding, @@ -2635,10 +2663,17 @@ impl Device { .map(|bgl| bgl.raw()) .collect::>(); + let additional_flags = if cfg!(feature = "indirect-validation") { + hal::PipelineLayoutFlags::INDIRECT_BUILTIN_UPDATE + } else { + hal::PipelineLayoutFlags::empty() + }; + let hal_desc = hal::PipelineLayoutDescriptor { label: desc.label.to_hal(self.instance_flags), flags: hal::PipelineLayoutFlags::FIRST_VERTEX_INSTANCE - | hal::PipelineLayoutFlags::NUM_WORK_GROUPS, + | hal::PipelineLayoutFlags::NUM_WORK_GROUPS + | additional_flags, bind_group_layouts: &raw_bind_group_layouts, push_constant_ranges: desc.push_constant_ranges.as_ref(), }; @@ -2663,11 +2698,11 @@ impl Device { pub(crate) fn derive_pipeline_layout( self: &Arc, - mut derived_group_layouts: ArrayVec, + mut derived_group_layouts: Box>, ) -> Result, pipeline::ImplicitLayoutError> { while derived_group_layouts .last() - .map_or(false, |map| map.is_empty()) + .is_some_and(|map| map.is_empty()) { derived_group_layouts.pop(); } @@ -2889,18 +2924,8 @@ impl Device { let mut shader_expects_dual_source_blending = false; let mut pipeline_expects_dual_source_blending = false; for (i, vb_state) in desc.vertex.buffers.iter().enumerate() { - let mut last_stride = 0; - for attribute in vb_state.attributes.iter() { - last_stride = last_stride.max(attribute.offset + attribute.format.size()); - } - vertex_steps.push(pipeline::VertexStep { - stride: vb_state.array_stride, - last_stride, - mode: vb_state.step_mode, - }); - if vb_state.attributes.is_empty() { - continue; - } + // https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gpuvertexbufferlayout + if vb_state.array_stride > self.limits.max_vertex_buffer_array_stride as u64 { return Err(pipeline::CreateRenderPipelineError::VertexStrideTooLarge { index: i as u32, @@ -2914,6 +2939,54 @@ impl Device { stride: vb_state.array_stride, }); } + + let max_stride = if vb_state.array_stride == 0 { + self.limits.max_vertex_buffer_array_stride as u64 + } else { + vb_state.array_stride + }; + let mut last_stride = 0; + for attribute in vb_state.attributes.iter() { + let attribute_stride = attribute.offset + attribute.format.size(); + if attribute_stride > max_stride { + return Err( + pipeline::CreateRenderPipelineError::VertexAttributeStrideTooLarge { + location: attribute.shader_location, + given: attribute_stride as u32, + limit: max_stride as u32, + }, + ); + } + + let required_offset_alignment = attribute.format.size().min(4); + if attribute.offset % required_offset_alignment != 0 { + return Err( + pipeline::CreateRenderPipelineError::InvalidVertexAttributeOffset { + location: attribute.shader_location, + offset: attribute.offset, + }, + ); + } + + if attribute.shader_location >= self.limits.max_vertex_attributes { + return Err( + pipeline::CreateRenderPipelineError::TooManyVertexAttributes { + given: attribute.shader_location, + limit: self.limits.max_vertex_attributes, + }, + ); + } + + last_stride = last_stride.max(attribute_stride); + } + vertex_steps.push(pipeline::VertexStep { + stride: vb_state.array_stride, + last_stride, + mode: vb_state.step_mode, + }); + if vb_state.attributes.is_empty() { + continue; + } vertex_buffers.push(hal::VertexBufferLayout { array_stride: vb_state.array_stride, step_mode: vb_state.step_mode, @@ -3660,12 +3733,12 @@ impl Device { // During these iterations, we discard all errors. We don't care! let trackers = self.trackers.lock(); for buffer in trackers.buffers.used_resources() { - if let Some(buffer) = Weak::upgrade(&buffer) { + if let Some(buffer) = Weak::upgrade(buffer) { let _ = buffer.destroy(); } } for texture in trackers.textures.used_resources() { - if let Some(texture) = Weak::upgrade(&texture) { + if let Some(texture) = Weak::upgrade(texture) { let _ = texture.destroy(); } } diff --git a/wgpu-core/src/global.rs b/wgpu-core/src/global.rs index bf68ed2f1d..0be903382c 100644 --- a/wgpu-core/src/global.rs +++ b/wgpu-core/src/global.rs @@ -31,7 +31,7 @@ pub struct Global { } impl Global { - pub fn new(name: &str, instance_desc: wgt::InstanceDescriptor) -> Self { + pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { profiling::scope!("Global::new"); Self { instance: Instance::new(name, instance_desc), diff --git a/wgpu-core/src/indirect_validation.rs b/wgpu-core/src/indirect_validation.rs index 35a95f8bbf..3045965435 100644 --- a/wgpu-core/src/indirect_validation.rs +++ b/wgpu-core/src/indirect_validation.rs @@ -123,7 +123,7 @@ impl IndirectValidation { }); let hal_desc = hal::ShaderModuleDescriptor { label: None, - runtime_checks: false, + runtime_checks: wgt::ShaderRuntimeChecks::unchecked(), }; let module = unsafe { device.create_shader_module(&hal_desc, hal_shader) }.map_err(|error| { @@ -180,7 +180,7 @@ impl IndirectValidation { let pipeline_layout_desc = hal::PipelineLayoutDescriptor { label: None, - flags: hal::PipelineLayoutFlags::FIRST_VERTEX_INSTANCE, + flags: hal::PipelineLayoutFlags::empty(), bind_group_layouts: &[ dst_bind_group_layout.as_ref(), src_bind_group_layout.as_ref(), diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 81af91e7b8..75340ddb78 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -63,7 +63,7 @@ pub struct Instance { } impl Instance { - pub fn new(name: &str, instance_desc: wgt::InstanceDescriptor) -> Self { + pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { fn init( _: A, instance_desc: &wgt::InstanceDescriptor, @@ -99,13 +99,13 @@ impl Instance { let mut instance_per_backend = Vec::new(); #[cfg(vulkan)] - init(hal::api::Vulkan, &instance_desc, &mut instance_per_backend); + init(hal::api::Vulkan, instance_desc, &mut instance_per_backend); #[cfg(metal)] - init(hal::api::Metal, &instance_desc, &mut instance_per_backend); + init(hal::api::Metal, instance_desc, &mut instance_per_backend); #[cfg(dx12)] - init(hal::api::Dx12, &instance_desc, &mut instance_per_backend); + init(hal::api::Dx12, instance_desc, &mut instance_per_backend); #[cfg(gles)] - init(hal::api::Gles, &instance_desc, &mut instance_per_backend); + init(hal::api::Gles, instance_desc, &mut instance_per_backend); Self { name: name.to_string(), diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index 07e08cb352..32e2378029 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -41,7 +41,7 @@ pub enum ShaderModuleSource<'a> { pub struct ShaderModuleDescriptor<'a> { pub label: Label<'a>, #[cfg_attr(feature = "serde", serde(default))] - pub shader_bound_checks: wgt::ShaderBoundChecks, + pub runtime_checks: wgt::ShaderRuntimeChecks, } #[derive(Debug)] @@ -475,6 +475,12 @@ pub enum CreateRenderPipelineError { TooManyVertexAttributes { given: u32, limit: u32 }, #[error("Vertex buffer {index} stride {given} exceeds the limit {limit}")] VertexStrideTooLarge { index: u32, given: u32, limit: u32 }, + #[error("Vertex attribute at location {location} stride {given} exceeds the limit {limit}")] + VertexAttributeStrideTooLarge { + location: wgt::ShaderLocation, + given: u32, + limit: u32, + }, #[error("Vertex buffer {index} stride {stride} does not respect `VERTEX_STRIDE_ALIGNMENT`")] UnalignedVertexStride { index: u32, diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 4846c257e2..0b13ad3bd0 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -570,10 +570,7 @@ impl Buffer { }; Ok(()) } - Err(e) => { - log::error!("Mapping failed: {e}"); - Err(e) - } + Err(e) => Err(e), } } else { *self.map_state.lock() = BufferMapState::Active { @@ -1060,6 +1057,7 @@ impl Texture { bind_groups: Mutex::new(rank::TEXTURE_BIND_GROUPS, WeakVec::new()), } } + /// Checks that the given texture usage contains the required texture usage, /// returns an error otherwise. pub(crate) fn check_usage( @@ -1380,7 +1378,7 @@ impl Global { if let Ok(mut cmd_buf_data_guard) = cmd_buf_data_guard { let cmd_buf_raw = cmd_buf_data_guard .encoder - .open(&cmd_buf.device) + .open() .ok() .and_then(|encoder| encoder.as_any_mut().downcast_mut()); let ret = hal_command_encoder_callback(cmd_buf_raw); @@ -1498,8 +1496,8 @@ pub enum CreateTextureError { )] InvalidMipLevelCount { requested: u32, maximum: u32 }, #[error( - "Texture usages {0:?} are not allowed on a texture of type {1:?}{}", - if *.2 { " due to downlevel restrictions" } else { "" } + "Texture usages {0:?} are not allowed on a texture of type {1:?}{downlevel_suffix}", + downlevel_suffix = if *.2 { " due to downlevel restrictions" } else { "" } )] InvalidFormatUsages(wgt::TextureUsages, wgt::TextureFormat, bool), #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")] @@ -1552,6 +1550,9 @@ pub struct TextureViewDescriptor<'a> { /// - For 2D textures it must be one of `D2`, `D2Array`, `Cube`, or `CubeArray`. /// - For 3D textures it must be `D3`. pub dimension: Option, + /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. + /// If not provided, defaults to the full set of usage flags of the texture. + pub usage: Option, /// Range within the texture that is accessible via this view. pub range: wgt::ImageSubresourceRange, } @@ -1560,6 +1561,7 @@ pub struct TextureViewDescriptor<'a> { pub(crate) struct HalTextureViewDescriptor { pub texture_format: wgt::TextureFormat, pub format: wgt::TextureFormat, + pub usage: wgt::TextureUsages, pub dimension: wgt::TextureViewDimension, pub range: wgt::ImageSubresourceRange, } @@ -1631,6 +1633,23 @@ impl TextureView { .map(|it| it.as_ref()) .ok_or_else(|| DestroyedResourceError(self.error_ident())) } + + /// Checks that the given texture usage contains the required texture usage, + /// returns an error otherwise. + pub(crate) fn check_usage( + &self, + expected: wgt::TextureUsages, + ) -> Result<(), MissingTextureUsageError> { + if self.desc.usage.contains(expected) { + Ok(()) + } else { + Err(MissingTextureUsageError { + res: self.error_ident(), + actual: self.desc.usage, + expected, + }) + } + } } #[derive(Clone, Debug, Error)] @@ -1645,6 +1664,15 @@ pub enum CreateTextureViewError { view: wgt::TextureViewDimension, texture: wgt::TextureDimension, }, + #[error("Texture view format `{0:?}` is not renderable")] + TextureViewFormatNotRenderable(wgt::TextureFormat), + #[error("Texture view format `{0:?}` is not storage bindable")] + TextureViewFormatNotStorage(wgt::TextureFormat), + #[error("Invalid texture view usage `{view:?}` with texture of usage `{texture:?}`")] + InvalidTextureViewUsage { + view: wgt::TextureUsages, + texture: wgt::TextureUsages, + }, #[error("Invalid texture view dimension `{0:?}` of a multisampled texture")] InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension), #[error("Invalid texture depth `{depth}` for texture view of dimension `Cubemap`. Cubemap views must use images of size 6.")] @@ -1680,6 +1708,8 @@ pub enum CreateTextureViewError { }, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), + #[error(transparent)] + MissingFeatures(#[from] MissingFeatures), } #[derive(Clone, Debug, Error)] diff --git a/wgpu-core/src/track/buffer.rs b/wgpu-core/src/track/buffer.rs index 0cdc9dc966..cfd166070d 100644 --- a/wgpu-core/src/track/buffer.rs +++ b/wgpu-core/src/track/buffer.rs @@ -311,7 +311,7 @@ impl BufferTracker { } /// Returns a list of all buffers tracked. - pub fn used_resources(&self) -> impl Iterator> + '_ { + pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } @@ -559,7 +559,7 @@ impl DeviceBufferTracker { } /// Returns a list of all buffers tracked. - pub fn used_resources(&self) -> impl Iterator> + '_ { + pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } diff --git a/wgpu-core/src/track/metadata.rs b/wgpu-core/src/track/metadata.rs index 7ecb2773e3..8f03caf7b0 100644 --- a/wgpu-core/src/track/metadata.rs +++ b/wgpu-core/src/track/metadata.rs @@ -111,13 +111,13 @@ impl ResourceMetadata { } /// Returns an iterator over the resources owned by `self`. - pub(super) fn owned_resources(&self) -> impl Iterator + '_ { + pub(super) fn owned_resources(&self) -> impl Iterator + '_ { if !self.owned.is_empty() { self.tracker_assert_in_bounds(self.owned.len() - 1) }; iterate_bitvec_indices(&self.owned).map(move |index| { let resource = unsafe { self.resources.get_unchecked(index) }; - resource.as_ref().unwrap().clone() + resource.as_ref().unwrap() }) } diff --git a/wgpu-core/src/track/texture.rs b/wgpu-core/src/track/texture.rs index 8ea1e04cd1..0a9a5f5489 100644 --- a/wgpu-core/src/track/texture.rs +++ b/wgpu-core/src/track/texture.rs @@ -197,6 +197,73 @@ impl TextureStateSet { fn set_size(&mut self, size: usize) { self.simple.resize(size, TextureUses::UNINITIALIZED); } + + fn size(&self) -> usize { + self.simple.len() + } + + /// SAFETY: `index` must be in bounds. + unsafe fn get_unchecked( + &self, + index: usize, + ) -> SingleOrManyStates { + let simple = unsafe { *self.simple.get_unchecked(index) }; + if simple == TextureUses::COMPLEX { + SingleOrManyStates::Many(unsafe { self.complex.get(&index).unwrap_unchecked() }) + } else { + SingleOrManyStates::Single(simple) + } + } + + /// # Safety + /// + /// The `index` must be in bounds. + unsafe fn get_mut_unchecked( + &mut self, + index: usize, + ) -> SingleOrManyStates<&mut TextureUses, &mut ComplexTextureState> { + let simple = unsafe { self.simple.get_unchecked_mut(index) }; + if *simple == TextureUses::COMPLEX { + SingleOrManyStates::Many(unsafe { self.complex.get_mut(&index).unwrap_unchecked() }) + } else { + SingleOrManyStates::Single(simple) + } + } + + /// # Safety + /// + /// The `index` must be in bounds. + unsafe fn insert_simple_unchecked(&mut self, index: usize, simple: TextureUses) { + unsafe { *self.simple.get_unchecked_mut(index) = simple }; + } + + /// # Safety + /// + /// The `index` must be in bounds. + unsafe fn insert_complex_unchecked(&mut self, index: usize, complex: ComplexTextureState) { + unsafe { *self.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; + self.complex.insert(index, complex); + } + + /// # Safety + /// + /// The `index` must be in bounds. + unsafe fn make_simple_unchecked(&mut self, index: usize, simple: TextureUses) { + unsafe { *self.simple.get_unchecked_mut(index) = simple }; + unsafe { self.complex.remove(&index).unwrap_unchecked() }; + } + + /// # Safety + /// + /// The `index` must be in bounds. + unsafe fn make_complex_unchecked(&mut self, index: usize, complex: ComplexTextureState) { + unsafe { *self.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; + self.complex.insert(index, complex); + } + + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.size()); + } } /// Stores all texture state within a single usage scope. @@ -218,16 +285,7 @@ impl Default for TextureUsageScope { impl TextureUsageScope { fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); - - strict_assert!(index < self.set.simple.len()); - - strict_assert!(if self.metadata.contains(index) - && self.set.simple[index] == TextureUses::COMPLEX - { - self.set.complex.contains_key(&index) - } else { - true - }); + self.set.tracker_assert_in_bounds(index); } pub fn clear(&mut self) { @@ -262,8 +320,8 @@ impl TextureUsageScope { &mut self, scope: &Self, ) -> Result<(), ResourceUsageCompatibilityError> { - let incoming_size = scope.set.simple.len(); - if incoming_size > self.set.simple.len() { + let incoming_size = scope.set.size(); + if incoming_size > self.set.size() { self.set_size(incoming_size); } @@ -385,24 +443,8 @@ impl TextureTracker { fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); - - strict_assert!(index < self.start_set.simple.len()); - strict_assert!(index < self.end_set.simple.len()); - - strict_assert!(if self.metadata.contains(index) - && self.start_set.simple[index] == TextureUses::COMPLEX - { - self.start_set.complex.contains_key(&index) - } else { - true - }); - strict_assert!(if self.metadata.contains(index) - && self.end_set.simple[index] == TextureUses::COMPLEX - { - self.end_set.complex.contains_key(&index) - } else { - true - }); + self.start_set.tracker_assert_in_bounds(index); + self.end_set.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. @@ -418,7 +460,7 @@ impl TextureTracker { /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { - if index >= self.start_set.simple.len() { + if index >= self.start_set.size() { self.set_size(index + 1); } } @@ -429,10 +471,9 @@ impl TextureTracker { } /// Returns a list of all textures tracked. - pub fn used_resources(&self) -> impl Iterator> + '_ { + pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } - /// Drain all currently pending transitions. pub fn drain_transitions<'a>( &'a mut self, @@ -498,8 +539,8 @@ impl TextureTracker { /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_tracker(&mut self, tracker: &Self) { - let incoming_size = tracker.start_set.simple.len(); - if incoming_size > self.start_set.simple.len() { + let incoming_size = tracker.start_set.size(); + if incoming_size > self.start_set.size() { self.set_size(incoming_size); } @@ -538,8 +579,8 @@ impl TextureTracker { /// If the ID is higher than the length of internal vectors, /// the vectors will be extended. A call to set_size is not needed. pub fn set_from_usage_scope(&mut self, scope: &TextureUsageScope) { - let incoming_size = scope.set.simple.len(); - if incoming_size > self.start_set.simple.len() { + let incoming_size = scope.set.size(); + if incoming_size > self.start_set.size() { self.set_size(incoming_size); } @@ -588,8 +629,8 @@ impl TextureTracker { scope: &mut TextureUsageScope, bind_group_state: &TextureViewBindGroupState, ) { - let incoming_size = scope.set.simple.len(); - if incoming_size > self.start_set.simple.len() { + let incoming_size = scope.set.size(); + if incoming_size > self.start_set.size() { self.set_size(incoming_size); } @@ -651,28 +692,19 @@ impl DeviceTextureTracker { fn tracker_assert_in_bounds(&self, index: usize) { self.metadata.tracker_assert_in_bounds(index); - - strict_assert!(index < self.current_state_set.simple.len()); - - strict_assert!(if self.metadata.contains(index) - && self.current_state_set.simple[index] == TextureUses::COMPLEX - { - self.current_state_set.complex.contains_key(&index) - } else { - true - }); + self.current_state_set.tracker_assert_in_bounds(index); } /// Extend the vectors to let the given index be valid. fn allow_index(&mut self, index: usize) { - if index >= self.current_state_set.simple.len() { + if index >= self.current_state_set.size() { self.current_state_set.set_size(index + 1); self.metadata.set_size(index + 1); } } /// Returns a list of all textures tracked. - pub fn used_resources(&self) -> impl Iterator> + '_ { + pub fn used_resources(&self) -> impl Iterator> + '_ { self.metadata.owned_resources() } @@ -923,19 +955,12 @@ impl<'a> TextureStateProvider<'a> { SingleOrManyStates::Many(EitherIter::Left(iter::once((selector, state)))) } } - TextureStateProvider::TextureSet { set } => { - let new_state = *unsafe { set.simple.get_unchecked(index) }; - - if new_state == TextureUses::COMPLEX { - let new_complex = unsafe { set.complex.get(&index).unwrap_unchecked() }; - - SingleOrManyStates::Many(EitherIter::Right( - new_complex.to_selector_state_iter(), - )) - } else { - SingleOrManyStates::Single(new_state) + TextureStateProvider::TextureSet { set } => match unsafe { set.get_unchecked(index) } { + SingleOrManyStates::Single(single) => SingleOrManyStates::Single(single), + SingleOrManyStates::Many(complex) => { + SingleOrManyStates::Many(EitherIter::Right(complex.to_selector_state_iter())) } - } + }, } } } @@ -1075,12 +1100,12 @@ unsafe fn insert( strict_assert_eq!(invalid_resource_state(state), false); if let Some(start_state) = start_state { - unsafe { *start_state.simple.get_unchecked_mut(index) = state }; + unsafe { start_state.insert_simple_unchecked(index, state) }; } // We only need to insert ourselves the end state if there is no end state provider. if end_state_provider.is_none() { - unsafe { *end_state.simple.get_unchecked_mut(index) = state }; + unsafe { end_state.insert_simple_unchecked(index, state) }; } } SingleOrManyStates::Many(state_iter) => { @@ -1090,14 +1115,12 @@ unsafe fn insert( unsafe { ComplexTextureState::from_selector_state_iter(full_range, state_iter) }; if let Some(start_state) = start_state { - unsafe { *start_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - start_state.complex.insert(index, complex.clone()); + unsafe { start_state.insert_complex_unchecked(index, complex.clone()) }; } // We only need to insert ourselves the end state if there is no end state provider. if end_state_provider.is_none() { - unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - end_state.complex.insert(index, complex); + unsafe { end_state.insert_complex_unchecked(index, complex) }; } } } @@ -1111,7 +1134,7 @@ unsafe fn insert( // We only need to insert into the end, as there is guaranteed to be // a start state provider. - unsafe { *end_state.simple.get_unchecked_mut(index) = state }; + unsafe { end_state.insert_simple_unchecked(index, state) }; } SingleOrManyStates::Many(state_iter) => { let full_range = texture_selector.unwrap().clone(); @@ -1122,8 +1145,7 @@ unsafe fn insert( // We only need to insert into the end, as there is guaranteed to be // a start state provider. - unsafe { *end_state.simple.get_unchecked_mut(index) = TextureUses::COMPLEX }; - end_state.complex.insert(index, complex); + unsafe { end_state.insert_complex_unchecked(index, complex) }; } } } @@ -1142,14 +1164,7 @@ unsafe fn merge( state_provider: TextureStateProvider<'_>, metadata_provider: ResourceMetadataProvider<'_, Arc>, ) -> Result<(), ResourceUsageCompatibilityError> { - let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; - let current_state = if *current_simple == TextureUses::COMPLEX { - SingleOrManyStates::Many(unsafe { - current_state_set.complex.get_mut(&index).unwrap_unchecked() - }) - } else { - SingleOrManyStates::Single(current_simple) - }; + let current_state = unsafe { current_state_set.get_mut_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; @@ -1204,8 +1219,7 @@ unsafe fn merge( } } - *current_simple = TextureUses::COMPLEX; - current_state_set.complex.insert(index, new_complex); + unsafe { current_state_set.make_complex_unchecked(index, new_complex) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_simple)) => { for (mip_id, mip) in current_complex.mips.iter_mut().enumerate() { @@ -1284,14 +1298,7 @@ unsafe fn barrier( state_provider: TextureStateProvider<'_>, barriers: &mut Vec>, ) { - let current_simple = unsafe { *current_state_set.simple.get_unchecked(index) }; - let current_state = if current_simple == TextureUses::COMPLEX { - SingleOrManyStates::Many(unsafe { - current_state_set.complex.get(&index).unwrap_unchecked() - }) - } else { - SingleOrManyStates::Single(current_simple) - }; + let current_state = unsafe { current_state_set.get_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; @@ -1393,7 +1400,6 @@ unsafe fn barrier( } } -#[allow(clippy::needless_option_as_deref)] // we use this for reborrowing Option<&mut T> #[inline(always)] unsafe fn update( texture_selector: &TextureSelector, @@ -1405,23 +1411,14 @@ unsafe fn update( // We only ever need to update the start state here if the state is complex. // // If the state is simple, the first insert to the tracker would cover it. - let mut start_complex = None; - if let Some(start_state_set) = start_state_set { - let start_simple = unsafe { *start_state_set.simple.get_unchecked(index) }; - if start_simple == TextureUses::COMPLEX { - start_complex = - Some(unsafe { start_state_set.complex.get_mut(&index).unwrap_unchecked() }); + let mut start_complex = start_state_set.and_then(|start_state_set| { + match unsafe { start_state_set.get_mut_unchecked(index) } { + SingleOrManyStates::Single(_) => None, + SingleOrManyStates::Many(complex) => Some(complex), } - } + }); - let current_simple = unsafe { current_state_set.simple.get_unchecked_mut(index) }; - let current_state = if *current_simple == TextureUses::COMPLEX { - SingleOrManyStates::Many(unsafe { - current_state_set.complex.get_mut(&index).unwrap_unchecked() - }) - } else { - SingleOrManyStates::Single(current_simple) - }; + let current_state = unsafe { current_state_set.get_mut_unchecked(index) }; let new_state = unsafe { state_provider.get_state(Some(texture_selector), index) }; @@ -1457,8 +1454,7 @@ unsafe fn update( } } - *current_simple = TextureUses::COMPLEX; - current_state_set.complex.insert(index, new_complex); + unsafe { current_state_set.make_complex_unchecked(index, new_complex) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Single(new_single)) => { for (mip_id, mip) in current_complex.mips.iter().enumerate() { @@ -1483,8 +1479,7 @@ unsafe fn update( } } - unsafe { *current_state_set.simple.get_unchecked_mut(index) = new_single }; - unsafe { current_state_set.complex.remove(&index).unwrap_unchecked() }; + unsafe { current_state_set.make_simple_unchecked(index, new_single) }; } (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index 833188aa5a..a930e26cc6 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -651,7 +651,7 @@ impl NumericType { use wgt::VertexFormat as Vf; let (dim, scalar) = match format { - Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32), + Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32), Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => { (NumericDimension::Vector(Vs::Bi), Scalar::U32) } @@ -659,7 +659,7 @@ impl NumericType { Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => { (NumericDimension::Vector(Vs::Quad), Scalar::U32) } - Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32), + Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32), Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => { (NumericDimension::Vector(Vs::Bi), Scalar::I32) } @@ -667,7 +667,9 @@ impl NumericType { Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => { (NumericDimension::Vector(Vs::Quad), Scalar::I32) } - Vf::Float32 => (NumericDimension::Scalar, Scalar::F32), + Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => { + (NumericDimension::Scalar, Scalar::F32) + } Vf::Unorm8x2 | Vf::Snorm8x2 | Vf::Unorm16x2 @@ -681,7 +683,8 @@ impl NumericType { | Vf::Snorm16x4 | Vf::Float16x4 | Vf::Float32x4 - | Vf::Unorm10_10_10_2 => (NumericDimension::Vector(Vs::Quad), Scalar::F32), + | Vf::Unorm10_10_10_2 + | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32), Vf::Float64 => (NumericDimension::Scalar, Scalar::F64), Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64), Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64), @@ -827,7 +830,7 @@ pub enum BindingLayoutSource<'a> { /// The binding layout is derived from the pipeline layout. /// /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces. - Derived(ArrayVec), + Derived(Box>), /// The binding layout is provided by the user in BGLs. /// /// This will be validated against the shader's interfaces. @@ -840,7 +843,7 @@ impl<'a> BindingLayoutSource<'a> { for _ in 0..limits.max_bind_groups { array.push(Default::default()); } - BindingLayoutSource::Derived(array) + BindingLayoutSource::Derived(Box::new(array)) } } @@ -1303,7 +1306,7 @@ pub fn validate_color_attachment_bytes_per_sample( attachment_formats: impl Iterator>, limit: u32, ) -> Result<(), u32> { - let mut total_bytes_per_sample = 0; + let mut total_bytes_per_sample: u32 = 0; for format in attachment_formats { let Some(format) = format else { continue; @@ -1312,10 +1315,7 @@ pub fn validate_color_attachment_bytes_per_sample( let byte_cost = format.target_pixel_byte_cost().unwrap(); let alignment = format.target_component_alignment().unwrap(); - let rem = total_bytes_per_sample % alignment; - if rem != 0 { - total_bytes_per_sample += alignment - rem; - } + total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment); total_bytes_per_sample += byte_cost; } diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index b175b9905a..4817a22e22 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -3,7 +3,7 @@ name = "wgpu-hal" version = "23.0.1" authors = ["gfx-rs developers"] edition = "2021" -description = "WebGPU hardware abstraction layer" +description = "Hardware abstraction layer for wgpu, the cross-platform, safe, pure-rust graphics API" homepage = "https://wgpu.rs/" repository = "https://github.com/gfx-rs/wgpu" keywords = ["graphics"] @@ -53,6 +53,7 @@ vulkan = [ "dep:libloading", "dep:smallvec", "dep:android_system_properties", + "dep:ordered-float", ] gles = [ "naga/glsl-out", @@ -108,6 +109,9 @@ device_lost_panic = [] # Only affects the d3d12 and vulkan backends. internal_error_panic = [] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(web_sys_unstable_apis)'] } + [[example]] name = "halmark" @@ -122,6 +126,7 @@ profiling = { workspace = true, default-features = false } raw-window-handle.workspace = true thiserror.workspace = true once_cell.workspace = true +ordered-float = { workspace = true, optional = true } # backends common arrayvec.workspace = true @@ -160,13 +165,18 @@ windows = { workspace = true, optional = true } bit-set = { workspace = true, optional = true } range-alloc = { workspace = true, optional = true } gpu-allocator = { workspace = true, optional = true } -mach-dxcompiler-rs = { workspace = true, optional = true } # For core macros. This crate is also reexported as windows::core. windows-core = { workspace = true, optional = true } # backend: Gles glutin_wgl_sys = { workspace = true, optional = true } +# This doesn't support aarch64. See https://github.com/gfx-rs/wgpu/issues/6860. +# +# ⚠️ Keep in sync with static_dxc cfg in build.rs and cfg_alias in `wgpu` crate ⚠️ +[target.'cfg(all(windows, not(target_arch = "aarch64")))'.dependencies] +mach-dxcompiler-rs = { workspace = true, optional = true } + [target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies] # backend: Metal block = { workspace = true, optional = true } diff --git a/wgpu-hal/build.rs b/wgpu-hal/build.rs index 7d17591605..510f2a35f6 100644 --- a/wgpu-hal/build.rs +++ b/wgpu-hal/build.rs @@ -10,6 +10,8 @@ fn main() { dx12: { all(target_os = "windows", feature = "dx12") }, gles: { all(feature = "gles") }, metal: { all(any(target_os = "ios", target_os = "macos"), feature = "metal") }, - vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") } + vulkan: { all(not(target_arch = "wasm32"), feature = "vulkan") }, + // ⚠️ Keep in sync with target.cfg() definition in Cargo.toml and cfg_alias in `wgpu` crate ⚠️ + static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64")) } } } diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index a5a1b660af..1418e67191 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -177,7 +177,7 @@ impl Example { }; let shader_desc = hal::ShaderModuleDescriptor { label: None, - runtime_checks: false, + runtime_checks: wgt::ShaderRuntimeChecks::checked(), }; let shader = unsafe { device diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index 52eaa80eb8..b81ef86525 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -371,7 +371,7 @@ impl Example { }; let shader_desc = hal::ShaderModuleDescriptor { label: None, - runtime_checks: false, + runtime_checks: wgt::ShaderRuntimeChecks::checked(), }; let shader_module = unsafe { device diff --git a/wgpu-hal/src/auxil/dxgi/conv.rs b/wgpu-hal/src/auxil/dxgi/conv.rs index ad64f044cc..0f94575df8 100644 --- a/wgpu-hal/src/auxil/dxgi/conv.rs +++ b/wgpu-hal/src/auxil/dxgi/conv.rs @@ -235,6 +235,10 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> Dxgi::Common::DXGI_FORMAT use Dxgi::Common::*; match format { + Vf::Unorm8 => DXGI_FORMAT_R8_UNORM, + Vf::Snorm8 => DXGI_FORMAT_R8_SNORM, + Vf::Uint8 => DXGI_FORMAT_R8_UINT, + Vf::Sint8 => DXGI_FORMAT_R8_SINT, Vf::Unorm8x2 => DXGI_FORMAT_R8G8_UNORM, Vf::Snorm8x2 => DXGI_FORMAT_R8G8_SNORM, Vf::Uint8x2 => DXGI_FORMAT_R8G8_UINT, @@ -243,6 +247,11 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> Dxgi::Common::DXGI_FORMAT Vf::Snorm8x4 => DXGI_FORMAT_R8G8B8A8_SNORM, Vf::Uint8x4 => DXGI_FORMAT_R8G8B8A8_UINT, Vf::Sint8x4 => DXGI_FORMAT_R8G8B8A8_SINT, + Vf::Unorm16 => DXGI_FORMAT_R16_UNORM, + Vf::Snorm16 => DXGI_FORMAT_R16_SNORM, + Vf::Uint16 => DXGI_FORMAT_R16_UINT, + Vf::Sint16 => DXGI_FORMAT_R16_SINT, + Vf::Float16 => DXGI_FORMAT_R16_FLOAT, Vf::Unorm16x2 => DXGI_FORMAT_R16G16_UNORM, Vf::Snorm16x2 => DXGI_FORMAT_R16G16_SNORM, Vf::Uint16x2 => DXGI_FORMAT_R16G16_UINT, @@ -266,6 +275,7 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> Dxgi::Common::DXGI_FORMAT Vf::Sint32x4 => DXGI_FORMAT_R32G32B32A32_SINT, Vf::Float32x4 => DXGI_FORMAT_R32G32B32A32_FLOAT, Vf::Unorm10_10_10_2 => DXGI_FORMAT_R10G10B10A2_UNORM, + Vf::Unorm8x4Bgra => DXGI_FORMAT_B8G8R8A8_UNORM, Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(), } } diff --git a/wgpu-hal/src/auxil/dxgi/exception.rs b/wgpu-hal/src/auxil/dxgi/exception.rs index c3d655c6e5..15311e3953 100644 --- a/wgpu-hal/src/auxil/dxgi/exception.rs +++ b/wgpu-hal/src/auxil/dxgi/exception.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, slice}; -use parking_lot::{lock_api::RawMutex, Mutex}; +use parking_lot::Mutex; use windows::Win32::{Foundation, System::Diagnostics::Debug}; // This is a mutex as opposed to an atomic as we need to completely @@ -9,7 +9,7 @@ use windows::Win32::{Foundation, System::Diagnostics::Debug}; // // By routing all the registration through these functions we can guarantee // there is either 1 or 0 exception handlers registered, not multiple. -static EXCEPTION_HANDLER_COUNT: Mutex = Mutex::const_new(parking_lot::RawMutex::INIT, 0); +static EXCEPTION_HANDLER_COUNT: Mutex = Mutex::new(0); pub fn register_exception_handler() { let mut count_guard = EXCEPTION_HANDLER_COUNT.lock(); diff --git a/wgpu-hal/src/auxil/dxgi/factory.rs b/wgpu-hal/src/auxil/dxgi/factory.rs index 4b71abda37..37ab52bf42 100644 --- a/wgpu-hal/src/auxil/dxgi/factory.rs +++ b/wgpu-hal/src/auxil/dxgi/factory.rs @@ -35,7 +35,7 @@ fn should_keep_adapter(adapter: &Dxgi::IDXGIAdapter1) -> bool { // // We don't want that and discourage that kind of filtering anyway, so we skip the integrated WARP. if desc.VendorId == 5140 - && Dxgi::DXGI_ADAPTER_FLAG(desc.Flags as i32).contains(Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) + && !Dxgi::DXGI_ADAPTER_FLAG(desc.Flags as i32).contains(Dxgi::DXGI_ADAPTER_FLAG_SOFTWARE) { let adapter_name = super::conv::map_adapter_name(desc.Description); if adapter_name.contains("Microsoft Basic Render Driver") { @@ -141,9 +141,6 @@ pub fn create_factory( if let Ok(Some(_)) = lib_dxgi.debug_interface1() { factory_flags |= Dxgi::DXGI_CREATE_FACTORY_DEBUG; } - - // Intercept `OutputDebugString` calls - super::exception::register_exception_handler(); } let factory4 = match lib_dxgi.create_factory4(factory_flags) { diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 62e224c56f..7859f06e5d 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -345,6 +345,12 @@ impl super::Adapter { shader_model >= naga::back::hlsl::ShaderModel::V5_1, ); + // See note below the table https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-support + features.set( + wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY, + options.ResourceBindingTier.0 >= Direct3D12::D3D12_RESOURCE_BINDING_TIER_3.0, + ); + let bgra8unorm_storage_supported = { let mut bgra8unorm_info = Direct3D12::D3D12_FEATURE_DATA_FORMAT_SUPPORT { Format: Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM, @@ -420,9 +426,8 @@ impl super::Adapter { // See https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels#feature-level-support let max_color_attachments = 8; - // TODO: determine this programmatically if possible. - // https://github.com/gpuweb/gpuweb/issues/2965#issuecomment-1361315447 - let max_color_attachment_bytes_per_sample = 64; + let max_color_attachment_bytes_per_sample = + max_color_attachments * wgt::TextureFormat::MAX_TARGET_PIXEL_BYTE_COST; Some(crate::ExposedAdapter { adapter: super::Adapter { diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index d4c60aa528..9296a20393 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -1223,13 +1223,25 @@ impl crate::CommandEncoder for super::CommandEncoder { } unsafe fn dispatch_indirect(&mut self, buffer: &super::Buffer, offset: wgt::BufferAddress) { - self.update_root_elements(); + if self + .pass + .layout + .special_constants + .as_ref() + .and_then(|sc| sc.indirect_cmd_signatures.as_ref()) + .is_some() + { + self.update_root_elements(); + } else { + self.prepare_dispatch([0; 3]); + } + let cmd_signature = &self .pass .layout .special_constants .as_ref() - .map(|sc| &sc.cmd_signatures) + .and_then(|sc| sc.indirect_cmd_signatures.as_ref()) .unwrap_or_else(|| &self.shared.cmd_signatures) .dispatch; unsafe { diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 9cba60b896..20dc20164f 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -37,6 +37,13 @@ impl super::Device { library: &Arc, dxc_container: Option>, ) -> Result { + if private_caps + .instance_flags + .contains(wgt::InstanceFlags::VALIDATION) + { + auxil::dxgi::exception::register_exception_handler(); + } + let mem_allocator = super::suballocation::create_allocator_wrapper(&raw, memory_hints)?; let idle_fence: Direct3D12::ID3D12Fence = unsafe { @@ -273,12 +280,12 @@ impl super::Device { let needs_temp_options = stage.zero_initialize_workgroup_memory != layout.naga_options.zero_initialize_workgroup_memory - || stage.module.runtime_checks != layout.naga_options.restrict_indexing; + || stage.module.runtime_checks.bounds_checks != layout.naga_options.restrict_indexing; let mut temp_options; let naga_options = if needs_temp_options { temp_options = layout.naga_options.clone(); temp_options.zero_initialize_workgroup_memory = stage.zero_initialize_workgroup_memory; - temp_options.restrict_indexing = stage.module.runtime_checks; + temp_options.restrict_indexing = stage.module.runtime_checks.bounds_checks; &temp_options } else { &layout.naga_options @@ -1122,78 +1129,85 @@ impl crate::Device for super::Device { .into_device_result("Root signature creation")?; let special_constants = if let Some(root_index) = special_constants_root_index { - let constant_indirect_argument_desc = Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { - Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT, - Anonymous: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0 { - Constant: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0_1 { - RootParameterIndex: root_index, - DestOffsetIn32BitValues: 0, - Num32BitValuesToSet: 3, + let cmd_signatures = if desc + .flags + .contains(crate::PipelineLayoutFlags::INDIRECT_BUILTIN_UPDATE) + { + let constant_indirect_argument_desc = Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT, + Anonymous: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0 { + Constant: Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC_0_1 { + RootParameterIndex: root_index, + DestOffsetIn32BitValues: 0, + Num32BitValuesToSet: 3, + }, }, - }, - }; - let special_constant_buffer_args_len = { - // Hack: construct a dummy value of the special constants buffer value we need to - // fill, and calculate the size of each member. - let super::RootElement::SpecialConstantBuffer { - first_vertex, - first_instance, - other, - } = (super::RootElement::SpecialConstantBuffer { - first_vertex: 0, - first_instance: 0, - other: 0, - }) - else { - unreachable!(); }; - size_of_val(&first_vertex) + size_of_val(&first_instance) + size_of_val(&other) - }; - let cmd_signatures = super::CommandSignatures { - draw: Self::create_command_signature( - &self.raw, - Some(&raw), - special_constant_buffer_args_len + size_of::(), - &[ - constant_indirect_argument_desc, - Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { - Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW, - ..Default::default() - }, - ], - 0, - )?, - draw_indexed: Self::create_command_signature( - &self.raw, - Some(&raw), - special_constant_buffer_args_len + size_of::(), - &[ - constant_indirect_argument_desc, - Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { - Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED, - ..Default::default() - }, - ], - 0, - )?, - dispatch: Self::create_command_signature( - &self.raw, - Some(&raw), - special_constant_buffer_args_len + size_of::(), - &[ - constant_indirect_argument_desc, - Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { - Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH, - ..Default::default() - }, - ], - 0, - )?, + let special_constant_buffer_args_len = { + // Hack: construct a dummy value of the special constants buffer value we need to + // fill, and calculate the size of each member. + let super::RootElement::SpecialConstantBuffer { + first_vertex, + first_instance, + other, + } = (super::RootElement::SpecialConstantBuffer { + first_vertex: 0, + first_instance: 0, + other: 0, + }) + else { + unreachable!(); + }; + size_of_val(&first_vertex) + size_of_val(&first_instance) + size_of_val(&other) + }; + Some(super::CommandSignatures { + draw: Self::create_command_signature( + &self.raw, + Some(&raw), + special_constant_buffer_args_len + size_of::(), + &[ + constant_indirect_argument_desc, + Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW, + ..Default::default() + }, + ], + 0, + )?, + draw_indexed: Self::create_command_signature( + &self.raw, + Some(&raw), + special_constant_buffer_args_len + + size_of::(), + &[ + constant_indirect_argument_desc, + Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED, + ..Default::default() + }, + ], + 0, + )?, + dispatch: Self::create_command_signature( + &self.raw, + Some(&raw), + special_constant_buffer_args_len + size_of::(), + &[ + constant_indirect_argument_desc, + Direct3D12::D3D12_INDIRECT_ARGUMENT_DESC { + Type: Direct3D12::D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH, + ..Default::default() + }, + ], + 0, + )?, + }) + } else { + None }; - Some(super::PipelineLayoutSpecialConstants { root_index, - cmd_signatures, + indirect_cmd_signatures: cmd_signatures, }) } else { None diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 17fe1236e5..c257afe804 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -12,14 +12,6 @@ use windows::{ use super::SurfaceTarget; use crate::{auxil, dx12::D3D12Lib}; -impl Drop for super::Instance { - fn drop(&mut self) { - if self.flags.contains(wgt::InstanceFlags::VALIDATION) { - auxil::dxgi::exception::unregister_exception_handler(); - } - } -} - impl crate::Instance for super::Instance { type A = super::Api; diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index a1427d7772..d58d79300a 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -605,6 +605,13 @@ pub struct Device { impl Drop for Device { fn drop(&mut self) { self.rtv_pool.lock().free_handle(self.null_rtv_handle); + if self + .private_caps + .instance_flags + .contains(wgt::InstanceFlags::VALIDATION) + { + auxil::dxgi::exception::unregister_exception_handler(); + } } } @@ -946,7 +953,7 @@ unsafe impl Sync for PipelineLayoutShared {} #[derive(Debug, Clone)] struct PipelineLayoutSpecialConstants { root_index: RootIndex, - cmd_signatures: CommandSignatures, + indirect_cmd_signatures: Option, } unsafe impl Send for PipelineLayoutSpecialConstants {} @@ -967,7 +974,7 @@ impl crate::DynPipelineLayout for PipelineLayout {} pub struct ShaderModule { naga: crate::NagaShader, raw_name: Option, - runtime_checks: bool, + runtime_checks: wgt::ShaderRuntimeChecks, } impl crate::DynShaderModule for ShaderModule {} diff --git a/wgpu-hal/src/dx12/shader_compilation.rs b/wgpu-hal/src/dx12/shader_compilation.rs index 9739627017..81e51b83d1 100644 --- a/wgpu-hal/src/dx12/shader_compilation.rs +++ b/wgpu-hal/src/dx12/shader_compilation.rs @@ -179,7 +179,7 @@ pub(super) fn get_dynamic_dxc_container( /// Creates a [`DxcContainer`] that delegates to the statically-linked version of DXC. pub(super) fn get_static_dxc_container() -> Result { - #[cfg(feature = "static-dxc")] + #[cfg(static_dxc)] { unsafe { let compiler = dxc_create_instance::(|clsid, iid, ppv| { @@ -206,7 +206,7 @@ pub(super) fn get_static_dxc_container() -> Result super: use wgt::VertexFormat as Vf; let (element_count, element_format, attrib_kind) = match vertex_format { + Vf::Unorm8 => (1, glow::UNSIGNED_BYTE, Vak::Float), + Vf::Snorm8 => (1, glow::BYTE, Vak::Float), + Vf::Uint8 => (1, glow::UNSIGNED_BYTE, Vak::Integer), + Vf::Sint8 => (1, glow::BYTE, Vak::Integer), Vf::Unorm8x2 => (2, glow::UNSIGNED_BYTE, Vak::Float), Vf::Snorm8x2 => (2, glow::BYTE, Vak::Float), Vf::Uint8x2 => (2, glow::UNSIGNED_BYTE, Vak::Integer), @@ -190,6 +194,11 @@ pub(super) fn describe_vertex_format(vertex_format: wgt::VertexFormat) -> super: Vf::Snorm8x4 => (4, glow::BYTE, Vak::Float), Vf::Uint8x4 => (4, glow::UNSIGNED_BYTE, Vak::Integer), Vf::Sint8x4 => (4, glow::BYTE, Vak::Integer), + Vf::Unorm16 => (1, glow::UNSIGNED_SHORT, Vak::Float), + Vf::Snorm16 => (1, glow::SHORT, Vak::Float), + Vf::Uint16 => (1, glow::UNSIGNED_SHORT, Vak::Integer), + Vf::Sint16 => (1, glow::SHORT, Vak::Integer), + Vf::Float16 => (1, glow::HALF_FLOAT, Vak::Float), Vf::Unorm16x2 => (2, glow::UNSIGNED_SHORT, Vak::Float), Vf::Snorm16x2 => (2, glow::SHORT, Vak::Float), Vf::Uint16x2 => (2, glow::UNSIGNED_SHORT, Vak::Integer), @@ -213,6 +222,7 @@ pub(super) fn describe_vertex_format(vertex_format: wgt::VertexFormat) -> super: Vf::Sint32x4 => (4, glow::INT, Vak::Integer), Vf::Float32x4 => (4, glow::FLOAT, Vak::Float), Vf::Unorm10_10_10_2 => (4, glow::UNSIGNED_INT_10_10_10_2, Vak::Float), + Vf::Unorm8x4Bgra => (glow::BGRA as i32, glow::UNSIGNED_BYTE, Vak::Float), Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(), }; diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 9924d1f5de..4cc0ef80bd 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -1513,10 +1513,15 @@ bitflags!( /// Pipeline layout creation flags. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PipelineLayoutFlags: u32 { - /// Include support for `first_vertex` / `first_instance` drawing. + /// D3D12: Add support for `first_vertex` and `first_instance` builtins + /// via push constants for direct execution. const FIRST_VERTEX_INSTANCE = 1 << 0; - /// Include support for num work groups builtin. + /// D3D12: Add support for `num_workgroups` builtins via push constants + /// for direct execution. const NUM_WORK_GROUPS = 1 << 1; + /// D3D12: Add support for the builtins that the other flags enable for + /// indirect execution. + const INDIRECT_BUILTIN_UPDATE = 1 << 2; } ); @@ -2103,26 +2108,12 @@ pub enum ShaderInput<'a> { pub struct ShaderModuleDescriptor<'a> { pub label: Label<'a>, - /// Enforce bounds checks in shaders, even if the underlying driver doesn't - /// support doing so natively. - /// - /// When this is `true`, `wgpu_hal` promises that shaders can only read or - /// write the [accessible region][ar] of a bindgroup's buffer bindings. If - /// the underlying graphics platform cannot implement these bounds checks - /// itself, `wgpu_hal` will inject bounds checks before presenting the - /// shader to the platform. - /// - /// When this is `false`, `wgpu_hal` only enforces such bounds checks if the - /// underlying platform provides a way to do so itself. `wgpu_hal` does not - /// itself add any bounds checks to generated shader code. + /// # Safety /// - /// Note that `wgpu_hal` users may try to initialize only those portions of - /// buffers that they anticipate might be read from. Passing `false` here - /// may allow shaders to see wider regions of the buffers than expected, - /// making such deferred initialization visible to the application. + /// See the documentation for each flag in [`ShaderRuntimeChecks`][src]. /// - /// [ar]: struct.BufferBinding.html#accessible-region - pub runtime_checks: bool, + /// [src]: wgt::ShaderRuntimeChecks + pub runtime_checks: wgt::ShaderRuntimeChecks, } #[derive(Debug, Clone)] diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 1315d98182..f674414211 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -387,12 +387,6 @@ const RESOURCE_HEAP_SUPPORT: &[MTLFeatureSet] = &[ MTLFeatureSet::macOS_GPUFamily1_v3, ]; -const ARGUMENT_BUFFER_SUPPORT: &[MTLFeatureSet] = &[ - MTLFeatureSet::iOS_GPUFamily1_v4, - MTLFeatureSet::tvOS_GPUFamily1_v3, - MTLFeatureSet::macOS_GPUFamily1_v3, -]; - const MUTABLE_COMPARISON_SAMPLER_SUPPORT: &[MTLFeatureSet] = &[ MTLFeatureSet::iOS_GPUFamily3_v1, MTLFeatureSet::macOS_GPUFamily1_v1, @@ -620,7 +614,7 @@ impl super::PrivateCapabilities { }, msaa_apple7: family_check && device.supports_family(MTLGPUFamily::Apple7), resource_heaps: Self::supports_any(device, RESOURCE_HEAP_SUPPORT), - argument_buffers: Self::supports_any(device, ARGUMENT_BUFFER_SUPPORT), + argument_buffers: device.argument_buffers_support(), shared_textures: !os_is_mac, mutable_comparison_samplers: Self::supports_any( device, @@ -915,18 +909,12 @@ impl super::PrivateCapabilities { features.set( F::TEXTURE_BINDING_ARRAY | F::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING - | F::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING, - self.msl_version >= MTLLanguageVersion::V2_0 && self.supports_arrays_of_textures, + | F::UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING + | F::PARTIALLY_BOUND_BINDING_ARRAY, + self.msl_version >= MTLLanguageVersion::V3_0 + && self.supports_arrays_of_textures + && self.argument_buffers as u64 >= metal::MTLArgumentBuffersTier::Tier2 as u64, ); - //// XXX: this is technically not true, as read-only storage images can be used in arrays - //// on precisely the same conditions that sampled textures can. But texel fetch from a - //// sampled texture is a thing; should we bother introducing another feature flag? - if self.msl_version >= MTLLanguageVersion::V2_2 - && self.supports_arrays_of_textures - && self.supports_arrays_of_textures_write - { - features.insert(F::STORAGE_RESOURCE_BINDING_ARRAY); - } features.set( F::SHADER_INT64, self.int64 && self.msl_version >= MTLLanguageVersion::V2_3, diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index c0b8331fb5..a66349cbf4 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -750,6 +750,11 @@ impl crate::CommandEncoder for super::CommandEncoder { Some(res.as_native()), ); } + + // Call useResource on all textures and buffers used indirectly so they are alive + for (resource, use_info) in group.resources_to_use.iter() { + encoder.use_resource_at(resource.as_native(), use_info.uses, use_info.stages); + } } if let Some(ref encoder) = self.state.compute { @@ -807,6 +812,14 @@ impl crate::CommandEncoder for super::CommandEncoder { Some(res.as_native()), ); } + + // Call useResource on all textures and buffers used indirectly so they are alive + for (resource, use_info) in group.resources_to_use.iter() { + if !use_info.visible_in_compute { + continue; + } + encoder.use_resource(resource.as_native(), use_info.uses); + } } } diff --git a/wgpu-hal/src/metal/conv.rs b/wgpu-hal/src/metal/conv.rs index 0b39d3016d..443c9f7960 100644 --- a/wgpu-hal/src/metal/conv.rs +++ b/wgpu-hal/src/metal/conv.rs @@ -197,6 +197,10 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> metal::MTLVertexFormat { use wgt::VertexFormat as Vf; match format { + Vf::Unorm8 => UCharNormalized, + Vf::Snorm8 => CharNormalized, + Vf::Uint8 => UChar, + Vf::Sint8 => Char, Vf::Unorm8x2 => UChar2Normalized, Vf::Snorm8x2 => Char2Normalized, Vf::Uint8x2 => UChar2, @@ -205,6 +209,11 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> metal::MTLVertexFormat { Vf::Snorm8x4 => Char4Normalized, Vf::Uint8x4 => UChar4, Vf::Sint8x4 => Char4, + Vf::Unorm16 => UShortNormalized, + Vf::Snorm16 => ShortNormalized, + Vf::Uint16 => UShort, + Vf::Sint16 => Short, + Vf::Float16 => Half, Vf::Unorm16x2 => UShort2Normalized, Vf::Snorm16x2 => Short2Normalized, Vf::Uint16x2 => UShort2, @@ -228,6 +237,7 @@ pub fn map_vertex_format(format: wgt::VertexFormat) -> metal::MTLVertexFormat { Vf::Sint32x4 => Int4, Vf::Float32x4 => Float4, Vf::Unorm10_10_10_2 => UInt1010102Normalized, + Vf::Unorm8x4Bgra => UChar4Normalized_BGRA, Vf::Float64 | Vf::Float64x2 | Vf::Float64x3 | Vf::Float64x4 => unimplemented!(), } } @@ -326,3 +336,31 @@ pub fn get_blit_option( metal::MTLBlitOption::None } } + +pub fn map_render_stages(stage: wgt::ShaderStages) -> metal::MTLRenderStages { + let mut raw_stages = metal::MTLRenderStages::empty(); + + if stage.contains(wgt::ShaderStages::VERTEX) { + raw_stages |= metal::MTLRenderStages::Vertex; + } + if stage.contains(wgt::ShaderStages::FRAGMENT) { + raw_stages |= metal::MTLRenderStages::Fragment; + } + + raw_stages +} + +pub fn map_resource_usage(ty: &wgt::BindingType) -> metal::MTLResourceUsage { + match ty { + wgt::BindingType::Texture { .. } => metal::MTLResourceUsage::Sample, + wgt::BindingType::StorageTexture { access, .. } => match access { + wgt::StorageTextureAccess::WriteOnly => metal::MTLResourceUsage::Write, + wgt::StorageTextureAccess::ReadOnly => metal::MTLResourceUsage::Read, + wgt::StorageTextureAccess::ReadWrite => { + metal::MTLResourceUsage::Read | metal::MTLResourceUsage::Write + } + }, + wgt::BindingType::Sampler(..) => metal::MTLResourceUsage::empty(), + _ => unreachable!(), + } +} diff --git a/wgpu-hal/src/metal/device.rs b/wgpu-hal/src/metal/device.rs index 577f5a7fcc..b2e514b4a3 100644 --- a/wgpu-hal/src/metal/device.rs +++ b/wgpu-hal/src/metal/device.rs @@ -1,7 +1,6 @@ use parking_lot::Mutex; use std::{ - num::NonZeroU32, - ptr, + ptr::NonNull, sync::{atomic, Arc}, thread, time, }; @@ -10,6 +9,8 @@ use super::conv; use crate::auxil::map_naga_stage; use crate::TlasInstance; +use metal::foreign_types::ForeignType; + type DeviceResult = Result; struct CompiledShader { @@ -62,22 +63,31 @@ fn create_depth_stencil_desc(state: &wgt::DepthStencilState) -> metal::DepthSten const fn convert_vertex_format_to_naga(format: wgt::VertexFormat) -> naga::back::msl::VertexFormat { match format { + wgt::VertexFormat::Uint8 => naga::back::msl::VertexFormat::Uint8, wgt::VertexFormat::Uint8x2 => naga::back::msl::VertexFormat::Uint8x2, wgt::VertexFormat::Uint8x4 => naga::back::msl::VertexFormat::Uint8x4, + wgt::VertexFormat::Sint8 => naga::back::msl::VertexFormat::Sint8, wgt::VertexFormat::Sint8x2 => naga::back::msl::VertexFormat::Sint8x2, wgt::VertexFormat::Sint8x4 => naga::back::msl::VertexFormat::Sint8x4, + wgt::VertexFormat::Unorm8 => naga::back::msl::VertexFormat::Unorm8, wgt::VertexFormat::Unorm8x2 => naga::back::msl::VertexFormat::Unorm8x2, wgt::VertexFormat::Unorm8x4 => naga::back::msl::VertexFormat::Unorm8x4, + wgt::VertexFormat::Snorm8 => naga::back::msl::VertexFormat::Snorm8, wgt::VertexFormat::Snorm8x2 => naga::back::msl::VertexFormat::Snorm8x2, wgt::VertexFormat::Snorm8x4 => naga::back::msl::VertexFormat::Snorm8x4, + wgt::VertexFormat::Uint16 => naga::back::msl::VertexFormat::Uint16, wgt::VertexFormat::Uint16x2 => naga::back::msl::VertexFormat::Uint16x2, wgt::VertexFormat::Uint16x4 => naga::back::msl::VertexFormat::Uint16x4, + wgt::VertexFormat::Sint16 => naga::back::msl::VertexFormat::Sint16, wgt::VertexFormat::Sint16x2 => naga::back::msl::VertexFormat::Sint16x2, wgt::VertexFormat::Sint16x4 => naga::back::msl::VertexFormat::Sint16x4, + wgt::VertexFormat::Unorm16 => naga::back::msl::VertexFormat::Unorm16, wgt::VertexFormat::Unorm16x2 => naga::back::msl::VertexFormat::Unorm16x2, wgt::VertexFormat::Unorm16x4 => naga::back::msl::VertexFormat::Unorm16x4, + wgt::VertexFormat::Snorm16 => naga::back::msl::VertexFormat::Snorm16, wgt::VertexFormat::Snorm16x2 => naga::back::msl::VertexFormat::Snorm16x2, wgt::VertexFormat::Snorm16x4 => naga::back::msl::VertexFormat::Snorm16x4, + wgt::VertexFormat::Float16 => naga::back::msl::VertexFormat::Float16, wgt::VertexFormat::Float16x2 => naga::back::msl::VertexFormat::Float16x2, wgt::VertexFormat::Float16x4 => naga::back::msl::VertexFormat::Float16x4, wgt::VertexFormat::Float32 => naga::back::msl::VertexFormat::Float32, @@ -93,7 +103,14 @@ const fn convert_vertex_format_to_naga(format: wgt::VertexFormat) -> naga::back: wgt::VertexFormat::Sint32x3 => naga::back::msl::VertexFormat::Sint32x3, wgt::VertexFormat::Sint32x4 => naga::back::msl::VertexFormat::Sint32x4, wgt::VertexFormat::Unorm10_10_10_2 => naga::back::msl::VertexFormat::Unorm10_10_10_2, - _ => unimplemented!(), + wgt::VertexFormat::Unorm8x4Bgra => naga::back::msl::VertexFormat::Unorm8x4Bgra, + + wgt::VertexFormat::Float64 + | wgt::VertexFormat::Float64x2 + | wgt::VertexFormat::Float64x3 + | wgt::VertexFormat::Float64x4 => { + unimplemented!() + } } } @@ -117,7 +134,7 @@ impl super::Device { let ep_resources = &layout.per_stage_map[naga_stage]; - let bounds_check_policy = if stage.module.runtime_checks { + let bounds_check_policy = if stage.module.bounds_checks.bounds_checks { naga::proc::BoundsCheckPolicy::Restrict } else { naga::proc::BoundsCheckPolicy::Unchecked @@ -151,6 +168,7 @@ impl super::Device { binding_array: naga::proc::BoundsCheckPolicy::Unchecked, }, zero_initialize_workgroup_memory: stage.zero_initialize_workgroup_memory, + force_loop_bounding: stage.module.bounds_checks.force_loop_bounding, }; let pipeline_options = naga::back::msl::PipelineOptions { @@ -367,7 +385,7 @@ impl crate::Device for super::Device { let ptr = buffer.raw.contents().cast::(); assert!(!ptr.is_null()); Ok(crate::BufferMapping { - ptr: ptr::NonNull::new(unsafe { ptr.offset(range.start as isize) }).unwrap(), + ptr: NonNull::new(unsafe { ptr.offset(range.start as isize) }).unwrap(), is_coherent: true, }) } @@ -563,6 +581,9 @@ impl crate::Device for super::Device { if let Some(label) = desc.label { descriptor.set_label(label); } + if self.features.contains(wgt::Features::TEXTURE_BINDING_ARRAY) { + descriptor.set_support_argument_buffers(true); + } let raw = self.shared.device.lock().new_sampler(&descriptor); self.counters.samplers.add(1); @@ -681,37 +702,42 @@ impl crate::Device for super::Device { } let mut target = naga::back::msl::BindTarget::default(); - let count = entry.count.map_or(1, NonZeroU32::get); - target.binding_array_size = entry.count.map(NonZeroU32::get); - match entry.ty { - wgt::BindingType::Buffer { ty, .. } => { - target.buffer = Some(info.counters.buffers as _); - info.counters.buffers += count; - if let wgt::BufferBindingType::Storage { read_only } = ty { - target.mutable = !read_only; + // Bindless path + if let Some(_) = entry.count { + target.buffer = Some(info.counters.buffers as _); + info.counters.buffers += 1; + } else { + match entry.ty { + wgt::BindingType::Buffer { ty, .. } => { + target.buffer = Some(info.counters.buffers as _); + info.counters.buffers += 1; + if let wgt::BufferBindingType::Storage { read_only } = ty { + target.mutable = !read_only; + } } + wgt::BindingType::Sampler { .. } => { + target.sampler = + Some(naga::back::msl::BindSamplerTarget::Resource( + info.counters.samplers as _, + )); + info.counters.samplers += 1; + } + wgt::BindingType::Texture { .. } => { + target.texture = Some(info.counters.textures as _); + info.counters.textures += 1; + } + wgt::BindingType::StorageTexture { access, .. } => { + target.texture = Some(info.counters.textures as _); + info.counters.textures += 1; + target.mutable = match access { + wgt::StorageTextureAccess::ReadOnly => false, + wgt::StorageTextureAccess::WriteOnly => true, + wgt::StorageTextureAccess::ReadWrite => true, + wgt::StorageTextureAccess::Atomic => true, + }; + } + wgt::BindingType::AccelerationStructure => unimplemented!(), } - wgt::BindingType::Sampler { .. } => { - target.sampler = Some(naga::back::msl::BindSamplerTarget::Resource( - info.counters.samplers as _, - )); - info.counters.samplers += count; - } - wgt::BindingType::Texture { .. } => { - target.texture = Some(info.counters.textures as _); - info.counters.textures += count; - } - wgt::BindingType::StorageTexture { access, .. } => { - target.texture = Some(info.counters.textures as _); - info.counters.textures += count; - target.mutable = match access { - wgt::StorageTextureAccess::ReadOnly => false, - wgt::StorageTextureAccess::WriteOnly => true, - wgt::StorageTextureAccess::ReadWrite => true, - wgt::StorageTextureAccess::Atomic => true, - }; - } - wgt::BindingType::AccelerationStructure => unimplemented!(), } let br = naga::ResourceBinding { @@ -789,90 +815,162 @@ impl crate::Device for super::Device { super::AccelerationStructure, >, ) -> DeviceResult { - let mut bg = super::BindGroup::default(); - for (&stage, counter) in super::NAGA_STAGES.iter().zip(bg.counters.iter_mut()) { - let stage_bit = map_naga_stage(stage); - let mut dynamic_offsets_count = 0u32; - let layout_and_entry_iter = desc.entries.iter().map(|entry| { - let layout = desc - .layout - .entries - .iter() - .find(|layout_entry| layout_entry.binding == entry.binding) - .expect("internal error: no layout entry found with binding slot"); - (entry, layout) - }); - for (entry, layout) in layout_and_entry_iter { - let size = layout.count.map_or(1, |c| c.get()); - if let wgt::BindingType::Buffer { - has_dynamic_offset: true, - .. - } = layout.ty - { - dynamic_offsets_count += size; - } - if !layout.visibility.contains(stage_bit) { - continue; - } - match layout.ty { - wgt::BindingType::Buffer { - ty, - has_dynamic_offset, - .. - } => { - let start = entry.resource_index as usize; - let end = start + size as usize; - bg.buffers - .extend(desc.buffers[start..end].iter().map(|source| { - // Given the restrictions on `BufferBinding::offset`, - // this should never be `None`. - let remaining_size = - wgt::BufferSize::new(source.buffer.size - source.offset); - let binding_size = match ty { - wgt::BufferBindingType::Storage { .. } => { - source.size.or(remaining_size) - } - _ => None, - }; - super::BufferResource { - ptr: source.buffer.as_raw(), - offset: source.offset, - dynamic_index: if has_dynamic_offset { - Some(dynamic_offsets_count - 1) - } else { - None - }, - binding_size, - binding_location: layout.binding, + objc::rc::autoreleasepool(|| { + let mut bg = super::BindGroup::default(); + for (&stage, counter) in super::NAGA_STAGES.iter().zip(bg.counters.iter_mut()) { + let stage_bit = map_naga_stage(stage); + let mut dynamic_offsets_count = 0u32; + let layout_and_entry_iter = desc.entries.iter().map(|entry| { + let layout = desc + .layout + .entries + .iter() + .find(|layout_entry| layout_entry.binding == entry.binding) + .expect("internal error: no layout entry found with binding slot"); + (entry, layout) + }); + for (entry, layout) in layout_and_entry_iter { + // Bindless path + if layout.count.is_some() { + let count = entry.count; + + let stages = conv::map_render_stages(layout.visibility); + let uses = conv::map_resource_usage(&layout.ty); + + // Create argument buffer for this array + let buffer = self.shared.device.lock().new_buffer( + 8 * count as u64, + metal::MTLResourceOptions::HazardTrackingModeUntracked + | metal::MTLResourceOptions::StorageModeShared, + ); + + let contents: &mut [metal::MTLResourceID] = unsafe { + std::slice::from_raw_parts_mut(buffer.contents().cast(), count as usize) + }; + + match layout.ty { + wgt::BindingType::Texture { .. } + | wgt::BindingType::StorageTexture { .. } => { + let start = entry.resource_index as usize; + let end = start + count as usize; + let textures = &desc.textures[start..end]; + + for (idx, tex) in textures.iter().enumerate() { + contents[idx] = tex.view.raw.gpu_resource_id(); + + let use_info = bg + .resources_to_use + .entry(tex.view.as_raw().cast()) + .or_default(); + use_info.stages |= stages; + use_info.uses |= uses; + use_info.visible_in_compute |= + layout.visibility.contains(wgt::ShaderStages::COMPUTE); + } + } + wgt::BindingType::Sampler { .. } => { + let start = entry.resource_index as usize; + let end = start + count as usize; + let samplers = &desc.samplers[start..end]; + + for (idx, &sampler) in samplers.iter().enumerate() { + contents[idx] = sampler.raw.gpu_resource_id(); + // Samplers aren't resources like buffers and textures, so don't + // need to be passed to useResource } - })); + } + _ => { + unimplemented!(); + } + } + + bg.buffers.push(super::BufferResource { + ptr: unsafe { NonNull::new_unchecked(buffer.as_ptr()) }, + offset: 0, + dynamic_index: None, + binding_size: None, + binding_location: layout.binding, + }); counter.buffers += 1; + + bg.argument_buffers.push(buffer) } - wgt::BindingType::Sampler { .. } => { - let start = entry.resource_index as usize; - let end = start + size as usize; - bg.samplers - .extend(desc.samplers[start..end].iter().map(|samp| samp.as_raw())); - counter.samplers += size; - } - wgt::BindingType::Texture { .. } | wgt::BindingType::StorageTexture { .. } => { - let start = entry.resource_index as usize; - let end = start + size as usize; - bg.textures.extend( - desc.textures[start..end] - .iter() - .map(|tex| tex.view.as_raw()), - ); - counter.textures += size; + // Bindfull path + else { + if let wgt::BindingType::Buffer { + has_dynamic_offset: true, + .. + } = layout.ty + { + dynamic_offsets_count += 1; + } + if !layout.visibility.contains(stage_bit) { + continue; + } + match layout.ty { + wgt::BindingType::Buffer { + ty, + has_dynamic_offset, + .. + } => { + let start = entry.resource_index as usize; + let end = start + 1; + bg.buffers + .extend(desc.buffers[start..end].iter().map(|source| { + // Given the restrictions on `BufferBinding::offset`, + // this should never be `None`. + let remaining_size = wgt::BufferSize::new( + source.buffer.size - source.offset, + ); + let binding_size = match ty { + wgt::BufferBindingType::Storage { .. } => { + source.size.or(remaining_size) + } + _ => None, + }; + super::BufferResource { + ptr: source.buffer.as_raw(), + offset: source.offset, + dynamic_index: if has_dynamic_offset { + Some(dynamic_offsets_count - 1) + } else { + None + }, + binding_size, + binding_location: layout.binding, + } + })); + counter.buffers += 1; + } + wgt::BindingType::Sampler { .. } => { + let start = entry.resource_index as usize; + let end = start + 1; + bg.samplers.extend( + desc.samplers[start..end].iter().map(|samp| samp.as_raw()), + ); + counter.samplers += 1; + } + wgt::BindingType::Texture { .. } + | wgt::BindingType::StorageTexture { .. } => { + let start = entry.resource_index as usize; + let end = start + 1; + bg.textures.extend( + desc.textures[start..end] + .iter() + .map(|tex| tex.view.as_raw()), + ); + counter.textures += 1; + } + wgt::BindingType::AccelerationStructure => unimplemented!(), + } } - wgt::BindingType::AccelerationStructure => unimplemented!(), } } - } - self.counters.bind_groups.add(1); + self.counters.bind_groups.add(1); - Ok(bg) + Ok(bg) + }) } unsafe fn destroy_bind_group(&self, _group: super::BindGroup) { @@ -889,7 +987,7 @@ impl crate::Device for super::Device { match shader { crate::ShaderInput::Naga(naga) => Ok(super::ShaderModule { naga, - runtime_checks: desc.runtime_checks, + bounds_checks: desc.runtime_checks, }), crate::ShaderInput::SpirV(_) => { panic!("SPIRV_SHADER_PASSTHROUGH is not enabled for this backend") diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 767216225a..448349e2b0 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -26,6 +26,7 @@ mod surface; mod time; use std::{ + collections::HashMap, fmt, iter, ops, ptr::NonNull, sync::{atomic, Arc}, @@ -199,7 +200,7 @@ struct PrivateCapabilities { msaa_apple3: bool, msaa_apple7: bool, resource_heaps: bool, - argument_buffers: bool, + argument_buffers: metal::MTLArgumentBuffersTier, shared_textures: bool, mutable_comparison_samplers: bool, sampler_clamp_to_border: bool, @@ -651,10 +652,23 @@ trait AsNative { fn as_native(&self) -> &Self::Native; } +type ResourcePtr = NonNull; type BufferPtr = NonNull; type TexturePtr = NonNull; type SamplerPtr = NonNull; +impl AsNative for ResourcePtr { + type Native = metal::ResourceRef; + #[inline] + fn from(native: &Self::Native) -> Self { + unsafe { NonNull::new_unchecked(native.as_ptr()) } + } + #[inline] + fn as_native(&self) -> &Self::Native { + unsafe { Self::Native::from_ptr(self.as_ptr()) } + } +} + impl AsNative for BufferPtr { type Native = metal::BufferRef; #[inline] @@ -710,12 +724,32 @@ struct BufferResource { binding_location: u32, } +#[derive(Debug)] +struct UseResourceInfo { + uses: metal::MTLResourceUsage, + stages: metal::MTLRenderStages, + visible_in_compute: bool, +} + +impl Default for UseResourceInfo { + fn default() -> Self { + Self { + uses: metal::MTLResourceUsage::empty(), + stages: metal::MTLRenderStages::empty(), + visible_in_compute: false, + } + } +} + #[derive(Debug, Default)] pub struct BindGroup { counters: MultiStageResourceCounters, buffers: Vec, samplers: Vec, textures: Vec, + + argument_buffers: Vec, + resources_to_use: HashMap, } impl crate::DynBindGroup for BindGroup {} @@ -726,7 +760,7 @@ unsafe impl Sync for BindGroup {} #[derive(Debug)] pub struct ShaderModule { naga: crate::NagaShader, - runtime_checks: bool, + bounds_checks: wgt::ShaderRuntimeChecks, } impl crate::DynShaderModule for ShaderModule {} diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 2bb68e1012..de9f85eaa6 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -777,6 +777,11 @@ impl PhysicalDeviceFeatures { caps.supports_extension(google::display_timing::NAME), ); + features.set( + F::VULKAN_EXTERNAL_MEMORY_WIN32, + caps.supports_extension(khr::external_memory_win32::NAME), + ); + (features, dl_flags) } @@ -1048,8 +1053,13 @@ impl PhysicalDeviceProperties { // TODO: programmatically determine this, if possible. It's unclear whether we can // as of https://github.com/gpuweb/gpuweb/issues/2965#issuecomment-1361315447. - // We could increase the limit when we aren't on a tiled GPU. - let max_color_attachment_bytes_per_sample = 32; + // + // In theory some tilers may not support this much. We can't tell however, and + // the driver will throw a DEVICE_REMOVED if it goes too high in usage. This is fine. + // + // 16 bytes per sample is the maximum size for a color attachment. + let max_color_attachment_bytes_per_sample = + limits.max_color_attachments * wgt::TextureFormat::MAX_TARGET_PIXEL_BYTE_COST; wgt::Limits { max_texture_dimension_1d: limits.max_image_dimension1_d, @@ -1499,7 +1509,7 @@ impl super::Instance { Some(features) => features.imageless_framebuffer == vk::TRUE, None => phd_features .imageless_framebuffer - .map_or(false, |ext| ext.imageless_framebuffer != 0), + .is_some_and(|ext| ext.imageless_framebuffer != 0), }, image_view_usage: phd_capabilities.device_api_version >= vk::API_VERSION_1_1 || phd_capabilities.supports_extension(khr::maintenance2::NAME), @@ -1507,7 +1517,7 @@ impl super::Instance { Some(features) => features.timeline_semaphore == vk::TRUE, None => phd_features .timeline_semaphore - .map_or(false, |ext| ext.timeline_semaphore != 0), + .is_some_and(|ext| ext.timeline_semaphore != 0), }, texture_d24: supports_format( &self.shared.raw, @@ -1538,7 +1548,7 @@ impl super::Instance { Some(ref f) => f.robust_image_access2 != 0, None => phd_features .image_robustness - .map_or(false, |ext| ext.robust_image_access != 0), + .is_some_and(|ext| ext.robust_image_access != 0), }, robust_buffer_access2: phd_features .robustness2 @@ -1552,14 +1562,13 @@ impl super::Instance { .unwrap_or_default(), zero_initialize_workgroup_memory: phd_features .zero_initialize_workgroup_memory - .map_or(false, |ext| { - ext.shader_zero_initialize_workgroup_memory == vk::TRUE - }), + .is_some_and(|ext| ext.shader_zero_initialize_workgroup_memory == vk::TRUE), image_format_list: phd_capabilities.device_api_version >= vk::API_VERSION_1_2 || phd_capabilities.supports_extension(khr::image_format_list::NAME), - #[cfg(windows)] - external_memory_win32: phd_capabilities - .supports_extension(khr::external_memory_win32::NAME), + maximum_samplers: phd_capabilities + .properties + .limits + .max_sampler_allocation_count, }; let capabilities = crate::Capabilities { limits: phd_capabilities.to_wgpu_limits(), @@ -1908,6 +1917,9 @@ impl super::Adapter { workarounds: self.workarounds, render_passes: Mutex::new(Default::default()), framebuffers: Mutex::new(Default::default()), + sampler_cache: Mutex::new(super::sampler::SamplerCache::new( + self.private_caps.maximum_samplers, + )), memory_allocations_counter: Default::default(), }); diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index 75dd6905df..257b5b626f 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -381,22 +381,31 @@ pub fn map_index_format(index_format: wgt::IndexFormat) -> vk::IndexType { pub fn map_vertex_format(vertex_format: wgt::VertexFormat) -> vk::Format { use wgt::VertexFormat as Vf; match vertex_format { + Vf::Uint8 => vk::Format::R8_UINT, Vf::Uint8x2 => vk::Format::R8G8_UINT, Vf::Uint8x4 => vk::Format::R8G8B8A8_UINT, + Vf::Sint8 => vk::Format::R8_SINT, Vf::Sint8x2 => vk::Format::R8G8_SINT, Vf::Sint8x4 => vk::Format::R8G8B8A8_SINT, + Vf::Unorm8 => vk::Format::R8_UNORM, Vf::Unorm8x2 => vk::Format::R8G8_UNORM, Vf::Unorm8x4 => vk::Format::R8G8B8A8_UNORM, + Vf::Snorm8 => vk::Format::R8_SNORM, Vf::Snorm8x2 => vk::Format::R8G8_SNORM, Vf::Snorm8x4 => vk::Format::R8G8B8A8_SNORM, + Vf::Uint16 => vk::Format::R16_UINT, Vf::Uint16x2 => vk::Format::R16G16_UINT, Vf::Uint16x4 => vk::Format::R16G16B16A16_UINT, + Vf::Sint16 => vk::Format::R16_SINT, Vf::Sint16x2 => vk::Format::R16G16_SINT, Vf::Sint16x4 => vk::Format::R16G16B16A16_SINT, + Vf::Unorm16 => vk::Format::R16_UNORM, Vf::Unorm16x2 => vk::Format::R16G16_UNORM, Vf::Unorm16x4 => vk::Format::R16G16B16A16_UNORM, + Vf::Snorm16 => vk::Format::R16_SNORM, Vf::Snorm16x2 => vk::Format::R16G16_SNORM, Vf::Snorm16x4 => vk::Format::R16G16B16A16_SNORM, + Vf::Float16 => vk::Format::R16_SFLOAT, Vf::Float16x2 => vk::Format::R16G16_SFLOAT, Vf::Float16x4 => vk::Format::R16G16B16A16_SFLOAT, Vf::Float32 => vk::Format::R32_SFLOAT, @@ -416,6 +425,7 @@ pub fn map_vertex_format(vertex_format: wgt::VertexFormat) -> vk::Format { Vf::Float64x3 => vk::Format::R64G64B64_SFLOAT, Vf::Float64x4 => vk::Format::R64G64B64A64_SFLOAT, Vf::Unorm10_10_10_2 => vk::Format::A2B10G10R10_UNORM_PACK32, + Vf::Unorm8x4Bgra => vk::Format::B8G8R8A8_UNORM, } } diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index 5076da9d06..fdf1f3e3ef 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -417,6 +417,10 @@ impl vk::DescriptorType::STORAGE_BUFFER_DYNAMIC, descriptor_count.storage_buffer_dynamic, ), + ( + vk::DescriptorType::ACCELERATION_STRUCTURE_KHR, + descriptor_count.acceleration_structure, + ), ]; let filtered_counts = unfiltered_counts @@ -786,7 +790,7 @@ impl super::Device { /// # Safety /// - /// - Vulkan 1.1+ (or VK_KHR_external_memory) + /// - Vulkan (with VK_KHR_external_memory_win32) /// - The `d3d11_shared_handle` must be valid and respecting `desc` /// - `VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT` flag is used because we need to hold a reference to the handle #[cfg(windows)] @@ -795,8 +799,12 @@ impl super::Device { d3d11_shared_handle: windows::Win32::Foundation::HANDLE, desc: &crate::TextureDescriptor, ) -> Result { - if !self.shared.private_caps.external_memory_win32 { - log::error!("VK_KHR_external_memory extension is required"); + if !self + .shared + .features + .contains(wgt::Features::VULKAN_EXTERNAL_MEMORY_WIN32) + { + log::error!("Vulkan driver does not support VK_KHR_external_memory_win32"); return Err(crate::DeviceError::ResourceCreationFailed); } @@ -897,14 +905,14 @@ impl super::Device { entry_point: stage.entry_point.to_string(), shader_stage: naga_stage, }; - let needs_temp_options = !runtime_checks + let needs_temp_options = !runtime_checks.bounds_checks || !binding_map.is_empty() || naga_shader.debug_source.is_some() || !stage.zero_initialize_workgroup_memory; let mut temp_options; let options = if needs_temp_options { temp_options = self.naga_options.clone(); - if !runtime_checks { + if !runtime_checks.bounds_checks { temp_options.bounds_check_policies = naga::proc::BoundsCheckPolicies { index: naga::proc::BoundsCheckPolicy::Unchecked, buffer: naga::proc::BoundsCheckPolicy::Unchecked, @@ -1296,7 +1304,7 @@ impl crate::Device for super::Device { &self, desc: &crate::SamplerDescriptor, ) -> Result { - let mut vk_info = vk::SamplerCreateInfo::default() + let mut create_info = vk::SamplerCreateInfo::default() .flags(vk::SamplerCreateFlags::empty()) .mag_filter(conv::map_filter_mode(desc.mag_filter)) .min_filter(conv::map_filter_mode(desc.min_filter)) @@ -1308,7 +1316,7 @@ impl crate::Device for super::Device { .max_lod(desc.lod_clamp.end); if let Some(fun) = desc.compare { - vk_info = vk_info + create_info = create_info .compare_enable(true) .compare_op(conv::map_comparison(fun)); } @@ -1316,32 +1324,38 @@ impl crate::Device for super::Device { if desc.anisotropy_clamp != 1 { // We only enable anisotropy if it is supported, and wgpu-hal interface guarantees // the clamp is in the range [1, 16] which is always supported if anisotropy is. - vk_info = vk_info + create_info = create_info .anisotropy_enable(true) .max_anisotropy(desc.anisotropy_clamp as f32); } if let Some(color) = desc.border_color { - vk_info = vk_info.border_color(conv::map_border_color(color)); + create_info = create_info.border_color(conv::map_border_color(color)); } - let raw = unsafe { - self.shared - .raw - .create_sampler(&vk_info, None) - .map_err(super::map_host_device_oom_and_ioca_err)? - }; + let raw = self + .shared + .sampler_cache + .lock() + .create_sampler(&self.shared.raw, create_info)?; + // Note: Cached samplers will just continually overwrite the label + // + // https://github.com/gfx-rs/wgpu/issues/6867 if let Some(label) = desc.label { unsafe { self.shared.set_object_name(raw, label) }; } self.counters.samplers.add(1); - Ok(super::Sampler { raw }) + Ok(super::Sampler { raw, create_info }) } unsafe fn destroy_sampler(&self, sampler: super::Sampler) { - unsafe { self.shared.raw.destroy_sampler(sampler.raw, None) }; + self.shared.sampler_cache.lock().destroy_sampler( + &self.shared.raw, + sampler.create_info, + sampler.raw, + ); self.counters.samplers.sub(1); } @@ -1813,7 +1827,7 @@ impl crate::Device for super::Device { file_name: d.file_name.as_ref().as_ref(), language: naga::back::spv::SourceLanguage::WGSL, }); - if !desc.runtime_checks { + if !desc.runtime_checks.bounds_checks { naga_options.bounds_check_policies = naga::proc::BoundsCheckPolicies { index: naga::proc::BoundsCheckPolicy::Unchecked, buffer: naga::proc::BoundsCheckPolicy::Unchecked, @@ -2347,6 +2361,7 @@ impl crate::Device for super::Device { for triangles in in_geometries { let mut triangle_data = vk::AccelerationStructureGeometryTrianglesDataKHR::default() + .index_type(vk::IndexType::NONE_KHR) .vertex_format(conv::map_vertex_format(triangles.vertex_format)) .max_vertex(triangles.vertex_count) .vertex_stride(triangles.vertex_stride); diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 7d4d7f7fe9..1f75bba215 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -29,6 +29,7 @@ mod command; mod conv; mod device; mod instance; +mod sampler; use std::{ borrow::Borrow, @@ -532,8 +533,7 @@ struct PrivateCapabilities { robust_image_access2: bool, zero_initialize_workgroup_memory: bool, image_format_list: bool, - #[cfg(windows)] - external_memory_win32: bool, + maximum_samplers: u32, } bitflags::bitflags!( @@ -643,6 +643,7 @@ struct DeviceShared { features: wgt::Features, render_passes: Mutex>, framebuffers: Mutex>, + sampler_cache: Mutex, memory_allocations_counter: InternalCounter, } @@ -830,6 +831,7 @@ impl TextureView { #[derive(Debug)] pub struct Sampler { raw: vk::Sampler, + create_info: vk::SamplerCreateInfo<'static>, } impl crate::DynSampler for Sampler {} @@ -974,7 +976,7 @@ pub enum ShaderModule { Raw(vk::ShaderModule), Intermediate { naga_shader: crate::NagaShader, - runtime_checks: bool, + runtime_checks: wgt::ShaderRuntimeChecks, }, } diff --git a/wgpu-hal/src/vulkan/sampler.rs b/wgpu-hal/src/vulkan/sampler.rs new file mode 100644 index 0000000000..11030226f0 --- /dev/null +++ b/wgpu-hal/src/vulkan/sampler.rs @@ -0,0 +1,178 @@ +//! Sampler cache for Vulkan backend. +//! +//! Nearly identical to the DX12 sampler cache, without descriptor heap management. + +use std::collections::{hash_map::Entry, HashMap}; + +use ash::vk; +use ordered_float::OrderedFloat; + +/// If the allowed sampler count is above this value, the sampler cache is disabled. +const ENABLE_SAMPLER_CACHE_CUTOFF: u32 = 1 << 20; + +/// [`vk::SamplerCreateInfo`] is not hashable, so we wrap it in a newtype that is. +/// +/// We use [`OrderedFloat`] to allow for floating point values to be compared and +/// hashed in a defined way. +#[derive(Copy, Clone)] +struct HashableSamplerCreateInfo(vk::SamplerCreateInfo<'static>); + +impl PartialEq for HashableSamplerCreateInfo { + fn eq(&self, other: &Self) -> bool { + self.0.flags == other.0.flags + && self.0.mag_filter == other.0.mag_filter + && self.0.min_filter == other.0.min_filter + && self.0.mipmap_mode == other.0.mipmap_mode + && self.0.address_mode_u == other.0.address_mode_u + && self.0.address_mode_v == other.0.address_mode_v + && self.0.address_mode_w == other.0.address_mode_w + && OrderedFloat(self.0.mip_lod_bias) == OrderedFloat(other.0.mip_lod_bias) + && self.0.anisotropy_enable == other.0.anisotropy_enable + && OrderedFloat(self.0.max_anisotropy) == OrderedFloat(other.0.max_anisotropy) + && self.0.compare_enable == other.0.compare_enable + && self.0.compare_op == other.0.compare_op + && OrderedFloat(self.0.min_lod) == OrderedFloat(other.0.min_lod) + && OrderedFloat(self.0.max_lod) == OrderedFloat(other.0.max_lod) + && self.0.border_color == other.0.border_color + && self.0.unnormalized_coordinates == other.0.unnormalized_coordinates + } +} + +impl Eq for HashableSamplerCreateInfo {} + +impl std::hash::Hash for HashableSamplerCreateInfo { + fn hash(&self, state: &mut H) { + self.0.flags.hash(state); + self.0.mag_filter.hash(state); + self.0.min_filter.hash(state); + self.0.mipmap_mode.hash(state); + self.0.address_mode_u.hash(state); + self.0.address_mode_v.hash(state); + self.0.address_mode_w.hash(state); + OrderedFloat(self.0.mip_lod_bias).hash(state); + self.0.anisotropy_enable.hash(state); + OrderedFloat(self.0.max_anisotropy).hash(state); + self.0.compare_enable.hash(state); + self.0.compare_op.hash(state); + OrderedFloat(self.0.min_lod).hash(state); + OrderedFloat(self.0.max_lod).hash(state); + self.0.border_color.hash(state); + self.0.unnormalized_coordinates.hash(state); + } +} + +/// Entry in the sampler cache. +struct CacheEntry { + sampler: vk::Sampler, + ref_count: u32, +} + +/// Global sampler cache. +/// +/// As some devices have a low limit (4000) on the number of unique samplers that can be created, +/// we need to cache samplers to avoid running out if people eagerly create duplicate samplers. +pub(crate) struct SamplerCache { + /// Mapping from the sampler description to sampler and reference count. + samplers: HashMap, + /// Maximum number of unique samplers that can be created. + total_capacity: u32, + /// If true, the sampler cache is disabled and all samplers are created on demand. + passthrough: bool, +} + +impl SamplerCache { + pub fn new(total_capacity: u32) -> Self { + let passthrough = total_capacity >= ENABLE_SAMPLER_CACHE_CUTOFF; + Self { + samplers: HashMap::new(), + total_capacity, + passthrough, + } + } + + /// Create a sampler, or return an existing one if it already exists. + /// + /// If the sampler already exists, the reference count is incremented. + /// + /// If the sampler does not exist, a new sampler is created and inserted into the cache. + /// + /// If the cache is full, an error is returned. + pub fn create_sampler( + &mut self, + device: &ash::Device, + create_info: vk::SamplerCreateInfo<'static>, + ) -> Result { + if self.passthrough { + return unsafe { device.create_sampler(&create_info, None) } + .map_err(super::map_host_device_oom_and_ioca_err); + }; + + // Get the number of used samplers. Needs to be done before to appease the borrow checker. + let used_samplers = self.samplers.len(); + + match self.samplers.entry(HashableSamplerCreateInfo(create_info)) { + Entry::Occupied(occupied_entry) => { + // We have found a match, so increment the refcount and return the index. + let value = occupied_entry.into_mut(); + value.ref_count += 1; + Ok(value.sampler) + } + Entry::Vacant(vacant_entry) => { + // We need to create a new sampler. + + // We need to check if we can create more samplers. + if used_samplers >= self.total_capacity as usize { + log::error!("There is no more room in the global sampler heap for more unique samplers. Your device supports a maximum of {} unique samplers.", self.samplers.len()); + return Err(crate::DeviceError::OutOfMemory); + } + + // Create the sampler. + let sampler = unsafe { device.create_sampler(&create_info, None) } + .map_err(super::map_host_device_oom_and_ioca_err)?; + + // Insert the new sampler into the mapping. + vacant_entry.insert(CacheEntry { + sampler, + ref_count: 1, + }); + + Ok(sampler) + } + } + } + + /// Decrease the reference count of a sampler and destroy it if the reference count reaches 0. + /// + /// The provided sampler is checked against the sampler in the cache to ensure there is no clerical error. + pub fn destroy_sampler( + &mut self, + device: &ash::Device, + create_info: vk::SamplerCreateInfo<'static>, + provided_sampler: vk::Sampler, + ) { + if self.passthrough { + unsafe { device.destroy_sampler(provided_sampler, None) }; + return; + }; + + let Entry::Occupied(mut hash_map_entry) = + self.samplers.entry(HashableSamplerCreateInfo(create_info)) + else { + log::error!("Trying to destroy a sampler that does not exist."); + return; + }; + let cache_entry = hash_map_entry.get_mut(); + + assert_eq!( + cache_entry.sampler, provided_sampler, + "Provided sampler does not match the sampler in the cache." + ); + + cache_entry.ref_count -= 1; + + if cache_entry.ref_count == 0 { + unsafe { device.destroy_sampler(cache_entry.sampler, None) }; + hash_map_entry.remove(); + } + } +} diff --git a/wgpu-info/Cargo.toml b/wgpu-info/Cargo.toml index 50bc89bf5c..7e3f54ec59 100644 --- a/wgpu-info/Cargo.toml +++ b/wgpu-info/Cargo.toml @@ -16,4 +16,4 @@ env_logger.workspace = true pico-args.workspace = true serde.workspace = true serde_json.workspace = true -wgpu = { workspace = true, features = ["serde"] } +wgpu = { workspace = true, features = ["serde", "dx12", "metal", "static-dxc"] } diff --git a/wgpu-info/src/report.rs b/wgpu-info/src/report.rs index 5e27fa7c0e..3d7d257e45 100644 --- a/wgpu-info/src/report.rs +++ b/wgpu-info/src/report.rs @@ -17,10 +17,11 @@ pub struct GpuReport { impl GpuReport { pub fn generate() -> Self { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::util::backend_bits_from_env().unwrap_or_default(), flags: wgpu::InstanceFlags::debugging().with_env(), - dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(), + dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env() + .unwrap_or(wgpu::Dx12Compiler::StaticDxc), gles_minor_version: wgpu::util::gles_minor_version_from_env().unwrap_or_default(), }); let adapters = instance.enumerate_adapters(wgpu::Backends::all()); diff --git a/wgpu-types/Cargo.toml b/wgpu-types/Cargo.toml index 3dc6d7f799..5db7cf1f5f 100644 --- a/wgpu-types/Cargo.toml +++ b/wgpu-types/Cargo.toml @@ -3,7 +3,7 @@ name = "wgpu-types" version = "23.0.0" authors = ["gfx-rs developers"] edition = "2021" -description = "WebGPU types" +description = "Common types and utilities for wgpu, the cross-platform, safe, pure-rust graphics API" homepage = "https://wgpu.rs/" repository = "https://github.com/gfx-rs/wgpu" keywords = ["graphics"] @@ -34,6 +34,9 @@ serde = ["dep:serde"] # Enables some internal instrumentation for debugging purposes. counters = [] +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(web_sys_unstable_apis)'] } + [dependencies] bitflags = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"], optional = true } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 533339d031..77309ca211 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -559,7 +559,6 @@ bitflags::bitflags! { /// - `buffer myBuffer { ... } buffer_array[10]` (GLSL) /// /// Supported platforms: - /// - DX12 /// - Vulkan /// /// This is a native only feature. @@ -629,18 +628,27 @@ bitflags::bitflags! { const UNIFORM_BUFFER_AND_STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING = 1 << 31; /// Allows the user to create bind groups containing arrays with less bindings than the BindGroupLayout. /// + /// Supported platforms: + /// - Vulkan + /// - DX12 + /// /// This is a native only feature. const PARTIALLY_BOUND_BINDING_ARRAY = 1 << 32; /// Allows the user to call [`RenderPass::multi_draw_indirect`] and [`RenderPass::multi_draw_indexed_indirect`]. /// /// Allows multiple indirect calls to be dispatched from a single buffer. /// - /// Supported platforms: + /// Natively Supported Platforms: /// - DX12 /// - Vulkan - /// - Metal on Apple3+ or Mac1+ (Emulated on top of `draw_indirect` and `draw_indexed_indirect`) /// - /// This is a native only feature. + /// Emulated Platforms: + /// - Metal + /// - OpenGL + /// - WebGPU + /// + /// Emulation is preformed by looping over the individual indirect draw calls in the backend. This is still significantly + /// faster than enulating it yourself, as wgpu only does draw call validation once. /// /// [`RenderPass::multi_draw_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indirect /// [`RenderPass::multi_draw_indexed_indirect`]: ../wgpu/struct.RenderPass.html#method.multi_draw_indexed_indirect @@ -948,6 +956,16 @@ bitflags::bitflags! { /// [VK_GOOGLE_display_timing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html /// [`Surface::as_hal()`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html#method.as_hal const VULKAN_GOOGLE_DISPLAY_TIMING = 1 << 62; + + /// Allows using the [VK_KHR_external_memory_win32] Vulkan extension. + /// + /// Supported platforms: + /// - Vulkan (with [VK_KHR_external_memory_win32]) + /// + /// This is a native only feature. + /// + /// [VK_KHR_external_memory_win32]: https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_external_memory_win32.html + const VULKAN_EXTERNAL_MEMORY_WIN32 = 1 << 63; } } @@ -1188,7 +1206,10 @@ pub struct Limits { /// The maximum allowed number of color attachments. pub max_color_attachments: u32, /// The maximum number of bytes necessary to hold one sample (pixel or subpixel) of render - /// pipeline output data, across all color attachments. + /// pipeline output data, across all color attachments as described by [`TextureFormat::target_pixel_byte_cost`] + /// and [`TextureFormat::target_component_alignment`]. Defaults to 32. Higher is "better". + /// + /// ⚠️ `Rgba8Unorm`/`Rgba8Snorm`/`Bgra8Unorm`/`Bgra8Snorm` are deceptively 8 bytes per sample. ⚠️ pub max_color_attachment_bytes_per_sample: u32, /// Maximum number of bytes used for workgroup memory in a compute entry point. Defaults to /// 16384. Higher is "better". @@ -1749,7 +1770,7 @@ pub enum ShaderModel { /// Supported physical device types. #[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DeviceType { /// Other or Unknown. @@ -1767,7 +1788,7 @@ pub enum DeviceType { //TODO: convert `vendor` and `device` to `u32` /// Information about an adapter. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AdapterInfo { /// Adapter name @@ -3779,6 +3800,9 @@ impl TextureFormat { } } + /// The largest number that can be returned by [`Self::target_pixel_byte_cost`]. + pub const MAX_TARGET_PIXEL_BYTE_COST: u32 = 16; + /// The number of bytes occupied per pixel in a color attachment /// #[must_use] @@ -3804,11 +3828,13 @@ impl TextureFormat { | Self::R32Uint | Self::R32Sint | Self::R32Float => Some(4), + // Despite being 4 bytes per pixel, these are 8 bytes per pixel in the table Self::Rgba8Unorm | Self::Rgba8UnormSrgb | Self::Rgba8Snorm | Self::Bgra8Unorm | Self::Bgra8UnormSrgb + // --- | Self::Rgba16Uint | Self::Rgba16Sint | Self::Rgba16Unorm @@ -3821,6 +3847,7 @@ impl TextureFormat { | Self::Rgb10a2Unorm | Self::Rg11b10Ufloat => Some(8), Self::Rgba32Uint | Self::Rgba32Sint | Self::Rgba32Float => Some(16), + // ⚠️ If you add formats with larger sizes, make sure you change `MAX_TARGET_PIXEL_BYTE_COST`` ⚠️ Self::Stencil8 | Self::Depth16Unorm | Self::Depth24Plus @@ -4886,6 +4913,95 @@ impl PartialEq for DepthBiasState { impl Eq for DepthBiasState {} +/// Operation to perform to the output attachment at the start of a render pass. +/// +/// Corresponds to [WebGPU `GPULoadOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpuloadop), +/// plus the corresponding clearValue. +#[repr(u8)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum LoadOp { + /// Loads the specified value for this attachment into the render pass. + /// + /// On some GPU hardware (primarily mobile), "clear" is significantly cheaper + /// because it avoids loading data from main memory into tile-local memory. + /// + /// On other GPU hardware, there isn’t a significant difference. + /// + /// As a result, it is recommended to use "clear" rather than "load" in cases + /// where the initial value doesn’t matter + /// (e.g. the render target will be cleared using a skybox). + Clear(V) = 0, + /// Loads the existing value for this attachment into the render pass. + Load = 1, +} + +impl LoadOp { + /// Returns true if variants are same (ignoring clear value) + pub fn eq_variant(&self, other: LoadOp) -> bool { + matches!( + (self, other), + (LoadOp::Clear(_), LoadOp::Clear(_)) | (LoadOp::Load, LoadOp::Load) + ) + } +} + +impl Default for LoadOp { + fn default() -> Self { + Self::Clear(Default::default()) + } +} + +/// Operation to perform to the output attachment at the end of a render pass. +/// +/// Corresponds to [WebGPU `GPUStoreOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). +#[repr(C)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum StoreOp { + /// Stores the resulting value of the render pass for this attachment. + #[default] + Store = 0, + /// Discards the resulting value of the render pass for this attachment. + /// + /// The attachment will be treated as uninitialized afterwards. + /// (If only either Depth or Stencil texture-aspects is set to `Discard`, + /// the respective other texture-aspect will be preserved.) + /// + /// This can be significantly faster on tile-based render hardware. + /// + /// Prefer this if the attachment is not read by subsequent passes. + Discard = 1, +} + +/// Pair of load and store operations for an attachment aspect. +/// +/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, +/// separate `loadOp` and `storeOp` fields are used instead. +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Operations { + /// How data should be read through this attachment. + pub load: LoadOp, + /// Whether data will be written to through this attachment. + /// + /// Note that resolve textures (if specified) are always written to, + /// regardless of this setting. + pub store: StoreOp, +} + +impl Default for Operations { + #[inline] + fn default() -> Self { + Self { + load: LoadOp::::default(), + store: StoreOp::default(), + } + } +} + /// Describes the depth/stencil state in a render pipeline. /// /// Corresponds to [WebGPU `GPUDepthStencilState`]( @@ -5180,77 +5296,98 @@ pub struct VertexAttribute { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum VertexFormat { + /// One unsigned byte (u8). `u32` in shaders. + Uint8 = 0, /// Two unsigned bytes (u8). `vec2` in shaders. - Uint8x2 = 0, + Uint8x2 = 1, /// Four unsigned bytes (u8). `vec4` in shaders. - Uint8x4 = 1, + Uint8x4 = 2, + /// One signed byte (i8). `i32` in shaders. + Sint8 = 3, /// Two signed bytes (i8). `vec2` in shaders. - Sint8x2 = 2, + Sint8x2 = 4, /// Four signed bytes (i8). `vec4` in shaders. - Sint8x4 = 3, + Sint8x4 = 5, + /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders. + Unorm8 = 6, /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2` in shaders. - Unorm8x2 = 4, + Unorm8x2 = 7, /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4` in shaders. - Unorm8x4 = 5, + Unorm8x4 = 8, + /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders. + Snorm8 = 9, /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2` in shaders. - Snorm8x2 = 6, + Snorm8x2 = 10, /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4` in shaders. - Snorm8x4 = 7, + Snorm8x4 = 11, + /// One unsigned short (u16). `u32` in shaders. + Uint16 = 12, /// Two unsigned shorts (u16). `vec2` in shaders. - Uint16x2 = 8, + Uint16x2 = 13, /// Four unsigned shorts (u16). `vec4` in shaders. - Uint16x4 = 9, + Uint16x4 = 14, + /// One signed short (u16). `i32` in shaders. + Sint16 = 15, /// Two signed shorts (i16). `vec2` in shaders. - Sint16x2 = 10, + Sint16x2 = 16, /// Four signed shorts (i16). `vec4` in shaders. - Sint16x4 = 11, + Sint16x4 = 17, + /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders. + Unorm16 = 18, /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2` in shaders. - Unorm16x2 = 12, + Unorm16x2 = 19, /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4` in shaders. - Unorm16x4 = 13, + Unorm16x4 = 20, + /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders. + Snorm16 = 21, /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2` in shaders. - Snorm16x2 = 14, + Snorm16x2 = 22, /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4` in shaders. - Snorm16x4 = 15, + Snorm16x4 = 23, + /// One half-precision float (no Rust equiv). `f32` in shaders. + Float16 = 24, /// Two half-precision floats (no Rust equiv). `vec2` in shaders. - Float16x2 = 16, + Float16x2 = 25, /// Four half-precision floats (no Rust equiv). `vec4` in shaders. - Float16x4 = 17, + Float16x4 = 26, /// One single-precision float (f32). `f32` in shaders. - Float32 = 18, + Float32 = 27, /// Two single-precision floats (f32). `vec2` in shaders. - Float32x2 = 19, + Float32x2 = 28, /// Three single-precision floats (f32). `vec3` in shaders. - Float32x3 = 20, + Float32x3 = 29, /// Four single-precision floats (f32). `vec4` in shaders. - Float32x4 = 21, + Float32x4 = 30, /// One unsigned int (u32). `u32` in shaders. - Uint32 = 22, + Uint32 = 31, /// Two unsigned ints (u32). `vec2` in shaders. - Uint32x2 = 23, + Uint32x2 = 32, /// Three unsigned ints (u32). `vec3` in shaders. - Uint32x3 = 24, + Uint32x3 = 33, /// Four unsigned ints (u32). `vec4` in shaders. - Uint32x4 = 25, + Uint32x4 = 34, /// One signed int (i32). `i32` in shaders. - Sint32 = 26, + Sint32 = 35, /// Two signed ints (i32). `vec2` in shaders. - Sint32x2 = 27, + Sint32x2 = 36, /// Three signed ints (i32). `vec3` in shaders. - Sint32x3 = 28, + Sint32x3 = 37, /// Four signed ints (i32). `vec4` in shaders. - Sint32x4 = 29, + Sint32x4 = 38, /// One double-precision float (f64). `f32` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64 = 30, + Float64 = 39, /// Two double-precision floats (f64). `vec2` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x2 = 31, + Float64x2 = 40, /// Three double-precision floats (f64). `vec3` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x3 = 32, + Float64x3 = 41, /// Four double-precision floats (f64). `vec4` in shaders. Requires [`Features::VERTEX_ATTRIBUTE_64BIT`]. - Float64x4 = 33, + Float64x4 = 42, /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4` in shaders. #[cfg_attr(feature = "serde", serde(rename = "unorm10-10-10-2"))] - Unorm10_10_10_2 = 34, + Unorm10_10_10_2 = 43, + /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4` in shaders. + #[cfg_attr(feature = "serde", serde(rename = "unorm8x4-bgra"))] + Unorm8x4Bgra = 44, } impl VertexFormat { @@ -5258,7 +5395,16 @@ impl VertexFormat { #[must_use] pub const fn size(&self) -> u64 { match self { - Self::Uint8x2 | Self::Sint8x2 | Self::Unorm8x2 | Self::Snorm8x2 => 2, + Self::Uint8 | Self::Sint8 | Self::Unorm8 | Self::Snorm8 => 1, + Self::Uint8x2 + | Self::Sint8x2 + | Self::Unorm8x2 + | Self::Snorm8x2 + | Self::Uint16 + | Self::Sint16 + | Self::Unorm16 + | Self::Snorm16 + | Self::Float16 => 2, Self::Uint8x4 | Self::Sint8x4 | Self::Unorm8x4 @@ -5271,7 +5417,8 @@ impl VertexFormat { | Self::Float32 | Self::Uint32 | Self::Sint32 - | Self::Unorm10_10_10_2 => 4, + | Self::Unorm10_10_10_2 + | Self::Unorm8x4Bgra => 4, Self::Uint16x4 | Self::Sint16x4 | Self::Unorm16x4 @@ -6098,6 +6245,9 @@ pub struct TextureViewDescriptor { /// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of /// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3` pub dimension: Option, + /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. + /// If not provided, defaults to the full set of usage flags of the texture. + pub usage: Option, /// Aspect of the texture. Color textures must be [`TextureAspect::All`]. pub aspect: TextureAspect, /// Base mip level. @@ -7490,50 +7640,73 @@ impl DispatchIndirectArgs { } /// Describes how shader bound checks should be performed. -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ShaderBoundChecks { - runtime_checks: bool, +pub struct ShaderRuntimeChecks { + /// Enforce bounds checks in shaders, even if the underlying driver doesn't + /// support doing so natively. + /// + /// When this is `true`, `wgpu` promises that shaders can only read or + /// write the accessible region of a bindgroup's buffer bindings. If + /// the underlying graphics platform cannot implement these bounds checks + /// itself, `wgpu` will inject bounds checks before presenting the + /// shader to the platform. + /// + /// When this is `false`, `wgpu` only enforces such bounds checks if the + /// underlying platform provides a way to do so itself. `wgpu` does not + /// itself add any bounds checks to generated shader code. + /// + /// Note that `wgpu` users may try to initialize only those portions of + /// buffers that they anticipate might be read from. Passing `false` here + /// may allow shaders to see wider regions of the buffers than expected, + /// making such deferred initialization visible to the application. + pub bounds_checks: bool, + /// + /// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops. + /// + /// If it does, backend compilers MAY treat such a loop as unreachable code and draw + /// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled + /// when running untrusted code. + pub force_loop_bounding: bool, } -impl ShaderBoundChecks { - /// Creates a new configuration where the shader is bound checked. +impl ShaderRuntimeChecks { + /// Creates a new configuration where the shader is fully checked. #[must_use] - pub fn new() -> Self { - ShaderBoundChecks { - runtime_checks: true, - } + pub fn checked() -> Self { + unsafe { Self::all(true) } } - /// Creates a new configuration where the shader isn't bound checked. + /// Creates a new configuration where none of the checks are performed. /// /// # Safety /// - /// The caller MUST ensure that all shaders built with this configuration - /// don't perform any out of bounds reads or writes. - /// - /// Note that `wgpu_core`, in particular, initializes only those portions of - /// buffers that it expects might be read, and it does not expect contents - /// outside the ranges bound in bindgroups to be accessible, so using this - /// configuration with ill-behaved shaders could expose uninitialized GPU - /// memory contents to the application. + /// See the documentation for the `set_*` methods for the safety requirements + /// of each sub-configuration. #[must_use] - pub unsafe fn unchecked() -> Self { - ShaderBoundChecks { - runtime_checks: false, - } + pub fn unchecked() -> Self { + unsafe { Self::all(false) } } - /// Query whether runtime bound checks are enabled in this configuration + /// Creates a new configuration where all checks are enabled or disabled. To safely + /// create a configuration with all checks enabled, use [`ShaderRuntimeChecks::checked`]. + /// + /// # Safety + /// + /// See the documentation for the `set_*` methods for the safety requirements + /// of each sub-configuration. #[must_use] - pub fn runtime_checks(&self) -> bool { - self.runtime_checks + pub unsafe fn all(all_checks: bool) -> Self { + Self { + bounds_checks: all_checks, + force_loop_bounding: all_checks, + } } } -impl Default for ShaderBoundChecks { +impl Default for ShaderRuntimeChecks { fn default() -> Self { - Self::new() + Self::checked() } } @@ -7566,7 +7739,9 @@ pub enum Dx12Compiler { dxil_path: PathBuf, }, /// The statically-linked variant of Dxc. - /// The `static-dxc` feature is required to use this. + /// + /// The `static-dxc` feature is required for this setting to be used successfully on DX12. + /// Not available on `windows-aarch64-pc-*` targets. StaticDxc, } @@ -7590,7 +7765,7 @@ pub enum Gles3MinorVersion { } /// Options for creating an instance. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct InstanceDescriptor { /// Which `Backends` to enable. pub backends: Backends, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d05731958d..ca757e122c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -3,7 +3,7 @@ name = "wgpu" version.workspace = true authors.workspace = true edition.workspace = true -description = "Rusty WebGPU API wrapper" +description = "Cross-platform, safe, pure-rust graphics API" homepage.workspace = true repository.workspace = true keywords.workspace = true diff --git a/wgpu/build.rs b/wgpu/build.rs index e77c0fdd39..c3218093d5 100644 --- a/wgpu/build.rs +++ b/wgpu/build.rs @@ -14,5 +14,7 @@ fn main() { // This alias is _only_ if _we_ need naga in the wrapper. wgpu-core provides // its own re-export of naga, which can be used in other situations naga: { any(feature = "naga-ir", feature = "spirv", feature = "glsl") }, + // ⚠️ Keep in sync with target.cfg() definition in wgpu-hal/Cargo.toml and cfg_alias in `wgpu-hal` crate ⚠️ + static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64")) }, } } diff --git a/wgpu/src/api/adapter.rs b/wgpu/src/api/adapter.rs index 8fb5225bfe..0b38c53103 100644 --- a/wgpu/src/api/adapter.rs +++ b/wgpu/src/api/adapter.rs @@ -13,7 +13,7 @@ use crate::*; /// Does not have to be kept alive. /// /// Corresponds to [WebGPU `GPUAdapter`](https://gpuweb.github.io/gpuweb/#gpu-adapter). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Adapter { pub(crate) inner: dispatch::DispatchAdapter, } diff --git a/wgpu/src/api/bind_group.rs b/wgpu/src/api/bind_group.rs index 1cb7337855..dcec46542e 100644 --- a/wgpu/src/api/bind_group.rs +++ b/wgpu/src/api/bind_group.rs @@ -8,7 +8,7 @@ use crate::*; /// [`ComputePass`] with [`ComputePass::set_bind_group`]. /// /// Corresponds to [WebGPU `GPUBindGroup`](https://gpuweb.github.io/gpuweb/#gpubindgroup). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BindGroup { pub(crate) inner: dispatch::DispatchBindGroup, } diff --git a/wgpu/src/api/bind_group_layout.rs b/wgpu/src/api/bind_group_layout.rs index 191752a239..a55921ccc8 100644 --- a/wgpu/src/api/bind_group_layout.rs +++ b/wgpu/src/api/bind_group_layout.rs @@ -11,7 +11,7 @@ use crate::*; /// /// Corresponds to [WebGPU `GPUBindGroupLayout`]( /// https://gpuweb.github.io/gpuweb/#gpubindgrouplayout). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BindGroupLayout { pub(crate) inner: dispatch::DispatchBindGroupLayout, } diff --git a/wgpu/src/api/blas.rs b/wgpu/src/api/blas.rs index b64c01ba8f..67082700eb 100644 --- a/wgpu/src/api/blas.rs +++ b/wgpu/src/api/blas.rs @@ -1,6 +1,5 @@ use crate::dispatch; use crate::{Buffer, Label}; -use std::sync::Arc; use wgt::WasmNotSendSync; /// Descriptor for the size defining attributes of a triangle geometry, for a bottom level acceleration structure. @@ -44,7 +43,7 @@ static_assertions::assert_impl_all!(CreateBlasDescriptor<'_>: Send, Sync); /// [TlasPackage]: crate::TlasPackage #[derive(Debug, Clone)] pub struct TlasInstance { - pub(crate) blas: Arc, + pub(crate) blas: dispatch::DispatchBlas, /// Affine transform matrix 3x4 (rows x columns, row major order). pub transform: [f32; 12], /// Custom index for the instance used inside the shader. @@ -71,7 +70,7 @@ impl TlasInstance { /// generate a validation error. pub fn new(blas: &Blas, transform: [f32; 12], custom_index: u32, mask: u8) -> Self { Self { - blas: blas.shared.clone(), + blas: blas.inner.clone(), transform, custom_index, mask, @@ -83,7 +82,7 @@ impl TlasInstance { /// See the note on [TlasInstance] about the /// guarantees of keeping a BLAS alive. pub fn set_blas(&mut self, blas: &Blas) { - self.blas = blas.shared.clone(); + self.blas = blas.inner.clone(); } } @@ -128,13 +127,7 @@ pub struct BlasBuildEntry<'a> { } static_assertions::assert_impl_all!(BlasBuildEntry<'_>: WasmNotSendSync); -#[derive(Debug)] -pub(crate) struct BlasShared { - pub(crate) inner: dispatch::DispatchBlas, -} -static_assertions::assert_impl_all!(BlasShared: WasmNotSendSync); - -#[derive(Debug)] +#[derive(Debug, Clone)] /// Bottom Level Acceleration Structure (BLAS). /// /// A BLAS is a device-specific raytracing acceleration structure that contains geometry data. @@ -144,11 +137,11 @@ static_assertions::assert_impl_all!(BlasShared: WasmNotSendSync); /// [Tlas]: crate::Tlas pub struct Blas { pub(crate) handle: Option, - pub(crate) shared: Arc, + pub(crate) inner: dispatch::DispatchBlas, } static_assertions::assert_impl_all!(Blas: WasmNotSendSync); -crate::cmp::impl_eq_ord_hash_proxy!(Blas => .shared.inner); +crate::cmp::impl_eq_ord_hash_proxy!(Blas => .inner); impl Blas { /// Raw handle to the acceleration structure, used inside raw instance buffers. @@ -157,7 +150,7 @@ impl Blas { } /// Destroy the associated native resources as soon as possible. pub fn destroy(&self) { - self.shared.inner.destroy(); + self.inner.destroy(); } } diff --git a/wgpu/src/api/buffer.rs b/wgpu/src/api/buffer.rs index eacfd9ecc5..e4e3f1ab71 100644 --- a/wgpu/src/api/buffer.rs +++ b/wgpu/src/api/buffer.rs @@ -1,6 +1,7 @@ use std::{ error, fmt, ops::{Bound, Deref, DerefMut, Range, RangeBounds}, + sync::Arc, }; use parking_lot::Mutex; @@ -167,10 +168,10 @@ use crate::*; /// [mac]: BufferDescriptor::mapped_at_creation /// [`MAP_READ`]: BufferUsages::MAP_READ /// [`MAP_WRITE`]: BufferUsages::MAP_WRITE -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Buffer { pub(crate) inner: dispatch::DispatchBuffer, - pub(crate) map_context: Mutex, + pub(crate) map_context: Arc>, pub(crate) size: wgt::BufferAddress, pub(crate) usage: BufferUsages, // Todo: missing map_state https://www.w3.org/TR/webgpu/#dom-gpubuffer-mapstate diff --git a/wgpu/src/api/command_buffer.rs b/wgpu/src/api/command_buffer.rs index e76ae2d5e9..b582bf1f05 100644 --- a/wgpu/src/api/command_buffer.rs +++ b/wgpu/src/api/command_buffer.rs @@ -1,3 +1,7 @@ +use std::sync::Arc; + +use parking_lot::Mutex; + use crate::*; /// Handle to a command buffer on the GPU. @@ -7,11 +11,11 @@ use crate::*; /// a [`CommandEncoder`] and then calling [`CommandEncoder::finish`]. /// /// Corresponds to [WebGPU `GPUCommandBuffer`](https://gpuweb.github.io/gpuweb/#command-buffer). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CommandBuffer { - pub(crate) inner: Option, + pub(crate) inner: Arc>>, } #[cfg(send_sync)] static_assertions::assert_impl_all!(CommandBuffer: Send, Sync); -crate::cmp::impl_eq_ord_hash_proxy!(CommandBuffer => .inner); +crate::cmp::impl_eq_ord_hash_arc_address!(CommandBuffer => .inner); diff --git a/wgpu/src/api/command_encoder.rs b/wgpu/src/api/command_encoder.rs index cd493587a7..08c45f22e3 100644 --- a/wgpu/src/api/command_encoder.rs +++ b/wgpu/src/api/command_encoder.rs @@ -1,4 +1,4 @@ -use std::ops::Range; +use std::{ops::Range, sync::Arc}; use crate::{ api::{ @@ -35,6 +35,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(CommandEncoder => .inner); pub type CommandEncoderDescriptor<'a> = wgt::CommandEncoderDescriptor>; static_assertions::assert_impl_all!(CommandEncoderDescriptor<'_>: Send, Sync); +use parking_lot::Mutex; pub use wgt::TexelCopyBufferInfo as TexelCopyBufferInfoBase; /// View of a buffer which can be used to copy to/from a texture. /// @@ -59,7 +60,7 @@ impl CommandEncoder { let buffer = self.inner.finish(); CommandBuffer { - inner: Some(buffer), + inner: Arc::new(Mutex::new(Some(buffer))), } } diff --git a/wgpu/src/api/compute_pipeline.rs b/wgpu/src/api/compute_pipeline.rs index b1919301cc..325f0ba1ff 100644 --- a/wgpu/src/api/compute_pipeline.rs +++ b/wgpu/src/api/compute_pipeline.rs @@ -6,7 +6,7 @@ use crate::*; /// It can be created with [`Device::create_compute_pipeline`]. /// /// Corresponds to [WebGPU `GPUComputePipeline`](https://gpuweb.github.io/gpuweb/#compute-pipeline). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ComputePipeline { pub(crate) inner: dispatch::DispatchComputePipeline, } diff --git a/wgpu/src/api/device.rs b/wgpu/src/api/device.rs index be2f2a908b..f923ab4ee5 100644 --- a/wgpu/src/api/device.rs +++ b/wgpu/src/api/device.rs @@ -2,7 +2,7 @@ use std::{error, fmt, future::Future, sync::Arc}; use parking_lot::Mutex; -use crate::api::blas::{Blas, BlasGeometrySizeDescriptors, BlasShared, CreateBlasDescriptor}; +use crate::api::blas::{Blas, BlasGeometrySizeDescriptors, CreateBlasDescriptor}; use crate::api::tlas::{CreateTlasDescriptor, Tlas}; use crate::*; @@ -14,7 +14,7 @@ use crate::*; /// A device may be requested from an adapter with [`Adapter::request_device`]. /// /// Corresponds to [WebGPU `GPUDevice`](https://gpuweb.github.io/gpuweb/#gpu-device). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Device { pub(crate) inner: dispatch::DispatchDevice, } @@ -62,7 +62,7 @@ impl Device { self.inner.limits() } - /// Creates a shader module from either SPIR-V or WGSL source code. + /// Creates a shader module. /// ///
// NOTE: Keep this in sync with `naga::front::wgsl::parse_str`! @@ -80,28 +80,52 @@ impl Device { pub fn create_shader_module(&self, desc: ShaderModuleDescriptor<'_>) -> ShaderModule { let module = self .inner - .create_shader_module(desc, wgt::ShaderBoundChecks::new()); + .create_shader_module(desc, wgt::ShaderRuntimeChecks::checked()); ShaderModule { inner: module } } - /// Creates a shader module from either SPIR-V or WGSL source code without runtime checks. + /// Deprecated: Use [`create_shader_module_trusted`][csmt] instead. /// /// # Safety - /// In contrast with [`create_shader_module`](Self::create_shader_module) this function - /// creates a shader module without runtime checks which allows shaders to perform - /// operations which can lead to undefined behavior like indexing out of bounds, thus it's - /// the caller responsibility to pass a shader which doesn't perform any of this - /// operations. /// - /// This has no effect on web. + /// See [`create_shader_module_trusted`][csmt]. + /// + /// [csmt]: Self::create_shader_module_trusted + #[deprecated( + since = "24.0.0", + note = "Use `Device::create_shader_module_trusted(desc, wgpu::ShaderRuntimeChecks::unchecked())` instead." + )] #[must_use] pub unsafe fn create_shader_module_unchecked( &self, desc: ShaderModuleDescriptor<'_>, ) -> ShaderModule { - let module = self - .inner - .create_shader_module(desc, unsafe { wgt::ShaderBoundChecks::unchecked() }); + unsafe { self.create_shader_module_trusted(desc, crate::ShaderRuntimeChecks::unchecked()) } + } + + /// Creates a shader module with flags to dictate runtime checks. + /// + /// When running on WebGPU, this will merely call [`create_shader_module`][csm]. + /// + /// # Safety + /// + /// In contrast with [`create_shader_module`][csm] this function + /// creates a shader module with user-customizable runtime checks which allows shaders to + /// perform operations which can lead to undefined behavior like indexing out of bounds, + /// thus it's the caller responsibility to pass a shader which doesn't perform any of this + /// operations. + /// + /// See the documentation for [`ShaderRuntimeChecks`][src] for more information about specific checks. + /// + /// [csm]: Self::create_shader_module + /// [src]: crate::ShaderRuntimeChecks + #[must_use] + pub unsafe fn create_shader_module_trusted( + &self, + desc: ShaderModuleDescriptor<'_>, + runtime_checks: crate::ShaderRuntimeChecks, + ) -> ShaderModule { + let module = self.inner.create_shader_module(desc, runtime_checks); ShaderModule { inner: module } } @@ -192,7 +216,7 @@ impl Device { Buffer { inner: buffer, - map_context: Mutex::new(map_context), + map_context: Arc::new(Mutex::new(map_context)), size: desc.size, usage: desc.usage, } @@ -273,7 +297,7 @@ impl Device { Buffer { inner: buffer.into(), - map_context: Mutex::new(map_context), + map_context: Arc::new(Mutex::new(map_context)), size: desc.size, usage: desc.usage, } @@ -463,7 +487,7 @@ impl Device { let (handle, blas) = self.inner.create_blas(desc, sizes); Blas { - shared: Arc::new(BlasShared { inner: blas }), + inner: blas, handle, } } @@ -482,8 +506,10 @@ impl Device { let tlas = self.inner.create_tlas(desc); Tlas { - inner: tlas, - max_instances: desc.max_instances, + shared: Arc::new(TlasShared { + inner: tlas, + max_instances: desc.max_instances, + }), } } } diff --git a/wgpu/src/api/instance.rs b/wgpu/src/api/instance.rs index f03e348183..4b1b6b901f 100644 --- a/wgpu/src/api/instance.rs +++ b/wgpu/src/api/instance.rs @@ -12,7 +12,7 @@ use std::future::Future; /// Does not have to be kept alive. /// /// Corresponds to [WebGPU `GPU`](https://gpuweb.github.io/gpuweb/#gpu-interface). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Instance { inner: dispatch::DispatchInstance, } @@ -31,7 +31,7 @@ impl Default for Instance { /// If no backend feature for the active target platform is enabled, /// this method will panic, see [`Instance::enabled_backend_features()`]. fn default() -> Self { - Self::new(InstanceDescriptor::default()) + Self::new(&InstanceDescriptor::default()) } } @@ -113,7 +113,7 @@ impl Instance { /// If no backend feature for the active target platform is enabled, /// this method will panic, see [`Instance::enabled_backend_features()`]. #[allow(unreachable_code)] - pub fn new(_instance_desc: InstanceDescriptor) -> Self { + pub fn new(_instance_desc: &InstanceDescriptor) -> Self { if Self::enabled_backend_features().is_empty() { panic!( "No wgpu backend feature that is implemented for the target platform was enabled. \ @@ -237,7 +237,7 @@ impl Instance { options: &RequestAdapterOptions<'_, '_>, ) -> impl Future> + WasmNotSend { let future = self.inner.request_adapter(options); - async move { future.await.map(|inner| Adapter { inner }) } + async move { future.await.map(|adapter| Adapter { inner: adapter }) } } /// Converts a wgpu-hal `ExposedAdapter` to a wgpu [`Adapter`]. diff --git a/wgpu/src/api/pipeline_cache.rs b/wgpu/src/api/pipeline_cache.rs index 4462a405eb..e3c8d60886 100644 --- a/wgpu/src/api/pipeline_cache.rs +++ b/wgpu/src/api/pipeline_cache.rs @@ -62,7 +62,7 @@ use crate::*; /// This type is unique to the Rust API of `wgpu`. /// /// [renaming]: std::fs::rename -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PipelineCache { pub(crate) inner: dispatch::DispatchPipelineCache, } diff --git a/wgpu/src/api/pipeline_layout.rs b/wgpu/src/api/pipeline_layout.rs index 604dd78efd..7baa91555c 100644 --- a/wgpu/src/api/pipeline_layout.rs +++ b/wgpu/src/api/pipeline_layout.rs @@ -6,7 +6,7 @@ use crate::*; /// It can be created with [`Device::create_pipeline_layout`]. /// /// Corresponds to [WebGPU `GPUPipelineLayout`](https://gpuweb.github.io/gpuweb/#gpupipelinelayout). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PipelineLayout { pub(crate) inner: dispatch::DispatchPipelineLayout, } diff --git a/wgpu/src/api/query_set.rs b/wgpu/src/api/query_set.rs index a0d358ed4d..24b5a02809 100644 --- a/wgpu/src/api/query_set.rs +++ b/wgpu/src/api/query_set.rs @@ -5,7 +5,7 @@ use crate::*; /// It can be created with [`Device::create_query_set`]. /// /// Corresponds to [WebGPU `GPUQuerySet`](https://gpuweb.github.io/gpuweb/#queryset). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct QuerySet { pub(crate) inner: dispatch::DispatchQuerySet, } diff --git a/wgpu/src/api/queue.rs b/wgpu/src/api/queue.rs index 89f505d572..ed1feeec9c 100644 --- a/wgpu/src/api/queue.rs +++ b/wgpu/src/api/queue.rs @@ -9,7 +9,7 @@ use crate::*; /// It can be created along with a [`Device`] by calling [`Adapter::request_device`]. /// /// Corresponds to [WebGPU `GPUQueue`](https://gpuweb.github.io/gpuweb/#gpu-queue). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Queue { pub(crate) inner: dispatch::DispatchQueue, } @@ -204,9 +204,12 @@ impl Queue { &self, command_buffers: I, ) -> SubmissionIndex { - let mut command_buffers = command_buffers - .into_iter() - .map(|mut comb| comb.inner.take().unwrap()); + let mut command_buffers = command_buffers.into_iter().map(|comb| { + comb.inner + .lock() + .take() + .expect("Command buffer already submitted") + }); let index = self.inner.submit(&mut command_buffers); diff --git a/wgpu/src/api/render_bundle.rs b/wgpu/src/api/render_bundle.rs index 1d603eab6b..95de976207 100644 --- a/wgpu/src/api/render_bundle.rs +++ b/wgpu/src/api/render_bundle.rs @@ -9,7 +9,7 @@ use crate::*; /// using [`RenderPass::execute_bundles`]. /// /// Corresponds to [WebGPU `GPURenderBundle`](https://gpuweb.github.io/gpuweb/#render-bundle). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RenderBundle { pub(crate) inner: dispatch::DispatchRenderBundle, } diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index 5e245dc3ef..6802025635 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -1,6 +1,7 @@ use std::ops::Range; use crate::*; +pub use wgt::{LoadOp, Operations, StoreOp}; /// In-progress recording of a render pass: a list of render commands in a [`CommandEncoder`]. /// @@ -496,81 +497,6 @@ impl RenderPass<'_> { } } -/// Operation to perform to the output attachment at the start of a render pass. -/// -/// Corresponds to [WebGPU `GPULoadOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpuloadop), -/// plus the corresponding clearValue. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum LoadOp { - /// Loads the specified value for this attachment into the render pass. - /// - /// On some GPU hardware (primarily mobile), "clear" is significantly cheaper - /// because it avoids loading data from main memory into tile-local memory. - /// - /// On other GPU hardware, there isn’t a significant difference. - /// - /// As a result, it is recommended to use "clear" rather than "load" in cases - /// where the initial value doesn’t matter - /// (e.g. the render target will be cleared using a skybox). - Clear(V), - /// Loads the existing value for this attachment into the render pass. - Load, -} - -impl Default for LoadOp { - fn default() -> Self { - Self::Clear(Default::default()) - } -} - -/// Operation to perform to the output attachment at the end of a render pass. -/// -/// Corresponds to [WebGPU `GPUStoreOp`](https://gpuweb.github.io/gpuweb/#enumdef-gpustoreop). -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum StoreOp { - /// Stores the resulting value of the render pass for this attachment. - #[default] - Store, - /// Discards the resulting value of the render pass for this attachment. - /// - /// The attachment will be treated as uninitialized afterwards. - /// (If only either Depth or Stencil texture-aspects is set to `Discard`, - /// the respective other texture-aspect will be preserved.) - /// - /// This can be significantly faster on tile-based render hardware. - /// - /// Prefer this if the attachment is not read by subsequent passes. - Discard, -} - -/// Pair of load and store operations for an attachment aspect. -/// -/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, -/// separate `loadOp` and `storeOp` fields are used instead. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Operations { - /// How data should be read through this attachment. - pub load: LoadOp, - /// Whether data will be written to through this attachment. - /// - /// Note that resolve textures (if specified) are always written to, - /// regardless of this setting. - pub store: StoreOp, -} - -impl Default for Operations { - #[inline] - fn default() -> Self { - Self { - load: LoadOp::::default(), - store: StoreOp::default(), - } - } -} - /// Describes the timestamp writes of a render pass. /// /// For use with [`RenderPassDescriptor`]. diff --git a/wgpu/src/api/render_pipeline.rs b/wgpu/src/api/render_pipeline.rs index 71131e941e..11600c56a2 100644 --- a/wgpu/src/api/render_pipeline.rs +++ b/wgpu/src/api/render_pipeline.rs @@ -8,7 +8,7 @@ use crate::*; /// buffers and targets. It can be created with [`Device::create_render_pipeline`]. /// /// Corresponds to [WebGPU `GPURenderPipeline`](https://gpuweb.github.io/gpuweb/#render-pipeline). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RenderPipeline { pub(crate) inner: dispatch::DispatchRenderPipeline, } @@ -25,8 +25,8 @@ impl RenderPipeline { /// /// This method will raise a validation error if there is no bind group layout at `index`. pub fn get_bind_group_layout(&self, index: u32) -> BindGroupLayout { - let inner = self.inner.get_bind_group_layout(index); - BindGroupLayout { inner } + let layout = self.inner.get_bind_group_layout(index); + BindGroupLayout { inner: layout } } } diff --git a/wgpu/src/api/sampler.rs b/wgpu/src/api/sampler.rs index 4c57819c99..49c988e85f 100644 --- a/wgpu/src/api/sampler.rs +++ b/wgpu/src/api/sampler.rs @@ -9,7 +9,7 @@ use crate::*; /// It can be created with [`Device::create_sampler`]. /// /// Corresponds to [WebGPU `GPUSampler`](https://gpuweb.github.io/gpuweb/#sampler-interface). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Sampler { pub(crate) inner: dispatch::DispatchSampler, } diff --git a/wgpu/src/api/surface_texture.rs b/wgpu/src/api/surface_texture.rs index 5059799888..2ca5155c64 100644 --- a/wgpu/src/api/surface_texture.rs +++ b/wgpu/src/api/surface_texture.rs @@ -8,7 +8,7 @@ use crate::*; /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// the [`GPUCanvasContext`](https://gpuweb.github.io/gpuweb/#canvas-context) provides /// a texture without any additional information. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SurfaceTexture { /// Accessible view of the frame. pub texture: Texture, diff --git a/wgpu/src/api/texture.rs b/wgpu/src/api/texture.rs index 3fdecd320b..03044bdf15 100644 --- a/wgpu/src/api/texture.rs +++ b/wgpu/src/api/texture.rs @@ -5,7 +5,7 @@ use crate::*; /// It can be created with [`Device::create_texture`]. /// /// Corresponds to [WebGPU `GPUTexture`](https://gpuweb.github.io/gpuweb/#texture-interface). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Texture { pub(crate) inner: dispatch::DispatchTexture, pub(crate) descriptor: TextureDescriptor<'static>, diff --git a/wgpu/src/api/texture_view.rs b/wgpu/src/api/texture_view.rs index f255603bcb..9b3a7d9386 100644 --- a/wgpu/src/api/texture_view.rs +++ b/wgpu/src/api/texture_view.rs @@ -6,7 +6,7 @@ use crate::*; /// [`RenderPipeline`] or [`BindGroup`]. /// /// Corresponds to [WebGPU `GPUTextureView`](https://gpuweb.github.io/gpuweb/#gputextureview). -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextureView { pub(crate) inner: dispatch::DispatchTextureView, } diff --git a/wgpu/src/api/tlas.rs b/wgpu/src/api/tlas.rs index 538f4e16c2..b260951152 100644 --- a/wgpu/src/api/tlas.rs +++ b/wgpu/src/api/tlas.rs @@ -1,6 +1,7 @@ use crate::{api::blas::TlasInstance, dispatch}; use crate::{BindingResource, Buffer, Label}; use std::ops::{Index, IndexMut, Range}; +use std::sync::Arc; use wgt::WasmNotSendSync; /// Descriptor to create top level acceleration structures. @@ -8,6 +9,12 @@ pub type CreateTlasDescriptor<'a> = wgt::CreateTlasDescriptor>; static_assertions::assert_impl_all!(CreateTlasDescriptor<'_>: Send, Sync); #[derive(Debug)] +pub(crate) struct TlasShared { + pub(crate) inner: dispatch::DispatchTlas, + pub(crate) max_instances: u32, +} + +#[derive(Debug, Clone)] /// Top Level Acceleration Structure (TLAS). /// /// A TLAS contains a series of [TLAS instances], which are a reference to @@ -18,17 +25,16 @@ static_assertions::assert_impl_all!(CreateTlasDescriptor<'_>: Send, Sync); /// /// [TLAS instances]: TlasInstance pub struct Tlas { - pub(crate) inner: dispatch::DispatchTlas, - pub(crate) max_instances: u32, + pub(crate) shared: Arc, } static_assertions::assert_impl_all!(Tlas: WasmNotSendSync); -crate::cmp::impl_eq_ord_hash_proxy!(Tlas => .inner); +crate::cmp::impl_eq_ord_hash_proxy!(Tlas => .shared.inner); impl Tlas { /// Destroy the associated native resources as soon as possible. pub fn destroy(&self) { - self.inner.destroy(); + self.shared.inner.destroy(); } } @@ -56,7 +62,7 @@ static_assertions::assert_impl_all!(TlasPackage: WasmNotSendSync); impl TlasPackage { /// Construct [TlasPackage] consuming the [Tlas] (prevents modification of the [Tlas] without using this package). pub fn new(tlas: Tlas) -> Self { - let max_instances = tlas.max_instances; + let max_instances = tlas.shared.max_instances; Self::new_with_instances(tlas, vec![None; max_instances as usize]) } diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 9797ad66b5..4b6b248612 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -526,22 +526,31 @@ fn map_vertex_format(format: wgt::VertexFormat) -> webgpu_sys::GpuVertexFormat { use webgpu_sys::GpuVertexFormat as vf; use wgt::VertexFormat; match format { + VertexFormat::Uint8 => vf::Uint8, VertexFormat::Uint8x2 => vf::Uint8x2, VertexFormat::Uint8x4 => vf::Uint8x4, + VertexFormat::Sint8 => vf::Sint8, VertexFormat::Sint8x2 => vf::Sint8x2, VertexFormat::Sint8x4 => vf::Sint8x4, + VertexFormat::Unorm8 => vf::Unorm8, VertexFormat::Unorm8x2 => vf::Unorm8x2, VertexFormat::Unorm8x4 => vf::Unorm8x4, + VertexFormat::Snorm8 => vf::Snorm8, VertexFormat::Snorm8x2 => vf::Snorm8x2, VertexFormat::Snorm8x4 => vf::Snorm8x4, + VertexFormat::Uint16 => vf::Uint16, VertexFormat::Uint16x2 => vf::Uint16x2, VertexFormat::Uint16x4 => vf::Uint16x4, + VertexFormat::Sint16 => vf::Sint16, VertexFormat::Sint16x2 => vf::Sint16x2, VertexFormat::Sint16x4 => vf::Sint16x4, + VertexFormat::Unorm16 => vf::Unorm16, VertexFormat::Unorm16x2 => vf::Unorm16x2, VertexFormat::Unorm16x4 => vf::Unorm16x4, + VertexFormat::Snorm16 => vf::Snorm16, VertexFormat::Snorm16x2 => vf::Snorm16x2, VertexFormat::Snorm16x4 => vf::Snorm16x4, + VertexFormat::Float16 => vf::Float16, VertexFormat::Float16x2 => vf::Float16x2, VertexFormat::Float16x4 => vf::Float16x4, VertexFormat::Float32 => vf::Float32, @@ -557,6 +566,7 @@ fn map_vertex_format(format: wgt::VertexFormat) -> webgpu_sys::GpuVertexFormat { VertexFormat::Sint32x3 => vf::Sint32x3, VertexFormat::Sint32x4 => vf::Sint32x4, VertexFormat::Unorm10_10_10_2 => vf::Unorm1010102, + VertexFormat::Unorm8x4Bgra => vf::Unorm8x4Bgra, VertexFormat::Float64 | VertexFormat::Float64x2 | VertexFormat::Float64x3 @@ -773,7 +783,8 @@ const FEATURES_MAPPING: [(wgt::Features, webgpu_sys::GpuFeatureName); 12] = [ ]; fn map_wgt_features(supported_features: webgpu_sys::GpuSupportedFeatures) -> wgt::Features { - let mut features = wgt::Features::empty(); + // We emulate MDI. + let mut features = wgt::Features::MULTI_DRAW_INDIRECT; for (wgpu_feat, web_feat) in FEATURES_MAPPING { match wasm_bindgen::JsValue::from(web_feat).as_string() { Some(value) if supported_features.has(&value) => features |= wgpu_feat, @@ -1451,7 +1462,7 @@ impl dispatch::InterfaceTypes for ContextWebGpu { } impl dispatch::InstanceInterface for ContextWebGpu { - fn new(_desc: crate::InstanceDescriptor) -> Self + fn new(_desc: &crate::InstanceDescriptor) -> Self where Self: Sized, { @@ -1668,7 +1679,7 @@ impl dispatch::DeviceInterface for WebDevice { fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, - _shader_bound_checks: wgt::ShaderBoundChecks, + _shader_runtime_checks: crate::ShaderRuntimeChecks, ) -> dispatch::DispatchShaderModule { let shader_module_result = match desc.source { #[cfg(feature = "spirv")] @@ -2677,6 +2688,7 @@ impl dispatch::TextureInterface for WebTexture { if let Some(label) = desc.label { mapped.set_label(label); } + mapped.set_usage(desc.usage.unwrap_or(wgt::TextureUsages::empty()).bits()); let view = self.inner.create_view_with_descriptor(&mapped).unwrap(); diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 15f8f3b63e..c49c65ab42 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -3,7 +3,7 @@ use crate::{ dispatch::{self, BufferMappedRangeInterface, InterfaceTypes}, BindingResource, BufferBinding, BufferDescriptor, CompilationInfo, CompilationMessage, CompilationMessageType, ErrorSource, Features, Label, LoadOp, MapMode, Operations, - ShaderSource, StoreOp, SurfaceTargetUnsafe, TextureDescriptor, + ShaderSource, SurfaceTargetUnsafe, TextureDescriptor, }; use arrayvec::ArrayVec; @@ -397,39 +397,23 @@ fn map_texture_tagged_copy_view( } } -fn map_store_op(op: StoreOp) -> wgc::command::StoreOp { - match op { - StoreOp::Store => wgc::command::StoreOp::Store, - StoreOp::Discard => wgc::command::StoreOp::Discard, +fn map_load_op(load: &LoadOp) -> LoadOp> { + match load { + LoadOp::Clear(clear_value) => LoadOp::Clear(Some(*clear_value)), + LoadOp::Load => LoadOp::Load, } } -fn map_pass_channel( - ops: Option<&Operations>, -) -> wgc::command::PassChannel { +fn map_pass_channel(ops: Option<&Operations>) -> wgc::command::PassChannel> { match ops { - Some(&Operations { - load: LoadOp::Clear(clear_value), - store, - }) => wgc::command::PassChannel { - load_op: wgc::command::LoadOp::Clear, - store_op: map_store_op(store), - clear_value, - read_only: false, - }, - Some(&Operations { - load: LoadOp::Load, - store, - }) => wgc::command::PassChannel { - load_op: wgc::command::LoadOp::Load, - store_op: map_store_op(store), - clear_value: V::default(), + Some(&Operations { load, store }) => wgc::command::PassChannel { + load_op: Some(map_load_op(&load)), + store_op: Some(store), read_only: false, }, None => wgc::command::PassChannel { - load_op: wgc::command::LoadOp::Load, - store_op: wgc::command::StoreOp::Store, - clear_value: V::default(), + load_op: None, + store_op: None, read_only: true, }, } @@ -783,7 +767,7 @@ impl InterfaceTypes for ContextWgpuCore { } impl dispatch::InstanceInterface for ContextWgpuCore { - fn new(desc: wgt::InstanceDescriptor) -> Self + fn new(desc: &wgt::InstanceDescriptor) -> Self where Self: Sized, { @@ -826,12 +810,13 @@ impl dispatch::InstanceInterface for ContextWgpuCore { }, }?; - Ok(dispatch::DispatchSurface::Core(CoreSurface { + Ok(CoreSurface { context: self.clone(), id, configured_device: Mutex::default(), error_sink: Mutex::default(), - })) + } + .into()) } fn request_adapter( @@ -971,11 +956,11 @@ impl dispatch::DeviceInterface for CoreDevice { fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, - shader_bound_checks: wgt::ShaderBoundChecks, + shader_bound_checks: wgt::ShaderRuntimeChecks, ) -> dispatch::DispatchShaderModule { let descriptor = wgc::pipeline::ShaderModuleDescriptor { label: desc.label.map(Borrowed), - shader_bound_checks, + runtime_checks: shader_bound_checks, }; let source = match desc.source { #[cfg(feature = "spirv")] @@ -1036,7 +1021,7 @@ impl dispatch::DeviceInterface for CoreDevice { label: desc.label.map(Borrowed), // Doesn't matter the value since spirv shaders aren't mutated to include // runtime checks - shader_bound_checks: unsafe { wgt::ShaderBoundChecks::unchecked() }, + runtime_checks: wgt::ShaderRuntimeChecks::unchecked(), }; let (id, error) = unsafe { self.context.0.device_create_shader_module_spirv( @@ -1170,7 +1155,7 @@ impl dispatch::DeviceInterface for CoreDevice { } BindingResource::AccelerationStructure(acceleration_structure) => { bm::BindingResource::AccelerationStructure( - acceleration_structure.inner.as_core().id, + acceleration_structure.shared.inner.as_core().id, ) } }, @@ -1971,6 +1956,7 @@ impl dispatch::TextureInterface for CoreTexture { label: desc.label.map(Borrowed), format: desc.format, dimension: desc.dimension, + usage: desc.usage, range: wgt::ImageSubresourceRange { aspect: desc.aspect, base_mip_level: desc.base_mip_level, @@ -2253,7 +2239,8 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { .map(|at| wgc::command::RenderPassColorAttachment { view: at.view.inner.as_core().id, resolve_target: at.resolve_target.map(|view| view.inner.as_core().id), - channel: map_pass_channel(Some(&at.ops)), + load_op: at.ops.load, + store_op: at.ops.store, }) }) .collect::>(); @@ -2465,14 +2452,14 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { } }; wgc::ray_tracing::BlasBuildEntry { - blas_id: e.blas.shared.inner.as_core().id, + blas_id: e.blas.inner.as_core().id, geometries, } }); let tlas = tlas.into_iter().map(|e: &crate::TlasBuildEntry<'a>| { wgc::ray_tracing::TlasBuildEntry { - tlas_id: e.tlas.inner.as_core().id, + tlas_id: e.tlas.shared.inner.as_core().id, instance_buffer_id: e.instance_buffer.inner.as_core().id, instance_count: e.instance_count, } @@ -2515,7 +2502,7 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { } }; wgc::ray_tracing::BlasBuildEntry { - blas_id: e.blas.shared.inner.as_core().id, + blas_id: e.blas.inner.as_core().id, geometries, } }); @@ -2528,14 +2515,14 @@ impl dispatch::CommandEncoderInterface for CoreCommandEncoder { instance .as_ref() .map(|instance| wgc::ray_tracing::TlasInstance { - blas_id: instance.blas.inner.as_core().id, + blas_id: instance.blas.as_core().id, transform: &instance.transform, custom_index: instance.custom_index, mask: instance.mask, }) }); wgc::ray_tracing::TlasPackage { - tlas_id: e.tlas.inner.as_core().id, + tlas_id: e.tlas.shared.inner.as_core().id, instances: Box::new(instances), lowest_unmodified: e.lowest_unmodified, } diff --git a/wgpu/src/dispatch.rs b/wgpu/src/dispatch.rs index 71826eb429..6b99664ea7 100644 --- a/wgpu/src/dispatch.rs +++ b/wgpu/src/dispatch.rs @@ -13,7 +13,7 @@ use crate::{WasmNotSend, WasmNotSendSync}; -use std::{any::Any, fmt::Debug, future::Future, hash::Hash, ops::Range, pin::Pin}; +use std::{any::Any, fmt::Debug, future::Future, hash::Hash, ops::Range, pin::Pin, sync::Arc}; use crate::backend; @@ -85,7 +85,7 @@ pub trait InterfaceTypes { } pub trait InstanceInterface: CommonTraits { - fn new(desc: crate::InstanceDescriptor) -> Self + fn new(desc: &wgt::InstanceDescriptor) -> Self where Self: Sized; @@ -134,7 +134,7 @@ pub trait DeviceInterface: CommonTraits { fn create_shader_module( &self, desc: crate::ShaderModuleDescriptor<'_>, - shader_bound_checks: wgt::ShaderBoundChecks, + shader_bound_checks: wgt::ShaderRuntimeChecks, ) -> DispatchShaderModule; unsafe fn create_shader_module_spirv( &self, @@ -555,146 +555,249 @@ pub trait BufferMappedRangeInterface: CommonTraits { /// arguments. These are similarly free when there is only one backend. /// /// In the future, we may want a truly generic backend, which could be extended from this enum. -macro_rules! dispatch_types { +macro_rules! dispatch_types_inner { ( wgpu_core = $wgpu_core_context:ty; webgpu = $webgpu_context:ty; - {$( - type $name:ident = InterfaceTypes::$subtype:ident: $trait:ident; - )*} + {ref type $name:ident = InterfaceTypes::$subtype:ident: $trait:ident}; ) => { - $( - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum $name { - #[cfg(wgpu_core)] - Core(<$wgpu_core_context as InterfaceTypes>::$subtype), - #[cfg(webgpu)] - WebGPU(<$webgpu_context as InterfaceTypes>::$subtype), - } + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] + pub enum $name { + #[cfg(wgpu_core)] + Core(Arc<<$wgpu_core_context as InterfaceTypes>::$subtype>), + #[cfg(webgpu)] + WebGPU(Arc<<$webgpu_context as InterfaceTypes>::$subtype>), + } - impl $name { - #[cfg(wgpu_core)] - #[inline] - #[allow(unused)] - pub fn as_core(&self) -> &<$wgpu_core_context as InterfaceTypes>::$subtype { - match self { - Self::Core(value) => value, - _ => panic!(concat!(stringify!($name), " is not core")), - } + impl $name { + #[cfg(wgpu_core)] + #[inline] + #[allow(unused)] + pub fn as_core(&self) -> &<$wgpu_core_context as InterfaceTypes>::$subtype { + match self { + Self::Core(value) => value, + _ => panic!(concat!(stringify!($name), " is not core")), } + } - #[cfg(wgpu_core)] - #[inline] - #[allow(unused)] - pub fn as_core_mut(&mut self) -> &mut <$wgpu_core_context as InterfaceTypes>::$subtype { - match self { - Self::Core(value) => value, - _ => panic!(concat!(stringify!($name), " is not core")), - } + #[cfg(wgpu_core)] + #[inline] + #[allow(unused)] + pub fn as_core_opt(&self) -> Option<&<$wgpu_core_context as InterfaceTypes>::$subtype> { + match self { + Self::Core(value) => Some(value), + _ => None, } + } - #[cfg(wgpu_core)] - #[inline] - #[allow(unused)] - pub fn as_core_opt(&self) -> Option<&<$wgpu_core_context as InterfaceTypes>::$subtype> { - match self { - Self::Core(value) => Some(value), - _ => None, - } + #[cfg(webgpu)] + #[inline] + #[allow(unused)] + pub fn as_webgpu(&self) -> &<$webgpu_context as InterfaceTypes>::$subtype { + match self { + Self::WebGPU(value) => value, + _ => panic!(concat!(stringify!($name), " is not webgpu")), } + } - #[cfg(wgpu_core)] - #[inline] - #[allow(unused)] - pub fn as_core_mut_opt(&mut self) -> Option<&mut <$wgpu_core_context as InterfaceTypes>::$subtype> { - match self { - Self::Core(value) => Some(value), - _ => None, - } + #[cfg(webgpu)] + #[inline] + #[allow(unused)] + pub fn as_webgpu_opt(&self) -> Option<&<$webgpu_context as InterfaceTypes>::$subtype> { + match self { + Self::WebGPU(value) => Some(value), + _ => None, } + } + } - #[cfg(webgpu)] - #[inline] - #[allow(unused)] - pub fn as_webgpu(&self) -> &<$webgpu_context as InterfaceTypes>::$subtype { - match self { - Self::WebGPU(value) => value, - _ => panic!(concat!(stringify!($name), " is not webgpu")), - } + #[cfg(wgpu_core)] + impl From<<$wgpu_core_context as InterfaceTypes>::$subtype> for $name { + #[inline] + fn from(value: <$wgpu_core_context as InterfaceTypes>::$subtype) -> Self { + Self::Core(Arc::new(value)) + } + } + + #[cfg(webgpu)] + impl From<<$webgpu_context as InterfaceTypes>::$subtype> for $name { + #[inline] + fn from(value: <$webgpu_context as InterfaceTypes>::$subtype) -> Self { + Self::WebGPU(Arc::new(value)) + } + } + + impl std::ops::Deref for $name { + type Target = dyn $trait; + + #[inline] + fn deref(&self) -> &Self::Target { + match self { + #[cfg(wgpu_core)] + Self::Core(value) => value.as_ref(), + #[cfg(webgpu)] + Self::WebGPU(value) => value.as_ref(), } + } + } + }; + ( + wgpu_core = $wgpu_core_context:ty; + webgpu = $webgpu_context:ty; + {mut type $name:ident = InterfaceTypes::$subtype:ident: $trait:ident}; + ) => { + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum $name { + #[cfg(wgpu_core)] + Core(<$wgpu_core_context as InterfaceTypes>::$subtype), + #[cfg(webgpu)] + WebGPU(<$webgpu_context as InterfaceTypes>::$subtype), + } - #[cfg(webgpu)] - #[inline] - #[allow(unused)] - pub fn as_webgpu_mut(&mut self) -> &mut <$webgpu_context as InterfaceTypes>::$subtype { - match self { - Self::WebGPU(value) => value, - _ => panic!(concat!(stringify!($name), " is not webgpu")), - } + impl $name { + #[cfg(wgpu_core)] + #[inline] + #[allow(unused)] + pub fn as_core(&self) -> &<$wgpu_core_context as InterfaceTypes>::$subtype { + match self { + Self::Core(value) => value, + _ => panic!(concat!(stringify!($name), " is not core")), } + } - #[cfg(webgpu)] - #[inline] - #[allow(unused)] - pub fn as_webgpu_opt(&self) -> Option<&<$webgpu_context as InterfaceTypes>::$subtype> { - match self { - Self::WebGPU(value) => Some(value), - _ => None, - } + #[cfg(wgpu_core)] + #[inline] + #[allow(unused)] + pub fn as_core_mut(&mut self) -> &mut <$wgpu_core_context as InterfaceTypes>::$subtype { + match self { + Self::Core(value) => value, + _ => panic!(concat!(stringify!($name), " is not core")), } + } - #[cfg(webgpu)] - #[inline] - #[allow(unused)] - pub fn as_webgpu_mut_opt(&mut self) -> Option<&mut <$webgpu_context as InterfaceTypes>::$subtype> { - match self { - Self::WebGPU(value) => Some(value), - _ => None, - } + #[cfg(wgpu_core)] + #[inline] + #[allow(unused)] + pub fn as_core_opt(&self) -> Option<&<$wgpu_core_context as InterfaceTypes>::$subtype> { + match self { + Self::Core(value) => Some(value), + _ => None, } } #[cfg(wgpu_core)] - impl From<<$wgpu_core_context as InterfaceTypes>::$subtype> for $name { - #[inline] - fn from(value: <$wgpu_core_context as InterfaceTypes>::$subtype) -> Self { - Self::Core(value) + #[inline] + #[allow(unused)] + pub fn as_core_mut_opt( + &mut self, + ) -> Option<&mut <$wgpu_core_context as InterfaceTypes>::$subtype> { + match self { + Self::Core(value) => Some(value), + _ => None, + } + } + + #[cfg(webgpu)] + #[inline] + #[allow(unused)] + pub fn as_webgpu(&self) -> &<$webgpu_context as InterfaceTypes>::$subtype { + match self { + Self::WebGPU(value) => value, + _ => panic!(concat!(stringify!($name), " is not webgpu")), + } + } + + #[cfg(webgpu)] + #[inline] + #[allow(unused)] + pub fn as_webgpu_mut(&mut self) -> &mut <$webgpu_context as InterfaceTypes>::$subtype { + match self { + Self::WebGPU(value) => value, + _ => panic!(concat!(stringify!($name), " is not webgpu")), } } #[cfg(webgpu)] - impl From<<$webgpu_context as InterfaceTypes>::$subtype> for $name { - #[inline] - fn from(value: <$webgpu_context as InterfaceTypes>::$subtype) -> Self { - Self::WebGPU(value) + #[inline] + #[allow(unused)] + pub fn as_webgpu_opt(&self) -> Option<&<$webgpu_context as InterfaceTypes>::$subtype> { + match self { + Self::WebGPU(value) => Some(value), + _ => None, } } - impl std::ops::Deref for $name { - type Target = dyn $trait; - - #[inline] - fn deref(&self) -> &Self::Target { - match self { - #[cfg(wgpu_core)] - Self::Core(value) => value, - #[cfg(webgpu)] - Self::WebGPU(value) => value, - } + #[cfg(webgpu)] + #[inline] + #[allow(unused)] + pub fn as_webgpu_mut_opt( + &mut self, + ) -> Option<&mut <$webgpu_context as InterfaceTypes>::$subtype> { + match self { + Self::WebGPU(value) => Some(value), + _ => None, } } + } - impl std::ops::DerefMut for $name { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - #[cfg(wgpu_core)] - Self::Core(value) => value, - #[cfg(webgpu)] - Self::WebGPU(value) => value, - } + #[cfg(wgpu_core)] + impl From<<$wgpu_core_context as InterfaceTypes>::$subtype> for $name { + #[inline] + fn from(value: <$wgpu_core_context as InterfaceTypes>::$subtype) -> Self { + Self::Core(value) + } + } + + #[cfg(webgpu)] + impl From<<$webgpu_context as InterfaceTypes>::$subtype> for $name { + #[inline] + fn from(value: <$webgpu_context as InterfaceTypes>::$subtype) -> Self { + Self::WebGPU(value) + } + } + + impl std::ops::Deref for $name { + type Target = dyn $trait; + + #[inline] + fn deref(&self) -> &Self::Target { + match self { + #[cfg(wgpu_core)] + Self::Core(value) => value, + #[cfg(webgpu)] + Self::WebGPU(value) => value, } } + } + + impl std::ops::DerefMut for $name { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + #[cfg(wgpu_core)] + Self::Core(value) => value, + #[cfg(webgpu)] + Self::WebGPU(value) => value, + } + } + } + }; +} + +macro_rules! dispatch_types { + ( + wgpu_core = $wgpu_core_context:ty; + webgpu = $webgpu_context:ty; + {$( + $type:tt; + )*} + ) => { + $( + dispatch_types_inner!{ + wgpu_core = backend::ContextWgpuCore; + webgpu = backend::ContextWebGpu; + $type; + } )* }; } @@ -703,33 +806,33 @@ dispatch_types! { wgpu_core = backend::ContextWgpuCore; webgpu = backend::ContextWebGpu; { - type DispatchInstance = InterfaceTypes::Instance: InstanceInterface; - type DispatchAdapter = InterfaceTypes::Adapter: AdapterInterface; - type DispatchDevice = InterfaceTypes::Device: DeviceInterface; - type DispatchQueue = InterfaceTypes::Queue: QueueInterface; - type DispatchShaderModule = InterfaceTypes::ShaderModule: ShaderModuleInterface; - type DispatchBindGroupLayout = InterfaceTypes::BindGroupLayout: BindGroupLayoutInterface; - type DispatchBindGroup = InterfaceTypes::BindGroup: BindGroupInterface; - type DispatchTextureView = InterfaceTypes::TextureView: TextureViewInterface; - type DispatchSampler = InterfaceTypes::Sampler: SamplerInterface; - type DispatchBuffer = InterfaceTypes::Buffer: BufferInterface; - type DispatchTexture = InterfaceTypes::Texture: TextureInterface; - type DispatchBlas = InterfaceTypes::Blas: BlasInterface; - type DispatchTlas = InterfaceTypes::Tlas: TlasInterface; - type DispatchQuerySet = InterfaceTypes::QuerySet: QuerySetInterface; - type DispatchPipelineLayout = InterfaceTypes::PipelineLayout: PipelineLayoutInterface; - type DispatchRenderPipeline = InterfaceTypes::RenderPipeline: RenderPipelineInterface; - type DispatchComputePipeline = InterfaceTypes::ComputePipeline: ComputePipelineInterface; - type DispatchPipelineCache = InterfaceTypes::PipelineCache: PipelineCacheInterface; - type DispatchCommandEncoder = InterfaceTypes::CommandEncoder: CommandEncoderInterface; - type DispatchComputePass = InterfaceTypes::ComputePass: ComputePassInterface; - type DispatchRenderPass = InterfaceTypes::RenderPass: RenderPassInterface; - type DispatchCommandBuffer = InterfaceTypes::CommandBuffer: CommandBufferInterface; - type DispatchRenderBundleEncoder = InterfaceTypes::RenderBundleEncoder: RenderBundleEncoderInterface; - type DispatchRenderBundle = InterfaceTypes::RenderBundle: RenderBundleInterface; - type DispatchSurface = InterfaceTypes::Surface: SurfaceInterface; - type DispatchSurfaceOutputDetail = InterfaceTypes::SurfaceOutputDetail: SurfaceOutputDetailInterface; - type DispatchQueueWriteBuffer = InterfaceTypes::QueueWriteBuffer: QueueWriteBufferInterface; - type DispatchBufferMappedRange = InterfaceTypes::BufferMappedRange: BufferMappedRangeInterface; + {ref type DispatchInstance = InterfaceTypes::Instance: InstanceInterface}; + {ref type DispatchAdapter = InterfaceTypes::Adapter: AdapterInterface}; + {ref type DispatchDevice = InterfaceTypes::Device: DeviceInterface}; + {ref type DispatchQueue = InterfaceTypes::Queue: QueueInterface}; + {ref type DispatchShaderModule = InterfaceTypes::ShaderModule: ShaderModuleInterface}; + {ref type DispatchBindGroupLayout = InterfaceTypes::BindGroupLayout: BindGroupLayoutInterface}; + {ref type DispatchBindGroup = InterfaceTypes::BindGroup: BindGroupInterface}; + {ref type DispatchTextureView = InterfaceTypes::TextureView: TextureViewInterface}; + {ref type DispatchSampler = InterfaceTypes::Sampler: SamplerInterface}; + {ref type DispatchBuffer = InterfaceTypes::Buffer: BufferInterface}; + {ref type DispatchTexture = InterfaceTypes::Texture: TextureInterface}; + {ref type DispatchBlas = InterfaceTypes::Blas: BlasInterface}; + {ref type DispatchTlas = InterfaceTypes::Tlas: TlasInterface}; + {ref type DispatchQuerySet = InterfaceTypes::QuerySet: QuerySetInterface}; + {ref type DispatchPipelineLayout = InterfaceTypes::PipelineLayout: PipelineLayoutInterface}; + {ref type DispatchRenderPipeline = InterfaceTypes::RenderPipeline: RenderPipelineInterface}; + {ref type DispatchComputePipeline = InterfaceTypes::ComputePipeline: ComputePipelineInterface}; + {ref type DispatchPipelineCache = InterfaceTypes::PipelineCache: PipelineCacheInterface}; + {mut type DispatchCommandEncoder = InterfaceTypes::CommandEncoder: CommandEncoderInterface}; + {mut type DispatchComputePass = InterfaceTypes::ComputePass: ComputePassInterface}; + {mut type DispatchRenderPass = InterfaceTypes::RenderPass: RenderPassInterface}; + {ref type DispatchCommandBuffer = InterfaceTypes::CommandBuffer: CommandBufferInterface}; + {mut type DispatchRenderBundleEncoder = InterfaceTypes::RenderBundleEncoder: RenderBundleEncoderInterface}; + {ref type DispatchRenderBundle = InterfaceTypes::RenderBundle: RenderBundleInterface}; + {ref type DispatchSurface = InterfaceTypes::Surface: SurfaceInterface}; + {ref type DispatchSurfaceOutputDetail = InterfaceTypes::SurfaceOutputDetail: SurfaceOutputDetailInterface}; + {mut type DispatchQueueWriteBuffer = InterfaceTypes::QueueWriteBuffer: QueueWriteBufferInterface}; + {mut type DispatchBufferMappedRange = InterfaceTypes::BufferMappedRange: BufferMappedRangeInterface}; } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d716fb1c5e..56813441ed 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -57,13 +57,14 @@ pub use wgt::{ Origin2d, Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, SamplerBindingType, SamplerBorderColor, - ShaderLocation, ShaderModel, ShaderStages, StencilFaceState, StencilOperation, StencilState, - StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, - TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, - TextureSampleType, TextureUsages, TextureViewDimension, VertexAttribute, VertexFormat, - VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, - COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, - QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT, + ShaderLocation, ShaderModel, ShaderRuntimeChecks, ShaderStages, StencilFaceState, + StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities, SurfaceStatus, + TexelCopyBufferLayout, TextureAspect, TextureDimension, TextureFormat, + TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureUsages, + TextureViewDimension, VertexAttribute, VertexFormat, VertexStepMode, WasmNotSend, + WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, + MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, + QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT, }; #[allow(deprecated)] pub use wgt::{ImageCopyBuffer, ImageCopyTexture, ImageCopyTextureTagged, ImageDataLayout}; diff --git a/wgpu/src/util/init.rs b/wgpu/src/util/init.rs index 87f787bcb8..cdb700aaf6 100644 --- a/wgpu/src/util/init.rs +++ b/wgpu/src/util/init.rs @@ -111,7 +111,7 @@ pub fn dx12_shader_compiler_from_env() -> Option { dxc_path: std::path::PathBuf::from("dxcompiler.dll"), dxil_path: std::path::PathBuf::from("dxil.dll"), }, - #[cfg(feature = "static-dxc")] + #[cfg(static_dxc)] Ok("static-dxc") => wgt::Dx12Compiler::StaticDxc, Ok("fxc") => wgt::Dx12Compiler::Fxc, _ => return None, @@ -138,6 +138,24 @@ pub fn gles_minor_version_from_env() -> Option { ) } +/// Get an instance descriptor from the following environment variables: +/// +/// - WGPU_BACKEND +/// - WGPU_DEBUG +/// - WGPU_VALIDATION +/// - WGPU_DX12_COMPILER +/// - WGPU_GLES_MINOR_VERSION +/// +/// If variables are missing, falls back to default or build config values +pub fn instance_descriptor_from_env() -> wgt::InstanceDescriptor { + wgt::InstanceDescriptor { + backends: backend_bits_from_env().unwrap_or_default(), + flags: wgt::InstanceFlags::from_build_config().with_env(), + dx12_shader_compiler: dx12_shader_compiler_from_env().unwrap_or_default(), + gles_minor_version: gles_minor_version_from_env().unwrap_or_default(), + } +} + /// Determines whether the [`Backends::BROWSER_WEBGPU`] backend is supported. /// /// The result can only be true if this is called from the main thread or a dedicated worker. @@ -156,9 +174,7 @@ pub async fn is_browser_webgpu_supported() -> bool { let adapter_promise = gpu.request_adapter(); wasm_bindgen_futures::JsFuture::from(adapter_promise) .await - .map_or(false, |adapter| { - !adapter.is_undefined() && !adapter.is_null() - }) + .is_ok_and(|adapter| !adapter.is_undefined() && !adapter.is_null()) } #[cfg(not(webgpu))] { @@ -183,8 +199,9 @@ pub async fn is_browser_webgpu_supported() -> bool { /// this method will panic, see [`Instance::enabled_backend_features()`]. #[allow(unused_mut)] pub async fn new_instance_with_webgpu_detection( - mut instance_desc: wgt::InstanceDescriptor, + instance_desc: &wgt::InstanceDescriptor, ) -> crate::Instance { + let mut instance_desc = instance_desc.clone(); if instance_desc .backends .contains(wgt::Backends::BROWSER_WEBGPU) @@ -193,5 +210,5 @@ pub async fn new_instance_with_webgpu_detection( instance_desc.backends.remove(wgt::Backends::BROWSER_WEBGPU); } - crate::Instance::new(instance_desc) + crate::Instance::new(&instance_desc) }