diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 978b788a3e..79cf04d129 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,11 +23,16 @@ jobs: - name: Install Rust WASM target run: rustup target add wasm32-unknown-unknown - - name: Install wasm-bindgen-cli - run: cargo +stable install wasm-bindgen-cli --version=0.2.87 + - name: Get wasm-bindgen version + run: | + WASM_BINDGEN_VERSION=$(cargo metadata --format-version 1 --all-features | jq '.packages[] | select(.name == "wasm-bindgen") | .version' | tr -d '"') + + echo $WASM_BINDGEN_VERSION + + echo "WASM_BINDGEN_VERSION=$WASM_BINDGEN_VERSION" >> "$GITHUB_ENV" - - name: Pin wasm-bindgen version - run: cargo update -p wasm-bindgen --precise 0.2.87 + - name: Install wasm-bindgen + run: cargo +stable install wasm-bindgen-cli --version=$WASM_BINDGEN_VERSION - name: Build WebGPU examples run: cargo build --release --target wasm32-unknown-unknown diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a0da99b4..7b1190c904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,12 @@ Bottom level categories: For naga changelogs at or before v0.14.0. See [naga's changelog](naga/CHANGELOG.md). +### Changes + +#### General + +- Log vulkan validation layer messages during instance creation and destruction: By @exrook in [#4586](https://github.com/gfx-rs/wgpu/pull/4586) + ### Bug Fixes #### WGL @@ -67,7 +73,7 @@ By @Zoxc in [#4248](https://github.com/gfx-rs/wgpu/pull/4248) Timestamp queries are now supported on both Metal and Desktop OpenGL. On Apple chips on Metal, they only support timestamp queries in command buffers or in the renderpass descriptor, they do not support them inside a pass. -Metal: By @Wumpf in [#4008](https://github.com/gfx-rs/wgpu/pull/4008) +Metal: By @Wumpf in [#4008](https://github.com/gfx-rs/wgpu/pull/4008) OpenGL: By @Zoxc in [#4267](https://github.com/gfx-rs/wgpu/pull/4267) ### Render/Compute Pass Query Writes @@ -190,7 +196,7 @@ let instance = wgpu::Instance::new(InstanceDescriptor { }); ``` -`gles_minor_version`: By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) +`gles_minor_version`: By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) `flags`: By @nical in [#4230](https://github.com/gfx-rs/wgpu/pull/4230) ### Many New Examples! @@ -236,7 +242,7 @@ By @teoxoy in [#4185](https://github.com/gfx-rs/wgpu/pull/4185) - Add trace-level logging for most entry points in wgpu-core By @nical in [4183](https://github.com/gfx-rs/wgpu/pull/4183) - Add `Rgb10a2Uint` format. By @teoxoy in [4199](https://github.com/gfx-rs/wgpu/pull/4199) - Validate that resources are used on the right device. By @nical in [4207](https://github.com/gfx-rs/wgpu/pull/4207) -- Expose instance flags. +- Expose instance flags. - Add support for the bgra8unorm-storage feature. By @jinleili and @nical in [#4228](https://github.com/gfx-rs/wgpu/pull/4228) - Calls to lost devices now return `DeviceError::Lost` instead of `DeviceError::Invalid`. By @bradwerth in [#4238]([https://github.com/gfx-rs/wgpu/pull/4238]) - Let the `"strict_asserts"` feature enable check that wgpu-core's lock-ordering tokens are unique per thread. By @jimblandy in [#4258]([https://github.com/gfx-rs/wgpu/pull/4258]) @@ -265,6 +271,7 @@ By @teoxoy in [#4185](https://github.com/gfx-rs/wgpu/pull/4185) - Fix `clear` texture views being leaked when `wgpu::SurfaceTexture` is dropped before it is presented. By @rajveermalviya in [#4057](https://github.com/gfx-rs/wgpu/pull/4057). - Add `Feature::SHADER_UNUSED_VERTEX_OUTPUT` to allow unused vertex shader outputs. By @Aaron1011 in [#4116](https://github.com/gfx-rs/wgpu/pull/4116). - Fix a panic in `surface_configure`. By @nical in [#4220](https://github.com/gfx-rs/wgpu/pull/4220) and [#4227](https://github.com/gfx-rs/wgpu/pull/4227) +- Pipelines register their implicit layouts in error cases. By @bradwerth in [#4624](https://github.com/gfx-rs/wgpu/pull/4624) #### Vulkan diff --git a/Cargo.lock b/Cargo.lock index 9263cc6fa4..a241b78fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,7 +185,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -241,7 +241,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -375,7 +375,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -531,7 +531,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -856,7 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -964,7 +964,7 @@ checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f" dependencies = [ "deno-proc-macro-rules-macros", "proc-macro2", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -976,7 +976,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1031,7 +1031,7 @@ dependencies = [ "regex", "strum", "strum_macros", - "syn 2.0.38", + "syn 2.0.39", "thiserror", ] @@ -1104,7 +1104,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1196,7 +1196,7 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1276,6 +1276,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1338,7 +1347,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1476,7 +1485,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1558,8 +1567,7 @@ checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" [[package]] name = "glow" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886c2a30b160c4c6fec8f987430c26b526b7988ca71f664e6a699ddf6f9601e4" +source = "git+https://github.com/grovesNL/glow.git?rev=29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e#29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e" dependencies = [ "js-sys", "slotmap", @@ -1940,7 +1948,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1951,9 +1959,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libfuzzer-sys" @@ -2436,7 +2444,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2632,7 +2640,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2703,7 +2711,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3099,7 +3107,7 @@ checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3344,7 +3352,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3360,9 +3368,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -3395,7 +3403,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3509,7 +3517,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3751,7 +3759,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -3785,7 +3793,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3798,9 +3806,9 @@ checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "c6433b7c56db97397842c46b67e11873eda263170afeb3a2dc74a7cb370fee0d" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3812,12 +3820,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "493fcbab756bb764fa37e6bee8cec2dd709eb4273d06d0c282a5e74275ded735" dependencies = [ "proc-macro2", "quote", + "syn 2.0.39", ] [[package]] @@ -4134,9 +4143,11 @@ dependencies = [ name = "wgpu-example" version = "0.18.0" dependencies = [ + "cfg-if", "console_error_panic_hook", "console_log", "env_logger", + "fern", "js-sys", "log", "png", @@ -4144,6 +4155,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "web-time", "wgpu", "wgpu-hal", "wgpu-test", @@ -4311,7 +4323,7 @@ version = "0.18.0" dependencies = [ "heck", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -5004,5 +5016,5 @@ checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] diff --git a/Cargo.toml b/Cargo.toml index a5ab8ce6fb..07a2793307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ ctor = "0.2" # https://github.com/SiegeEngine/ddsfile/issues/15 (Updated dependencies) ddsfile = { version = "0.5.2-unstable", git = "https://github.com/SiegeEngine/ddsfile.git", rev = "9b597930edc00502391cbb1a39708dadde0fd0ff" } env_logger = "0.10" +fern = "0.6" flume = "0.11" futures-lite = "1" futures-intrusive = "0.5" @@ -160,6 +161,7 @@ js-sys = "0.3.65" wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4.38" wasm-bindgen-test = "0.3" +web-time = "0.2.3" web-sys = "0.3.64" # deno dependencies diff --git a/examples/bunnymark/screenshot.png b/examples/bunnymark/screenshot.png index d26f5f81c3..132a1f79bb 100644 Binary files a/examples/bunnymark/screenshot.png and b/examples/bunnymark/screenshot.png differ diff --git a/examples/bunnymark/src/main.rs b/examples/bunnymark/src/main.rs index 28417246ce..fc6d4414c9 100644 --- a/examples/bunnymark/src/main.rs +++ b/examples/bunnymark/src/main.rs @@ -58,7 +58,7 @@ struct Example { impl Example { fn spawn_bunnies(&mut self) { - let spawn_count = 64 + self.bunnies.len() / 2; + let spawn_count = 64; let color = self.rng.generate::(); println!( "Spawning {} bunnies, total at {}", @@ -331,7 +331,7 @@ impl wgpu_example::framework::Example for Example { let rng = WyRand::new_seed(42); - Example { + let mut ex = Example { pipeline, global_group, local_group, @@ -339,7 +339,11 @@ impl wgpu_example::framework::Example for Example { local_buffer, extent: [config.width, config.height], rng, - } + }; + + ex.spawn_bunnies(); + + ex } fn update(&mut self, event: winit::event::WindowEvent) { @@ -367,11 +371,7 @@ impl wgpu_example::framework::Example for Example { } fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) { - self.spawn_bunnies(); - - for _frame_number in 0..3 { - self.render_inner(view, device, queue); - } + self.render_inner(view, device, queue); } } diff --git a/examples/common/Cargo.toml b/examples/common/Cargo.toml index 7c0d54985c..55d9281791 100644 --- a/examples/common/Cargo.toml +++ b/examples/common/Cargo.toml @@ -11,10 +11,12 @@ license.workspace = true publish = false [dependencies] +cfg-if.workspace = true env_logger.workspace = true log.workspace = true pollster.workspace = true png.workspace = true +web-time.workspace = true winit.workspace = true wgpu.workspace = true wgpu-test.workspace = true @@ -22,6 +24,7 @@ wgpu-test.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook.workspace = true console_log.workspace = true +fern.workspace = true js-sys.workspace = true wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true diff --git a/examples/common/src/framework.rs b/examples/common/src/framework.rs index bced1eaa8b..90c1a853a6 100644 --- a/examples/common/src/framework.rs +++ b/examples/common/src/framework.rs @@ -1,34 +1,13 @@ -#[cfg(target_arch = "wasm32")] -use std::str::FromStr; -#[cfg(not(target_arch = "wasm32"))] -use std::time::Instant; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; -#[cfg(target_arch = "wasm32")] -use web_sys::{ImageBitmapRenderingContext, OffscreenCanvas}; -use wgpu::{WasmNotSend, WasmNotSync}; +use wgpu::{Instance, Surface, WasmNotSend, WasmNotSync}; use wgpu_test::GpuTestConfiguration; use winit::{ - event::{self, KeyEvent, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + dpi::PhysicalSize, + event::{Event, KeyEvent, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, keyboard::{Key, NamedKey}, window::Window, }; -#[allow(dead_code)] -pub fn cast_slice(data: &[T]) -> &[u8] { - use std::{mem::size_of_val, slice::from_raw_parts}; - - unsafe { from_raw_parts(data.as_ptr() as *const u8, size_of_val(data)) } -} - -#[allow(dead_code)] -pub enum ShaderStage { - Vertex, - Fragment, - Compute, -} - pub trait Example: 'static + Sized { const SRGB: bool = true; @@ -71,256 +50,366 @@ pub trait Example: 'static + Sized { fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue); } -struct Setup { - _window: Window, +// Initialize logging in platform dependant ways. +fn init_logger() { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + // As we don't have an environment to pull logging level from, we use the query string. + let query_string = web_sys::window().unwrap().location().search().unwrap(); + let query_level: Option = parse_url_query_string(&query_string, "RUST_LOG") + .and_then(|x| x.parse().ok()); + + // We keep wgpu at Error level, as it's very noisy. + let base_level = query_level.unwrap_or(log::LevelFilter::Info); + let wgpu_level = query_level.unwrap_or(log::LevelFilter::Error); + + // On web, we use fern, as console_log doesn't have filtering on a per-module level. + fern::Dispatch::new() + .level(base_level) + .level_for("wgpu_core", wgpu_level) + .level_for("wgpu_hal", wgpu_level) + .level_for("naga", wgpu_level) + .chain(fern::Output::call(console_log::log)) + .apply() + .unwrap(); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + } else { + // parse_default_env will read the RUST_LOG environment variable and apply it on top + // of these default filters. + env_logger::builder() + .filter_level(log::LevelFilter::Info) + // We keep wgpu at Error level, as it's very noisy. + .filter_module("wgpu_core", log::LevelFilter::Error) + .filter_module("wgpu_hal", log::LevelFilter::Error) + .filter_module("naga", log::LevelFilter::Error) + .parse_default_env() + .init(); + } + } +} + +struct EventLoopWrapper { event_loop: EventLoop<()>, - instance: wgpu::Instance, - size: winit::dpi::PhysicalSize, - surface: wgpu::Surface, - adapter: wgpu::Adapter, - device: wgpu::Device, - queue: wgpu::Queue, - #[cfg(target_arch = "wasm32")] - offscreen_canvas_setup: Option, + window: Window, } -#[cfg(target_arch = "wasm32")] -struct OffscreenCanvasSetup { - offscreen_canvas: OffscreenCanvas, - bitmap_renderer: ImageBitmapRenderingContext, +impl EventLoopWrapper { + pub fn new(title: &str) -> Self { + let event_loop = EventLoop::new().unwrap(); + let mut builder = winit::window::WindowBuilder::new(); + builder = builder.with_title(title); + let window = builder.build(&event_loop).unwrap(); + + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + let canvas = window.canvas().expect("Couldn't get canvas"); + canvas.style().set_css_text("height: 100%; width: 100%;"); + // On wasm, append the canvas to the document body + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| body.append_child(&canvas).ok()) + .expect("couldn't append canvas to document body"); + } + + Self { event_loop, window } + } +} + +/// Wrapper type which manages the surface and surface configuration. +/// +/// As surface usage varies per platform, wrapping this up cleans up the event loop code. +struct SurfaceWrapper { + surface: Option, + config: Option, } -async fn setup(title: &str) -> Setup { - #[cfg(not(target_arch = "wasm32"))] - { - env_logger::init(); - }; - - let event_loop = EventLoop::new().unwrap(); - let mut builder = winit::window::WindowBuilder::new(); - builder = builder.with_title(title); - #[cfg(windows_OFF)] // TODO - { - use winit::platform::windows::WindowBuilderExtWindows; - builder = builder.with_no_redirection_bitmap(true); +impl SurfaceWrapper { + /// Create a new surface wrapper with no surface or configuration. + fn new() -> Self { + Self { + surface: None, + config: None, + } } - let window = builder.build(&event_loop).unwrap(); - - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; - let query_string = web_sys::window().unwrap().location().search().unwrap(); - let level: log::Level = parse_url_query_string(&query_string, "RUST_LOG") - .and_then(|x| x.parse().ok()) - .unwrap_or(log::Level::Error); - console_log::init_with_level(level).expect("could not initialize logger"); - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - // On wasm, append the canvas to the document body - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.body()) - .and_then(|body| { - body.append_child(&web_sys::Element::from( - window.canvas().expect("Couldn't get canvas"), - )) - .ok() - }) - .expect("couldn't append canvas to document body"); + + /// Called after the instance is created, but before we request an adapter. + /// + /// On wasm, we need to create the surface here, as the WebGL backend needs + /// a surface (and hence a canvas) to be present to create the adapter. + /// + /// We cannot unconditionally create a surface here, as Android requires + /// us to wait until we recieve the `Resumed` event to do so. + fn pre_adapter(&mut self, instance: &Instance, window: &Window) { + if cfg!(target_arch = "wasm32") { + self.surface = Some(unsafe { instance.create_surface(&window).unwrap() }); + } } - #[cfg(target_arch = "wasm32")] - let mut offscreen_canvas_setup: Option = None; - #[cfg(target_arch = "wasm32")] - { - use winit::platform::web::WindowExtWebSys; + /// Check if the event is the start condition for the surface. + fn start_condition(e: &Event<()>) -> bool { + match e { + // On all other platforms, we can create the surface immediately. + Event::NewEvents(StartCause::Init) => !cfg!(target_os = "android"), + // On android we need to wait for a resumed event to create the surface. + Event::Resumed => cfg!(target_os = "android"), + _ => false, + } + } - let query_string = web_sys::window().unwrap().location().search().unwrap(); - if let Some(offscreen_canvas_param) = - parse_url_query_string(&query_string, "offscreen_canvas") - { - if FromStr::from_str(offscreen_canvas_param) == Ok(true) { - log::info!("Creating OffscreenCanvasSetup"); - - let offscreen_canvas = - OffscreenCanvas::new(1024, 768).expect("couldn't create OffscreenCanvas"); - - let bitmap_renderer = window - .canvas() - .expect("Couldn't get html canvas") - .get_context("bitmaprenderer") - .expect("couldn't create ImageBitmapRenderingContext (Result)") - .expect("couldn't create ImageBitmapRenderingContext (Option)") - .dyn_into::() - .expect("couldn't convert into ImageBitmapRenderingContext"); - - offscreen_canvas_setup = Some(OffscreenCanvasSetup { - offscreen_canvas, - bitmap_renderer, - }) - } + /// Called when an event which matches [`Self::start_condition`] is recieved. + /// + /// On all native platforms, this is where we create the surface. + /// + /// Additionally, we configure the surface based on the (now valid) window size. + fn resume(&mut self, context: &ExampleContext, window: &Window, srgb: bool) { + // Window size is only actually valid after we enter the event loop. + let window_size = window.inner_size(); + let width = window_size.width.max(1); + let height = window_size.height.max(1); + + log::info!("Surface resume {window_size:?}"); + + // We didn't create the surface in pre_adapter, so we need to do so now. + if !cfg!(target_arch = "wasm32") { + self.surface = Some(unsafe { context.instance.create_surface(&window).unwrap() }); } - }; - - log::info!("Initializing the surface..."); - - 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 (size, surface) = unsafe { - let size = window.inner_size(); - - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] - let surface = instance.create_surface(&window).unwrap(); - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - let surface = { - if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { - log::info!("Creating surface from OffscreenCanvas"); - instance.create_surface_from_offscreen_canvas( - offscreen_canvas_setup.offscreen_canvas.clone(), - ) - } else { - instance.create_surface(&window) + + // From here on, self.surface should be Some. + + let surface = self.surface.as_ref().unwrap(); + + // Get the default configuration, + let mut config = surface + .get_default_config(&context.adapter, width, height) + .expect("Surface isn't supported by the adapter."); + if srgb { + // Not all platforms (WebGPU) support sRGB swapchains, so we need to use view formats + let view_format = config.format.add_srgb_suffix(); + config.view_formats.push(view_format); + } else { + // All platforms support non-sRGB swapchains, so we can just use the format directly. + let format = config.format.remove_srgb_suffix(); + config.format = format; + config.view_formats.push(format); + }; + + surface.configure(&context.device, &config); + self.config = Some(config); + } + + /// Resize the surface, making sure to not resize to zero. + fn resize(&mut self, context: &ExampleContext, size: PhysicalSize) { + log::info!("Surface resize {size:?}"); + + let config = self.config.as_mut().unwrap(); + config.width = size.width.max(1); + config.height = size.height.max(1); + let surface = self.surface.as_ref().unwrap(); + surface.configure(&context.device, config); + } + + /// Acquire the next surface texture. + fn acquire(&mut self, context: &ExampleContext) -> wgpu::SurfaceTexture { + let surface = self.surface.as_ref().unwrap(); + + match surface.get_current_texture() { + Ok(frame) => frame, + Err(_) => { + surface.configure(&context.device, self.config()); + surface + .get_current_texture() + .expect("Failed to acquire next surface texture!") } } - .unwrap(); + } + + /// On suspend on android, we drop the surface, as it's no longer valid. + /// + /// A suspend event is always followed by at least one resume event. + fn suspend(&mut self) { + if cfg!(target_os = "android") { + self.surface = None; + } + } + + fn get(&self) -> Option<&Surface> { + self.surface.as_ref() + } - (size, surface) - }; - let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface)) - .await - .expect("No suitable GPU adapters found on the system!"); + fn config(&self) -> &wgpu::SurfaceConfiguration { + self.config.as_ref().unwrap() + } +} + +/// Context containing global wgpu resources. +struct ExampleContext { + instance: wgpu::Instance, + adapter: wgpu::Adapter, + device: wgpu::Device, + queue: wgpu::Queue, +} +impl ExampleContext { + /// Initializes the example context. + async fn init_async(surface: &mut SurfaceWrapper, window: &Window) -> 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, + }); + 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!"); - #[cfg(not(target_arch = "wasm32"))] - { let adapter_info = adapter.get_info(); - println!("Using {} ({:?})", adapter_info.name, adapter_info.backend); + 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 + ); + + // 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()); + + let trace_dir = std::env::var("WGPU_TRACE"); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: (optional_features & adapter_features) | required_features, + limits: needed_limits, + }, + trace_dir.ok().as_ref().map(std::path::Path::new), + ) + .await + .expect("Unable to find a suitable GPU adapter!"); + + Self { + instance, + adapter, + device, + queue, + } } +} - 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 - ); +struct FrameCounter { + // Instant of the last time we printed the frame time. + last_printed_instant: web_time::Instant, + // Number of frames since the last time we printed the frame time. + frame_count: u32, +} - 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 - ); +impl FrameCounter { + fn new() -> Self { + Self { + last_printed_instant: web_time::Instant::now(), + frame_count: 0, + } + } - // 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()); - - let trace_dir = std::env::var("WGPU_TRACE"); - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: (optional_features & adapter_features) | required_features, - limits: needed_limits, - }, - trace_dir.ok().as_ref().map(std::path::Path::new), - ) - .await - .expect("Unable to find a suitable GPU adapter!"); - - Setup { - _window: window, - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - #[cfg(target_arch = "wasm32")] - offscreen_canvas_setup, + fn update(&mut self) { + self.frame_count += 1; + let new_instant = web_time::Instant::now(); + let elasped_secs = (new_instant - self.last_printed_instant).as_secs_f32(); + if elasped_secs > 1.0 { + let elapsed_ms = elasped_secs * 1000.0; + let frame_time = elapsed_ms / self.frame_count as f32; + let fps = self.frame_count as f32 / elasped_secs; + log::info!("Frame time {:.2}ms ({:.1} FPS)", frame_time, fps); + + self.last_printed_instant = new_instant; + self.frame_count = 0; + } } } -fn start( - #[cfg(not(target_arch = "wasm32"))] Setup { - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - .. - }: Setup, - #[cfg(target_arch = "wasm32")] Setup { - event_loop, - instance, - size, - surface, - adapter, - device, - queue, - offscreen_canvas_setup, - .. - }: Setup, -) { - let mut config = surface - .get_default_config(&adapter, size.width, size.height) - .expect("Surface isn't supported by the adapter."); - let surface_view_format = if E::SRGB { - config.format.add_srgb_suffix() - } else { - config.format.remove_srgb_suffix() - }; - config.format = surface_view_format; - config.view_formats.push(surface_view_format); - surface.configure(&device, &config); - - log::info!("Initializing the example..."); - let mut example = E::init(&config, &adapter, &device, &queue); - - #[cfg(not(target_arch = "wasm32"))] - let mut last_frame_inst = Instant::now(); - #[cfg(not(target_arch = "wasm32"))] - let (mut frame_count, mut accum_time) = (0, 0.0); - - log::info!("Entering render loop..."); - event_loop - .run(move |event, target| { - let _ = (&instance, &adapter); // force ownership by the closure - target.set_control_flow(ControlFlow::Poll); +async fn start(title: &str) { + init_logger(); + let window_loop = EventLoopWrapper::new(title); + let mut surface = SurfaceWrapper::new(); + let context = ExampleContext::init_async::(&mut surface, &window_loop.window).await; - if cfg!(feature = "metal-auto-capture") { - target.exit(); - }; + let mut frame_counter = FrameCounter::new(); + + // We wait to create the example until we have a valid surface. + let mut example = None; + + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use winit::platform::web::EventLoopExtWebSys; + let event_loop_function = EventLoop::spawn; + } else { + let event_loop_function = EventLoop::run; + } + } + + log::info!("Entering event loop..."); + // On native this is a result, but on wasm it's a unit type. + #[allow(clippy::let_unit_value)] + let _ = (event_loop_function)( + window_loop.event_loop, + move |event: Event<()>, target: &EventLoopWindowTarget<()>| { + // We set to refresh as fast as possible. + target.set_control_flow(ControlFlow::Poll); match event { - event::Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - config.width = size.width.max(1); - config.height = size.height.max(1); - example.resize(&config, &device, &queue); - surface.configure(&device, &config); + ref e if SurfaceWrapper::start_condition(e) => { + surface.resume(&context, &window_loop.window, E::SRGB); + + // If we haven't created the example yet, do so now. + if example.is_none() { + example = Some(E::init( + surface.config(), + &context.adapter, + &context.device, + &context.queue, + )); + } + } + Event::Suspended => { + surface.suspend(); } - event::Event::WindowEvent { event, .. } => match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::Resized(size) => { + surface.resize(&context, size); + example.as_mut().unwrap().resize( + surface.config(), + &context.device, + &context.queue, + ); + + window_loop.window.request_redraw(); + } WindowEvent::KeyboardInput { event: KeyEvent { @@ -341,97 +430,42 @@ fn start( }, .. } if s == "r" => { - println!("{:#?}", instance.generate_report()); + println!("{:#?}", context.instance.generate_report()); } - event::WindowEvent::RedrawRequested => { - #[cfg(not(target_arch = "wasm32"))] - { - accum_time += last_frame_inst.elapsed().as_secs_f32(); - last_frame_inst = Instant::now(); - frame_count += 1; - if frame_count == 100 { - println!( - "Avg frame time {}ms", - accum_time * 1000.0 / frame_count as f32 - ); - accum_time = 0.0; - frame_count = 0; - } - } - - let frame = match surface.get_current_texture() { - Ok(frame) => frame, - Err(_) => { - surface.configure(&device, &config); - surface - .get_current_texture() - .expect("Failed to acquire next surface texture!") - } - }; + WindowEvent::RedrawRequested => { + frame_counter.update(); + + let frame = surface.acquire(&context); let view = frame.texture.create_view(&wgpu::TextureViewDescriptor { - format: Some(surface_view_format), + format: Some(surface.config().view_formats[0]), ..wgpu::TextureViewDescriptor::default() }); - example.render(&view, &device, &queue); + example + .as_mut() + .unwrap() + .render(&view, &context.device, &context.queue); frame.present(); - #[cfg(target_arch = "wasm32")] - { - if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { - let image_bitmap = offscreen_canvas_setup - .offscreen_canvas - .transfer_to_image_bitmap() - .expect("couldn't transfer offscreen canvas to image bitmap."); - offscreen_canvas_setup - .bitmap_renderer - .transfer_from_image_bitmap(&image_bitmap); - - log::info!("Transferring OffscreenCanvas to ImageBitmapRenderer"); - } - } + window_loop.window.request_redraw(); } - _ => example.update(event), + _ => example.as_mut().unwrap().update(event), }, _ => {} } - }) - .unwrap(); -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn run(title: &str) { - let setup = pollster::block_on(setup::(title)); - start::(setup); + }, + ); } -#[cfg(target_arch = "wasm32")] -pub fn run(title: &str) { - let title = title.to_owned(); - wasm_bindgen_futures::spawn_local(async move { - let setup = setup::(&title).await; - let start_closure = Closure::once_into_js(move || start::(setup)); - - // make sure to handle JS exceptions thrown inside start. - // Otherwise wasm_bindgen_futures Queue would break and never handle any tasks again. - // This is required, because winit uses JS exception for control flow to escape from `run`. - if let Err(error) = call_catch(&start_closure) { - let is_control_flow_exception = error.dyn_ref::().map_or(false, |e| { - e.message().includes("Using exceptions for control flow", 0) - }); - - if !is_control_flow_exception { - web_sys::console::error_1(&error); - } +pub fn run(title: &'static str) { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + wasm_bindgen_futures::spawn_local(async move { start::(title).await }) + } else { + pollster::block_on(start::(title)); } - - #[wasm_bindgen] - extern "C" { - #[wasm_bindgen(catch, js_namespace = Function, js_name = "prototype.call.call")] - fn call_catch(this: &JsValue) -> Result<(), JsValue>; - } - }); + } } #[cfg(target_arch = "wasm32")] diff --git a/examples/hello-triangle/src/main.rs b/examples/hello-triangle/src/main.rs index fb836de68d..6a9d1414d0 100644 --- a/examples/hello-triangle/src/main.rs +++ b/examples/hello-triangle/src/main.rs @@ -6,7 +6,9 @@ use winit::{ }; async fn run(event_loop: EventLoop<()>, window: Window) { - let size = window.inner_size(); + let mut size = window.inner_size(); + size.width = size.width.max(1); + size.height = size.height.max(1); let instance = wgpu::Instance::default(); @@ -97,8 +99,8 @@ async fn run(event_loop: EventLoop<()>, window: Window) { match event { WindowEvent::Resized(new_size) => { // Reconfigure the surface with the new size - config.width = new_size.width; - config.height = new_size.height; + config.width = new_size.width.max(1); + config.height = new_size.height.max(1); surface.configure(&device, &config); // On macos the window needs to be redrawn manually after resizing window.request_redraw(); @@ -158,14 +160,13 @@ fn main() { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init().expect("could not initialize logger"); use winit::platform::web::WindowExtWebSys; + let canvas = window.canvas().expect("Couldn't get canvas"); + canvas.style().set_css_text("height: 100%; width: 100%;"); // On wasm, append the canvas to the document body web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.body()) - .and_then(|body| { - body.append_child(&web_sys::Element::from(window.canvas().unwrap())) - .ok() - }) + .and_then(|body| body.append_child(&canvas).ok()) .expect("couldn't append canvas to document body"); wasm_bindgen_futures::spawn_local(run(event_loop, window)); } diff --git a/examples/uniform-values/src/main.rs b/examples/uniform-values/src/main.rs index 50b7e7d6ab..48faf857c5 100644 --- a/examples/uniform-values/src/main.rs +++ b/examples/uniform-values/src/main.rs @@ -362,14 +362,14 @@ fn main() { console_log::init().expect("could not initialize logger"); use winit::platform::web::WindowExtWebSys; + let canvas = window.canvas().expect("Couldn't get canvas"); + canvas.style().set_css_text("height: 100%; width: 100%;"); + let document = web_sys::window() .and_then(|win| win.document()) .expect("Failed to get document."); let body = document.body().unwrap(); - body.append_child(&web_sys::Element::from( - window.canvas().expect("Couldn't get canvas"), - )) - .unwrap(); + body.append_child(&canvas).unwrap(); let controls_text = document .create_element("p") .expect("Failed to create controls text as element."); diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 592c72a9a5..b33c904f30 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -309,6 +309,8 @@ pub struct ReflectionInfo { pub uniforms: crate::FastHashMap, String>, /// Mapping between names and attribute locations. pub varying: crate::FastHashMap, + /// List of push constant items in the shader. + pub push_constant_items: Vec, } /// Mapping between a texture and its sampler, if it exists. @@ -328,6 +330,50 @@ pub struct TextureMapping { pub sampler: Option>, } +/// All information to bind a single uniform value to the shader. +/// +/// Push constants are emulated using traditional uniforms in OpenGL. +/// +/// These are composed of a set of primatives (scalar, vector, matrix) that +/// are given names. Because they are not backed by the concept of a buffer, +/// we must do the work of calculating the offset of each primative in the +/// push constant block. +#[derive(Debug, Clone)] +pub struct PushConstantItem { + /// GL uniform name for the item. This name is the same as if you were + /// to access it directly from a GLSL shader. + /// + /// The with the following example, the following names will be generated, + /// one name per GLSL uniform. + /// + /// ```glsl + /// struct InnerStruct { + /// value: f32, + /// } + /// + /// struct PushConstant { + /// InnerStruct inner; + /// vec4 array[2]; + /// } + /// + /// uniform PushConstants _push_constant_binding_cs; + /// ``` + /// + /// ```text + /// - _push_constant_binding_cs.inner.value + /// - _push_constant_binding_cs.array[0] + /// - _push_constant_binding_cs.array[1] + /// ``` + /// + pub access_path: String, + /// Type of the uniform. This will only ever be a scalar, vector, or matrix. + pub ty: Handle, + /// The offset in the push constant memory block this uniform maps to. + /// + /// The size of the uniform can be derived from the type. + pub offset: u32, +} + /// Helper structure that generates a number #[derive(Default)] struct IdGenerator(u32); @@ -1264,8 +1310,8 @@ impl<'a, W: Write> Writer<'a, W> { handle: Handle, global: &crate::GlobalVariable, ) -> String { - match global.binding { - Some(ref br) => { + match (&global.binding, global.space) { + (&Some(ref br), _) => { format!( "_group_{}_binding_{}_{}", br.group, @@ -1273,7 +1319,10 @@ impl<'a, W: Write> Writer<'a, W> { self.entry_point.stage.to_str() ) } - None => self.names[&NameKey::GlobalVariable(handle)].clone(), + (&None, crate::AddressSpace::PushConstant) => { + format!("_push_constant_binding_{}", self.entry_point.stage.to_str()) + } + (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(), } } @@ -1283,15 +1332,20 @@ impl<'a, W: Write> Writer<'a, W> { handle: Handle, global: &crate::GlobalVariable, ) -> BackendResult { - match global.binding { - Some(ref br) => write!( + match (&global.binding, global.space) { + (&Some(ref br), _) => write!( self.out, "_group_{}_binding_{}_{}", br.group, br.binding, self.entry_point.stage.to_str() )?, - None => write!( + (&None, crate::AddressSpace::PushConstant) => write!( + self.out, + "_push_constant_binding_{}", + self.entry_point.stage.to_str() + )?, + (&None, _) => write!( self.out, "{}", &self.names[&NameKey::GlobalVariable(handle)] @@ -4069,6 +4123,7 @@ impl<'a, W: Write> Writer<'a, W> { } } + let mut push_constant_info = None; for (handle, var) in self.module.global_variables.iter() { if info[handle].is_empty() { continue; @@ -4093,17 +4148,105 @@ impl<'a, W: Write> Writer<'a, W> { let name = self.reflection_names_globals[&handle].clone(); uniforms.insert(handle, name); } + crate::AddressSpace::PushConstant => { + let name = self.reflection_names_globals[&handle].clone(); + push_constant_info = Some((name, var.ty)); + } _ => (), }, } } + let mut push_constant_segments = Vec::new(); + let mut push_constant_items = vec![]; + + if let Some((name, ty)) = push_constant_info { + // We don't have a layouter available to us, so we need to create one. + // + // This is potentially a bit wasteful, but the set of types in the program + // shouldn't be too large. + let mut layouter = crate::proc::Layouter::default(); + layouter.update(self.module.to_ctx()).unwrap(); + + // We start with the name of the binding itself. + push_constant_segments.push(name); + + // We then recursively collect all the uniform fields of the push constant. + self.collect_push_constant_items( + ty, + &mut push_constant_segments, + &layouter, + &mut 0, + &mut push_constant_items, + ); + } + Ok(ReflectionInfo { texture_mapping, uniforms, varying: mem::take(&mut self.varying), + push_constant_items, }) } + + fn collect_push_constant_items( + &mut self, + ty: Handle, + segments: &mut Vec, + layouter: &crate::proc::Layouter, + offset: &mut u32, + items: &mut Vec, + ) { + // At this point in the recursion, `segments` contains the path + // needed to access `ty` from the root. + + let layout = &layouter[ty]; + *offset = layout.alignment.round_up(*offset); + match self.module.types[ty].inner { + // All these types map directly to GL uniforms. + TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => { + // Build the full name, by combining all current segments. + let name: String = segments.iter().map(String::as_str).collect(); + items.push(PushConstantItem { + access_path: name, + offset: *offset, + ty, + }); + *offset += layout.size; + } + // Arrays are recursed into. + TypeInner::Array { base, size, .. } => { + let crate::ArraySize::Constant(count) = size else { + unreachable!("Cannot have dynamic arrays in push constants"); + }; + + for i in 0..count.get() { + // Add the array accessor and recurse. + segments.push(format!("[{}]", i)); + self.collect_push_constant_items(base, segments, layouter, offset, items); + segments.pop(); + } + + // Ensure the stride is kept by rounding up to the alignment. + *offset = layout.alignment.round_up(*offset) + } + TypeInner::Struct { ref members, .. } => { + for (index, member) in members.iter().enumerate() { + // Add struct accessor and recurse. + segments.push(format!( + ".{}", + self.names[&NameKey::StructMember(ty, index as u32)] + )); + self.collect_push_constant_items(member.ty, segments, layouter, offset, items); + segments.pop(); + } + + // Ensure ending padding is kept by rounding up to the alignment. + *offset = layout.alignment.round_up(*offset) + } + _ => unreachable!(), + } + } } /// Structure returned by [`glsl_scalar`] diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 9143a8c07e..e4d3e6d325 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1,4 +1,5 @@ use crate::front::wgsl::parse::lexer::Token; +use crate::front::wgsl::Scalar; use crate::proc::{Alignment, ConstantEvaluatorError, ResolveError}; use crate::{SourceLocation, Span}; use codespan_reporting::diagnostic::{Diagnostic, Label}; @@ -139,7 +140,7 @@ pub enum Error<'a> { UnexpectedComponents(Span), UnexpectedOperationInConstContext(Span), BadNumber(Span, NumberError), - BadMatrixScalarKind(Span, crate::ScalarKind, u8), + BadMatrixScalarKind(Span, Scalar), BadAccessor(Span), BadTexture(Span), BadTypeCast { @@ -149,8 +150,7 @@ pub enum Error<'a> { }, BadTextureSampleType { span: Span, - kind: crate::ScalarKind, - width: u8, + scalar: Scalar, }, BadIncrDecrReferenceType(Span), InvalidResolve(ResolveError), @@ -304,10 +304,10 @@ impl<'a> Error<'a> { labels: vec![(bad_span, err.to_string().into())], notes: vec![], }, - Error::BadMatrixScalarKind(span, kind, width) => ParseError { + Error::BadMatrixScalarKind(span, scalar) => ParseError { message: format!( "matrix scalar type must be floating-point, but found `{}`", - kind.to_wgsl(width) + scalar.to_wgsl() ), labels: vec![(span, "must be floating-point (e.g. `f32`)".into())], notes: vec![], @@ -327,10 +327,10 @@ impl<'a> Error<'a> { labels: vec![(bad_span, "unknown scalar type".into())], notes: vec!["Valid scalar types are f32, f64, i32, u32, bool".into()], }, - Error::BadTextureSampleType { span, kind, width } => ParseError { + Error::BadTextureSampleType { span, scalar } => ParseError { message: format!( "texture sample type must be one of f32, i32 or u32, but found {}", - kind.to_wgsl(width) + scalar.to_wgsl() ), labels: vec![(span, "must be one of f32, i32 or u32".into())], notes: vec![], diff --git a/naga/src/front/wgsl/lower/construction.rs b/naga/src/front/wgsl/lower/construction.rs index 912713f911..1393022f8b 100644 --- a/naga/src/front/wgsl/lower/construction.rs +++ b/naga/src/front/wgsl/lower/construction.rs @@ -516,13 +516,13 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ctx: &mut ExpressionContext<'source, '_, 'out>, ) -> Result>, Error<'source>> { let handle = match *constructor { - ast::ConstructorType::Scalar { width, kind } => { - let ty = ctx.ensure_type_exists(crate::TypeInner::Scalar { width, kind }); + ast::ConstructorType::Scalar(scalar) => { + let ty = ctx.ensure_type_exists(scalar.to_inner_scalar()); Constructor::Type(ty) } ast::ConstructorType::PartialVector { size } => Constructor::PartialVector { size }, - ast::ConstructorType::Vector { size, kind, width } => { - let ty = ctx.ensure_type_exists(crate::TypeInner::Vector { size, kind, width }); + ast::ConstructorType::Vector { size, scalar } => { + let ty = ctx.ensure_type_exists(scalar.to_inner_vector(size)); Constructor::Type(ty) } ast::ConstructorType::PartialMatrix { columns, rows } => { diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index c48413aba6..567360b5d8 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -2549,10 +2549,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ctx: &mut GlobalContext<'source, '_, '_>, ) -> Result, Error<'source>> { let inner = match ctx.types[handle] { - ast::Type::Scalar { kind, width } => crate::TypeInner::Scalar { kind, width }, - ast::Type::Vector { size, kind, width } => { - crate::TypeInner::Vector { size, kind, width } - } + ast::Type::Scalar(scalar) => scalar.to_inner_scalar(), + ast::Type::Vector { size, scalar } => scalar.to_inner_vector(size), ast::Type::Matrix { rows, columns, @@ -2562,7 +2560,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { rows, width, }, - ast::Type::Atomic { kind, width } => crate::TypeInner::Atomic { kind, width }, + ast::Type::Atomic(scalar) => scalar.to_inner_atomic(), ast::Type::Pointer { base, space } => { let base = self.resolve_ast_type(base, ctx)?; crate::TypeInner::Pointer { base, space } diff --git a/naga/src/front/wgsl/mod.rs b/naga/src/front/wgsl/mod.rs index 56834d5d92..c1d263ee69 100644 --- a/naga/src/front/wgsl/mod.rs +++ b/naga/src/front/wgsl/mod.rs @@ -104,9 +104,10 @@ impl crate::TypeInner { use crate::TypeInner as Ti; match *self { - Ti::Scalar { kind, width } => kind.to_wgsl(width), + Ti::Scalar { kind, width } => Scalar { kind, width }.to_wgsl(), Ti::Vector { size, kind, width } => { - format!("vec{}<{}>", size as u32, kind.to_wgsl(width)) + let scalar = Scalar { kind, width }; + format!("vec{}<{}>", size as u32, scalar.to_wgsl()) } Ti::Matrix { columns, @@ -117,11 +118,15 @@ impl crate::TypeInner { "mat{}x{}<{}>", columns as u32, rows as u32, - crate::ScalarKind::Float.to_wgsl(width), + Scalar { + kind: crate::ScalarKind::Float, + width + } + .to_wgsl(), ) } Ti::Atomic { kind, width } => { - format!("atomic<{}>", kind.to_wgsl(width)) + format!("atomic<{}>", Scalar { kind, width }.to_wgsl()) } Ti::Pointer { base, .. } => { let base = &gctx.types[base]; @@ -129,7 +134,7 @@ impl crate::TypeInner { format!("ptr<{name}>") } Ti::ValuePointer { kind, width, .. } => { - format!("ptr<{}>", kind.to_wgsl(width)) + format!("ptr<{}>", Scalar { kind, width }.to_wgsl()) } Ti::Array { base, size, .. } => { let member_type = &gctx.types[base]; @@ -169,7 +174,7 @@ impl crate::TypeInner { // Note: The only valid widths are 4 bytes wide. // The lexer has already verified this, so we can safely assume it here. // https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type - let element_type = kind.to_wgsl(4); + let element_type = Scalar { kind, width: 4 }.to_wgsl(); format!("<{element_type}>") } crate::ImageClass::Depth { multi: _ } => String::new(), @@ -287,17 +292,49 @@ mod type_inner_tests { } } -impl crate::ScalarKind { +/// Characteristics of a scalar type. +#[derive(Clone, Copy, Debug)] +pub struct Scalar { + /// How the value's bits are to be interpreted. + pub kind: crate::ScalarKind, + + /// The size of the value in bytes. + pub width: crate::Bytes, +} + +impl Scalar { /// Format a scalar kind+width as a type is written in wgsl. /// /// Examples: `f32`, `u64`, `bool`. - fn to_wgsl(self, width: u8) -> String { - let prefix = match self { + fn to_wgsl(self) -> String { + let prefix = match self.kind { crate::ScalarKind::Sint => "i", crate::ScalarKind::Uint => "u", crate::ScalarKind::Float => "f", crate::ScalarKind::Bool => return "bool".to_string(), }; - format!("{}{}", prefix, width * 8) + format!("{}{}", prefix, self.width * 8) + } + + const fn to_inner_scalar(self) -> crate::TypeInner { + crate::TypeInner::Scalar { + kind: self.kind, + width: self.width, + } + } + + const fn to_inner_vector(self, size: crate::VectorSize) -> crate::TypeInner { + crate::TypeInner::Vector { + size, + kind: self.kind, + width: self.width, + } + } + + const fn to_inner_atomic(self) -> crate::TypeInner { + crate::TypeInner::Atomic { + kind: self.kind, + width: self.width, + } } } diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs index f88e880a3f..dbaac523cb 100644 --- a/naga/src/front/wgsl/parse/ast.rs +++ b/naga/src/front/wgsl/parse/ast.rs @@ -1,4 +1,5 @@ use crate::front::wgsl::parse::number::Number; +use crate::front::wgsl::Scalar; use crate::{Arena, FastIndexSet, Handle, Span}; use std::hash::Hash; @@ -212,24 +213,17 @@ pub enum ArraySize<'a> { #[derive(Debug)] pub enum Type<'a> { - Scalar { - kind: crate::ScalarKind, - width: crate::Bytes, - }, + Scalar(Scalar), Vector { size: crate::VectorSize, - kind: crate::ScalarKind, - width: crate::Bytes, + scalar: Scalar, }, Matrix { columns: crate::VectorSize, rows: crate::VectorSize, width: crate::Bytes, }, - Atomic { - kind: crate::ScalarKind, - width: crate::Bytes, - }, + Atomic(Scalar), Pointer { base: Handle>, space: crate::AddressSpace, @@ -344,10 +338,7 @@ pub struct SwitchCase<'a> { #[derive(Debug)] pub enum ConstructorType<'a> { /// A scalar type or conversion: `f32(1)`. - Scalar { - kind: crate::ScalarKind, - width: crate::Bytes, - }, + Scalar(Scalar), /// A vector construction whose component type is inferred from the /// argument: `vec3(1.0)`. @@ -357,8 +348,7 @@ pub enum ConstructorType<'a> { /// `vec3(1.0)`. Vector { size: crate::VectorSize, - kind: crate::ScalarKind, - width: crate::Bytes, + scalar: Scalar, }, /// A matrix construction whose component type is inferred from the diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 51977173d6..08f1e39285 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -1,4 +1,5 @@ use super::Error; +use crate::front::wgsl::Scalar; use crate::Span; pub fn map_address_space(word: &str, span: Span) -> Result> { @@ -103,14 +104,30 @@ pub fn map_storage_format(word: &str, span: Span) -> Result Option<(crate::ScalarKind, crate::Bytes)> { +pub fn get_scalar_type(word: &str) -> Option { + use crate::ScalarKind as Sk; match word { - // "f16" => Some((crate::ScalarKind::Float, 2)), - "f32" => Some((crate::ScalarKind::Float, 4)), - "f64" => Some((crate::ScalarKind::Float, 8)), - "i32" => Some((crate::ScalarKind::Sint, 4)), - "u32" => Some((crate::ScalarKind::Uint, 4)), - "bool" => Some((crate::ScalarKind::Bool, crate::BOOL_WIDTH)), + // "f16" => Some(Scalar { kind: Sk::Float, width: 2 }), + "f32" => Some(Scalar { + kind: Sk::Float, + width: 4, + }), + "f64" => Some(Scalar { + kind: Sk::Float, + width: 8, + }), + "i32" => Some(Scalar { + kind: Sk::Sint, + width: 4, + }), + "u32" => Some(Scalar { + kind: Sk::Uint, + width: 4, + }), + "bool" => Some(Scalar { + kind: Sk::Bool, + width: crate::BOOL_WIDTH, + }), _ => None, } } diff --git a/naga/src/front/wgsl/parse/lexer.rs b/naga/src/front/wgsl/parse/lexer.rs index ed273fbbb1..dc229bb5fa 100644 --- a/naga/src/front/wgsl/parse/lexer.rs +++ b/naga/src/front/wgsl/parse/lexer.rs @@ -1,6 +1,7 @@ use super::{number::consume_number, Error, ExpectedToken}; use crate::front::wgsl::error::NumberError; use crate::front::wgsl::parse::{conv, Number}; +use crate::front::wgsl::Scalar; use crate::Span; type TokenSpan<'a> = (Token<'a>, Span); @@ -374,9 +375,7 @@ impl<'a> Lexer<'a> { } /// Parses a generic scalar type, for example ``. - pub(in crate::front::wgsl) fn next_scalar_generic( - &mut self, - ) -> Result<(crate::ScalarKind, crate::Bytes), Error<'a>> { + pub(in crate::front::wgsl) fn next_scalar_generic(&mut self) -> Result> { self.expect_generic_paren('<')?; let pair = match self.next() { (Token::Word(word), span) => { @@ -393,11 +392,11 @@ impl<'a> Lexer<'a> { /// Returns the span covering the inner type, excluding the brackets. pub(in crate::front::wgsl) fn next_scalar_generic_with_span( &mut self, - ) -> Result<(crate::ScalarKind, crate::Bytes, Span), Error<'a>> { + ) -> Result<(Scalar, Span), Error<'a>> { self.expect_generic_paren('<')?; let pair = match self.next() { (Token::Word(word), span) => conv::get_scalar_type(word) - .map(|(a, b)| (a, b, span)) + .map(|scalar| (scalar, span)) .ok_or(Error::UnknownScalarType(span)), (_, span) => Err(Error::UnknownScalarType(span)), }?; diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index ae690018f1..bd635b9189 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -1,6 +1,7 @@ use crate::front::wgsl::error::{Error, ExpectedToken}; use crate::front::wgsl::parse::lexer::{Lexer, Token}; use crate::front::wgsl::parse::number::Number; +use crate::front::wgsl::Scalar; use crate::front::SymbolTable; use crate::{Arena, FastIndexSet, Handle, ShaderStage, Span}; @@ -277,8 +278,8 @@ impl Parser { span: Span, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result>, Error<'a>> { - if let Some((kind, width)) = conv::get_scalar_type(word) { - return Ok(Some(ast::ConstructorType::Scalar { kind, width })); + if let Some(scalar) = conv::get_scalar_type(word) { + return Ok(Some(ast::ConstructorType::Scalar(scalar))); } let partial = match word { @@ -288,22 +289,28 @@ impl Parser { "vec2i" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, })) } "vec2u" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, })) } "vec2f" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, })) } "vec3" => ast::ConstructorType::PartialVector { @@ -312,22 +319,28 @@ impl Parser { "vec3i" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, })) } "vec3u" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, })) } "vec3f" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, })) } "vec4" => ast::ConstructorType::PartialVector { @@ -336,22 +349,28 @@ impl Parser { "vec4i" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, })) } "vec4u" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, })) } "vec4f" => { return Ok(Some(ast::ConstructorType::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, })) } "mat2x2" => ast::ConstructorType::PartialMatrix { @@ -483,18 +502,18 @@ impl Parser { // parse component type if present match (lexer.peek().0, partial) { (Token::Paren('<'), ast::ConstructorType::PartialVector { size }) => { - let (kind, width) = lexer.next_scalar_generic()?; - Ok(Some(ast::ConstructorType::Vector { size, kind, width })) + let scalar = lexer.next_scalar_generic()?; + Ok(Some(ast::ConstructorType::Vector { size, scalar })) } (Token::Paren('<'), ast::ConstructorType::PartialMatrix { columns, rows }) => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - match kind { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + match scalar.kind { crate::ScalarKind::Float => Ok(Some(ast::ConstructorType::Matrix { columns, rows, - width, + width: scalar.width, })), - _ => Err(Error::BadMatrixScalarKind(span, kind, width)), + _ => Err(Error::BadMatrixScalarKind(span, scalar)), } } (Token::Paren('<'), ast::ConstructorType::PartialArray) => { @@ -1045,14 +1064,14 @@ impl Parser { columns: crate::VectorSize, rows: crate::VectorSize, ) -> Result, Error<'a>> { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - match kind { + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + match scalar.kind { crate::ScalarKind::Float => Ok(ast::Type::Matrix { columns, rows, - width, + width: scalar.width, }), - _ => Err(Error::BadMatrixScalarKind(span, kind, width)), + _ => Err(Error::BadMatrixScalarKind(span, scalar)), } } @@ -1062,79 +1081,94 @@ impl Parser { word: &'a str, ctx: &mut ExpressionContext<'a, '_, '_>, ) -> Result>, Error<'a>> { - if let Some((kind, width)) = conv::get_scalar_type(word) { - return Ok(Some(ast::Type::Scalar { kind, width })); + if let Some(scalar) = conv::get_scalar_type(word) { + return Ok(Some(ast::Type::Scalar(scalar))); } Ok(Some(match word { "vec2" => { - let (kind, width) = lexer.next_scalar_generic()?; + let scalar = lexer.next_scalar_generic()?; ast::Type::Vector { size: crate::VectorSize::Bi, - kind, - width, + scalar, } } "vec2i" => ast::Type::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, }, "vec2u" => ast::Type::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, }, "vec2f" => ast::Type::Vector { size: crate::VectorSize::Bi, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, }, "vec3" => { - let (kind, width) = lexer.next_scalar_generic()?; + let scalar = lexer.next_scalar_generic()?; ast::Type::Vector { size: crate::VectorSize::Tri, - kind, - width, + scalar, } } "vec3i" => ast::Type::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, }, "vec3u" => ast::Type::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, }, "vec3f" => ast::Type::Vector { size: crate::VectorSize::Tri, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, }, "vec4" => { - let (kind, width) = lexer.next_scalar_generic()?; + let scalar = lexer.next_scalar_generic()?; ast::Type::Vector { size: crate::VectorSize::Quad, - kind, - width, + scalar, } } "vec4i" => ast::Type::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Sint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Sint, + width: 4, + }, }, "vec4u" => ast::Type::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Uint, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Uint, + width: 4, + }, }, "vec4f" => ast::Type::Vector { size: crate::VectorSize::Quad, - kind: crate::ScalarKind::Float, - width: 4, + scalar: Scalar { + kind: crate::ScalarKind::Float, + width: 4, + }, }, "mat2x2" => { self.matrix_scalar_type(lexer, crate::VectorSize::Bi, crate::VectorSize::Bi)? @@ -1209,8 +1243,8 @@ impl Parser { width: 4, }, "atomic" => { - let (kind, width) = lexer.next_scalar_generic()?; - ast::Type::Atomic { kind, width } + let scalar = lexer.next_scalar_generic()?; + ast::Type::Atomic(scalar) } "ptr" => { lexer.expect_generic_paren('<')?; @@ -1257,84 +1291,111 @@ impl Parser { "sampler" => ast::Type::Sampler { comparison: false }, "sampler_comparison" => ast::Type::Sampler { comparison: true }, "texture_1d" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D1, arrayed: false, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_1d_array" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D1, arrayed: true, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_2d" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D2, arrayed: false, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_2d_array" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D2, arrayed: true, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_3d" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D3, arrayed: false, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_cube" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::Cube, arrayed: false, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_cube_array" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::Cube, arrayed: true, - class: crate::ImageClass::Sampled { kind, multi: false }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: false, + }, } } "texture_multisampled_2d" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D2, arrayed: false, - class: crate::ImageClass::Sampled { kind, multi: true }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: true, + }, } } "texture_multisampled_2d_array" => { - let (kind, width, span) = lexer.next_scalar_generic_with_span()?; - Self::check_texture_sample_type(kind, width, span)?; + let (scalar, span) = lexer.next_scalar_generic_with_span()?; + Self::check_texture_sample_type(scalar, span)?; ast::Type::Image { dim: crate::ImageDimension::D2, arrayed: true, - class: crate::ImageClass::Sampled { kind, multi: true }, + class: crate::ImageClass::Sampled { + kind: scalar.kind, + multi: true, + }, } } "texture_depth_2d" => ast::Type::Image { @@ -1410,16 +1471,15 @@ impl Parser { })) } - const fn check_texture_sample_type( - kind: crate::ScalarKind, - width: u8, - span: Span, - ) -> Result<(), Error<'static>> { + const fn check_texture_sample_type(scalar: Scalar, span: Span) -> Result<(), Error<'static>> { use crate::ScalarKind::*; // Validate according to https://gpuweb.github.io/gpuweb/wgsl/#sampled-texture-type - match (kind, width) { - (Float | Sint | Uint, 4) => Ok(()), - _ => Err(Error::BadTextureSampleType { span, kind, width }), + match scalar { + Scalar { + kind: Float | Sint | Uint, + width: 4, + } => Ok(()), + _ => Err(Error::BadTextureSampleType { span, scalar }), } } diff --git a/naga/tests/out/glsl/push-constants.main.Fragment.glsl b/naga/tests/out/glsl/push-constants.main.Fragment.glsl index fa1be9f61f..8131e9e897 100644 --- a/naga/tests/out/glsl/push-constants.main.Fragment.glsl +++ b/naga/tests/out/glsl/push-constants.main.Fragment.glsl @@ -9,14 +9,14 @@ struct PushConstants { struct FragmentIn { vec4 color; }; -uniform PushConstants pc; +uniform PushConstants _push_constant_binding_fs; layout(location = 0) smooth in vec4 _vs2fs_location0; layout(location = 0) out vec4 _fs2p_location0; void main() { FragmentIn in_ = FragmentIn(_vs2fs_location0); - float _e4 = pc.multiplier; + float _e4 = _push_constant_binding_fs.multiplier; _fs2p_location0 = (in_.color * _e4); return; } diff --git a/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl b/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl index 27cd7037ab..4519dc4c6c 100644 --- a/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl +++ b/naga/tests/out/glsl/push-constants.vert_main.Vertex.glsl @@ -9,14 +9,14 @@ struct PushConstants { struct FragmentIn { vec4 color; }; -uniform PushConstants pc; +uniform PushConstants _push_constant_binding_vs; layout(location = 0) in vec2 _p2vs_location0; void main() { vec2 pos = _p2vs_location0; uint vi = uint(gl_VertexID); - float _e5 = pc.multiplier; + float _e5 = _push_constant_binding_vs.multiplier; gl_Position = vec4(((float(vi) * _e5) * pos), 0.0, 1.0); return; } diff --git a/tests/src/image.rs b/tests/src/image.rs index 0e3ea9ea8e..66f6abf16a 100644 --- a/tests/src/image.rs +++ b/tests/src/image.rs @@ -625,12 +625,16 @@ impl ReadbackBuffers { buffer_zero && stencil_buffer_zero } - pub fn check_buffer_contents(&self, device: &Device, expected_data: &[u8]) -> bool { - let result = self - .retrieve_buffer(device, &self.buffer, self.buffer_aspect()) - .iter() - .eq(expected_data.iter()); + pub fn assert_buffer_contents(&self, device: &Device, expected_data: &[u8]) { + let result_buffer = self.retrieve_buffer(device, &self.buffer, self.buffer_aspect()); + assert!( + result_buffer.len() >= expected_data.len(), + "Result buffer ({}) smaller than expected buffer ({})", + result_buffer.len(), + expected_data.len() + ); + let result_buffer = &result_buffer[..expected_data.len()]; + assert_eq!(result_buffer, expected_data); self.buffer.unmap(); - result } } diff --git a/tests/tests/gpu.rs b/tests/tests/gpu.rs index a5fbcde9da..c10df13ed7 100644 --- a/tests/tests/gpu.rs +++ b/tests/tests/gpu.rs @@ -1,4 +1,5 @@ mod regression { + mod issue_3349; mod issue_3457; mod issue_4024; mod issue_4122; @@ -19,6 +20,7 @@ mod occlusion_query; mod partially_bounded_arrays; mod pipeline; mod poll; +mod push_constants; mod query_set; mod queue_transfer; mod resource_descriptor_accessor; diff --git a/tests/tests/partially_bounded_arrays/mod.rs b/tests/tests/partially_bounded_arrays/mod.rs index acadaad67b..5a41ae8f29 100644 --- a/tests/tests/partially_bounded_arrays/mod.rs +++ b/tests/tests/partially_bounded_arrays/mod.rs @@ -97,9 +97,6 @@ static PARTIALLY_BOUNDED_ARRAY: GpuTestConfiguration = GpuTestConfiguration::new ctx.queue.submit(Some(encoder.finish())); - assert!( - readback_buffers - .check_buffer_contents(device, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])), - "texture storage values are incorrect!" - ); + readback_buffers + .assert_buffer_contents(device, bytemuck::bytes_of(&[4.0f32, 3.0, 2.0, 1.0])); }); diff --git a/tests/tests/push_constants.rs b/tests/tests/push_constants.rs new file mode 100644 index 0000000000..e39000173c --- /dev/null +++ b/tests/tests/push_constants.rs @@ -0,0 +1,151 @@ +use std::num::NonZeroU64; + +use wgpu_test::{gpu_test, GpuTestConfiguration, TestParameters, TestingContext}; + +/// We want to test that partial updates to push constants work as expected. +/// +/// As such, we dispatch two compute passes, one which writes the values +/// before a parital update, and one which writes the values after the partial update. +/// +/// If the update code is working correctly, the values not written to by the second update +/// will remain unchanged. +#[gpu_test] +static PARTIAL_UPDATE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(wgpu::Features::PUSH_CONSTANTS) + .limits(wgpu::Limits { + max_push_constant_size: 32, + ..Default::default() + }), + ) + .run_sync(partial_update_test); + +const SHADER: &str = r#" + struct Pc { + offset: u32, + vector: vec4f, + } + + var pc: Pc; + + @group(0) @binding(0) + var output: array; + + @compute @workgroup_size(1) + fn main() { + output[pc.offset] = pc.vector; + } +"#; + +fn partial_update_test(ctx: TestingContext) { + let sm = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("shader"), + source: wgpu::ShaderSource::Wgsl(SHADER.into()), + }); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(16), + }, + count: None, + }], + }); + + let gpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("gpu_buffer"), + size: 32, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let cpu_buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("cpu_buffer"), + size: 32, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + let bind_group = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind_group"), + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: gpu_buffer.as_entire_binding(), + }], + }); + + let pipeline_layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pipeline_layout"), + bind_group_layouts: &[&bgl], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::COMPUTE, + range: 0..32, + }], + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("pipeline"), + layout: Some(&pipeline_layout), + module: &sm, + entry_point: "main", + }); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + { + let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("compute_pass"), + timestamp_writes: None, + }); + cpass.set_pipeline(&pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + + // -- Dispatch 0 -- + + // Dispatch number + cpass.set_push_constants(0, bytemuck::bytes_of(&[0_u32])); + // Update the whole vector. + cpass.set_push_constants(16, bytemuck::bytes_of(&[1.0_f32, 2.0, 3.0, 4.0])); + cpass.dispatch_workgroups(1, 1, 1); + + // -- Dispatch 1 -- + + // Dispatch number + cpass.set_push_constants(0, bytemuck::bytes_of(&[1_u32])); + // Update just the y component of the vector. + cpass.set_push_constants(20, bytemuck::bytes_of(&[5.0_f32])); + cpass.dispatch_workgroups(1, 1, 1); + } + + encoder.copy_buffer_to_buffer(&gpu_buffer, 0, &cpu_buffer, 0, 32); + ctx.queue.submit([encoder.finish()]); + cpu_buffer.slice(..).map_async(wgpu::MapMode::Read, |_| ()); + ctx.device.poll(wgpu::Maintain::Wait); + + let data = cpu_buffer.slice(..).get_mapped_range(); + + let floats: &[f32] = bytemuck::cast_slice(&data); + + // first 4 floats the initial value + // second 4 floats the first update + assert_eq!(floats, [1.0, 2.0, 3.0, 4.0, 1.0, 5.0, 3.0, 4.0]); +} diff --git a/tests/tests/regression/issue_3349.fs.wgsl b/tests/tests/regression/issue_3349.fs.wgsl new file mode 100644 index 0000000000..d6a5ea5ceb --- /dev/null +++ b/tests/tests/regression/issue_3349.fs.wgsl @@ -0,0 +1,46 @@ +struct ShaderData { + a: f32, + b: f32, + c: f32, + d: f32, +} + +@group(0) @binding(0) +var data1: ShaderData; + +var data2: ShaderData; + +struct FsIn { + @builtin(position) position: vec4f, + @location(0) data1: vec4f, + @location(1) data2: vec4f, +} + +@fragment +fn fs_main(fs_in: FsIn) -> @location(0) vec4f { + let floored = vec2u(floor(fs_in.position.xy)); + // We're outputting a 2x2 image, each pixel coming from a different source + let serial = floored.x + floored.y * 2u; + + switch serial { + // (0, 0) - uniform buffer from the vertex shader + case 0u: { + return fs_in.data1; + } + // (1, 0) - push constant from the vertex shader + case 1u: { + return fs_in.data2; + } + // (0, 1) - uniform buffer from the fragment shader + case 2u: { + return vec4f(data1.a, data1.b, data1.c, data1.d); + } + // (1, 1) - push constant from the fragment shader + case 3u: { + return vec4f(data2.a, data2.b, data2.c, data2.d); + } + default: { + return vec4f(0.0); + } + } +} diff --git a/tests/tests/regression/issue_3349.rs b/tests/tests/regression/issue_3349.rs new file mode 100644 index 0000000000..5db5575ddf --- /dev/null +++ b/tests/tests/regression/issue_3349.rs @@ -0,0 +1,178 @@ +use wgpu::util::DeviceExt; +use wgpu_test::{ + gpu_test, image::ReadbackBuffers, GpuTestConfiguration, TestParameters, TestingContext, +}; + +/// We thought we had an OpenGL bug that, when running without explicit in-shader locations, +/// we will not properly bind uniform buffers to both the vertex and fragment +/// shaders. This turned out to not reproduce at all with this test case. +/// +/// However, it also caught issues with the push constant implementation, +/// making sure that it works correctly with different definitions for the push constant +/// block in vertex and fragment shaders. +/// +/// This test needs to be able to run on GLES 3.0 +/// +/// What this test does is render a 2x2 texture. Each pixel corresponds to a different +/// data source. +/// +/// top left: Vertex Shader / Uniform Buffer +/// top right: Vertex Shader / Push Constant +/// bottom left: Fragment Shader / Uniform Buffer +/// bottom right: Fragment Shader / Push Constant +/// +/// We then validate the data is correct from every position. +#[gpu_test] +static MULTI_STAGE_DATA_BINDING: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .features(wgpu::Features::PUSH_CONSTANTS) + .limits(wgpu::Limits { + max_push_constant_size: 16, + ..Default::default() + }), + ) + .run_sync(multi_stage_data_binding_test); + +fn multi_stage_data_binding_test(ctx: TestingContext) { + // We use different shader modules to allow us to use different + // types for the uniform and push constant blocks between stages. + let vs_sm = ctx + .device + .create_shader_module(wgpu::include_wgsl!("issue_3349.vs.wgsl")); + + let fs_sm = ctx + .device + .create_shader_module(wgpu::include_wgsl!("issue_3349.fs.wgsl")); + + // We start with u8s then convert to float, to make sure we don't have + // cross-vendor rounding issues unorm. + let input_as_unorm: [u8; 4] = [25_u8, 50, 75, 100]; + let input = input_as_unorm.map(|v| v as f32 / 255.0); + + let buffer = ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("buffer"), + contents: bytemuck::cast_slice(&input), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let bgl = ctx + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("bgl"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bg"), + layout: &bgl, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + let pll = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("pll"), + bind_group_layouts: &[&bgl], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::VERTEX_FRAGMENT, + range: 0..16, + }], + }); + + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("pipeline"), + layout: Some(&pll), + vertex: wgpu::VertexState { + module: &vs_sm, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &fs_sm, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8Unorm, + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let texture = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: Some("texture"), + size: wgpu::Extent3d { + width: 2, + height: 2, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + // Important: NOT srgb. + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("rpass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.set_bind_group(0, &bg, &[]); + rpass.set_push_constants( + wgpu::ShaderStages::VERTEX_FRAGMENT, + 0, + bytemuck::cast_slice(&input), + ); + rpass.draw(0..3, 0..1); + } + + let buffers = ReadbackBuffers::new(&ctx.device, &texture); + buffers.copy_from(&ctx.device, &mut encoder, &texture); + ctx.queue.submit([encoder.finish()]); + + let result = input_as_unorm.repeat(4); + buffers.assert_buffer_contents(&ctx.device, &result); +} diff --git a/tests/tests/regression/issue_3349.vs.wgsl b/tests/tests/regression/issue_3349.vs.wgsl new file mode 100644 index 0000000000..85992a756b --- /dev/null +++ b/tests/tests/regression/issue_3349.vs.wgsl @@ -0,0 +1,22 @@ +@group(0) @binding(0) +var data1: vec4f; + +// D3DCompile requires this to be a struct +struct Pc { + inner: vec4f, +} + +var data2: Pc; + +struct VsOut { + @builtin(position) position: vec4f, + @location(0) data1: vec4f, + @location(1) data2: vec4f, +} + +@vertex +fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VsOut { + let uv = vec2f(f32((vertexIndex << 1u) & 2u), f32(vertexIndex & 2u)); + let position = vec4f(uv * 2.0 - 1.0, 0.0, 1.0); + return VsOut(position, data1, data2.inner); +} diff --git a/tests/tests/scissor_tests/mod.rs b/tests/tests/scissor_tests/mod.rs index d53d31cdac..40801a343a 100644 --- a/tests/tests/scissor_tests/mod.rs +++ b/tests/tests/scissor_tests/mod.rs @@ -94,7 +94,7 @@ fn scissor_test_impl(ctx: &TestingContext, scissor_rect: Rect, expected_data: [u readback_buffer.copy_from(&ctx.device, &mut encoder, &texture); ctx.queue.submit(Some(encoder.finish())); } - assert!(readback_buffer.check_buffer_contents(&ctx.device, &expected_data)); + readback_buffer.assert_buffer_contents(&ctx.device, &expected_data); } #[gpu_test] diff --git a/tests/tests/shader/mod.rs b/tests/tests/shader/mod.rs index a8ca9a27bb..48800bfb35 100644 --- a/tests/tests/shader/mod.rs +++ b/tests/tests/shader/mod.rs @@ -40,6 +40,8 @@ impl InputStorageType { struct ShaderTest { /// Human readable name name: String, + /// Header text. This is arbitrary code injected at the top of the shader. Replaces {{header}} + header: String, /// This text will be the body of the `Input` struct. Replaces "{{input_members}}" /// in the shader_test shader. custom_struct_members: String, @@ -132,6 +134,7 @@ impl ShaderTest { ) -> Self { Self { name, + header: String::new(), custom_struct_members, body, input_type: String::from("CustomStruct"), @@ -144,6 +147,12 @@ impl ShaderTest { } } + fn header(mut self, header: String) -> Self { + self.header = header; + + self + } + /// Add another set of possible outputs. If any of the given /// output values are seen it's considered a success (i.e. this is OR, not AND). /// @@ -272,6 +281,7 @@ fn shader_input_output_test( // This isn't terribly efficient but the string is short and it's a test. // The body and input members are the longest part, so do them last. let mut processed = source + .replace("{{header}}", &test.header) .replace("{{storage_type}}", storage_type.as_str()) .replace("{{input_type}}", &test.input_type) .replace("{{output_type}}", &test.output_type) diff --git a/tests/tests/shader/shader_test.wgsl b/tests/tests/shader/shader_test.wgsl index efe8692bd5..91c8636574 100644 --- a/tests/tests/shader/shader_test.wgsl +++ b/tests/tests/shader/shader_test.wgsl @@ -1,3 +1,5 @@ +{{header}} + struct CustomStruct { {{input_members}} } diff --git a/tests/tests/shader/struct_layout.rs b/tests/tests/shader/struct_layout.rs index f17dceac08..a7460b9abd 100644 --- a/tests/tests/shader/struct_layout.rs +++ b/tests/tests/shader/struct_layout.rs @@ -99,7 +99,7 @@ fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec } } - // https://github.com/gfx-rs/naga/issues/1785 + // https://github.com/gfx-rs/wgpu/issues/4371 let failures = if storage_type == InputStorageType::Uniform && rows == 2 { Backends::GL } else { @@ -171,6 +171,51 @@ fn create_struct_layout_tests(storage_type: InputStorageType) -> Vec } } + // Nested struct and array test. + // + // This tries to exploit all the weird edge cases of the struct layout algorithm. + { + let header = + String::from("struct Inner { scalar: f32, member: array, 2>, scalar2: f32 }"); + let members = String::from("inner: Inner, scalar3: f32, vector: vec3, scalar4: f32"); + let direct = String::from( + "\ + output[0] = bitcast(input.inner.scalar); + output[1] = bitcast(input.inner.member[0].x); + output[2] = bitcast(input.inner.member[0].y); + output[3] = bitcast(input.inner.member[0].z); + output[4] = bitcast(input.inner.member[1].x); + output[5] = bitcast(input.inner.member[1].y); + output[6] = bitcast(input.inner.member[1].z); + output[7] = bitcast(input.inner.scalar2); + output[8] = bitcast(input.scalar3); + output[9] = bitcast(input.vector.x); + output[10] = bitcast(input.vector.y); + output[11] = bitcast(input.vector.z); + output[12] = bitcast(input.scalar4); + ", + ); + + tests.push( + ShaderTest::new( + String::from("nested struct and array"), + members, + direct, + &input_values, + &[ + 0, // inner.scalar + 4, 5, 6, // inner.member[0] + 8, 9, 10, // inner.member[1] + 12, // scalar2 + 16, // scalar3 + 20, 21, 22, // vector + 23, // scalar4 + ], + ) + .header(header), + ); + } + tests } @@ -215,8 +260,7 @@ static PUSH_CONSTANT_INPUT: GpuTestConfiguration = GpuTestConfiguration::new() .limits(Limits { max_push_constant_size: MAX_BUFFER_SIZE as u32, ..Limits::downlevel_defaults() - }) - .expect_fail(FailureCase::backend(Backends::GL)), + }), ) .run_sync(|ctx| { shader_input_output_test( diff --git a/tests/tests/shader_primitive_index/mod.rs b/tests/tests/shader_primitive_index/mod.rs index e5157a7c93..13ba76a328 100644 --- a/tests/tests/shader_primitive_index/mod.rs +++ b/tests/tests/shader_primitive_index/mod.rs @@ -192,5 +192,5 @@ fn pulling_common( } readback_buffer.copy_from(&ctx.device, &mut encoder, &color_texture); ctx.queue.submit(Some(encoder.finish())); - assert!(readback_buffer.check_buffer_contents(&ctx.device, expected)); + readback_buffer.assert_buffer_contents(&ctx.device, expected); } diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index fa6ece21d4..9c2e51cb1a 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -26,7 +26,9 @@ use wgt::{BufferAddress, TextureFormat}; use std::{borrow::Cow, iter, mem, ops::Range, ptr}; -use super::{BufferMapPendingClosure, ImplicitPipelineIds, InvalidDevice, UserClosures}; +use super::{ + BufferMapPendingClosure, ImplicitPipelineIds, InvalidDevice, UserClosures, IMPLICIT_FAILURE, +}; impl Global { pub fn adapter_is_surface_supported( @@ -1849,6 +1851,7 @@ impl Global { let fid = hub.render_pipelines.prepare(id_in); let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + let implicit_error_context = implicit_context.clone(); let (adapter_guard, mut token) = hub.adapters.read(&mut token); let (device_guard, mut token) = hub.devices.read(&mut token); @@ -1897,6 +1900,24 @@ impl Global { }; let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + + // We also need to assign errors to the implicit pipeline layout and the + // implicit bind group layout. We have to remove any existing entries first. + let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(&mut token); + let (mut bgl_guard, _token) = hub.bind_group_layouts.write(&mut token); + if let Some(ref ids) = implicit_error_context { + if pipeline_layout_guard.contains(ids.root_id) { + pipeline_layout_guard.remove(ids.root_id); + } + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + for &bgl_id in ids.group_ids.iter() { + if bgl_guard.contains(bgl_id) { + bgl_guard.remove(bgl_id); + } + bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + } + } + (id, Some(error)) } @@ -2022,6 +2043,7 @@ impl Global { let fid = hub.compute_pipelines.prepare(id_in); let implicit_context = implicit_pipeline_ids.map(|ipi| ipi.prepare(hub)); + let implicit_error_context = implicit_context.clone(); let (device_guard, mut token) = hub.devices.read(&mut token); let error = loop { @@ -2041,7 +2063,6 @@ impl Global { implicit_context: implicit_context.clone(), }); } - let pipeline = match device.create_compute_pipeline( device_id, desc, @@ -2066,6 +2087,24 @@ impl Global { }; let id = fid.assign_error(desc.label.borrow_or_default(), &mut token); + + // We also need to assign errors to the implicit pipeline layout and the + // implicit bind group layout. We have to remove any existing entries first. + let (mut pipeline_layout_guard, mut token) = hub.pipeline_layouts.write(&mut token); + let (mut bgl_guard, _token) = hub.bind_group_layouts.write(&mut token); + if let Some(ref ids) = implicit_error_context { + if pipeline_layout_guard.contains(ids.root_id) { + pipeline_layout_guard.remove(ids.root_id); + } + pipeline_layout_guard.insert_error(ids.root_id, IMPLICIT_FAILURE); + for &bgl_id in ids.group_ids.iter() { + if bgl_guard.contains(bgl_id) { + bgl_guard.remove(bgl_id); + } + bgl_guard.insert_error(bgl_id, IMPLICIT_FAILURE); + } + } + (id, Some(error)) } @@ -2296,12 +2335,12 @@ impl Global { log::info!("configuring surface with {:?}", config); let error = 'outer: loop { - let hub = A::hub(self); - let mut token = Token::root(); - // User callbacks must not be called while we are holding locks. let user_callbacks; { + let hub = A::hub(self); + let mut token = Token::root(); + let (mut surface_guard, mut token) = self.surfaces.write(&mut token); let (adapter_guard, mut token) = hub.adapters.read(&mut token); let (device_guard, mut token) = hub.devices.read(&mut token); diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index a2160b9ee5..d61aa2c5a0 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -1325,18 +1325,19 @@ impl Device { .contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES), ); - let debug_source = if self.instance_flags.contains(wgt::InstanceFlags::DEBUG) { - Some(hal::DebugSource { - file_name: Cow::Owned( - desc.label - .as_ref() - .map_or("shader".to_string(), |l| l.to_string()), - ), - source_code: Cow::Owned(source.clone()), - }) - } else { - None - }; + let debug_source = + if self.instance_flags.contains(wgt::InstanceFlags::DEBUG) && !source.is_empty() { + Some(hal::DebugSource { + file_name: Cow::Owned( + desc.label + .as_ref() + .map_or("shader".to_string(), |l| l.to_string()), + ), + source_code: Cow::Owned(source.clone()), + }) + } else { + None + }; let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), caps) .validate(&module) diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 261a148f26..6d1d056b56 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -96,7 +96,7 @@ rustc-hash = "1.1" log = "0.4" # backend: Gles -glow = { version = "0.13", optional = true } +glow = { version = "0.13", git = "https://github.com/grovesNL/glow.git", rev = "29ff917a2b2ff7ce0a81b2cc5681de6d4735b36e", optional = true } [dependencies.wgt] package = "wgpu-types" @@ -180,7 +180,9 @@ features = ["wgsl-in"] [dev-dependencies] cfg-if = "1" env_logger = "0.10" -winit = { version = "0.29.2", features = [ "android-native-activity" ] } # for "halmark" example +winit = { version = "0.29.2", features = [ + "android-native-activity", +] } # for "halmark" example [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] glutin = "0.29.1" # for "gles" example diff --git a/wgpu-hal/src/dx11/command.rs b/wgpu-hal/src/dx11/command.rs index 17cd5a22d2..3bbdf0a7ee 100644 --- a/wgpu-hal/src/dx11/command.rs +++ b/wgpu-hal/src/dx11/command.rs @@ -96,7 +96,7 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { todo!() diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index 719e63a36f..2e3b78e522 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -911,15 +911,16 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, _stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { + let offset_words = offset_bytes as usize / 4; + let info = layout.shared.root_constant_info.as_ref().unwrap(); self.pass.root_elements[info.root_index as usize] = super::RootElement::Constant; - self.pass.constant_data[(offset as usize)..(offset as usize + data.len())] - .copy_from_slice(data); + self.pass.constant_data[offset_words..(offset_words + data.len())].copy_from_slice(data); if self.pass.layout.signature == layout.shared.signature { self.pass.dirty_root_elements |= 1 << info.root_index; diff --git a/wgpu-hal/src/dx12/shader_compilation.rs b/wgpu-hal/src/dx12/shader_compilation.rs index 430c734267..a034f54a7f 100644 --- a/wgpu-hal/src/dx12/shader_compilation.rs +++ b/wgpu-hal/src/dx12/shader_compilation.rs @@ -142,9 +142,12 @@ mod dxc { log::Level, ) { profiling::scope!("compile_dxc"); - let mut compile_flags = arrayvec::ArrayVec::<&str, 4>::new_const(); + let mut compile_flags = arrayvec::ArrayVec::<&str, 6>::new_const(); compile_flags.push("-Ges"); // d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS compile_flags.push("-Vd"); // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory. + compile_flags.push("-HV"); // Use HLSL 2018, Naga doesn't supported 2021 yet. + compile_flags.push("2018"); + if device .private_caps .instance_flags diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index d0f659f461..64bcf3109b 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -327,7 +327,7 @@ impl crate::CommandEncoder for Encoder { &mut self, layout: &Resource, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { } diff --git a/wgpu-hal/src/gles/command.rs b/wgpu-hal/src/gles/command.rs index 1234b97292..abbbe8d427 100644 --- a/wgpu-hal/src/gles/command.rs +++ b/wgpu-hal/src/gles/command.rs @@ -8,7 +8,6 @@ struct TextureSlotDesc { sampler_index: Option, } -#[derive(Default)] pub(super) struct State { topology: u32, primitive: super::PrimitiveState, @@ -30,10 +29,41 @@ pub(super) struct State { instance_vbuf_mask: usize, dirty_vbuf_mask: usize, active_first_instance: u32, - push_offset_to_uniform: ArrayVec, + push_constant_descs: ArrayVec, + // The current state of the push constant data block. + current_push_constant_data: [u32; super::MAX_PUSH_CONSTANTS], end_of_pass_timestamp: Option, } +impl Default for State { + fn default() -> Self { + Self { + topology: Default::default(), + primitive: Default::default(), + index_format: Default::default(), + index_offset: Default::default(), + vertex_buffers: Default::default(), + vertex_attributes: Default::default(), + color_targets: Default::default(), + stencil: Default::default(), + depth_bias: Default::default(), + alpha_to_coverage_enabled: Default::default(), + samplers: Default::default(), + texture_slots: Default::default(), + render_size: Default::default(), + resolve_attachments: Default::default(), + invalidate_attachments: Default::default(), + has_pass_label: Default::default(), + instance_vbuf_mask: Default::default(), + dirty_vbuf_mask: Default::default(), + active_first_instance: Default::default(), + push_constant_descs: Default::default(), + current_push_constant_data: [0; super::MAX_PUSH_CONSTANTS], + end_of_pass_timestamp: Default::default(), + } + } +} + impl super::CommandBuffer { fn clear(&mut self) { self.label = None; @@ -176,10 +206,7 @@ impl super::CommandEncoder { fn set_pipeline_inner(&mut self, inner: &super::PipelineInner) { self.cmd_buffer.commands.push(C::SetProgram(inner.program)); - self.state.push_offset_to_uniform.clear(); - self.state - .push_offset_to_uniform - .extend(inner.uniforms.iter().cloned()); + self.state.push_constant_descs = inner.push_constant_descs.clone(); // rebind textures, if needed let mut dirty_textures = 0u32; @@ -729,24 +756,46 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, _layout: &super::PipelineLayout, _stages: wgt::ShaderStages, - start_offset: u32, + offset_bytes: u32, data: &[u32], ) { - let range = self.cmd_buffer.add_push_constant_data(data); - - let end = start_offset + data.len() as u32 * 4; - let mut offset = start_offset; - while offset < end { - let uniform = self.state.push_offset_to_uniform[offset as usize / 4].clone(); - let size = uniform.size; - if uniform.location.is_none() { - panic!("No uniform for push constant"); + // There is nothing preventing the user from trying to update a single value within + // a vector or matrix in the set_push_constant call, as to the user, all of this is + // just memory. However OpenGL does not allow parital uniform updates. + // + // As such, we locally keep a copy of the current state of the push constant memory + // block. If the user tries to update a single value, we have the data to update the entirety + // of the uniform. + let start_words = offset_bytes / 4; + let end_words = start_words + data.len() as u32; + self.state.current_push_constant_data[start_words as usize..end_words as usize] + .copy_from_slice(data); + + // We iterate over the uniform list as there may be multiple uniforms that need + // updating from the same push constant memory (one for each shader stage). + // + // Additionally, any statically unused uniform descs will have been removed from this list + // by OpenGL, so the uniform list is not contiguous. + for uniform in self.state.push_constant_descs.iter().cloned() { + let uniform_size_words = uniform.size_bytes / 4; + let uniform_start_words = uniform.offset / 4; + let uniform_end_words = uniform_start_words + uniform_size_words; + + // Is true if any word within the uniform binding was updated + let needs_updating = + start_words < uniform_end_words || uniform_start_words <= end_words; + + if needs_updating { + let uniform_data = &self.state.current_push_constant_data + [uniform_start_words as usize..uniform_end_words as usize]; + + let range = self.cmd_buffer.add_push_constant_data(uniform_data); + + self.cmd_buffer.commands.push(C::SetPushConstants { + uniform, + offset: range.start, + }); } - self.cmd_buffer.commands.push(C::SetPushConstants { - uniform, - offset: range.start + offset, - }); - offset += size; } } diff --git a/wgpu-hal/src/gles/conv.rs b/wgpu-hal/src/gles/conv.rs index c0ad4054d7..3fb8383a51 100644 --- a/wgpu-hal/src/gles/conv.rs +++ b/wgpu-hal/src/gles/conv.rs @@ -417,108 +417,6 @@ pub(super) fn map_storage_access(access: wgt::StorageTextureAccess) -> u32 { } } -pub(super) fn is_sampler(glsl_uniform_type: u32) -> bool { - match glsl_uniform_type { - glow::INT_SAMPLER_1D - | glow::INT_SAMPLER_1D_ARRAY - | glow::INT_SAMPLER_2D - | glow::INT_SAMPLER_2D_ARRAY - | glow::INT_SAMPLER_2D_MULTISAMPLE - | glow::INT_SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::INT_SAMPLER_2D_RECT - | glow::INT_SAMPLER_3D - | glow::INT_SAMPLER_CUBE - | glow::INT_SAMPLER_CUBE_MAP_ARRAY - | glow::UNSIGNED_INT_SAMPLER_1D - | glow::UNSIGNED_INT_SAMPLER_1D_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D - | glow::UNSIGNED_INT_SAMPLER_2D_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE - | glow::UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::UNSIGNED_INT_SAMPLER_2D_RECT - | glow::UNSIGNED_INT_SAMPLER_3D - | glow::UNSIGNED_INT_SAMPLER_CUBE - | glow::UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY - | glow::SAMPLER_1D - | glow::SAMPLER_1D_SHADOW - | glow::SAMPLER_1D_ARRAY - | glow::SAMPLER_1D_ARRAY_SHADOW - | glow::SAMPLER_2D - | glow::SAMPLER_2D_SHADOW - | glow::SAMPLER_2D_ARRAY - | glow::SAMPLER_2D_ARRAY_SHADOW - | glow::SAMPLER_2D_MULTISAMPLE - | glow::SAMPLER_2D_MULTISAMPLE_ARRAY - | glow::SAMPLER_2D_RECT - | glow::SAMPLER_2D_RECT_SHADOW - | glow::SAMPLER_3D - | glow::SAMPLER_CUBE - | glow::SAMPLER_CUBE_MAP_ARRAY - | glow::SAMPLER_CUBE_MAP_ARRAY_SHADOW - | glow::SAMPLER_CUBE_SHADOW => true, - _ => false, - } -} - -pub(super) fn is_image(glsl_uniform_type: u32) -> bool { - match glsl_uniform_type { - glow::INT_IMAGE_1D - | glow::INT_IMAGE_1D_ARRAY - | glow::INT_IMAGE_2D - | glow::INT_IMAGE_2D_ARRAY - | glow::INT_IMAGE_2D_MULTISAMPLE - | glow::INT_IMAGE_2D_MULTISAMPLE_ARRAY - | glow::INT_IMAGE_2D_RECT - | glow::INT_IMAGE_3D - | glow::INT_IMAGE_CUBE - | glow::INT_IMAGE_CUBE_MAP_ARRAY - | glow::UNSIGNED_INT_IMAGE_1D - | glow::UNSIGNED_INT_IMAGE_1D_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D - | glow::UNSIGNED_INT_IMAGE_2D_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D_MULTISAMPLE - | glow::UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY - | glow::UNSIGNED_INT_IMAGE_2D_RECT - | glow::UNSIGNED_INT_IMAGE_3D - | glow::UNSIGNED_INT_IMAGE_CUBE - | glow::UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY - | glow::IMAGE_1D - | glow::IMAGE_1D_ARRAY - | glow::IMAGE_2D - | glow::IMAGE_2D_ARRAY - | glow::IMAGE_2D_MULTISAMPLE - | glow::IMAGE_2D_MULTISAMPLE_ARRAY - | glow::IMAGE_2D_RECT - | glow::IMAGE_3D - | glow::IMAGE_CUBE - | glow::IMAGE_CUBE_MAP_ARRAY => true, - _ => false, - } -} - -pub(super) fn is_atomic_counter(glsl_uniform_type: u32) -> bool { - glsl_uniform_type == glow::UNSIGNED_INT_ATOMIC_COUNTER -} - -pub(super) fn is_opaque_type(glsl_uniform_type: u32) -> bool { - is_sampler(glsl_uniform_type) - || is_image(glsl_uniform_type) - || is_atomic_counter(glsl_uniform_type) -} - -pub(super) fn uniform_byte_size(glsl_uniform_type: u32) -> u32 { - match glsl_uniform_type { - glow::FLOAT | glow::INT => 4, - glow::FLOAT_VEC2 | glow::INT_VEC2 => 8, - glow::FLOAT_VEC3 | glow::INT_VEC3 => 12, - glow::FLOAT_VEC4 | glow::INT_VEC4 => 16, - glow::FLOAT_MAT2 => 16, - glow::FLOAT_MAT3 => 36, - glow::FLOAT_MAT4 => 64, - _ => panic!("Unsupported uniform datatype! {glsl_uniform_type:#X}"), - } -} - pub(super) fn is_layered_target(target: u32) -> bool { match target { glow::TEXTURE_2D | glow::TEXTURE_CUBE_MAP => false, diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index a0048c5ec2..7934c4be01 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -23,6 +23,7 @@ struct CompilationContext<'a> { layout: &'a super::PipelineLayout, sampler_map: &'a mut super::SamplerBindMap, name_binding_map: &'a mut NameBindingMap, + push_constant_items: &'a mut Vec, multiview: Option, } @@ -53,7 +54,7 @@ impl CompilationContext<'_> { Some(name) => name.clone(), None => continue, }; - log::debug!( + log::trace!( "Rebind buffer: {:?} -> {}, register={:?}, slot={}", var.name.as_ref(), &name, @@ -101,6 +102,8 @@ impl CompilationContext<'_> { naga::ShaderStage::Compute => {} } } + + *self.push_constant_items = reflection_info.push_constant_items; } } @@ -279,7 +282,7 @@ impl super::Device { unsafe fn create_pipeline<'a>( &self, gl: &glow::Context, - shaders: ArrayVec, 3>, + shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, multiview: Option, @@ -327,7 +330,7 @@ impl super::Device { unsafe fn create_program<'a>( gl: &glow::Context, - shaders: ArrayVec, 3>, + shaders: ArrayVec, { crate::MAX_CONCURRENT_SHADER_STAGES }>, layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, multiview: Option, @@ -348,16 +351,22 @@ impl super::Device { } let mut name_binding_map = NameBindingMap::default(); + let mut push_constant_items = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); let mut sampler_map = [None; super::MAX_TEXTURE_SLOTS]; let mut has_stages = wgt::ShaderStages::empty(); - let mut shaders_to_delete = arrayvec::ArrayVec::<_, 3>::new(); + let mut shaders_to_delete = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); - for (naga_stage, stage) in shaders { + for &(naga_stage, stage) in &shaders { has_stages |= map_naga_stage(naga_stage); + let pc_item = { + push_constant_items.push(Vec::new()); + push_constant_items.last_mut().unwrap() + }; let context = CompilationContext { layout, sampler_map: &mut sampler_map, name_binding_map: &mut name_binding_map, + push_constant_items: pc_item, multiview, }; @@ -409,6 +418,7 @@ impl super::Device { match register { super::BindingRegister::UniformBuffers => { let index = unsafe { gl.get_uniform_block_index(program, name) }.unwrap(); + log::trace!("\tBinding slot {slot} to block index {index}"); unsafe { gl.uniform_block_binding(program, index, slot as _) }; } super::BindingRegister::StorageBuffers => { @@ -429,41 +439,38 @@ impl super::Device { } } - let mut uniforms: [super::UniformDesc; super::MAX_PUSH_CONSTANTS] = - [None; super::MAX_PUSH_CONSTANTS].map(|_: Option<()>| Default::default()); - let count = unsafe { gl.get_active_uniforms(program) }; - let mut offset = 0; - - for uniform in 0..count { - let glow::ActiveUniform { utype, name, .. } = - unsafe { gl.get_active_uniform(program, uniform) }.unwrap(); - - if conv::is_opaque_type(utype) { - continue; - } - - if let Some(location) = unsafe { gl.get_uniform_location(program, &name) } { - if uniforms[offset / 4].location.is_some() { - panic!("Offset already occupied") + let mut uniforms = ArrayVec::new(); + + for (stage_idx, stage_items) in push_constant_items.into_iter().enumerate() { + for item in stage_items { + let naga_module = &shaders[stage_idx].1.module.naga.module; + let type_inner = &naga_module.types[item.ty].inner; + + let location = unsafe { gl.get_uniform_location(program, &item.access_path) }; + + log::trace!( + "push constant item: name={}, ty={:?}, offset={}, location={:?}", + item.access_path, + type_inner, + item.offset, + location, + ); + + if let Some(location) = location { + uniforms.push(super::PushConstantDesc { + location, + offset: item.offset, + size_bytes: type_inner.size(naga_module.to_ctx()), + ty: type_inner.clone(), + }); } - - // `size` will always be 1 so we need to guess the real size from the type - let uniform_size = conv::uniform_byte_size(utype); - - uniforms[offset / 4] = super::UniformDesc { - location: Some(location), - size: uniform_size, - utype, - }; - - offset += uniform_size as usize; } } Ok(Arc::new(super::PipelineInner { program, sampler_map, - uniforms, + push_constant_descs: uniforms, })) } } diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index bfc55e634f..0af5ad4a6e 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -108,6 +108,8 @@ const MAX_SAMPLERS: usize = 16; const MAX_VERTEX_ATTRIBUTES: usize = 16; const ZERO_BUFFER_SIZE: usize = 256 << 10; const MAX_PUSH_CONSTANTS: usize = 64; +// We have to account for each push constant may need to be set for every shader. +const MAX_PUSH_CONSTANT_COMMANDS: usize = MAX_PUSH_CONSTANTS * crate::MAX_CONCURRENT_SHADER_STAGES; impl crate::Api for Api { type Instance = Instance; @@ -483,11 +485,12 @@ struct VertexBufferDesc { stride: u32, } -#[derive(Clone, Debug, Default)] -struct UniformDesc { - location: Option, - size: u32, - utype: u32, +#[derive(Clone, Debug)] +struct PushConstantDesc { + location: glow::UniformLocation, + ty: naga::TypeInner, + offset: u32, + size_bytes: u32, } #[cfg(all( @@ -495,13 +498,13 @@ struct UniformDesc { feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics") ))] -unsafe impl Sync for UniformDesc {} +unsafe impl Sync for PushConstantDesc {} #[cfg(all( target_arch = "wasm32", feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics") ))] -unsafe impl Send for UniformDesc {} +unsafe impl Send for PushConstantDesc {} /// For each texture in the pipeline layout, store the index of the only /// sampler (in this layout) that the texture is used with. @@ -510,7 +513,7 @@ type SamplerBindMap = [Option; MAX_TEXTURE_SLOTS]; struct PipelineInner { program: glow::Program, sampler_map: SamplerBindMap, - uniforms: [UniformDesc; MAX_PUSH_CONSTANTS], + push_constant_descs: ArrayVec, } #[derive(Clone, Debug)] @@ -882,7 +885,7 @@ enum Command { PushDebugGroup(Range), PopDebugGroup, SetPushConstants { - uniform: UniformDesc, + uniform: PushConstantDesc, /// Offset from the start of the `data_bytes` offset: u32, }, diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index 6125363aa7..c395a2004a 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -1441,64 +1441,235 @@ impl super::Queue { ref uniform, offset, } => { - fn get_data(data: &[u8], offset: u32) -> &[T] { - let raw = &data[(offset as usize)..]; - unsafe { - slice::from_raw_parts( - raw.as_ptr() as *const _, - raw.len() / mem::size_of::(), - ) - } + // T must be POD + // + // This function is absolutely sketchy and we really should be using bytemuck. + unsafe fn get_data(data: &[u8], offset: u32) -> &[T; COUNT] { + let data_required = mem::size_of::() * COUNT; + + let raw = &data[(offset as usize)..][..data_required]; + + debug_assert_eq!(data_required, raw.len()); + + let slice: &[T] = + unsafe { slice::from_raw_parts(raw.as_ptr() as *const _, COUNT) }; + + slice.try_into().unwrap() } - let location = uniform.location.as_ref(); + let location = Some(&uniform.location); - match uniform.utype { - glow::FLOAT => { - let data = get_data::(data_bytes, offset)[0]; + match uniform.ty { + // + // --- Float 1-4 Component --- + // + naga::TypeInner::Scalar { + kind: naga::ScalarKind::Float, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; unsafe { gl.uniform_1_f32(location, data) }; } - glow::FLOAT_VEC2 => { - let data = get_data::<[f32; 2]>(data_bytes, offset)[0]; - unsafe { gl.uniform_2_f32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Float, + size: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_f32_slice(location, data) }; } - glow::FLOAT_VEC3 => { - let data = get_data::<[f32; 3]>(data_bytes, offset)[0]; - unsafe { gl.uniform_3_f32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Float, + size: naga::VectorSize::Tri, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_f32_slice(location, data) }; } - glow::FLOAT_VEC4 => { - let data = get_data::<[f32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_4_f32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Float, + size: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_f32_slice(location, data) }; } - glow::INT => { - let data = get_data::(data_bytes, offset)[0]; + + // + // --- Int 1-4 Component --- + // + naga::TypeInner::Scalar { + kind: naga::ScalarKind::Sint, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; unsafe { gl.uniform_1_i32(location, data) }; } - glow::INT_VEC2 => { - let data = get_data::<[i32; 2]>(data_bytes, offset)[0]; - unsafe { gl.uniform_2_i32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Sint, + size: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_i32_slice(location, data) }; + } + naga::TypeInner::Vector { + kind: naga::ScalarKind::Sint, + size: naga::VectorSize::Tri, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_i32_slice(location, data) }; } - glow::INT_VEC3 => { - let data = get_data::<[i32; 3]>(data_bytes, offset)[0]; - unsafe { gl.uniform_3_i32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Sint, + size: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_i32_slice(location, data) }; + } + + // + // --- Uint 1-4 Component --- + // + naga::TypeInner::Scalar { + kind: naga::ScalarKind::Uint, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset)[0] }; + unsafe { gl.uniform_1_u32(location, data) }; } - glow::INT_VEC4 => { - let data = get_data::<[i32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_4_i32_slice(location, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Uint, + size: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_2_u32_slice(location, data) }; } - glow::FLOAT_MAT2 => { - let data = get_data::<[f32; 4]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_2_f32_slice(location, false, &data) }; + naga::TypeInner::Vector { + kind: naga::ScalarKind::Uint, + size: naga::VectorSize::Tri, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_3_u32_slice(location, data) }; + } + naga::TypeInner::Vector { + kind: naga::ScalarKind::Uint, + size: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_4_u32_slice(location, data) }; + } + + // + // --- Matrix 2xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_2_f32_slice(location, false, data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Tri, + width: 4, + } => { + // repack 2 vec3s into 6 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + ]; + unsafe { gl.uniform_matrix_2x3_f32_slice(location, false, &packed_data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Bi, + rows: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_2x4_f32_slice(location, false, data) }; + } + + // + // --- Matrix 3xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_3x2_f32_slice(location, false, data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Tri, + width: 4, + } => { + // repack 3 vec3s into 9 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + unpacked_data[8], unpacked_data[9], unpacked_data[10], + ]; + unsafe { gl.uniform_matrix_3_f32_slice(location, false, &packed_data) }; + } + naga::TypeInner::Matrix { + columns: naga::VectorSize::Tri, + rows: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_3x4_f32_slice(location, false, data) }; + } + + // + // --- Matrix 4xR --- + // + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Bi, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_4x2_f32_slice(location, false, data) }; } - glow::FLOAT_MAT3 => { - let data = get_data::<[f32; 9]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_3_f32_slice(location, false, &data) }; + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Tri, + width: 4, + } => { + // repack 4 vec3s into 12 values. + let unpacked_data = unsafe { get_data::(data_bytes, offset) }; + #[rustfmt::skip] + let packed_data = [ + unpacked_data[0], unpacked_data[1], unpacked_data[2], + unpacked_data[4], unpacked_data[5], unpacked_data[6], + unpacked_data[8], unpacked_data[9], unpacked_data[10], + unpacked_data[12], unpacked_data[13], unpacked_data[14], + ]; + unsafe { gl.uniform_matrix_4x3_f32_slice(location, false, &packed_data) }; } - glow::FLOAT_MAT4 => { - let data = get_data::<[f32; 16]>(data_bytes, offset)[0]; - unsafe { gl.uniform_matrix_4_f32_slice(location, false, &data) }; + naga::TypeInner::Matrix { + columns: naga::VectorSize::Quad, + rows: naga::VectorSize::Quad, + width: 4, + } => { + let data = unsafe { get_data::(data_bytes, offset) }; + unsafe { gl.uniform_matrix_4_f32_slice(location, false, data) }; } - _ => panic!("Unsupported uniform datatype!"), + _ => panic!("Unsupported uniform datatype: {:?}!", uniform.ty), } } } diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 2e989499e4..6c8e36ab7c 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -97,6 +97,9 @@ use bitflags::bitflags; use thiserror::Error; use wgt::{WasmNotSend, WasmNotSync}; +// - Vertex + Fragment +// - Compute +pub const MAX_CONCURRENT_SHADER_STAGES: usize = 2; pub const MAX_ANISOTROPY: u8 = 16; pub const MAX_BIND_GROUPS: usize = 8; pub const MAX_VERTEX_BUFFERS: usize = 16; @@ -500,11 +503,19 @@ pub trait CommandEncoder: WasmNotSend + WasmNotSync + fmt::Debug { dynamic_offsets: &[wgt::DynamicOffset], ); + /// Sets a range in push constant data. + /// + /// IMPORTANT: while the data is passed as words, the offset is in bytes! + /// + /// # Safety + /// + /// - `offset_bytes` must be a multiple of 4. + /// - The range of push constants written must be valid for the pipeline layout at draw time. unsafe fn set_push_constants( &mut self, layout: &A::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ); diff --git a/wgpu-hal/src/metal/command.rs b/wgpu-hal/src/metal/command.rs index c4b37f9932..5196e0447d 100644 --- a/wgpu-hal/src/metal/command.rs +++ b/wgpu-hal/src/metal/command.rs @@ -798,17 +798,17 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { let state_pc = &mut self.state.push_constants; if state_pc.len() < layout.total_push_constants as usize { state_pc.resize(layout.total_push_constants as usize, 0); } - assert_eq!(offset as usize % WORD_SIZE, 0); + debug_assert_eq!(offset_bytes as usize % WORD_SIZE, 0); - let offset = offset as usize / WORD_SIZE; - state_pc[offset..offset + data.len()].copy_from_slice(data); + let offset_words = offset_bytes as usize / WORD_SIZE; + state_pc[offset_words..offset_words + data.len()].copy_from_slice(data); if stages.contains(wgt::ShaderStages::COMPUTE) { self.state.compute.as_ref().unwrap().set_bytes( @@ -1104,48 +1104,55 @@ impl crate::CommandEncoder for super::CommandEncoder { let raw = self.raw_cmd_buf.as_ref().unwrap(); objc::rc::autoreleasepool(|| { - let descriptor = metal::ComputePassDescriptor::new(); - - let mut sba_index = 0; - let mut next_sba_descriptor = || { - let sba_descriptor = descriptor - .sample_buffer_attachments() - .object_at(sba_index) - .unwrap(); - sba_index += 1; - sba_descriptor - }; + // TimeStamp Queries and ComputePassDescriptor were both introduced in Metal 2.3 (macOS 11, iOS 14) + // and we currently only need ComputePassDescriptor for timestamp queries + let encoder = if self.shared.private_caps.timestamp_query_support.is_empty() { + raw.new_compute_command_encoder() + } else { + let descriptor = metal::ComputePassDescriptor::new(); + + let mut sba_index = 0; + let mut next_sba_descriptor = || { + let sba_descriptor = descriptor + .sample_buffer_attachments() + .object_at(sba_index) + .unwrap(); + sba_index += 1; + sba_descriptor + }; + + for (set, index) in self.state.pending_timer_queries.drain(..) { + let sba_descriptor = next_sba_descriptor(); + sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap()); + sba_descriptor.set_start_of_encoder_sample_index(index as _); + sba_descriptor.set_end_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE); + } - for (set, index) in self.state.pending_timer_queries.drain(..) { - let sba_descriptor = next_sba_descriptor(); - sba_descriptor.set_sample_buffer(set.counter_sample_buffer.as_ref().unwrap()); - sba_descriptor.set_start_of_encoder_sample_index(index as _); - sba_descriptor.set_end_of_encoder_sample_index(metal::COUNTER_DONT_SAMPLE); - } + if let Some(timestamp_writes) = desc.timestamp_writes.as_ref() { + let sba_descriptor = next_sba_descriptor(); + sba_descriptor.set_sample_buffer( + timestamp_writes + .query_set + .counter_sample_buffer + .as_ref() + .unwrap(), + ); - if let Some(timestamp_writes) = desc.timestamp_writes.as_ref() { - let sba_descriptor = next_sba_descriptor(); - sba_descriptor.set_sample_buffer( - timestamp_writes - .query_set - .counter_sample_buffer - .as_ref() - .unwrap(), - ); + sba_descriptor.set_start_of_encoder_sample_index( + timestamp_writes + .beginning_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); + sba_descriptor.set_end_of_encoder_sample_index( + timestamp_writes + .end_of_pass_write_index + .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), + ); + } - sba_descriptor.set_start_of_encoder_sample_index( - timestamp_writes - .beginning_of_pass_write_index - .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), - ); - sba_descriptor.set_end_of_encoder_sample_index( - timestamp_writes - .end_of_pass_write_index - .map_or(metal::COUNTER_DONT_SAMPLE, |i| i as _), - ); - } + raw.compute_command_encoder_with_descriptor(descriptor) + }; - let encoder = raw.compute_command_encoder_with_descriptor(descriptor); if let Some(label) = desc.label { encoder.set_label(label); } diff --git a/wgpu-hal/src/vulkan/command.rs b/wgpu-hal/src/vulkan/command.rs index 391b754d33..dedc054e6b 100644 --- a/wgpu-hal/src/vulkan/command.rs +++ b/wgpu-hal/src/vulkan/command.rs @@ -600,7 +600,7 @@ impl crate::CommandEncoder for super::CommandEncoder { &mut self, layout: &super::PipelineLayout, stages: wgt::ShaderStages, - offset: u32, + offset_bytes: u32, data: &[u32], ) { unsafe { @@ -608,7 +608,7 @@ impl crate::CommandEncoder for super::CommandEncoder { self.active, layout.raw, conv::map_shader_stage(stages), - offset, + offset_bytes, slice::from_raw_parts(data.as_ptr() as _, data.len() * 4), ) }; diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index d88b48ef73..8eb2935a32 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -1588,7 +1588,7 @@ impl crate::Device for super::Device { multiview: desc.multiview, ..Default::default() }; - let mut stages = ArrayVec::<_, 2>::new(); + let mut stages = ArrayVec::<_, { crate::MAX_CONCURRENT_SHADER_STAGES }>::new(); let mut vertex_buffers = Vec::with_capacity(desc.vertex_buffers.len()); let mut vertex_attributes = Vec::new(); diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index 9e88749b8e..da9eaea8b2 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -151,6 +151,17 @@ unsafe extern "system" fn debug_utils_messenger_callback( vk::FALSE } +impl super::DebugUtilsCreateInfo { + fn to_vk_create_info(&self) -> vk::DebugUtilsMessengerCreateInfoEXTBuilder<'_> { + let user_data_ptr: *const super::DebugUtilsMessengerUserData = &*self.callback_data; + vk::DebugUtilsMessengerCreateInfoEXT::builder() + .message_severity(self.severity) + .message_type(self.message_type) + .user_data(user_data_ptr as *mut _) + .pfn_user_callback(Some(debug_utils_messenger_callback)) + } +} + impl super::Swapchain { /// # Safety /// @@ -297,7 +308,7 @@ impl super::Instance { raw_instance: ash::Instance, instance_api_version: u32, android_sdk_version: u32, - debug_utils_user_data: Option, + debug_utils_create_info: Option, extensions: Vec<&'static CStr>, flags: wgt::InstanceFlags, has_nv_optimus: bool, @@ -305,42 +316,19 @@ impl super::Instance { ) -> Result { log::info!("Instance version: 0x{:x}", instance_api_version); - let debug_utils = if let Some(debug_callback_user_data) = debug_utils_user_data { + let debug_utils = if let Some(debug_utils_create_info) = debug_utils_create_info { if extensions.contains(&ext::DebugUtils::name()) { log::info!("Enabling debug utils"); - // Move the callback data to the heap, to ensure it will never be - // moved. - let callback_data = Box::new(debug_callback_user_data); let extension = ext::DebugUtils::new(&entry, &raw_instance); - // having ERROR unconditionally because Vk doesn't like empty flags - let mut severity = vk::DebugUtilsMessageSeverityFlagsEXT::ERROR; - if log::max_level() >= log::LevelFilter::Debug { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE; - } - if log::max_level() >= log::LevelFilter::Info { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::INFO; - } - if log::max_level() >= log::LevelFilter::Warn { - severity |= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING; - } - let user_data_ptr: *const super::DebugUtilsMessengerUserData = &*callback_data; - let vk_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() - .flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty()) - .message_severity(severity) - .message_type( - vk::DebugUtilsMessageTypeFlagsEXT::GENERAL - | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION - | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, - ) - .pfn_user_callback(Some(debug_utils_messenger_callback)) - .user_data(user_data_ptr as *mut _); + let vk_info = debug_utils_create_info.to_vk_create_info(); let messenger = unsafe { extension.create_debug_utils_messenger(&vk_info, None) }.unwrap(); + Some(super::DebugUtils { extension, messenger, - callback_data, + callback_data: debug_utils_create_info.callback_data, }) } else { log::info!("Debug utils not enabled: extension not listed"); @@ -559,10 +547,12 @@ impl super::Instance { impl Drop for super::InstanceShared { fn drop(&mut self) { unsafe { - if let Some(du) = self.debug_utils.take() { + // Keep du alive since destroy_instance may also log + let _du = self.debug_utils.take().map(|du| { du.extension .destroy_debug_utils_messenger(du.messenger, None); - } + du + }); if let Some(_drop_guard) = self.drop_guard.take() { self.raw.destroy_instance(None); } @@ -653,21 +643,52 @@ impl crate::Instance for super::Instance { let mut layers: Vec<&'static CStr> = Vec::new(); // Request validation layer if asked. - let mut debug_callback_user_data = None; - if desc.flags.contains(wgt::InstanceFlags::VALIDATION) { + let mut debug_utils = None; + if desc.flags.intersects(wgt::InstanceFlags::VALIDATION) { let validation_layer_name = CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap(); if let Some(layer_properties) = find_layer(&instance_layers, validation_layer_name) { layers.push(validation_layer_name); - debug_callback_user_data = Some(super::DebugUtilsMessengerUserData { - validation_layer_description: cstr_from_bytes_until_nul( - &layer_properties.description, - ) - .unwrap() - .to_owned(), - validation_layer_spec_version: layer_properties.spec_version, - has_obs_layer, - }); + + if extensions.contains(&ext::DebugUtils::name()) { + // Put the callback data on the heap, to ensure it will never be + // moved. + let callback_data = Box::new(super::DebugUtilsMessengerUserData { + validation_layer_description: cstr_from_bytes_until_nul( + &layer_properties.description, + ) + .unwrap() + .to_owned(), + validation_layer_spec_version: layer_properties.spec_version, + has_obs_layer, + }); + + // having ERROR unconditionally because Vk doesn't like empty flags + let mut severity = vk::DebugUtilsMessageSeverityFlagsEXT::ERROR; + if log::max_level() >= log::LevelFilter::Debug { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE; + } + if log::max_level() >= log::LevelFilter::Info { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::INFO; + } + if log::max_level() >= log::LevelFilter::Warn { + severity |= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING; + } + + let message_type = vk::DebugUtilsMessageTypeFlagsEXT::GENERAL + | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION + | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE; + + let create_info = super::DebugUtilsCreateInfo { + severity, + message_type, + callback_data, + }; + + let vk_create_info = create_info.to_vk_create_info().build(); + + debug_utils = Some((create_info, vk_create_info)); + } } else { log::warn!( "InstanceFlags::VALIDATION requested, but unable to find layer: {}", @@ -706,23 +727,26 @@ impl crate::Instance for super::Instance { if extensions.contains(&ash::vk::KhrPortabilityEnumerationFn::name()) { flags |= vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR; } - let vk_instance = { let str_pointers = layers .iter() .chain(extensions.iter()) - .map(|&s| { + .map(|&s: &&'static _| { // Safe because `layers` and `extensions` entries have static lifetime. s.as_ptr() }) .collect::>(); - let create_info = vk::InstanceCreateInfo::builder() + let mut create_info = vk::InstanceCreateInfo::builder() .flags(flags) .application_info(&app_info) .enabled_layer_names(&str_pointers[..layers.len()]) .enabled_extension_names(&str_pointers[layers.len()..]); + if let Some(&mut (_, ref mut vk_create_info)) = debug_utils.as_mut() { + create_info = create_info.push_next(vk_create_info); + } + unsafe { profiling::scope!("vkCreateInstance"); entry.create_instance(&create_info, None) @@ -741,7 +765,7 @@ impl crate::Instance for super::Instance { vk_instance, instance_api_version, android_sdk_version, - debug_callback_user_data, + debug_utils.map(|(i, _)| i), extensions, desc.flags, has_nv_optimus, diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 5cdb7f11ca..a0f7123552 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -85,6 +85,12 @@ struct DebugUtils { callback_data: Box, } +pub struct DebugUtilsCreateInfo { + severity: vk::DebugUtilsMessageSeverityFlagsEXT, + message_type: vk::DebugUtilsMessageTypeFlagsEXT, + callback_data: Box, +} + /// User data needed by `instance::debug_utils_messenger_callback`. /// /// When we create the [`vk::DebugUtilsMessengerEXT`], the `pUserData` diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index e705d34e92..2804078068 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -1217,7 +1217,7 @@ impl crate::Context for Context { if let Some(cause) = error { if let wgc::pipeline::CreateRenderPipelineError::Internal { stage, ref error } = cause { log::error!("Shader translation error for stage {:?}: {}", stage, error); - log::error!("Please report it to https://github.com/gfx-rs/naga"); + log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.handle_error( &device_data.error_sink, @@ -1262,12 +1262,12 @@ impl crate::Context for Context { )); if let Some(cause) = error { if let wgc::pipeline::CreateComputePipelineError::Internal(ref error) = cause { - log::warn!( + log::error!( "Shader translation error for stage {:?}: {}", wgt::ShaderStages::COMPUTE, error ); - log::warn!("Please report it to https://github.com/gfx-rs/naga"); + log::error!("Please report it to https://github.com/gfx-rs/wgpu"); } self.handle_error( &device_data.error_sink, diff --git a/xtask/Cargo.lock b/xtask/Cargo.lock index c213ff96be..9ee4a72e91 100644 --- a/xtask/Cargo.lock +++ b/xtask/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "base64" @@ -24,11 +24,17 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cargo-run-wasm" @@ -41,12 +47,6 @@ dependencies = [ "wasm-bindgen-cli-support", ] -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -70,33 +70,19 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "cc", "libc", + "windows-sys", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "heck" @@ -107,43 +93,17 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "id-arena" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "leb128" @@ -153,21 +113,21 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.145" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "log" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "pico-args" @@ -177,29 +137,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "proc-macro2" -version = "1.0.59" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -210,23 +170,22 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.37.25" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safemem" @@ -236,15 +195,29 @@ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -262,24 +235,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" @@ -310,14 +294,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "wasm-bindgen-cli-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21c60239a09bf9bab8dfa752be4e6c637db22296b9ded493800090448692da9" +checksum = "f2252adf46913da7b729caf556b81cedd1335165576e6446d84618e8835d89dd" dependencies = [ "anyhow", "base64", @@ -337,9 +321,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-externref-xform" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafbe1984f67cc12645f12ab65e6145e8ddce1ab265d0be58435f25bb0ce2608" +checksum = "43f3b73cf8fcb86da78c6649c74acef205723f57af99b9f549b2609c83fe7815" dependencies = [ "anyhow", "walrus", @@ -347,9 +331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-multi-value-xform" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581419e3995571a1d2d066e360ca1c0c09da097f5a53c98e6f00d96eddaf0ffe" +checksum = "930dd8e8226379aebb7d512f31b9241a3c59a1801452932e5a15bebfd3b708fb" dependencies = [ "anyhow", "walrus", @@ -357,15 +341,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-bindgen-threads-xform" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05d272073981137e8426cf2a6830d43d1f84f988a050b2f8b210f0e266b8983" +checksum = "759b1e9784f903a7890bcf147aa7c8c529a6318a2db05f88c054194a3e6c6d57" dependencies = [ "anyhow", "walrus", @@ -374,9 +358,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-conventions" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9c65b1ff5041ea824ca24c519948aec16fb6611c617d601623c0657dfcd47b" +checksum = "2dc12bc175c837239520b8aa9dcfb68a025fcf56a718a02551a75a972711c816" dependencies = [ "anyhow", "walrus", @@ -384,9 +368,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-interpreter" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5c796220738ab5d44666f37205728a74141c0039d1166bcf8110b26bafaa1e" +checksum = "6a5510ab88377b4e3160a7e5d90a876d0a1da2d9b9b67495f437246714c0980f" dependencies = [ "anyhow", "log", @@ -400,152 +384,86 @@ version = "0.77.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe3d5405e9ea6c1317a656d6e0820912d8b7b3607823a7596117c8f666daf6f" -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "xshell" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" +checksum = "ce2107fe03e558353b4c71ad7626d58ed82efaf56c54134228608893c77023ad" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" +checksum = "7e2c411759b501fb9501aac2b1b2d287a6e93e5bdcf13c25306b23e1b716dd0e" [[package]] name = "xtask"