diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..95d2a35175 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,7 @@ +[alias] +xtask = "run --manifest-path xtask/Cargo.toml --" + +[build] +rustflags = [ +"--cfg=web_sys_unstable_apis" +] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7fefad320c..1d6e147803 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,4 @@ -/cts_runner/ @crowlKats -/deno_webgpu/ @crowlKats +* @gfx-rs/wgpu + +/cts_runner/ @gfx-rs/deno @gfx-rs/wgpu +/deno_webgpu/ @gfx-rs/deno @gfx-rs/wgpu diff --git a/CHANGELOG.md b/CHANGELOG.md index 535a805b1c..068827b564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) ### Added/New Features - Add `gles_minor_version` field to `wgpu::InstanceDescriptor`. By @PJB3005 in [#3998](https://github.com/gfx-rs/wgpu/pull/3998) +- Re-export Naga. By @exrook in [#4172](https://github.com/gfx-rs/wgpu/pull/4172) ### Changes @@ -102,6 +103,9 @@ By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) - Add validation in accordance with WebGPU `setViewport` valid usage for `x`, `y` and `this.[[attachment_size]]`. By @James2022-rgb in [#4058](https://github.com/gfx-rs/wgpu/pull/4058) - `wgpu::CreateSurfaceError` and `wgpu::RequestDeviceError` now give details of the failure, but no longer implement `PartialEq` and cannot be constructed. By @kpreid in [#4066](https://github.com/gfx-rs/wgpu/pull/4066) and [#4145](https://github.com/gfx-rs/wgpu/pull/4145) - Make `WGPU_POWER_PREF=none` a valid value. By @fornwall in [4076](https://github.com/gfx-rs/wgpu/pull/4076) +- Support dual source blending in OpenGL ES, Metal, Vulkan & DX12. By @freqmod in [4022](https://github.com/gfx-rs/wgpu/pull/4022) +- Add stub support for device destroy and device validity. By @bradwerth in [4163](https://github.com/gfx-rs/wgpu/pull/4163) +- Add trace-level logging for most entry points in wgpu-core By @nical in [4183](https://github.com/gfx-rs/wgpu/pull/4183) #### Vulkan @@ -111,7 +115,8 @@ By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) ### Documentation -- Use WGSL for VertexFormat example types. By @ScanMountGoat in [#4305](https://github.com/gfx-rs/wgpu/pull/4035) +- Use WGSL for VertexFormat example types. By @ScanMountGoat in [#4035](https://github.com/gfx-rs/wgpu/pull/4035) +- Fix description of `Features::TEXTURE_COMPRESSION_ASTC_HDR` in [#4157](https://github.com/gfx-rs/wgpu/pull/4157) #### Metal @@ -124,6 +129,7 @@ By @wumpf in [#4147](https://github.com/gfx-rs/wgpu/pull/4147) - Derive storage bindings via `naga::StorageAccess` instead of `naga::GlobalUse`. By @teoxoy in [#3985](https://github.com/gfx-rs/wgpu/pull/3985). - `Queue::on_submitted_work_done` callbacks will now always be called after all previous `BufferSlice::map_async` callbacks, even when there are no active submissions. By @cwfitzgerald in [#4036](https://github.com/gfx-rs/wgpu/pull/4036). - 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). #### Vulkan - Fix enabling `wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY` not being actually enabled in vulkan backend. By @39ali in[#3772](https://github.com/gfx-rs/wgpu/pull/3772). diff --git a/Cargo.lock b/Cargo.lock index 65d90eb0b1..64f878a7fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,11 +35,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom 0.2.10", + "cfg-if", "once_cell", "version_check", ] @@ -53,6 +53,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" version = "0.4.2" @@ -124,9 +130,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "78f2db9467baa66a700abce2a18c5ad793f6f83310aca1284796fc3921d113fd" dependencies = [ "async-lock", "async-task", @@ -850,12 +856,9 @@ dependencies = [ [[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 = "fdeflate" @@ -1036,13 +1039,8 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ - "fastrand", "futures-core", - "futures-io", - "memchr", - "parking", "pin-project-lite", - "waker-fn", ] [[package]] @@ -1127,9 +1125,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" [[package]] name = "glow" @@ -1242,13 +1240,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "gpu-descriptor-types", - "hashbrown 0.12.3", + "hashbrown 0.14.0", ] [[package]] @@ -1265,15 +1263,16 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "hassle-rs" @@ -1951,12 +1950,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.12.1" @@ -2060,7 +2053,7 @@ dependencies = [ "serde", "wgpu-core", "wgpu-types", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -2129,9 +2122,9 @@ dependencies = [ [[package]] name = "profiling" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f10e75d83c7aec79a6aa46f897075890e156b105eebe51cfa0abce51af025f" +checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" [[package]] name = "quote" @@ -2543,9 +2536,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay-client-toolkit" @@ -2644,27 +2637,27 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", @@ -2945,12 +2938,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3179,7 +3166,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3194,7 +3181,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3212,7 +3199,7 @@ dependencies = [ "wasm-bindgen-test", "wgpu", "wgpu-example", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3223,7 +3210,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3259,7 +3246,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3280,7 +3267,7 @@ dependencies = [ "wgpu", "wgpu-hal", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3323,7 +3310,7 @@ dependencies = [ "web-sys", "wgpu-types", "winapi", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3341,7 +3328,7 @@ dependencies = [ "wasm-bindgen-test", "wgpu", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3371,7 +3358,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "wgpu", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3383,7 +3370,7 @@ dependencies = [ "env_logger", "pollster", "wgpu", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3410,7 +3397,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3424,7 +3411,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3437,7 +3424,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3453,7 +3440,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3465,7 +3452,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3503,7 +3490,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3521,7 +3508,7 @@ dependencies = [ "wasm-bindgen-test", "wgpu", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3547,7 +3534,7 @@ dependencies = [ "wgpu", "wgpu-example", "wgpu-test", - "winit 0.28.6", + "winit 0.28.7", ] [[package]] @@ -3817,9 +3804,9 @@ dependencies = [ [[package]] name = "winit" -version = "0.28.6" +version = "0.28.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196" +checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" dependencies = [ "android-activity", "bitflags 1.3.2", diff --git a/Cargo.toml b/Cargo.toml index 40b8c46c96..8794c68e11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ ddsfile = "0.5" env_logger = "0.10" futures-intrusive = "0.5" rustc-hash = "1.1.0" -glam = "0.24.1" +glam = "0.24.2" image = { version = "0.24", default-features = false, features = ["png"] } # libloading 0.8 switches from `winapi` to `windows-sys`; permit either libloading = ">=0.7,<0.9" @@ -98,13 +98,7 @@ wgpu-core = { version = "0.17.0", path = "./wgpu-core" } wgpu-example = { version = "0.17.0", path = "./examples/common" } wgpu-test = { version = "0.17", path = "./tests"} wgpu-types = { version = "0.17.0", path = "./wgpu-types" } -winit = { version = "0.28.6", features = [ "android-native-activity" ] } - -# Metal dependencies -block = "0.1" -metal = "0.26.0" -objc = "0.2.5" -core-graphics-types = "0.1" +winit = { version = "0.28.7", features = [ "android-native-activity" ] } # Vulkan dependencies ash = "0.37.3" @@ -142,7 +136,7 @@ deno_web = "0.137.0" deno_webidl = "0.106.0" deno_webgpu = { path = "./deno_webgpu" } tokio = "1.32.0" -termcolor = "1.2.0" +termcolor = "1.3.0" [patch."https://github.com/gfx-rs/naga"] #naga = { path = "../naga" } @@ -157,9 +151,6 @@ termcolor = "1.2.0" #naga = { path = "../naga" } #glow = { path = "../glow" } #d3d12 = { path = "../d3d12-rs" } -#metal = { path = "../metal-rs" } -#metal = { path = "../metal-rs" } -metal = { git = "https://github.com/gfx-rs/metal-rs/", rev = "d24f1a4" } # More timer support via https://github.com/gfx-rs/metal-rs/pull/280 #web-sys = { path = "../wasm-bindgen/crates/web-sys" } #js-sys = { path = "../wasm-bindgen/crates/js-sys" } #wasm-bindgen = { path = "../wasm-bindgen" } diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index fff502ebe2..679407315d 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -365,6 +365,9 @@ fn deserialize_features(features: &wgpu_types::Features) -> Vec<&'static str> { if features.contains(wgpu_types::Features::SHADER_EARLY_DEPTH_TEST) { return_features.push("shader-early-depth-test"); } + if features.contains(wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT) { + return_features.push("shader-unused-vertex-output"); + } return_features } @@ -625,6 +628,10 @@ impl From for wgpu_types::Features { wgpu_types::Features::SHADER_EARLY_DEPTH_TEST, required_features.0.contains("shader-early-depth-test"), ); + features.set( + wgpu_types::Features::SHADER_UNUSED_VERTEX_OUTPUT, + required_features.0.contains("shader-unused-vertex-output"), + ); features } diff --git a/tests/tests/device.rs b/tests/tests/device.rs index 1b8c4d765c..ff749f4cb1 100644 --- a/tests/tests/device.rs +++ b/tests/tests/device.rs @@ -1,6 +1,6 @@ use wasm_bindgen_test::*; -use wgpu_test::{initialize_test, FailureCase, TestParameters}; +use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; #[test] #[wasm_bindgen_test] @@ -141,3 +141,355 @@ async fn request_device_error_message() { } assert!(device_error.contains(expected), "{device_error}"); } + +#[test] +fn device_destroy_then_more() { + // This is a test of device behavior after device.destroy. Specifically, all operations + // should trigger errors since the device is lost. + // + // On DX12 this test fails with a validation error in the very artifical actions taken + // after lose the device. The error is "ID3D12CommandAllocator::Reset: The command + // allocator cannot be reset because a command list is currently being recorded with the + // allocator." That may indicate that DX12 doesn't like opened command buffers staying + // open even after they return an error. For now, this test is skipped on DX12. + // + // The DX12 issue may be related to https://github.com/gfx-rs/wgpu/issues/3193. + initialize_test( + TestParameters::default() + .features(wgpu::Features::CLEAR_TEXTURE) + .skip(FailureCase::backend(wgpu::Backends::DX12)), + |ctx| { + // Create some resources on the device that we will attempt to use *after* losing + // the device. + + // Create some 512 x 512 2D textures. + let texture_extent = wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }; + let texture_for_view = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let target_view = texture_for_view.create_view(&wgpu::TextureViewDescriptor::default()); + + let texture_for_read = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let texture_for_write = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: texture_extent, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + // Create some buffers. + let buffer_source = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + let buffer_dest = ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + // Create a bind group layout. + let bind_group_layout = + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + + // Create a shader module. + let shader_module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), + }); + + // Create some command encoders. + let mut encoder_for_clear = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_compute_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_render_pass = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_buffer_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_buffer_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + let mut encoder_for_texture_texture_copy = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + // Destroy the device. This will cause all other requests to return some variation of + // a device invalid error. + ctx.device.destroy(); + + // TODO: verify the following operations will return an invalid device error: + // * Run a compute pass + // * Run a render pass + // * Finish a render bundle encoder + // * Create a texture from HAL + // * Create a buffer from HAL + // * Create a sampler + // * Validate a surface configuration + // * Start capture + // * Stop capture + // * Buffer map + + // TODO: figure out how to structure a test around these operations which panic when + // the device is invalid: + // * device.features() + // * device.limits() + // * device.downlevel_properties() + // * device.create_query_set() + + // TODO: change these fail calls to check for the specific errors which indicate that + // the device is not valid. + + // Creating a commmand encoder should fail. + fail(&ctx.device, || { + ctx.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + }); + + // Creating a buffer should fail. + fail(&ctx.device, || { + ctx.device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: 256, + usage: wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + }); + + // Creating a texture should fail. + fail(&ctx.device, || { + ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 512, + height: 512, + depth_or_array_layers: 1, + }, + mip_level_count: 2, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg8Uint, + usage: wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + }); + + // Texture clear should fail. + fail(&ctx.device, || { + encoder_for_clear.clear_texture( + &texture_for_write, + &wgpu::ImageSubresourceRange { + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }, + ); + }); + + // Creating a compute pass should fail. + fail(&ctx.device, || { + encoder_for_compute_pass.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: None, + timestamp_writes: None, + }); + }); + + // Creating a render pass should fail. + fail(&ctx.device, || { + encoder_for_render_pass.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + ops: wgpu::Operations::default(), + resolve_target: None, + view: &target_view, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + }); + + // Copying a buffer to a buffer should fail. + fail(&ctx.device, || { + encoder_for_buffer_buffer_copy.copy_buffer_to_buffer( + &buffer_source, + 0, + &buffer_dest, + 0, + 256, + ); + }); + + // Copying a buffer to a texture should fail. + fail(&ctx.device, || { + encoder_for_buffer_texture_copy.copy_buffer_to_texture( + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Copying a texture to a buffer should fail. + fail(&ctx.device, || { + encoder_for_texture_buffer_copy.copy_texture_to_buffer( + texture_for_read.as_image_copy(), + wgpu::ImageCopyBuffer { + buffer: &buffer_source, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4), + rows_per_image: None, + }, + }, + texture_extent, + ); + }); + + // Copying a texture to a texture should fail. + fail(&ctx.device, || { + encoder_for_texture_texture_copy.copy_texture_to_texture( + texture_for_read.as_image_copy(), + texture_for_write.as_image_copy(), + texture_extent, + ); + }); + + // Creating a bind group layout should fail. + fail(&ctx.device, || { + ctx.device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[], + }); + }); + + // Creating a bind group should fail. + fail(&ctx.device, || { + ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + buffer_source.as_entire_buffer_binding(), + ), + }], + }); + }); + + // Creating a pipeline layout should fail. + fail(&ctx.device, || { + ctx.device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + }); + + // Creating a shader module should fail. + fail(&ctx.device, || { + ctx.device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed("")), + }); + }); + + // Creating a shader module spirv should fail. + fail(&ctx.device, || unsafe { + ctx.device + .create_shader_module_spirv(&wgpu::ShaderModuleDescriptorSpirV { + label: None, + source: std::borrow::Cow::Borrowed(&[]), + }); + }); + + // Creating a render pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader_module, + entry_point: "", + buffers: &[], + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment: None, + multiview: None, + }); + }); + + // Creating a compute pipeline should fail. + fail(&ctx.device, || { + ctx.device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &shader_module, + entry_point: "", + }); + }); + }, + ) +} diff --git a/tests/tests/pipeline.rs b/tests/tests/pipeline.rs new file mode 100644 index 0000000000..37046dd6b9 --- /dev/null +++ b/tests/tests/pipeline.rs @@ -0,0 +1,37 @@ +use wasm_bindgen_test::*; +use wgpu_test::{fail, initialize_test, FailureCase, TestParameters}; + +#[test] +#[wasm_bindgen_test] +fn pipeline_default_layout_bad_module() { + // Create an invalid shader and a compute pipeline that uses it + // with a default bindgroup layout, and then ask for that layout. + // Validation should fail, but wgpu should not panic. + let parameters = TestParameters::default() + .skip(FailureCase::webgl2()) + // https://github.com/gfx-rs/wgpu/issues/4167 + .expect_fail(FailureCase::always()); + initialize_test(parameters, |ctx| { + ctx.device.push_error_scope(wgpu::ErrorFilter::Validation); + + fail(&ctx.device, || { + let module = ctx + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl("not valid wgsl".into()), + }); + + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("mandelbrot compute pipeline"), + layout: None, + module: &module, + entry_point: "doesn't exist", + }); + + pipeline.get_bind_group_layout(0); + }); + }); +} diff --git a/tests/tests/root.rs b/tests/tests/root.rs index 3354f5e99e..7d1fc834a1 100644 --- a/tests/tests/root.rs +++ b/tests/tests/root.rs @@ -20,6 +20,7 @@ mod instance; mod mem_leaks; mod occlusion_query; mod partially_bounded_arrays; +mod pipeline; mod poll; mod query_set; mod queue_transfer; diff --git a/wgpu-core/src/command/clear.rs b/wgpu-core/src/command/clear.rs index a2e1bba11d..b99887e058 100644 --- a/wgpu-core/src/command/clear.rs +++ b/wgpu-core/src/command/clear.rs @@ -77,7 +77,8 @@ impl Global { offset: BufferAddress, size: Option, ) -> Result<(), ClearError> { - profiling::scope!("CommandEncoder::fill_buffer"); + profiling::scope!("CommandEncoder::clear_buffer"); + log::trace!("CommandEncoder::clear_buffer {dst:?}"); let hub = A::hub(self); @@ -162,6 +163,7 @@ impl Global { subresource_range: &ImageSubresourceRange, ) -> Result<(), ClearError> { profiling::scope!("CommandEncoder::clear_texture"); + log::trace!("CommandEncoder::clear_texture {dst:?}"); let hub = A::hub(self); @@ -222,6 +224,9 @@ impl Global { } let device = &cmd_buf.device; + if !device.is_valid() { + return Err(ClearError::InvalidDevice(cmd_buf.device_id.value.0)); + } let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker(); clear_texture( diff --git a/wgpu-core/src/command/compute.rs b/wgpu-core/src/command/compute.rs index 341743985f..877ef19031 100644 --- a/wgpu-core/src/command/compute.rs +++ b/wgpu-core/src/command/compute.rs @@ -15,6 +15,7 @@ use crate::{ global::Global, hal_api::HalApi, id, + id::DeviceId, identity::GlobalIdentityHandlerFactory, init_tracker::MemoryInitKind, pipeline, @@ -192,6 +193,8 @@ pub enum ComputePassErrorInner { Encoder(#[from] CommandEncoderError), #[error("Bind group {0:?} is invalid")] InvalidBindGroup(id::BindGroupId), + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), #[error("Bind group index {index} is greater than the device's requested `max_bind_group` limit {max}")] BindGroupIndexOutOfRange { index: u32, max: u32 }, #[error("Compute pipeline {0:?} is invalid")] @@ -365,6 +368,14 @@ impl Global { let hub = A::hub(self); let cmd_buf = CommandBuffer::get_encoder(hub, encoder_id).map_pass_err(init_scope)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(ComputePassErrorInner::InvalidDevice( + cmd_buf.device_id.value.0, + )) + .map_pass_err(init_scope); + } + let mut cmd_buf_data = cmd_buf.data.lock(); let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); @@ -389,7 +400,6 @@ impl Global { // will be reset to true if recording is done without errors *status = CommandEncoderStatus::Error; let raw = encoder.open(); - let device = &cmd_buf.device; let bind_group_guard = hub.bind_groups.read(); let pipeline_guard = hub.compute_pipelines.read(); diff --git a/wgpu-core/src/command/mod.rs b/wgpu-core/src/command/mod.rs index 0f40478d6e..59336a41cc 100644 --- a/wgpu-core/src/command/mod.rs +++ b/wgpu-core/src/command/mod.rs @@ -436,6 +436,7 @@ impl Global { label: &str, ) -> Result<(), CommandEncoderError> { profiling::scope!("CommandEncoder::push_debug_group"); + log::trace!("CommandEncoder::push_debug_group {label}"); let hub = A::hub(self); @@ -460,6 +461,7 @@ impl Global { label: &str, ) -> Result<(), CommandEncoderError> { profiling::scope!("CommandEncoder::insert_debug_marker"); + log::trace!("CommandEncoder::insert_debug_marker {label}"); let hub = A::hub(self); @@ -484,6 +486,7 @@ impl Global { encoder_id: id::CommandEncoderId, ) -> Result<(), CommandEncoderError> { profiling::scope!("CommandEncoder::pop_debug_marker"); + log::trace!("CommandEncoder::pop_debug_group"); let hub = A::hub(self); diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 346ef24363..284d61e33f 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -18,6 +18,7 @@ use crate::{ global::Global, hal_api::HalApi, id, + id::DeviceId, identity::GlobalIdentityHandlerFactory, init_tracker::{MemoryInitKind, TextureInitRange, TextureInitTrackerAction}, pipeline::{self, PipelineFlags}, @@ -523,6 +524,8 @@ pub enum RenderPassErrorInner { ColorAttachment(#[from] ColorAttachmentError), #[error(transparent)] Encoder(#[from] CommandEncoderError), + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), #[error("Attachment texture view {0:?} is invalid")] InvalidAttachment(id::TextureViewId), #[error("The format of the depth-stencil attachment ({0:?}) is not a depth-stencil format")] @@ -1304,6 +1307,14 @@ impl Global { occlusion_query_set_id, }); } + + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(RenderPassErrorInner::InvalidDevice( + cmd_buf.device_id.value.0, + )) + .map_pass_err(init_scope); + } let encoder = &mut cmd_buf_data.encoder; let status = &mut cmd_buf_data.status; @@ -1318,7 +1329,6 @@ impl Global { encoder.close(); // We will reset this to `Recording` if we succeed, acts as a fail-safe. *status = CommandEncoderStatus::Error; - let device = &cmd_buf.device; encoder.open_pass(base.label); let bundle_guard = hub.render_bundles.read(); @@ -2339,6 +2349,7 @@ pub mod render_ffi { pass: &mut RenderPass, pipeline_id: id::RenderPipelineId, ) { + log::trace!("RenderPass::set_pipeline {pipeline_id:?}"); if pass.current_pipeline.set_and_check_redundant(pipeline_id) { return; } @@ -2356,6 +2367,7 @@ pub mod render_ffi { offset: BufferAddress, size: Option, ) { + log::trace!("RenderPass::set_vertex_buffer {buffer_id:?}"); pass.base.commands.push(RenderCommand::SetVertexBuffer { slot, buffer_id, @@ -2372,11 +2384,13 @@ pub mod render_ffi { offset: BufferAddress, size: Option, ) { + log::trace!("RenderPass::set_index_buffer {buffer:?}"); pass.set_index_buffer(buffer, index_format, offset, size); } #[no_mangle] pub extern "C" fn wgpu_render_pass_set_blend_constant(pass: &mut RenderPass, color: &Color) { + log::trace!("RenderPass::set_blend_constant"); pass.base .commands .push(RenderCommand::SetBlendConstant(*color)); @@ -2384,6 +2398,7 @@ pub mod render_ffi { #[no_mangle] pub extern "C" fn wgpu_render_pass_set_stencil_reference(pass: &mut RenderPass, value: u32) { + log::trace!("RenderPass::set_stencil_reference {value}"); pass.base .commands .push(RenderCommand::SetStencilReference(value)); @@ -2399,6 +2414,7 @@ pub mod render_ffi { depth_min: f32, depth_max: f32, ) { + log::trace!("RenderPass::set_viewport {x} {y} {w} {h}"); pass.base.commands.push(RenderCommand::SetViewport { rect: Rect { x, y, w, h }, depth_min, @@ -2414,6 +2430,7 @@ pub mod render_ffi { w: u32, h: u32, ) { + log::trace!("RenderPass::set_scissor_rect {x} {y} {w} {h}"); pass.base .commands .push(RenderCommand::SetScissor(Rect { x, y, w, h })); @@ -2431,6 +2448,7 @@ pub mod render_ffi { size_bytes: u32, data: *const u8, ) { + log::trace!("RenderPass::set_push_constants"); assert_eq!( offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1), 0, @@ -2468,6 +2486,10 @@ pub mod render_ffi { first_vertex: u32, first_instance: u32, ) { + log::trace!( + "RenderPass::draw {vertex_count} {instance_count} {first_vertex} {first_instance}" + ); + pass.base.commands.push(RenderCommand::Draw { vertex_count, instance_count, @@ -2485,6 +2507,7 @@ pub mod render_ffi { base_vertex: i32, first_instance: u32, ) { + log::trace!("RenderPass::draw_indexed {index_count} {instance_count} {first_index} {base_vertex} {first_instance}"); pass.base.commands.push(RenderCommand::DrawIndexed { index_count, instance_count, @@ -2500,6 +2523,7 @@ pub mod render_ffi { buffer_id: id::BufferId, offset: BufferAddress, ) { + log::trace!("RenderPass::draw_indirect {buffer_id:?} {offset}"); pass.base.commands.push(RenderCommand::MultiDrawIndirect { buffer_id, offset, @@ -2514,6 +2538,7 @@ pub mod render_ffi { buffer_id: id::BufferId, offset: BufferAddress, ) { + log::trace!("RenderPass::draw_indexed_indirect {buffer_id:?} {offset}"); pass.base.commands.push(RenderCommand::MultiDrawIndirect { buffer_id, offset, @@ -2529,6 +2554,7 @@ pub mod render_ffi { offset: BufferAddress, count: u32, ) { + log::trace!("RenderPass::multi_draw_indirect {buffer_id:?} {offset} {count}"); pass.base.commands.push(RenderCommand::MultiDrawIndirect { buffer_id, offset, @@ -2544,6 +2570,7 @@ pub mod render_ffi { offset: BufferAddress, count: u32, ) { + log::trace!("RenderPass::multi_draw_indexed_indirect {buffer_id:?} {offset} {count}"); pass.base.commands.push(RenderCommand::MultiDrawIndirect { buffer_id, offset, @@ -2561,6 +2588,7 @@ pub mod render_ffi { count_buffer_offset: BufferAddress, max_count: u32, ) { + log::trace!("RenderPass::multi_draw_indirect_count {buffer_id:?} {offset} {count_buffer_id:?} {count_buffer_offset} {max_count}"); pass.base .commands .push(RenderCommand::MultiDrawIndirectCount { @@ -2582,6 +2610,7 @@ pub mod render_ffi { count_buffer_offset: BufferAddress, max_count: u32, ) { + log::trace!("RenderPass::multi_draw_indexed_indirect_count {buffer_id:?} {offset} {count_buffer_id:?} {count_buffer_offset} {max_count}"); pass.base .commands .push(RenderCommand::MultiDrawIndirectCount { @@ -2604,7 +2633,10 @@ pub mod render_ffi { label: RawString, color: u32, ) { - let bytes = unsafe { ffi::CStr::from_ptr(label) }.to_bytes(); + let cstr = unsafe { ffi::CStr::from_ptr(label) }; + log::trace!("RenderPass::push_debug_group {cstr:?}"); + + let bytes = cstr.to_bytes(); pass.base.string_data.extend_from_slice(bytes); pass.base.commands.push(RenderCommand::PushDebugGroup { @@ -2615,6 +2647,7 @@ pub mod render_ffi { #[no_mangle] pub extern "C" fn wgpu_render_pass_pop_debug_group(pass: &mut RenderPass) { + log::trace!("RenderPass::pop_debug_group"); pass.base.commands.push(RenderCommand::PopDebugGroup); } @@ -2628,7 +2661,10 @@ pub mod render_ffi { label: RawString, color: u32, ) { - let bytes = unsafe { ffi::CStr::from_ptr(label) }.to_bytes(); + let cstr = unsafe { ffi::CStr::from_ptr(label) }; + log::trace!("RenderPass::insert_debug_marker {cstr:?}"); + + let bytes = cstr.to_bytes(); pass.base.string_data.extend_from_slice(bytes); pass.base.commands.push(RenderCommand::InsertDebugMarker { @@ -2643,6 +2679,7 @@ pub mod render_ffi { query_set_id: id::QuerySetId, query_index: u32, ) { + log::trace!("RenderPass::write_timestamps {query_set_id:?} {query_index}"); pass.base.commands.push(RenderCommand::WriteTimestamp { query_set_id, query_index, @@ -2654,6 +2691,7 @@ pub mod render_ffi { pass: &mut RenderPass, query_index: u32, ) { + log::trace!("RenderPass::begin_occlusion_query {query_index}"); pass.base .commands .push(RenderCommand::BeginOcclusionQuery { query_index }); @@ -2661,6 +2699,7 @@ pub mod render_ffi { #[no_mangle] pub extern "C" fn wgpu_render_pass_end_occlusion_query(pass: &mut RenderPass) { + log::trace!("RenderPass::end_occlusion_query"); pass.base.commands.push(RenderCommand::EndOcclusionQuery); } @@ -2670,6 +2709,7 @@ pub mod render_ffi { query_set_id: id::QuerySetId, query_index: u32, ) { + log::trace!("RenderPass::begin_pipeline_statistics_query {query_set_id:?} {query_index}"); pass.base .commands .push(RenderCommand::BeginPipelineStatisticsQuery { @@ -2680,6 +2720,7 @@ pub mod render_ffi { #[no_mangle] pub extern "C" fn wgpu_render_pass_end_pipeline_statistics_query(pass: &mut RenderPass) { + log::trace!("RenderPass::end_pipeline_statistics_query"); pass.base .commands .push(RenderCommand::EndPipelineStatisticsQuery); @@ -2695,6 +2736,7 @@ pub mod render_ffi { render_bundle_ids: *const id::RenderBundleId, render_bundle_ids_length: usize, ) { + log::trace!("RenderPass::execute_bundles"); for &bundle_id in unsafe { slice::from_raw_parts(render_bundle_ids, render_bundle_ids_length) } { diff --git a/wgpu-core/src/command/transfer.rs b/wgpu-core/src/command/transfer.rs index 98b96e2dda..4afbc97ed8 100644 --- a/wgpu-core/src/command/transfer.rs +++ b/wgpu-core/src/command/transfer.rs @@ -7,7 +7,7 @@ use crate::{ error::{ErrorFormatter, PrettyError}, global::Global, hal_api::HalApi, - id::{BufferId, CommandEncoderId, TextureId}, + id::{BufferId, CommandEncoderId, DeviceId, TextureId, Valid}, identity::GlobalIdentityHandlerFactory, init_tracker::{ has_copy_partial_init_tracker_coverage, MemoryInitKind, TextureInitRange, @@ -41,6 +41,8 @@ pub enum CopySide { #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum TransferError { + #[error("Device {0:?} is invalid")] + InvalidDevice(DeviceId), #[error("Buffer {0:?} is invalid or destroyed")] InvalidBuffer(BufferId), #[error("Texture {0:?} is invalid or destroyed")] @@ -576,6 +578,9 @@ impl Global { let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into()); + } #[cfg(feature = "trace")] if let Some(ref mut list) = cmd_buf_data.commands { @@ -726,6 +731,11 @@ impl Global { let hub = A::hub(self); let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into()); + } + let mut cmd_buf_data = cmd_buf.data.lock(); let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); @@ -745,8 +755,6 @@ impl Global { let texture_guard = hub.textures.read(); - let device = &cmd_buf.device; - if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_buffer_to_texture of size 0"); return Ok(()); @@ -881,6 +889,11 @@ impl Global { let hub = A::hub(self); let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into()); + } + let mut cmd_buf_data = cmd_buf.data.lock(); let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); @@ -899,7 +912,6 @@ impl Global { let texture_guard = hub.textures.read(); - let device = &cmd_buf.device; if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_buffer of size 0"); @@ -1048,6 +1060,11 @@ impl Global { let hub = A::hub(self); let cmd_buf = CommandBuffer::get_encoder(hub, command_encoder_id)?; + let device = &cmd_buf.device; + if !device.is_valid() { + return Err(TransferError::InvalidDevice(cmd_buf.device_id.value.0).into()); + } + let mut cmd_buf_data = cmd_buf.data.lock(); let cmd_buf_data = cmd_buf_data.as_mut().unwrap(); @@ -1065,7 +1082,6 @@ impl Global { let texture_guard = hub.textures.read(); - let device = &cmd_buf.device; if copy_size.width == 0 || copy_size.height == 0 || copy_size.depth_or_array_layers == 0 { log::trace!("Ignoring copy_texture_to_texture of size 0"); diff --git a/wgpu-core/src/device/global.rs b/wgpu-core/src/device/global.rs index 8c90b141a7..4d4e5ac2f8 100644 --- a/wgpu-core/src/device/global.rs +++ b/wgpu-core/src/device/global.rs @@ -95,7 +95,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.valid { + return Err(InvalidDevice); + } Ok(device.features) } @@ -105,7 +109,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.valid { + return Err(InvalidDevice); + } Ok(device.limits.clone()) } @@ -115,7 +123,11 @@ impl Global { device_id: DeviceId, ) -> Result { let hub = A::hub(self); + let device = hub.devices.get(device_id).map_err(|_| InvalidDevice)?; + if !device.valid { + return Err(InvalidDevice); + } Ok(device.downlevel.clone()) } @@ -138,6 +150,9 @@ impl Global { return (id, Some(DeviceError::Invalid.into())); } }; + if !device.valid { + break DeviceError::Invalid.into(); + } if desc.usage.is_empty() { // Per spec, `usage` must not be zero. @@ -460,6 +475,7 @@ impl Global { pub fn buffer_drop(&self, buffer_id: id::BufferId, wait: bool) { profiling::scope!("Buffer::drop"); + log::debug!("Buffer {:?} is asked to be dropped", buffer_id); let hub = A::hub(self); @@ -515,6 +531,9 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateTexture(fid.id(), desc.clone())); @@ -524,6 +543,7 @@ impl Global { Ok(texture) => texture, Err(error) => break error, }; + let (id, resource) = fid.assign(texture); log::info!("Created Texture {:?} with {:?}", id, desc); @@ -552,7 +572,7 @@ impl Global { desc: &resource::TextureDescriptor, id_in: Input, ) -> (id::TextureId, Option) { - profiling::scope!("Device::create_texture"); + profiling::scope!("Device::create_texture_from_hal"); let hub = A::hub(self); @@ -563,6 +583,9 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } // NB: Any change done through the raw texture handle will not be // recorded in the replay @@ -633,6 +656,9 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } // NB: Any change done through the raw buffer handle will not be // recorded in the replay @@ -668,6 +694,7 @@ impl Global { texture_id: id::TextureId, ) -> Result<(), resource::DestroyError> { profiling::scope!("Texture::destroy"); + log::trace!("Texture::destroy {texture_id:?}"); let hub = A::hub(self); @@ -708,6 +735,7 @@ impl Global { pub fn texture_drop(&self, texture_id: id::TextureId, wait: bool) { profiling::scope!("Texture::drop"); + log::debug!("Texture {:?} is asked to be dropped", texture_id); let hub = A::hub(self); @@ -775,12 +803,14 @@ impl Global { Ok(view) => view, Err(e) => break e, }; + let (id, resource) = fid.assign(view); log::info!("Created TextureView {:?}", id); device.trackers.lock().views.insert_single(id, resource); return (id, None); }; + log::error!("Texture::create_view {:?} error {:?}", texture_id, error); let id = fid.assign_error(desc.label.borrow_or_default()); (id, Some(error)) } @@ -795,6 +825,7 @@ impl Global { wait: bool, ) -> Result<(), resource::TextureViewDestroyError> { profiling::scope!("TextureView::drop"); + log::debug!("TextureView {:?} is asked to be dropped", texture_view_id); let hub = A::hub(self); @@ -837,6 +868,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateSampler(fid.id(), desc.clone())); @@ -850,7 +885,7 @@ impl Global { let (id, resource) = fid.assign(sampler); log::info!("Created Sampler {:?}", id); device.trackers.lock().samplers.insert_single(id, resource); - + return (id, None); }; @@ -896,6 +931,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateBindGroupLayout(fid.id(), desc.clone())); @@ -934,7 +973,15 @@ impl Global { layout.compatible_layout = compatible_layout; let (id, _) = fid.assign(layout); - log::info!("Created BindGroupLayout {:?}", id); + if let Some(dupe) = compatible_layout { + log::info!("Created BindGroupLayout (duplicate of {dupe:?}) -> {:?}", id); + log::trace!( + "Device::create_bind_group_layout (duplicate of {dupe:?}) -> {:?}", + id.0 + ); + } else { + log::info!("Created BindGroupLayout {:?}", id); + } return (id, None); }; @@ -949,6 +996,7 @@ impl Global { pub fn bind_group_layout_drop(&self, bind_group_layout_id: id::BindGroupLayoutId) { profiling::scope!("BindGroupLayout::drop"); + log::debug!( "BindGroupLayout {:?} is asked to be dropped", bind_group_layout_id @@ -984,6 +1032,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreatePipelineLayout(fid.id(), desc.clone())); @@ -1012,6 +1064,7 @@ impl Global { pub fn pipeline_layout_drop(&self, pipeline_layout_id: id::PipelineLayoutId) { profiling::scope!("PipelineLayout::drop"); + log::debug!( "PipelineLayout {:?} is asked to be dropped", pipeline_layout_id @@ -1043,6 +1096,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateBindGroup(fid.id(), desc.clone())); @@ -1084,6 +1141,7 @@ impl Global { pub fn bind_group_drop(&self, bind_group_id: id::BindGroupId) { profiling::scope!("BindGroup::drop"); + log::debug!("BindGroup {:?} is asked to be dropped", bind_group_id); let hub = A::hub(self); @@ -1117,6 +1175,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { let data = match source { @@ -1145,6 +1207,7 @@ impl Global { Ok(shader) => shader, Err(e) => break e, }; + let (id, _) = fid.assign(shader); log::info!("Created ShaderModule {:?} with {:?}", id, desc); return (id, None); @@ -1180,6 +1243,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { let data = trace.make_binary("spv", unsafe { @@ -1211,8 +1278,9 @@ impl Global { pub fn shader_module_drop(&self, shader_module_id: id::ShaderModuleId) { profiling::scope!("ShaderModule::drop"); + log::debug!("ShaderModule {:?} is asked to be dropped", shader_module_id); - + let hub = A::hub(self); hub.shader_modules.unregister(shader_module_id); } @@ -1233,6 +1301,9 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid, }; + if !device.valid { + break DeviceError::Invalid; + } let queue = match hub.queues.get(device.queue_id.read().unwrap()) { Ok(queue) => queue, Err(_) => break DeviceError::InvalidQueueId, @@ -1270,6 +1341,7 @@ impl Global { pub fn command_encoder_drop(&self, command_encoder_id: id::CommandEncoderId) { profiling::scope!("CommandEncoder::drop"); + log::debug!( "CommandEncoder {:?} is asked to be dropped", command_encoder_id @@ -1286,6 +1358,7 @@ impl Global { pub fn command_buffer_drop(&self, command_buffer_id: id::CommandBufferId) { profiling::scope!("CommandBuffer::drop"); + log::debug!( "CommandBuffer {:?} is asked to be dropped", command_buffer_id @@ -1302,6 +1375,7 @@ impl Global { Option, ) { profiling::scope!("Device::create_render_bundle_encoder"); + log::trace!("Device::device_create_render_bundle_encoder"); let (encoder, error) = match command::RenderBundleEncoder::new(desc, device_id, None) { Ok(encoder) => (encoder, None), Err(e) => (command::RenderBundleEncoder::dummy(device_id), Some(e)), @@ -1326,6 +1400,10 @@ impl Global { Ok(device) => device, Err(_) => break command::RenderBundleError::INVALID_DEVICE, }; + if !device.valid { + break command::RenderBundleError::INVALID_DEVICE; + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateRenderBundle { @@ -1361,7 +1439,9 @@ impl Global { pub fn render_bundle_drop(&self, render_bundle_id: id::RenderBundleId) { profiling::scope!("RenderBundle::drop"); + log::debug!("RenderBundle {:?} is asked to be dropped", render_bundle_id); + let hub = A::hub(self); if let Some(bundle) = hub.render_bundles.unregister(render_bundle_id) { @@ -1389,6 +1469,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateQuerySet { @@ -1413,12 +1497,13 @@ impl Global { return (id, None); }; - let id = fid.assign_error(""); + let id = fid.assign_error(""); (id, Some(error)) } pub fn query_set_drop(&self, query_set_id: id::QuerySetId) { profiling::scope!("QuerySet::drop"); + log::debug!("QuerySet {:?} is asked to be dropped", query_set_id); let hub = A::hub(self); @@ -1464,6 +1549,9 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateRenderPipeline { @@ -1478,7 +1566,7 @@ impl Global { Ok(pair) => pair, Err(e) => break e, }; - + let (id, resource) = fid.assign(pipeline); log::info!("Created RenderPipeline {:?} with {:?}", id, desc); @@ -1537,10 +1625,12 @@ impl Global { pub fn render_pipeline_drop(&self, render_pipeline_id: id::RenderPipelineId) { profiling::scope!("RenderPipeline::drop"); + log::debug!( "RenderPipeline {:?} is asked to be dropped", render_pipeline_id ); + let hub = A::hub(self); if let Some(pipeline) = hub.render_pipelines.unregister(render_pipeline_id) { @@ -1579,6 +1669,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::CreateComputePipeline { @@ -1653,10 +1747,12 @@ impl Global { pub fn compute_pipeline_drop(&self, compute_pipeline_id: id::ComputePipelineId) { profiling::scope!("ComputePipeline::drop"); + log::debug!( "ComputePipeline {:?} is asked to be dropped", compute_pipeline_id ); + let hub = A::hub(self); if let Some(pipeline) = hub.compute_pipelines.unregister(compute_pipeline_id) { @@ -1802,6 +1898,10 @@ impl Global { Ok(device) => device, Err(_) => break DeviceError::Invalid.into(), }; + if !device.valid { + break DeviceError::Invalid.into(); + } + #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::ConfigureSurface(surface_id, config.clone())); @@ -1947,6 +2047,8 @@ impl Global { device_id: DeviceId, maintain: wgt::Maintain, ) -> Result { + log::trace!("Device::poll"); + let (closures, queue_empty) = { if let wgt::Maintain::WaitForSubmissionIndex(submission_index) = maintain { if submission_index.queue_id != device_id { @@ -2054,21 +2156,34 @@ impl Global { } pub fn device_start_capture(&self, id: DeviceId) { + log::trace!("Device::start_capture"); + let hub = A::hub(self); + if let Ok(device) = hub.devices.get(id) { + if !device.valid { + return; + } unsafe { device.raw().start_capture() }; } } pub fn device_stop_capture(&self, id: DeviceId) { + log::trace!("Device::stop_capture"); + let hub = A::hub(self); + if let Ok(device) = hub.devices.get(id) { + if !device.valid { + return; + } unsafe { device.raw().stop_capture() }; } } pub fn device_drop(&self, device_id: DeviceId) { profiling::scope!("Device::drop"); + log::debug!("Device {:?} is asked to be dropped", device_id); let hub = A::hub(self); @@ -2087,6 +2202,81 @@ impl Global { drop(device); } } + + pub fn device_destroy(&self, device_id: DeviceId) { + log::trace!("Device::destroy {device_id:?}"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut device_guard, _) = hub.devices.write(&mut token); + if let Ok(device) = device_guard.get_mut(device_id) { + // Follow the steps at + // https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy. + + // It's legal to call destroy multiple times, but if the device + // is already invalid, there's nothing more to do. There's also + // no need to return an error. + if !device.valid { + return; + } + + // The last part of destroy is to lose the device. The spec says + // delay that until all "currently-enqueued operations on any + // queue on this device are completed." + + // TODO: implement this delay. + + // Finish by losing the device. + + // TODO: associate this "destroyed" reason more tightly with + // the GPUDeviceLostReason defined in webgpu.idl. + device.lose(Some("destroyed")); + } + } + + pub fn device_lose(&self, device_id: DeviceId, reason: Option<&str>) { + log::trace!("Device::lose {device_id:?}"); + + let hub = A::hub(self); + let mut token = Token::root(); + + let (mut device_guard, _) = hub.devices.write(&mut token); + if let Ok(device) = device_guard.get_mut(device_id) { + device.lose(reason); + } + } + + /// Exit the unreferenced, inactive device `device_id`. + fn exit_device(&self, device_id: DeviceId) { + let hub = A::hub(self); + let mut token = Token::root(); + let mut free_adapter_id = None; + { + let (device, mut _token) = hub.devices.unregister(device_id, &mut token); + if let Some(mut device) = device { + // The things `Device::prepare_to_die` takes care are mostly + // unnecessary here. We know our queue is empty, so we don't + // need to wait for submissions or triage them. We know we were + // just polled, so `life_tracker.free_resources` is empty. + debug_assert!(device.lock_life(&mut _token).queue_empty()); + device.pending_writes.deactivate(); + + // Adapter is only referenced by the device and itself. + // This isn't a robust way to destroy them, we should find a better one. + if device.adapter_id.ref_count.load() == 1 { + free_adapter_id = Some(device.adapter_id.value.0); + } + + device.dispose(); + } + } + + let hub = A::hub(self); + if let Some(queue) = hub.queues.unregister(queue_id) { + drop(queue); + } + } pub fn queue_drop(&self, queue_id: QueueId) { profiling::scope!("Queue::drop"); @@ -2104,6 +2294,8 @@ impl Global { range: Range, op: BufferMapOperation, ) -> BufferAccessResult { + log::trace!("Buffer::map_async {buffer_id:?}"); + // User callbacks must not be called while holding buffer_map_async_inner's locks, so we // defer the error callback if it needs to be called immediately (typically when running // into errors). @@ -2151,6 +2343,11 @@ impl Global { } }; + let device = &device_guard[buffer.device_id.value]; + if !device.valid { + return Err((op, BufferAccessError::Invalid)); + } + if let Err(e) = check_buffer_usage(buffer.usage, pub_usage) { return Err((op, e.into())); } @@ -2216,6 +2413,7 @@ impl Global { size: Option, ) -> Result<(*mut u8, u64), BufferAccessError> { profiling::scope!("Buffer::get_mapped_range"); + log::trace!("Buffer::get_mapped_range {buffer_id:?}"); let hub = A::hub(self); @@ -2277,6 +2475,7 @@ impl Global { } pub fn buffer_unmap(&self, buffer_id: id::BufferId) -> BufferAccessResult { profiling::scope!("unmap", "Buffer"); + log::trace!("Buffer::unmap {buffer_id:?}"); let closure; { diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index ae53e83f1d..af7638a507 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -296,11 +296,11 @@ pub struct InvalidDevice; #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum DeviceError { - #[error("Parent device is invalid")] + #[error("Parent device is invalid.")] Invalid, #[error("Parent device is lost")] Lost, - #[error("Not enough memory left")] + #[error("Not enough memory left.")] OutOfMemory, #[error("Creation of a resource failed for a reason other than running out of memory.")] ResourceCreationFailed, @@ -311,7 +311,7 @@ pub enum DeviceError { impl From for DeviceError { fn from(error: hal::DeviceError) -> Self { match error { - hal::DeviceError::Lost => DeviceError::Lost, + hal::DeviceError::Lost => DeviceError::Invalid, hal::DeviceError::OutOfMemory => DeviceError::OutOfMemory, hal::DeviceError::ResourceCreationFailed => DeviceError::ResourceCreationFailed, } diff --git a/wgpu-core/src/device/queue.rs b/wgpu-core/src/device/queue.rs index 0d60974dac..204e148fe5 100644 --- a/wgpu-core/src/device/queue.rs +++ b/wgpu-core/src/device/queue.rs @@ -1103,6 +1103,7 @@ impl Global { command_buffer_ids: &[id::CommandBufferId], ) -> Result { profiling::scope!("Queue::submit"); + log::trace!("Queue::submit {queue_id:?}"); let (submit_index, callbacks) = { let hub = A::hub(self); @@ -1501,6 +1502,8 @@ impl Global { queue_id: QueueId, closure: SubmittedWorkDoneClosure, ) -> Result<(), InvalidQueue> { + log::trace!("Queue::on_submitted_work_done {queue_id:?}"); + //TODO: flush pending writes let hub = A::hub(self); match hub.queues.get(queue_id) { diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index c6a3514703..70e13d441f 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -96,6 +96,19 @@ pub struct Device { pub(crate) active_submission_index: AtomicU64, //SubmissionIndex, pub(crate) fence: RwLock>, + /// Is this device valid? Valid is closely associated with "lose the device", + /// which can be triggered by various methods, including at the end of device + /// destroy, and by any GPU errors that cause us to no longer trust the state + /// of the device. Ideally we would like to fold valid into the storage of + /// the device itself (for example as an Error enum), but unfortunately we + /// need to continue to be able to retrieve the device in poll_devices to + /// determine if it can be dropped. If our internal accesses of devices were + /// done through ref-counted references and external accesses checked for + /// Error enums, we wouldn't need this. For now, we need it. All the call + /// sites where we check it are areas that should be revisited if we start + /// using ref-counted references for internal access. + pub(crate) valid: bool, + /// All live resources allocated with this [`Device`]. /// /// Has to be locked temporarily only (locked last) @@ -239,6 +252,7 @@ impl Device { command_allocator: Mutex::new(Some(com_alloc)), active_submission_index: AtomicU64::new(0), fence: RwLock::new(Some(fence)), + valid: true, trackers: Mutex::new(Tracker::new()), life_tracker: Mutex::new(life::LifetimeTracker::new()), temp_suspected: Mutex::new(Some(life::ResourceMaps::new::())), @@ -264,6 +278,10 @@ impl Device { }) } + pub fn is_valid(&self) -> bool { + self.valid + } + pub(crate) fn release_queue(&self, queue: A::Queue) { self.queue_to_drop.write().replace(queue); } @@ -1291,6 +1309,10 @@ impl Device { .flags .contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING), ); + caps.set( + Caps::DUAL_SOURCE_BLENDING, + self.features.contains(wgt::Features::DUAL_SOURCE_BLENDING), + ); let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), caps) .validate(&module) @@ -1301,7 +1323,8 @@ impl Device { inner: Box::new(inner), }) })?; - let interface = validation::Interface::new(&module, &info, self.limits.clone()); + let interface = + validation::Interface::new(&module, &info, self.limits.clone(), self.features); let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { module, info }); let hal_desc = hal::ShaderModuleDescriptor { @@ -2555,6 +2578,8 @@ impl Device { let mut vertex_steps = Vec::with_capacity(desc.vertex.buffers.len()); let mut vertex_buffers = Vec::with_capacity(desc.vertex.buffers.len()); let mut total_attributes = 0; + let mut shader_expects_dual_source_blending = false; + let mut pipeline_expects_dual_source_blending = false; for (i, vb_state) in desc.vertex.buffers.iter().enumerate() { vertex_steps.push(pipeline::VertexStep { stride: vb_state.array_stride, @@ -2695,7 +2720,25 @@ impl Device { { break Some(pipeline::ColorStateError::FormatNotMultisampled(cs.format)); } - + if let Some(blend_mode) = cs.blend { + for factor in [ + blend_mode.color.src_factor, + blend_mode.color.dst_factor, + blend_mode.alpha.src_factor, + blend_mode.alpha.dst_factor, + ] { + if factor.ref_second_blend_source() { + self.require_features(wgt::Features::DUAL_SOURCE_BLENDING)?; + if i == 0 { + pipeline_expects_dual_source_blending = true; + break; + } else { + return Err(crate::pipeline::CreateRenderPipelineError + ::BlendFactorOnUnsupportedTarget { factor, target: i as u32 }); + } + } + } + } break None; }; if let Some(e) = error { @@ -2855,6 +2898,15 @@ impl Device { } } + if let Some(ref interface) = shader_module.interface { + shader_expects_dual_source_blending = interface + .fragment_uses_dual_source_blending(&fragment.stage.entry_point) + .map_err(|error| pipeline::CreateRenderPipelineError::Stage { + stage: flag, + error, + })?; + } + Some(hal::ProgrammableStage { module: shader_module.raw(), entry_point: fragment.stage.entry_point.as_ref(), @@ -2863,6 +2915,17 @@ impl Device { None => None, }; + if !pipeline_expects_dual_source_blending && shader_expects_dual_source_blending { + return Err( + pipeline::CreateRenderPipelineError::ShaderExpectsPipelineToUseDualSourceBlending, + ); + } + if pipeline_expects_dual_source_blending && !shader_expects_dual_source_blending { + return Err( + pipeline::CreateRenderPipelineError::PipelineExpectsShaderToUseDualSourceBlending, + ); + } + if validated_stages.contains(wgt::ShaderStages::FRAGMENT) { for (i, output) in io.iter() { match color_targets.get(*i as usize) { @@ -3111,6 +3174,27 @@ impl Device { desc: desc.map_label(|_| ()), }) } + + pub(crate) fn lose(&mut self, _reason: Option<&str>) { + // Follow the steps at https://gpuweb.github.io/gpuweb/#lose-the-device. + + // Mark the device explicitly as invalid. This is checked in various + // places to prevent new work from being submitted. + self.valid = false; + + // The following steps remain in "lose the device": + // 1) Resolve the GPUDevice device.lost promise. + + // TODO: triggger this passively or actively, and supply the reason. + + // 2) Complete any outstanding mapAsync() steps. + // 3) Complete any outstanding onSubmittedWorkDone() steps. + + // These parts are passively accomplished by setting valid to false, + // since that will prevent any new work from being added to the queues. + // Future calls to poll_devices will continue to check the work queues + // until they are cleared, and then drop the device. + } } impl Device { diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 48890528df..c19dfd1eb7 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -308,6 +308,8 @@ impl Adapter { desc: &DeviceDescriptor, trace_path: Option<&std::path::Path>, ) -> Result<(Device, Queue), RequestDeviceError> { + log::info!("Adapter::create_device"); + let caps = &self.raw.capabilities; if let Ok(device) = Device::new( hal_device.device, @@ -721,6 +723,8 @@ impl Global { pub fn surface_drop(&self, id: SurfaceId) { profiling::scope!("Surface::drop"); + + log::info!("Surface::drop {id:?}"); fn unconfigure( global: &Global, @@ -786,6 +790,7 @@ impl Global { pub fn enumerate_adapters(&self, inputs: AdapterInputs>) -> Vec { profiling::scope!("Instance::enumerate_adapters"); + log::trace!("Instance::enumerate_adapters"); let mut adapters = Vec::new(); @@ -842,6 +847,7 @@ impl Global { inputs: AdapterInputs>, ) -> Result { profiling::scope!("Instance::pick_adapter"); + log::trace!("Instance::pick_adapter"); fn gather( _: A, @@ -1115,6 +1121,7 @@ impl Global { pub fn adapter_drop(&self, adapter_id: AdapterId) { profiling::scope!("Adapter::drop"); + log::trace!("Adapter::drop {adapter_id:?}"); let hub = A::hub(self); let mut adapters_locked = hub.adapters.write(); @@ -1140,6 +1147,7 @@ impl Global { queue_id_in: Input, ) -> (DeviceId, QueueId, Option) { profiling::scope!("Adapter::request_device"); + log::trace!("Adapter::request_device"); let hub = A::hub(self); let device_fid = hub.devices.prepare::(device_id_in); diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index c09a2265bf..b76a9277f9 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -437,6 +437,15 @@ pub enum CreateRenderPipelineError { }, #[error("In the provided shader, the type given for group {group} binding {binding} has a size of {size}. As the device does not support `DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED`, the type must have a size that is a multiple of 16 bytes.")] UnalignedShader { group: u32, binding: u32, size: u64 }, + #[error("Using the blend factor {factor:?} for render target {target} is not possible. Only the first render target may be used when dual-source blending.")] + BlendFactorOnUnsupportedTarget { + factor: wgt::BlendFactor, + target: u32, + }, + #[error("Pipeline expects the shader entry point to make use of dual-source blending.")] + PipelineExpectsShaderToUseDualSourceBlending, + #[error("Shader entry point expects the pipeline to make use of dual-source blending.")] + ShaderExpectsPipelineToUseDualSourceBlending, } bitflags::bitflags! { diff --git a/wgpu-core/src/present.rs b/wgpu-core/src/present.rs index 76db63472a..d84a5a2099 100644 --- a/wgpu-core/src/present.rs +++ b/wgpu-core/src/present.rs @@ -135,7 +135,12 @@ impl Global { let (device, config) = if let Some(ref present) = *surface.presentation.lock() { match present.device.downcast_clone::() { - Some(device) => (device, present.config.clone()), + Some(device) => { + if !device.is_valid() { + return Err(DeviceError::Invalid.into()); + } + (device, present.config.clone()) + }, None => return Err(SurfaceError::NotConfigured), } } else { @@ -283,6 +288,9 @@ impl Global { }; let device = present.device.downcast_ref::().unwrap(); + if !device.is_valid() { + return Err(DeviceError::Invalid.into()); + } let queue_id = device.queue_id.read().unwrap(); let queue = hub.queues.get(queue_id).unwrap(); @@ -376,6 +384,9 @@ impl Global { }; let device = present.device.downcast_ref::().unwrap(); + if !device.is_valid() { + return Err(DeviceError::Invalid.into()); + } #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index e3ecb916d3..ef5c65ed00 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -116,11 +116,13 @@ struct EntryPoint { spec_constants: Vec, sampling_pairs: FastHashSet<(naga::Handle, naga::Handle)>, workgroup_size: [u32; 3], + dual_source_blending: bool, } #[derive(Debug)] pub struct Interface { limits: wgt::Limits, + features: wgt::Features, resources: naga::Arena, entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>, } @@ -830,7 +832,12 @@ impl Interface { list.push(varying); } - pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self { + pub fn new( + module: &naga::Module, + info: &naga::valid::ModuleInfo, + limits: wgt::Limits, + features: wgt::Features, + ) -> Self { let mut resources = naga::Arena::new(); let mut resource_mapping = FastHashMap::default(); for (var_handle, var) in module.global_variables.iter() { @@ -903,7 +910,7 @@ impl Interface { ep.sampling_pairs .insert((resource_mapping[&key.image], resource_mapping[&key.sampler])); } - + ep.dual_source_blending = info.dual_source_blending; ep.workgroup_size = entry_point.workgroup_size; entry_points.insert((entry_point.stage, entry_point.name.clone()), ep); @@ -911,6 +918,7 @@ impl Interface { Self { limits, + features, resources, entry_points, } @@ -1120,7 +1128,12 @@ impl Interface { } // Check all vertex outputs and make sure the fragment shader consumes them. - if shader_stage == naga::ShaderStage::Fragment { + // This requirement is removed if the `SHADER_UNUSED_VERTEX_OUTPUT` feature is enabled. + if shader_stage == naga::ShaderStage::Fragment + && !self + .features + .contains(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT) + { for &index in inputs.keys() { // This is a linear scan, but the count should be low enough // that this should be fine. @@ -1177,4 +1190,15 @@ impl Interface { .collect(); Ok(outputs) } + + pub fn fragment_uses_dual_source_blending( + &self, + entry_point_name: &str, + ) -> Result { + let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string()); + self.entry_points + .get(&pair) + .ok_or(StageError::MissingEntryPoint(pair.1)) + .map(|ep| ep.dual_source_blending) + } } diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 3db7363616..18e63b3d4e 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -103,7 +103,7 @@ d3d12 = { version = "0.7", features = ["libloading"], optional = true } # backend: Metal block = { version = "0.1", optional = true } -metal = "0.26.0" +metal = { git = "https://github.com/gfx-rs/metal-rs/", rev = "d24f1a4" } # More timer support via https://github.com/gfx-rs/metal-rs/pull/280 objc = "0.2.5" core-graphics-types = "0.1" @@ -134,7 +134,7 @@ features = ["wgsl-in"] [dev-dependencies] cfg-if = "1" env_logger = "0.10" -winit = { version = "0.28.6", features = [ "android-native-activity" ] } # for "halmark" example +winit = { version = "0.28.7", 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/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 30b3af27a9..2a5c97b59e 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -251,7 +251,9 @@ impl super::Adapter { | wgt::Features::TEXTURE_FORMAT_16BIT_NORM | wgt::Features::PUSH_CONSTANTS | wgt::Features::SHADER_PRIMITIVE_INDEX - | wgt::Features::RG11B10UFLOAT_RENDERABLE; + | wgt::Features::RG11B10UFLOAT_RENDERABLE + | wgt::Features::DUAL_SOURCE_BLENDING; + //TODO: in order to expose this, we need to run a compute shader // that extract the necessary statistics out of the D3D12 result. // Alternatively, we could allocate a buffer for the query set, diff --git a/wgpu-hal/src/dx12/conv.rs b/wgpu-hal/src/dx12/conv.rs index 908944567a..f484d1a9e2 100644 --- a/wgpu-hal/src/dx12/conv.rs +++ b/wgpu-hal/src/dx12/conv.rs @@ -246,12 +246,12 @@ fn map_blend_factor(factor: wgt::BlendFactor, is_alpha: bool) -> d3d12_ty::D3D12 Bf::Constant => d3d12_ty::D3D12_BLEND_BLEND_FACTOR, Bf::OneMinusConstant => d3d12_ty::D3D12_BLEND_INV_BLEND_FACTOR, Bf::SrcAlphaSaturated => d3d12_ty::D3D12_BLEND_SRC_ALPHA_SAT, - //Bf::Src1Color if is_alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, - //Bf::Src1Color => d3d12_ty::D3D12_BLEND_SRC1_COLOR, - //Bf::OneMinusSrc1Color if is_alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, - //Bf::OneMinusSrc1Color => d3d12_ty::D3D12_BLEND_INV_SRC1_COLOR, - //Bf::Src1Alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, - //Bf::OneMinusSrc1Alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, + Bf::Src1 if is_alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, + Bf::Src1 => d3d12_ty::D3D12_BLEND_SRC1_COLOR, + Bf::OneMinusSrc1 if is_alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, + Bf::OneMinusSrc1 => d3d12_ty::D3D12_BLEND_INV_SRC1_COLOR, + Bf::Src1Alpha => d3d12_ty::D3D12_BLEND_SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => d3d12_ty::D3D12_BLEND_INV_SRC1_ALPHA, } } diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index dcf7801ad0..bac02bae40 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -364,11 +364,16 @@ impl super::Adapter { wgt::Features::MULTIVIEW, extensions.contains("OVR_multiview2"), ); + features.set( + wgt::Features::DUAL_SOURCE_BLENDING, + extensions.contains("GL_EXT_blend_func_extended"), + ); features.set( wgt::Features::SHADER_PRIMITIVE_INDEX, ver >= (3, 2) || extensions.contains("OES_geometry_shader"), ); features.set(wgt::Features::SHADER_EARLY_DEPTH_TEST, ver >= (3, 1)); + features.set(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT, true); let gles_bcn_exts = [ "GL_EXT_texture_compression_s3tc_srgb", "GL_EXT_texture_compression_rgtc", diff --git a/wgpu-hal/src/gles/conv.rs b/wgpu-hal/src/gles/conv.rs index dd5d764c6a..9bfac022a1 100644 --- a/wgpu-hal/src/gles/conv.rs +++ b/wgpu-hal/src/gles/conv.rs @@ -376,6 +376,10 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> u32 { Bf::Constant => glow::CONSTANT_COLOR, Bf::OneMinusConstant => glow::ONE_MINUS_CONSTANT_COLOR, Bf::SrcAlphaSaturated => glow::SRC_ALPHA_SATURATE, + Bf::Src1 => glow::SRC1_COLOR, + Bf::OneMinusSrc1 => glow::ONE_MINUS_SRC1_COLOR, + Bf::Src1Alpha => glow::SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => glow::ONE_MINUS_SRC1_ALPHA, } } diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 126741d257..c4617deaa0 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -833,6 +833,10 @@ impl super::PrivateCapabilities { self.timestamp_query_support .contains(TimestampQuerySupport::INSIDE_WGPU_PASSES), ); + features.set( + F::DUAL_SOURCE_BLENDING, + self.msl_version >= MTLLanguageVersion::V1_2 && self.dual_source_blending, + ); features.set(F::TEXTURE_COMPRESSION_ASTC, self.format_astc); features.set(F::TEXTURE_COMPRESSION_ASTC_HDR, self.format_astc_hdr); features.set(F::TEXTURE_COMPRESSION_BC, self.format_bc); @@ -867,6 +871,7 @@ impl super::PrivateCapabilities { features.set(F::ADDRESS_MODE_CLAMP_TO_ZERO, true); features.set(F::RG11B10UFLOAT_RENDERABLE, self.format_rg11b10_all); + features.set(F::SHADER_UNUSED_VERTEX_OUTPUT, true); features } diff --git a/wgpu-hal/src/metal/conv.rs b/wgpu-hal/src/metal/conv.rs index a1ceb287ab..8f6439b50b 100644 --- a/wgpu-hal/src/metal/conv.rs +++ b/wgpu-hal/src/metal/conv.rs @@ -152,13 +152,11 @@ pub fn map_blend_factor(factor: wgt::BlendFactor) -> metal::MTLBlendFactor { Bf::OneMinusDstAlpha => OneMinusDestinationAlpha, Bf::Constant => BlendColor, Bf::OneMinusConstant => OneMinusBlendColor, - //Bf::ConstantAlpha => BlendAlpha, - //Bf::OneMinusConstantAlpha => OneMinusBlendAlpha, Bf::SrcAlphaSaturated => SourceAlphaSaturated, - //Bf::Src1 => Source1Color, - //Bf::OneMinusSrc1 => OneMinusSource1Color, - //Bf::Src1Alpha => Source1Alpha, - //Bf::OneMinusSrc1Alpha => OneMinusSource1Alpha, + Bf::Src1 => Source1Color, + Bf::OneMinusSrc1 => OneMinusSource1Color, + Bf::Src1Alpha => Source1Alpha, + Bf::OneMinusSrc1Alpha => OneMinusSource1Alpha, } } diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 08aeb39337..fd5f28d697 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -181,6 +181,7 @@ impl PhysicalDeviceFeatures { //.shader_resource_residency(requested_features.contains(wgt::Features::SHADER_RESOURCE_RESIDENCY)) .geometry_shader(requested_features.contains(wgt::Features::SHADER_PRIMITIVE_INDEX)) .depth_clamp(requested_features.contains(wgt::Features::DEPTH_CLIP_CONTROL)) + .dual_src_blend(requested_features.contains(wgt::Features::DUAL_SOURCE_BLENDING)) .build(), descriptor_indexing: if requested_features.intersects(indexing_features()) { Some( @@ -464,6 +465,7 @@ impl PhysicalDeviceFeatures { } features.set(F::DEPTH_CLIP_CONTROL, self.core.depth_clamp != 0); + features.set(F::DUAL_SOURCE_BLENDING, self.core.dual_src_blend != 0); if let Some(ref multiview) = self.multiview { features.set(F::MULTIVIEW, multiview.multiview != 0); @@ -524,6 +526,7 @@ impl PhysicalDeviceFeatures { | vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND, ); features.set(F::RG11B10UFLOAT_RENDERABLE, rg11b10ufloat_renderable); + features.set(F::SHADER_UNUSED_VERTEX_OUTPUT, true); (features, dl_flags) } diff --git a/wgpu-hal/src/vulkan/conv.rs b/wgpu-hal/src/vulkan/conv.rs index e2398c2689..459b7f858f 100644 --- a/wgpu-hal/src/vulkan/conv.rs +++ b/wgpu-hal/src/vulkan/conv.rs @@ -792,6 +792,10 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> vk::BlendFactor { Bf::SrcAlphaSaturated => vk::BlendFactor::SRC_ALPHA_SATURATE, Bf::Constant => vk::BlendFactor::CONSTANT_COLOR, Bf::OneMinusConstant => vk::BlendFactor::ONE_MINUS_CONSTANT_COLOR, + Bf::Src1 => vk::BlendFactor::SRC1_COLOR, + Bf::OneMinusSrc1 => vk::BlendFactor::ONE_MINUS_SRC1_COLOR, + Bf::Src1Alpha => vk::BlendFactor::SRC1_ALPHA, + Bf::OneMinusSrc1Alpha => vk::BlendFactor::ONE_MINUS_SRC1_ALPHA, } } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 9f61e2e490..a680d91296 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -371,7 +371,7 @@ bitflags::bitflags! { /// Compressed textures sacrifice some quality in exchange for significantly reduced /// bandwidth usage. /// - /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats. + /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats with Unorm/UnormSrgb channel type. /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] may enable additional usages. /// /// Supported Platforms: @@ -409,7 +409,7 @@ bitflags::bitflags! { /// Compressed textures sacrifice some quality in exchange for significantly reduced /// bandwidth usage. /// - /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for BCn formats. + /// Support for this feature guarantees availability of [`TextureUsages::COPY_SRC | TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING`] for ASTC formats with the HDR channel type. /// [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] may enable additional usages. /// /// Supported Platforms: @@ -737,6 +737,15 @@ bitflags::bitflags! { /// This is a native only feature. const VERTEX_ATTRIBUTE_64BIT = 1 << 53; + /// Allows vertex shaders to have outputs which are not consumed + /// by the fragment shader. + /// + /// Supported platforms: + /// - Vulkan + /// - Metal + /// - OpenGL + const SHADER_UNUSED_VERTEX_OUTPUT = 1 << 54; + // 54..59 available // Shader: @@ -781,7 +790,17 @@ bitflags::bitflags! { /// This is a native only feature. const SHADER_EARLY_DEPTH_TEST = 1 << 62; - // 62..64 available + /// Allows two outputs from a shader to be used for blending. + /// Note that dual-source blending doesn't support multiple render targets. + /// + /// For more info see the OpenGL ES extension GL_EXT_blend_func_extended. + /// + /// Supported platforms: + /// - OpenGL ES (with GL_EXT_blend_func_extended) + /// - Metal (with MSL 1.2+) + /// - Vulkan (with dualSrcBlend) + /// - DX12 + const DUAL_SOURCE_BLENDING = 1 << 63; } } @@ -1549,6 +1568,8 @@ impl TextureViewDimension { /// /// Corresponds to [WebGPU `GPUBlendFactor`]( /// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor). +/// Values using S1 requires [`Features::DUAL_SOURCE_BLENDING`] and can only be +/// used with the first render target. #[repr(C)] #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "trace", derive(Serialize))] @@ -1581,6 +1602,29 @@ pub enum BlendFactor { Constant = 11, /// 1.0 - Constant OneMinusConstant = 12, + /// S1.component + Src1 = 13, + /// 1.0 - S1.component + OneMinusSrc1 = 14, + /// S1.alpha + Src1Alpha = 15, + /// 1.0 - S1.alpha + OneMinusSrc1Alpha = 16, +} + +impl BlendFactor { + /// Returns `true` if the blend factor references the second blend source. + /// + /// Note that the usage of those blend factors require [`Features::DUAL_SOURCE_BLENDING`]. + pub fn ref_second_blend_source(&self) -> bool { + match self { + BlendFactor::Src1 + | BlendFactor::OneMinusSrc1 + | BlendFactor::Src1Alpha + | BlendFactor::OneMinusSrc1Alpha => true, + _ => false, + } + } } /// Alpha blend operation. @@ -6429,6 +6473,7 @@ pub enum Gles3MinorVersion { } /// Options for creating an instance. +#[derive(Debug)] pub struct InstanceDescriptor { /// Which `Backends` to enable. pub backends: Backends, diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 7e0d95d6b4..ed0b86ca0d 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -1444,6 +1444,15 @@ impl crate::Context for Context { let global = &self.0; wgc::gfx_select!(queue => global.queue_drop(*queue)); } + fn device_destroy(&self, device: &Self::DeviceId, _device_data: &Self::DeviceData) { + let global = &self.0; + wgc::gfx_select!(device => global.device_destroy(*device)); + } + fn device_lose(&self, device: &Self::DeviceId, _device_data: &Self::DeviceData) { + // TODO: accept a reason, and pass it to device_lose. + let global = &self.0; + wgc::gfx_select!(device => global.device_lose(*device, None)); + } fn device_poll( &self, device: &Self::DeviceId, diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index a29237a683..e627cd6747 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -421,6 +421,15 @@ fn map_blend_factor(factor: wgt::BlendFactor) -> web_sys::GpuBlendFactor { BlendFactor::SrcAlphaSaturated => bf::SrcAlphaSaturated, BlendFactor::Constant => bf::Constant, BlendFactor::OneMinusConstant => bf::OneMinusConstant, + BlendFactor::Src1 + | BlendFactor::OneMinusSrc1 + | BlendFactor::Src1Alpha + | BlendFactor::OneMinusSrc1Alpha => { + panic!( + "{:?} is not enabled for this backend", + wgt::Features::DUAL_SOURCE_BLENDING + ) + } } } @@ -1905,6 +1914,16 @@ impl crate::context::Context for Context { // Device is dropped automatically } + fn device_destroy(&self, _buffer: &Self::DeviceId, device_data: &Self::DeviceData) { + device_data.0.destroy(); + } + + fn device_lose(&self, _device: &Self::DeviceId, _device_data: &Self::DeviceData) { + // TODO: figure out the GPUDevice implementation of this, including resolving + // the device.lost promise, which will require a different invocation pattern + // with a callback. + } + fn queue_drop(&self, _queue: &Self::QueueId, _queue_data: &Self::QueueData) { // Queue is dropped automatically } diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index 24465d25d1..5da6ee5463 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -269,6 +269,8 @@ pub trait Context: Debug + WasmNotSend + WasmNotSync + Sized { desc: &RenderBundleEncoderDescriptor, ) -> (Self::RenderBundleEncoderId, Self::RenderBundleEncoderData); fn device_drop(&self, device: &Self::DeviceId, device_data: &Self::DeviceData); + fn device_destroy(&self, device: &Self::DeviceId, device_data: &Self::DeviceData); + fn device_lose(&self, device: &Self::DeviceId, device_data: &Self::DeviceData); fn queue_drop(&self, queue: &Self::QueueId, queue_data: &Self::QueueData); fn device_poll( &self, @@ -1364,6 +1366,8 @@ pub(crate) trait DynContext: Debug + WasmNotSend + WasmNotSync { desc: &RenderBundleEncoderDescriptor, ) -> (ObjectId, Box); fn device_drop(&self, device: &ObjectId, device_data: &crate::Data); + fn device_destroy(&self, device: &ObjectId, device_data: &crate::Data); + fn device_lose(&self, device: &ObjectId, device_data: &crate::Data); fn queue_drop(&self, queue: &ObjectId, queue_data: &crate::Data); fn device_poll(&self, device: &ObjectId, device_data: &crate::Data, maintain: Maintain) -> bool; @@ -2426,6 +2430,18 @@ where Context::device_drop(self, &device, device_data) } + fn device_destroy(&self, device: &ObjectId, device_data: &crate::Data) { + let device = ::from(*device); + let device_data = downcast_ref(device_data); + Context::device_destroy(self, &device, device_data) + } + + fn device_lose(&self, device: &ObjectId, device_data: &crate::Data) { + let device = ::from(*device); + let device_data = downcast_ref(device_data); + Context::device_lose(self, &device, device_data) + } + fn queue_drop(&self, queue: &ObjectId, queue_data: &crate::Data) { let queue = ::from(*queue); let queue_data = downcast_ref(queue_data); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 14d5c6450c..03fe097b51 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -54,6 +54,8 @@ pub use wgt::{ ))] #[doc(hidden)] pub use ::hal; +#[cfg(feature = "naga")] +pub use ::naga; #[cfg(any( not(target_arch = "wasm32"), feature = "webgl", @@ -523,7 +525,7 @@ impl Drop for ShaderModule { /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// only WGSL source code strings are accepted. #[cfg_attr(feature = "naga", allow(clippy::large_enum_variant))] -#[derive(Clone)] +#[derive(Clone, Debug)] #[non_exhaustive] pub enum ShaderSource<'a> { /// SPIR-V module represented as a slice of words. @@ -560,7 +562,7 @@ static_assertions::assert_impl_all!(ShaderSource: Send, Sync); /// /// Corresponds to [WebGPU `GPUShaderModuleDescriptor`]( /// https://gpuweb.github.io/gpuweb/#dictdef-gpushadermoduledescriptor). -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ShaderModuleDescriptor<'a> { /// Debug label of the shader module. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, @@ -574,6 +576,7 @@ static_assertions::assert_impl_all!(ShaderModuleDescriptor: Send, Sync); /// /// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, /// only WGSL source code strings are accepted. +#[derive(Debug)] pub struct ShaderModuleDescriptorSpirV<'a> { /// Debug label of the shader module. This will show up in graphics debuggers for easy identification. pub label: Label<'a>, @@ -2772,6 +2775,11 @@ impl Device { ) } } + + /// Destroy this device. + pub fn destroy(&self) { + DynContext::device_destroy(&*self.context, &self.id, self.data.as_ref()) + } } impl Drop for Device {